From 302720b8b1db1a9b52b6a65ba0d1405db85dc955 Mon Sep 17 00:00:00 2001 From: Tim Whiting Date: Sat, 23 Apr 2022 21:15:28 -0600 Subject: [PATCH 01/30] boilerplate remote config --- .../firebase_remote_config_dart/.gitignore | 7 +++ .../firebase_remote_config_dart/CHANGELOG.md | 3 ++ .../firebase_remote_config_dart/LICENSE | 29 ++++++++++++ .../firebase_remote_config_dart/README.md | 0 .../lib/firebase_remote_config_dart.dart | 5 +++ .../firebase_remote_config_dart/pubspec.yaml | 19 ++++++++ .../firebase_remote_config_desktop/.gitignore | 10 +++++ .../firebase_remote_config_desktop/.metadata | 10 +++++ .../CHANGELOG.md | 3 ++ .../firebase_remote_config_desktop/LICENSE | 29 ++++++++++++ .../firebase_remote_config_desktop/README.md | 0 .../lib/firebase_remote_config_desktop.dart | 45 +++++++++++++++++++ .../pubspec.yaml | 42 +++++++++++++++++ .../windows/.gitignore | 17 +++++++ 14 files changed, 219 insertions(+) create mode 100644 packages/firebase_remote_config/firebase_remote_config_dart/.gitignore create mode 100644 packages/firebase_remote_config/firebase_remote_config_dart/CHANGELOG.md create mode 100644 packages/firebase_remote_config/firebase_remote_config_dart/LICENSE create mode 100644 packages/firebase_remote_config/firebase_remote_config_dart/README.md create mode 100644 packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart create mode 100644 packages/firebase_remote_config/firebase_remote_config_dart/pubspec.yaml create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/.gitignore create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/.metadata create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/CHANGELOG.md create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/LICENSE create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/README.md create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/lib/firebase_remote_config_desktop.dart create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/pubspec.yaml create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/windows/.gitignore diff --git a/packages/firebase_remote_config/firebase_remote_config_dart/.gitignore b/packages/firebase_remote_config/firebase_remote_config_dart/.gitignore new file mode 100644 index 00000000..e9dc58d3 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_dart/.gitignore @@ -0,0 +1,7 @@ +.DS_Store +.dart_tool/ + +.packages +.pub/ + +build/ diff --git a/packages/firebase_remote_config/firebase_remote_config_dart/CHANGELOG.md b/packages/firebase_remote_config/firebase_remote_config_dart/CHANGELOG.md new file mode 100644 index 00000000..3c0ac3b0 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_dart/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.1.0-dev.0 + +- Initial release. diff --git a/packages/firebase_remote_config/firebase_remote_config_dart/LICENSE b/packages/firebase_remote_config/firebase_remote_config_dart/LICENSE new file mode 100644 index 00000000..5841c4de --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_dart/LICENSE @@ -0,0 +1,29 @@ +BSD-3-Clause +------------ + +Copyright (c) 2016-present Invertase Limited & Contributors + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +Creative Commons Attribution 3.0 License +---------------------------------------- + +Copyright (c) 2016-present Invertase Limited & Contributors + +Documentation and other instructional materials provided for this project +(including on a separate documentation repository or it's documentation website) are +licensed under the Creative Commons Attribution 3.0 License. Code samples/blocks +contained therein are licensed under the BSD-3-Clause License (the "License"), as above. + +You may obtain a copy of the Creative Commons Attribution 3.0 License at + + https://creativecommons.org/licenses/by/3.0/ diff --git a/packages/firebase_remote_config/firebase_remote_config_dart/README.md b/packages/firebase_remote_config/firebase_remote_config_dart/README.md new file mode 100644 index 00000000..e69de29b diff --git a/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart b/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart new file mode 100644 index 00000000..5dea4969 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart @@ -0,0 +1,5 @@ +// Copyright 2021 Invertase Limited. All rights reserved. +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file. + +library firebase_remote_config_dart; diff --git a/packages/firebase_remote_config/firebase_remote_config_dart/pubspec.yaml b/packages/firebase_remote_config/firebase_remote_config_dart/pubspec.yaml new file mode 100644 index 00000000..f8a2a6a1 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_dart/pubspec.yaml @@ -0,0 +1,19 @@ +name: firebase_remote_config_dart +description: Pure Dart implementation of FlutterFire RemoteConfig API. +homepage: https://firebase.flutter.dev +repository: https://github.com/invertase/flutterfire_dart +version: 0.1.0-dev.0 + +environment: + sdk: '>=2.12.0 <3.0.0' + +dependencies: + collection: ^1.15.0 + firebase_auth_dart: ^0.1.1 + firebase_core_dart: ^0.1.1 + http: ^0.13.4 + meta: ^1.7.0 + +dev_dependencies: + test: ^1.16.0 + diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/.gitignore b/packages/firebase_remote_config/firebase_remote_config_desktop/.gitignore new file mode 100644 index 00000000..65c34dc8 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_desktop/.gitignore @@ -0,0 +1,10 @@ +# Files and directories created by pub. +.dart_tool/ +.packages + +# Conventional directory for build outputs. +build/ + +# Omit committing pubspec.lock for library packages; see +# https://dart.dev/guides/libraries/private-files#pubspeclock. +pubspec.lock diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/.metadata b/packages/firebase_remote_config/firebase_remote_config_desktop/.metadata new file mode 100644 index 00000000..cb12308d --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_desktop/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 3595343e20a61ff16d14e8ecc25f364276bb1b8b + channel: stable + +project_type: app diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/CHANGELOG.md b/packages/firebase_remote_config/firebase_remote_config_desktop/CHANGELOG.md new file mode 100644 index 00000000..3c0ac3b0 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_desktop/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.1.0-dev.0 + +- Initial release. diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/LICENSE b/packages/firebase_remote_config/firebase_remote_config_desktop/LICENSE new file mode 100644 index 00000000..5841c4de --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_desktop/LICENSE @@ -0,0 +1,29 @@ +BSD-3-Clause +------------ + +Copyright (c) 2016-present Invertase Limited & Contributors + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +Creative Commons Attribution 3.0 License +---------------------------------------- + +Copyright (c) 2016-present Invertase Limited & Contributors + +Documentation and other instructional materials provided for this project +(including on a separate documentation repository or it's documentation website) are +licensed under the Creative Commons Attribution 3.0 License. Code samples/blocks +contained therein are licensed under the BSD-3-Clause License (the "License"), as above. + +You may obtain a copy of the Creative Commons Attribution 3.0 License at + + https://creativecommons.org/licenses/by/3.0/ diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/README.md b/packages/firebase_remote_config/firebase_remote_config_desktop/README.md new file mode 100644 index 00000000..e69de29b diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/lib/firebase_remote_config_desktop.dart b/packages/firebase_remote_config/firebase_remote_config_desktop/lib/firebase_remote_config_desktop.dart new file mode 100644 index 00000000..f1223ec5 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_desktop/lib/firebase_remote_config_desktop.dart @@ -0,0 +1,45 @@ +// Copyright 2021 Invertase Limited. All rights reserved. +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file. + +library firebase_remote_config_desktop; + +import 'package:firebase_core/firebase_core.dart'; +import 'package:firebase_core_dart/firebase_core_dart.dart' as core_dart; + +import 'package:firebase_remote_config_platform_interface/firebase_remote_config_platform_interface.dart'; + +/// Desktop implementation of FirebaseRemoteConfigPlatform for managing FirebaseRemoteConfig +class FirebaseRemoteConfigDesktop extends FirebaseRemoteConfigPlatform { + /// Constructs a FirebaseRemoteConfigDesktop + FirebaseRemoteConfigDesktop({ + required FirebaseApp app, + }) : _app = core_dart.Firebase.app(app.name), + super(appInstance: app); + + FirebaseRemoteConfigDesktop._() + : _app = null, + super(appInstance: null); + + /// Called by PluginRegistry to register this plugin as the implementation for Desktop + static void registerWith() { + FirebaseRemoteConfigPlatform.instance = + FirebaseRemoteConfigDesktop.instance; + } + + /// Stub initializer to allow creating an instance without + /// registering delegates or listeners. + /// + // ignore: prefer_constructors_over_static_methods + static FirebaseRemoteConfigDesktop get instance { + return FirebaseRemoteConfigDesktop._(); + } + + final core_dart.FirebaseApp? _app; + + @override + FirebaseRemoteConfigPlatform delegateFor({ + FirebaseApp? app, + }) => + FirebaseRemoteConfigDesktop(app: app ?? Firebase.app()); +} diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/pubspec.yaml b/packages/firebase_remote_config/firebase_remote_config_desktop/pubspec.yaml new file mode 100644 index 00000000..00f5e784 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_desktop/pubspec.yaml @@ -0,0 +1,42 @@ +name: firebase_remote_config_desktop +description: Desktop implementation of remote_config +homepage: https://firebase.flutter.dev +repository: https://github.com/invertase/flutterfire_dart +version: 0.1.0-dev.0 + +environment: + sdk: '>=2.12.0 <3.0.0' + flutter: '>=1.20.0' + +dependencies: + firebase_remote_config: ^2.0.5 + firebase_remote_config_platform_interface: any + firebase_core: ^1.10.0 + firebase_core_dart: ^0.1.1 + firebase_core_desktop: ^0.1.1 + firebase_functions_dart: ^0.1.0 + flutter: + sdk: flutter + http: ^0.13.4 + meta: ^1.3.0 + +dev_dependencies: + firebase_core_platform_interface: ^4.1.0 + flutter_test: + sdk: flutter + mockito: ^5.0.0 + plugin_platform_interface: ^2.0.2 + +flutter: + plugin: + implements: firebase_remote_config + platforms: + macos: + dartPluginClass: FirebaseRemoteConfigDesktop + pluginClass: none + linux: + dartPluginClass: FirebaseRemoteConfigDesktop + pluginClass: none + windows: + dartPluginClass: FirebaseRemoteConfigDesktop + pluginClass: none diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/windows/.gitignore b/packages/firebase_remote_config/firebase_remote_config_desktop/windows/.gitignore new file mode 100644 index 00000000..b3eb2be1 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_desktop/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ From e7fb6584a6efc767f2ccbee80f1cdd01ed7df6a0 Mon Sep 17 00:00:00 2001 From: Tim Whiting Date: Sat, 23 Apr 2022 21:36:55 -0600 Subject: [PATCH 02/30] start of dart --- .../lib/firebase_remote_config_dart.dart | 151 ++++++++++++++++++ .../lib/src/internal/api.dart | 6 + .../lib/src/remote_config_settings.dart | 18 +++ .../lib/src/remote_config_status.dart | 16 ++ .../lib/src/remote_config_value.dart | 85 ++++++++++ 5 files changed, 276 insertions(+) create mode 100644 packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/api.dart create mode 100644 packages/firebase_remote_config/firebase_remote_config_dart/lib/src/remote_config_settings.dart create mode 100644 packages/firebase_remote_config/firebase_remote_config_dart/lib/src/remote_config_status.dart create mode 100644 packages/firebase_remote_config/firebase_remote_config_dart/lib/src/remote_config_value.dart diff --git a/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart b/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart index 5dea4969..ef8ab48c 100644 --- a/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart +++ b/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart @@ -3,3 +3,154 @@ // that can be found in the LICENSE file. library firebase_remote_config_dart; + +import 'dart:convert'; + +import 'package:firebase_core_dart/firebase_core_dart.dart'; + +import 'src/remote_config_settings.dart'; +import 'src/remote_config_status.dart'; +import 'src/remote_config_value.dart'; +export 'src/remote_config_settings.dart'; +export 'src/remote_config_status.dart'; +export 'src/remote_config_value.dart'; + +part 'src/internal/api.dart'; + +/// The entry point for accessing Remote Config. +/// +/// You can get an instance by calling [RemoteConfig.instance]. Note +/// [RemoteConfig.instance] is async. +// TODO(TimWhiting): Figure out how to introduce ChangeNotifier like class +class RemoteConfig { + RemoteConfig._({required this.app}); + + // Cached instances of [FirebaseRemoteConfig]. + static final Map _firebaseRemoteConfigInstances = {}; + + /// The [FirebaseApp] this instance was initialized with. + final FirebaseApp app; + + /// Returns an instance using the default [FirebaseApp]. + static RemoteConfig get instance { + return RemoteConfig.instanceFor(app: Firebase.app()); + } + + /// Returns an instance using the specified [FirebaseApp]. + // ignore: prefer_constructors_over_static_methods + static RemoteConfig instanceFor({required FirebaseApp app}) { + return _firebaseRemoteConfigInstances.putIfAbsent(app.name, () { + return RemoteConfig._(app: app); + }); + } + + RemoteConfigApi _api; + + /// Returns the [DateTime] of the last successful fetch. + /// + /// If no successful fetch has been made a [DateTime] representing + /// the epoch (1970-01-01 UTC) is returned. + DateTime get lastFetchTime {} + final DateTime _lastFetchTime = DateTime.fromMillisecondsSinceEpoch(0); + + /// Returns the status of the last fetch attempt. + RemoteConfigFetchStatus get lastFetchStatus {} + final RemoteConfigFetchStatus _lastFetchStatus = + RemoteConfigFetchStatus.noFetchYet; + + /// Returns the [RemoteConfigSettings] of the current instance. + RemoteConfigSettings get settings {} + RemoteConfigSettings _settings = RemoteConfigSettings(); + + /// Makes the last fetched config available to getters. + /// + /// Returns a [bool] that is true if the config parameters + /// were activated. Returns a [bool] that is false if the + /// config parameters were already activated. + Future activate() async { + final bool configChanged = await _api.activate(); + notifyListeners(); + return configChanged; + } + + /// Ensures the last activated config are available to getters. + Future ensureInitialized() { + return _api.ensureInitialized(); + } + + /// Fetches and caches configuration from the Remote Config service. + Future fetch() async { + // TODO: Implement + await _api.fetch(); + _lastFetchedConfig = {}; + } + + /// Performs a fetch and activate operation, as a convenience. + /// + /// Returns [bool] in the same way that is done for [activate]. + Future fetchAndActivate() async { + final bool configChanged = await _api.fetchAndActivate(); + notifyListeners(); + _lastFetchedConfig = {}; + return configChanged; + } + + Map _lastFetchedConfig = {}; + Map _defaultParameters; + + /// Returns a Map of all Remote Config parameters. + Map getAll() { + return _lastFetchedConfig; + } + + /// Gets the value for a given key as a bool. + bool getBool(String key) { + return _lastFetchedConfig[key]?.asBool() ?? + _defaultParameters[key]! as bool; + } + + /// Gets the value for a given key as an int. + int getInt(String key) { + return _lastFetchedConfig[key]?.asInt() ?? _defaultParameters[key]! as int; + } + + /// Gets the value for a given key as a double. + double getDouble(String key) { + return _lastFetchedConfig[key]?.asDouble() ?? + _defaultParameters[key]! as double; + } + + /// Gets the value for a given key as a String. + String getString(String key) { + return _lastFetchedConfig[key]?.asString() ?? + _defaultParameters[key]! as String; + } + + /// Gets the [RemoteConfigValue] for a given key. + RemoteConfigValue getValue(String key) { + return _lastFetchedConfig[key] ?? + RemoteConfigValue( + const Utf8Codec().encode('${_defaultParameters[key]}'), + ValueSource.valueDefault, + ); + } + + /// Sets the [RemoteConfigSettings] for the current instance. + Future setConfigSettings( + RemoteConfigSettings remoteConfigSettings, + ) async { + assert(!remoteConfigSettings.fetchTimeout.isNegative); + assert(!remoteConfigSettings.minimumFetchInterval.isNegative); + // To be consistent with iOS fetchTimeout is set to the default + // 1 minute (60 seconds) if an attempt is made to set it to zero seconds. + if (remoteConfigSettings.fetchTimeout.inSeconds == 0) { + remoteConfigSettings.fetchTimeout = const Duration(seconds: 60); + } + _settings = remoteConfigSettings; + } + + /// Sets the default parameter values for the current instance. + Future setDefaults(Map defaultParameters) async { + _defaultParameters = defaultParameters; + } +} diff --git a/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/api.dart b/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/api.dart new file mode 100644 index 00000000..d8b65425 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/api.dart @@ -0,0 +1,6 @@ +// Copyright 2022 Invertase Limited. All rights reserved. +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file. +part of '../../firebase_remote_config_dart.dart'; + +class RemoteConfigApi {} diff --git a/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/remote_config_settings.dart b/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/remote_config_settings.dart new file mode 100644 index 00000000..3aadd712 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/remote_config_settings.dart @@ -0,0 +1,18 @@ +// ignore_for_file: require_trailing_commas +/// Defines the options for the corresponding Remote Config instance. +class RemoteConfigSettings { + /// Constructs an instance of [RemoteConfigSettings] with given [fetchTimeout] + /// and [minimumFetchInterval]. + RemoteConfigSettings({ + required this.fetchTimeout, + required this.minimumFetchInterval, + }); + + /// Maximum Duration to wait for a response when fetching configuration from + /// the Remote Config server. Defaults to one minute. + Duration fetchTimeout; + + /// Maximum age of a cached config before it is considered stale. Defaults + /// to twelve hours. + Duration minimumFetchInterval; +} diff --git a/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/remote_config_status.dart b/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/remote_config_status.dart new file mode 100644 index 00000000..f5cbd769 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/remote_config_status.dart @@ -0,0 +1,16 @@ +// ignore_for_file: require_trailing_commas +/// The outcome of the last attempt to fetch config from the +/// Firebase Remote Config server. +enum RemoteConfigFetchStatus { + /// Indicates instance has not yet attempted a fetch. + noFetchYet, + + /// Indicates the last fetch attempt succeeded. + success, + + /// Indicates the last fetch attempt failed. + failure, + + /// Indicates the last fetch attempt was rate-limited. + throttle +} diff --git a/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/remote_config_value.dart b/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/remote_config_value.dart new file mode 100644 index 00000000..f53beb2f --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/remote_config_value.dart @@ -0,0 +1,85 @@ +// ignore_for_file: require_trailing_commas +import 'dart:convert'; + +import 'package:meta/meta.dart'; + +/// ValueSource defines the possible sources of a config parameter value. +enum ValueSource { + /// The value was defined by a static constant. + valueStatic, + + /// The value was defined by default config. + valueDefault, + + /// The value was defined by fetched config. + valueRemote, +} + +/// RemoteConfigValue encapsulates the value and source of a Remote Config +/// parameter. +class RemoteConfigValue { + /// Wraps a value with metadata and type-safe getters. + @protected + RemoteConfigValue(this._value, this.source); + + /// Default value for String + static const String defaultValueForString = ''; + + /// Default value for Int + static const int defaultValueForInt = 0; + + /// Default value for Double + static const double defaultValueForDouble = 0; + + /// Default value for Bool + static const bool defaultValueForBool = false; + + final List? _value; + + /// Indicates at which source this value came from. + final ValueSource source; + + /// Decode value to string. + String asString() { + final value = _value; + return value != null + ? const Utf8Codec().decode(value) + : defaultValueForString; + } + + /// Decode value to int. + int asInt() { + final value = _value; + if (value != null) { + final strValue = const Utf8Codec().decode(value); + final intValue = int.tryParse(strValue) ?? defaultValueForInt; + return intValue; + } else { + return defaultValueForInt; + } + } + + /// Decode value to double. + double asDouble() { + final value = _value; + if (value != null) { + final strValue = const Utf8Codec().decode(value); + final doubleValue = double.tryParse(strValue) ?? defaultValueForDouble; + return doubleValue; + } else { + return defaultValueForDouble; + } + } + + /// Decode value to bool. + bool asBool() { + final value = _value; + if (value != null) { + final strValue = const Utf8Codec().decode(value); + final lowerCase = strValue.toLowerCase(); + return lowerCase == 'true' || lowerCase == '1'; + } else { + return defaultValueForBool; + } + } +} From bff7c987b6192269df5de6c51832f8bcd8d0301a Mon Sep 17 00:00:00 2001 From: Tim Whiting Date: Sat, 23 Apr 2022 23:26:45 -0600 Subject: [PATCH 03/30] add in todos, fix static errors --- .../lib/firebase_remote_config_dart.dart | 87 +++++++++---------- .../lib/src/remote_config_settings.dart | 4 +- 2 files changed, 44 insertions(+), 47 deletions(-) diff --git a/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart b/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart index ef8ab48c..e330811e 100644 --- a/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart +++ b/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart @@ -44,44 +44,57 @@ class RemoteConfig { }); } - RemoteConfigApi _api; + final _api = RemoteConfigApi(); /// Returns the [DateTime] of the last successful fetch. /// /// If no successful fetch has been made a [DateTime] representing /// the epoch (1970-01-01 UTC) is returned. - DateTime get lastFetchTime {} - final DateTime _lastFetchTime = DateTime.fromMillisecondsSinceEpoch(0); + DateTime get lastFetchTime => _lastFetchTime; + DateTime _lastFetchTime = DateTime.fromMillisecondsSinceEpoch(0); /// Returns the status of the last fetch attempt. - RemoteConfigFetchStatus get lastFetchStatus {} - final RemoteConfigFetchStatus _lastFetchStatus = - RemoteConfigFetchStatus.noFetchYet; - - /// Returns the [RemoteConfigSettings] of the current instance. - RemoteConfigSettings get settings {} + RemoteConfigFetchStatus get lastFetchStatus => _lastFetchStatus; + RemoteConfigFetchStatus _lastFetchStatus = RemoteConfigFetchStatus.noFetchYet; + + /// Returns a copy of the [RemoteConfigSettings] of the current instance. + RemoteConfigSettings get settings => RemoteConfigSettings( + fetchTimeout: _settings.fetchTimeout, + minimumFetchInterval: _settings.minimumFetchInterval, + ); RemoteConfigSettings _settings = RemoteConfigSettings(); + /// The latest cached config loaded from storage or the server + // TODO: Consider moving to storage provider? + Map _lastFetchedConfig = {}; + + /// Default parameters set via [setDefaults] + Map _defaultParameters = {}; + /// Makes the last fetched config available to getters. /// /// Returns a [bool] that is true if the config parameters /// were activated. Returns a [bool] that is false if the /// config parameters were already activated. Future activate() async { - final bool configChanged = await _api.activate(); - notifyListeners(); - return configChanged; + // TODO: Load config from storage into local map + // final bool configChanged + // return configChanged; + return false; } /// Ensures the last activated config are available to getters. - Future ensureInitialized() { - return _api.ensureInitialized(); + Future ensureInitialized() async { + // TODO: Wait for storage to be ready and loaded } /// Fetches and caches configuration from the Remote Config service. Future fetch() async { - // TODO: Implement - await _api.fetch(); + // TODO: Implement & wrap in try / catch etc + // await _api.fetch(); + // TODO: Track last fetch time, status, config in storage instead of here + _lastFetchTime = DateTime.now(); + _lastFetchStatus = RemoteConfigFetchStatus.success; _lastFetchedConfig = {}; } @@ -89,51 +102,34 @@ class RemoteConfig { /// /// Returns [bool] in the same way that is done for [activate]. Future fetchAndActivate() async { - final bool configChanged = await _api.fetchAndActivate(); - notifyListeners(); - _lastFetchedConfig = {}; - return configChanged; + await fetch(); + return activate(); } - Map _lastFetchedConfig = {}; - Map _defaultParameters; - /// Returns a Map of all Remote Config parameters. Map getAll() { return _lastFetchedConfig; } /// Gets the value for a given key as a bool. - bool getBool(String key) { - return _lastFetchedConfig[key]?.asBool() ?? - _defaultParameters[key]! as bool; - } + bool getBool(String key) => getValue(key).asBool(); /// Gets the value for a given key as an int. - int getInt(String key) { - return _lastFetchedConfig[key]?.asInt() ?? _defaultParameters[key]! as int; - } + int getInt(String key) => getValue(key).asInt(); /// Gets the value for a given key as a double. - double getDouble(String key) { - return _lastFetchedConfig[key]?.asDouble() ?? - _defaultParameters[key]! as double; - } + double getDouble(String key) => getValue(key).asDouble(); /// Gets the value for a given key as a String. - String getString(String key) { - return _lastFetchedConfig[key]?.asString() ?? - _defaultParameters[key]! as String; - } + String getString(String key) => getValue(key).asString(); /// Gets the [RemoteConfigValue] for a given key. - RemoteConfigValue getValue(String key) { - return _lastFetchedConfig[key] ?? - RemoteConfigValue( - const Utf8Codec().encode('${_defaultParameters[key]}'), - ValueSource.valueDefault, - ); - } + RemoteConfigValue getValue(String key) => + _lastFetchedConfig[key] ?? + RemoteConfigValue( + const Utf8Codec().encode('${_defaultParameters[key]}'), + ValueSource.valueDefault, + ); /// Sets the [RemoteConfigSettings] for the current instance. Future setConfigSettings( @@ -151,6 +147,7 @@ class RemoteConfig { /// Sets the default parameter values for the current instance. Future setDefaults(Map defaultParameters) async { + // TODO: Copy rather than trusting user to not mutate? _defaultParameters = defaultParameters; } } diff --git a/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/remote_config_settings.dart b/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/remote_config_settings.dart index 3aadd712..3c0beae0 100644 --- a/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/remote_config_settings.dart +++ b/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/remote_config_settings.dart @@ -4,8 +4,8 @@ class RemoteConfigSettings { /// Constructs an instance of [RemoteConfigSettings] with given [fetchTimeout] /// and [minimumFetchInterval]. RemoteConfigSettings({ - required this.fetchTimeout, - required this.minimumFetchInterval, + this.fetchTimeout = const Duration(minutes: 10), + this.minimumFetchInterval = const Duration(minutes: 12), }); /// Maximum Duration to wait for a response when fetching configuration from From dec2ddd9f8dca4e52fddcaf38bdc71308240990e Mon Sep 17 00:00:00 2001 From: Tim Whiting Date: Sun, 24 Apr 2022 13:17:51 -0600 Subject: [PATCH 04/30] start on storage --- .../lib/firebase_remote_config_dart.dart | 12 +++--- .../lib/src/internal/storage.dart | 39 +++++++++++++++++++ 2 files changed, 45 insertions(+), 6 deletions(-) create mode 100644 packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/storage.dart diff --git a/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart b/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart index e330811e..3c226e39 100644 --- a/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart +++ b/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart @@ -6,16 +6,19 @@ library firebase_remote_config_dart; import 'dart:convert'; +import 'package:firebase_auth_dart/firebase_auth_dart.dart'; import 'package:firebase_core_dart/firebase_core_dart.dart'; import 'src/remote_config_settings.dart'; import 'src/remote_config_status.dart'; import 'src/remote_config_value.dart'; + export 'src/remote_config_settings.dart'; export 'src/remote_config_status.dart'; export 'src/remote_config_value.dart'; part 'src/internal/api.dart'; +part 'src/internal/storage.dart'; /// The entry point for accessing Remote Config. /// @@ -45,17 +48,16 @@ class RemoteConfig { } final _api = RemoteConfigApi(); + final _storage = _RemoteConfigStorage(); /// Returns the [DateTime] of the last successful fetch. /// /// If no successful fetch has been made a [DateTime] representing /// the epoch (1970-01-01 UTC) is returned. - DateTime get lastFetchTime => _lastFetchTime; - DateTime _lastFetchTime = DateTime.fromMillisecondsSinceEpoch(0); + DateTime get lastFetchTime => _storage.lastFetchTime; /// Returns the status of the last fetch attempt. - RemoteConfigFetchStatus get lastFetchStatus => _lastFetchStatus; - RemoteConfigFetchStatus _lastFetchStatus = RemoteConfigFetchStatus.noFetchYet; + RemoteConfigFetchStatus get lastFetchStatus => _storage.lastFetchStatus; /// Returns a copy of the [RemoteConfigSettings] of the current instance. RemoteConfigSettings get settings => RemoteConfigSettings( @@ -65,8 +67,6 @@ class RemoteConfig { RemoteConfigSettings _settings = RemoteConfigSettings(); /// The latest cached config loaded from storage or the server - // TODO: Consider moving to storage provider? - Map _lastFetchedConfig = {}; /// Default parameters set via [setDefaults] Map _defaultParameters = {}; diff --git a/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/storage.dart b/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/storage.dart new file mode 100644 index 00000000..3a35ffc0 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/storage.dart @@ -0,0 +1,39 @@ +part of '../../firebase_remote_config_dart.dart'; + +class _RemoteConfigStorage { + final StorageBox _options = StorageBox.instanceOf('remote_config'); + + /// The last fetched config + Map _lastFetchedConfig = {}; + + DateTime _lastFetchTime = DateTime.fromMillisecondsSinceEpoch(0); + DateTime get lastFetchTime => _lastFetchTime; + + RemoteConfigFetchStatus _lastFetchStatus = RemoteConfigFetchStatus.noFetchYet; + RemoteConfigFetchStatus get lastFetchStatus => _lastFetchStatus; + + Future load() async { + final config = _options.getValue('lastFetchConfig') as Map; + _lastFetchedConfig = { + for (final entry in config.entries) + if (entry.value != null) + entry.key: + _RemoteConfigJson.fromJson(entry.value! as Map) + }; + } + + void update(Map config) { + _lastFetchTime = DateTime.now(); + _lastFetchStatus = RemoteConfigFetchStatus.success; + _lastFetchedConfig = config; + _options.putValue( + 'lastFetchConfig', + {for (final entry in config.entries) entry.key: entry.value.toJson()}, + ); + } +} + +extension _RemoteConfigJson on RemoteConfigValue { + static RemoteConfigValue fromJson(Map remoteConfigValue) {} + Map toJson() {} +} From e00a5d01866552eb87007983b5c9456bf1ef6c30 Mon Sep 17 00:00:00 2001 From: Tim Whiting Date: Sun, 24 Apr 2022 13:27:45 -0600 Subject: [PATCH 05/30] start on storage --- .../lib/firebase_remote_config_dart.dart | 10 +++---- .../lib/src/internal/storage.dart | 27 ++++++++++++++++--- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart b/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart index 3c226e39..9c121cb5 100644 --- a/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart +++ b/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart @@ -93,9 +93,9 @@ class RemoteConfig { // TODO: Implement & wrap in try / catch etc // await _api.fetch(); // TODO: Track last fetch time, status, config in storage instead of here - _lastFetchTime = DateTime.now(); - _lastFetchStatus = RemoteConfigFetchStatus.success; - _lastFetchedConfig = {}; + // _lastFetchTime = DateTime.now(); + // _lastFetchStatus = RemoteConfigFetchStatus.success; + // _lastFetchedConfig = {}; } /// Performs a fetch and activate operation, as a convenience. @@ -108,7 +108,7 @@ class RemoteConfig { /// Returns a Map of all Remote Config parameters. Map getAll() { - return _lastFetchedConfig; + return _storage._lastFetchedConfig; } /// Gets the value for a given key as a bool. @@ -125,7 +125,7 @@ class RemoteConfig { /// Gets the [RemoteConfigValue] for a given key. RemoteConfigValue getValue(String key) => - _lastFetchedConfig[key] ?? + _storage._lastFetchedConfig[key] ?? RemoteConfigValue( const Utf8Codec().encode('${_defaultParameters[key]}'), ValueSource.valueDefault, diff --git a/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/storage.dart b/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/storage.dart index 3a35ffc0..aac1079e 100644 --- a/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/storage.dart +++ b/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/storage.dart @@ -2,6 +2,15 @@ part of '../../firebase_remote_config_dart.dart'; class _RemoteConfigStorage { final StorageBox _options = StorageBox.instanceOf('remote_config'); + static const activeConfigKey = 'active_config'; + // TODO: Finish storage + static const activeConfigEtagKey = 'active_config_etag'; + static const lastFetchStatusKey = 'last_fetch_status'; + static const lastSuccessfulFetchTimeKey = + 'last_successful_fetch_timestamp_millis'; + static const lastSuccessfulFetchKey = 'last_successful_fetch_response'; + static const settingsKey = 'settings'; + static const throttleMetadataKey = 'throttle_metadata'; /// The last fetched config Map _lastFetchedConfig = {}; @@ -13,7 +22,7 @@ class _RemoteConfigStorage { RemoteConfigFetchStatus get lastFetchStatus => _lastFetchStatus; Future load() async { - final config = _options.getValue('lastFetchConfig') as Map; + final config = _options.getValue(activeConfigKey) as Map; _lastFetchedConfig = { for (final entry in config.entries) if (entry.value != null) @@ -27,13 +36,23 @@ class _RemoteConfigStorage { _lastFetchStatus = RemoteConfigFetchStatus.success; _lastFetchedConfig = config; _options.putValue( - 'lastFetchConfig', + activeConfigKey, {for (final entry in config.entries) entry.key: entry.value.toJson()}, ); } } extension _RemoteConfigJson on RemoteConfigValue { - static RemoteConfigValue fromJson(Map remoteConfigValue) {} - Map toJson() {} + static RemoteConfigValue fromJson(Map remoteConfigValue) { + return RemoteConfigValue( + const Utf8Codec().encode( + remoteConfigValue['value']! as String, + ), + ValueSource.values.byName(remoteConfigValue['source']! as String), + ); + } + + Map toJson() { + return {'source': source.name, 'value': asString()}; + } } From de2506d975673355354665febbf5b2230d28acd5 Mon Sep 17 00:00:00 2001 From: Tim Whiting Date: Sun, 24 Apr 2022 13:37:30 -0600 Subject: [PATCH 06/30] clean up some things --- .../lib/firebase_remote_config_dart.dart | 9 ++++----- .../lib/src/internal/storage.dart | 4 ++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart b/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart index 9c121cb5..f8c1a387 100644 --- a/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart +++ b/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart @@ -66,8 +66,6 @@ class RemoteConfig { ); RemoteConfigSettings _settings = RemoteConfigSettings(); - /// The latest cached config loaded from storage or the server - /// Default parameters set via [setDefaults] Map _defaultParameters = {}; @@ -77,7 +75,7 @@ class RemoteConfig { /// were activated. Returns a [bool] that is false if the /// config parameters were already activated. Future activate() async { - // TODO: Load config from storage into local map + // TODO: Load config from storage // final bool configChanged // return configChanged; return false; @@ -85,14 +83,15 @@ class RemoteConfig { /// Ensures the last activated config are available to getters. Future ensureInitialized() async { - // TODO: Wait for storage to be ready and loaded + // Unnecessary for desktop because we do synchronous file reads for storage + // Will be necessary if we ever support pure dart on web } /// Fetches and caches configuration from the Remote Config service. Future fetch() async { // TODO: Implement & wrap in try / catch etc // await _api.fetch(); - // TODO: Track last fetch time, status, config in storage instead of here + // TODO: Update these parameters in storage // _lastFetchTime = DateTime.now(); // _lastFetchStatus = RemoteConfigFetchStatus.success; // _lastFetchedConfig = {}; diff --git a/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/storage.dart b/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/storage.dart index aac1079e..d398795d 100644 --- a/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/storage.dart +++ b/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/storage.dart @@ -12,7 +12,7 @@ class _RemoteConfigStorage { static const settingsKey = 'settings'; static const throttleMetadataKey = 'throttle_metadata'; - /// The last fetched config + /// The latest cached config loaded from storage or the server Map _lastFetchedConfig = {}; DateTime _lastFetchTime = DateTime.fromMillisecondsSinceEpoch(0); @@ -21,7 +21,7 @@ class _RemoteConfigStorage { RemoteConfigFetchStatus _lastFetchStatus = RemoteConfigFetchStatus.noFetchYet; RemoteConfigFetchStatus get lastFetchStatus => _lastFetchStatus; - Future load() async { + Future loadFromStorage() async { final config = _options.getValue(activeConfigKey) as Map; _lastFetchedConfig = { for (final entry in config.entries) From faa19a67cb594c985199398ecd57b8c16e1f0407 Mon Sep 17 00:00:00 2001 From: Tim Whiting Date: Mon, 25 Apr 2022 20:55:45 -0600 Subject: [PATCH 07/30] finish basic storage, move on to rest api --- .../lib/firebase_remote_config_dart.dart | 23 +-- .../lib/src/internal/api.dart | 39 ++++- .../lib/src/internal/storage.dart | 133 +++++++++++++++--- .../lib/src/remote_config_settings.dart | 2 +- .../firebase_remote_config_dart/pubspec.yaml | 2 +- 5 files changed, 171 insertions(+), 28 deletions(-) diff --git a/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart b/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart index f8c1a387..b3e737a4 100644 --- a/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart +++ b/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart @@ -8,6 +8,7 @@ import 'dart:convert'; import 'package:firebase_auth_dart/firebase_auth_dart.dart'; import 'package:firebase_core_dart/firebase_core_dart.dart'; +import 'package:http/http.dart'; import 'src/remote_config_settings.dart'; import 'src/remote_config_status.dart'; @@ -26,7 +27,10 @@ part 'src/internal/storage.dart'; /// [RemoteConfig.instance] is async. // TODO(TimWhiting): Figure out how to introduce ChangeNotifier like class class RemoteConfig { - RemoteConfig._({required this.app}); + RemoteConfig._({required this.app}) + : _storage = _RemoveConfigStorageCache( + _RemoteConfigStorage(app.options.appId, app.name, ''), + ); // Cached instances of [FirebaseRemoteConfig]. static final Map _firebaseRemoteConfigInstances = {}; @@ -47,17 +51,19 @@ class RemoteConfig { }); } - final _api = RemoteConfigApi(); - final _storage = _RemoteConfigStorage(); + // final _api = _RemoteConfigApiClient(); + final _RemoveConfigStorageCache _storage; /// Returns the [DateTime] of the last successful fetch. /// /// If no successful fetch has been made a [DateTime] representing /// the epoch (1970-01-01 UTC) is returned. - DateTime get lastFetchTime => _storage.lastFetchTime; + DateTime get lastFetchTime => + _storage.lastFetchTime ?? DateTime.fromMicrosecondsSinceEpoch(0); /// Returns the status of the last fetch attempt. - RemoteConfigFetchStatus get lastFetchStatus => _storage.lastFetchStatus; + RemoteConfigFetchStatus get lastFetchStatus => + _storage.lastFetchStatus ?? RemoteConfigFetchStatus.noFetchYet; /// Returns a copy of the [RemoteConfigSettings] of the current instance. RemoteConfigSettings get settings => RemoteConfigSettings( @@ -78,6 +84,7 @@ class RemoteConfig { // TODO: Load config from storage // final bool configChanged // return configChanged; + return false; } @@ -106,8 +113,8 @@ class RemoteConfig { } /// Returns a Map of all Remote Config parameters. - Map getAll() { - return _storage._lastFetchedConfig; + Map? getAll() { + return _storage.activeConfig; } /// Gets the value for a given key as a bool. @@ -124,7 +131,7 @@ class RemoteConfig { /// Gets the [RemoteConfigValue] for a given key. RemoteConfigValue getValue(String key) => - _storage._lastFetchedConfig[key] ?? + _storage.activeConfig?[key] ?? RemoteConfigValue( const Utf8Codec().encode('${_defaultParameters[key]}'), ValueSource.valueDefault, diff --git a/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/api.dart b/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/api.dart index d8b65425..65febcee 100644 --- a/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/api.dart +++ b/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/api.dart @@ -3,4 +3,41 @@ // that can be found in the LICENSE file. part of '../../firebase_remote_config_dart.dart'; -class RemoteConfigApi {} +class _RemoteConfigApiClient { + _RemoteConfigApiClient( + this.projectId, + this.namespace, + this.apiKey, + this.appId, + ); + + final String projectId; + final String appId; + final String namespace; + final String apiKey; + Future fetch({String? eTag}) async { + // TODO: Firebase installations + final client = Client(); + final response = await client.post( + Uri.parse( + 'https://firebaseremoteconfig.googleapis.com/v1' + '/projects/$projectId/namespaces/$namespace:fetch?key=$apiKey', + ), + headers: { + 'Content-Type': 'application/json', + 'Content-Encoding': 'gzip', + // Deviates from pure decorator by not passing max-age header since we don't currently have + // service behavior using that header. + 'If-None-Match': eTag ?? '*' + }, + body: jsonEncode({ + // 'sdk_version': this.sdkVersion, + // 'app_instance_id': installationId, + // 'app_instance_id_token': installationToken, + 'app_id': appId, + // 'language_code': getUserLanguage() + }), + ); + // TODO: Timeout + } +} diff --git a/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/storage.dart b/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/storage.dart index d398795d..5f52ceb5 100644 --- a/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/storage.dart +++ b/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/storage.dart @@ -1,29 +1,53 @@ part of '../../firebase_remote_config_dart.dart'; class _RemoteConfigStorage { - final StorageBox _options = StorageBox.instanceOf('remote_config'); + _RemoteConfigStorage(this.appId, this.appName, this.namespace); + final String appId; + final String appName; + final String namespace; + final StorageBox _storageBox = + StorageBox.instanceOf('firebase_remote_config'); static const activeConfigKey = 'active_config'; - // TODO: Finish storage - static const activeConfigEtagKey = 'active_config_etag'; static const lastFetchStatusKey = 'last_fetch_status'; static const lastSuccessfulFetchTimeKey = 'last_successful_fetch_timestamp_millis'; + // TODO: Finish storage + static const activeConfigEtagKey = 'active_config_etag'; static const lastSuccessfulFetchKey = 'last_successful_fetch_response'; static const settingsKey = 'settings'; static const throttleMetadataKey = 'throttle_metadata'; /// The latest cached config loaded from storage or the server - Map _lastFetchedConfig = {}; + final Map _lastFetchedConfig = {}; - DateTime _lastFetchTime = DateTime.fromMillisecondsSinceEpoch(0); - DateTime get lastFetchTime => _lastFetchTime; + DateTime? get lastFetchTime => + (_storageBox.getValue(lastSuccessfulFetchTimeKey) as int?) + .mapNullable(DateTime.fromMicrosecondsSinceEpoch); - RemoteConfigFetchStatus _lastFetchStatus = RemoteConfigFetchStatus.noFetchYet; - RemoteConfigFetchStatus get lastFetchStatus => _lastFetchStatus; + /// Sets the last fetch time + set lastFetchTime(DateTime? value) { + _storageBox.putValue( + lastSuccessfulFetchTimeKey, + value?.microsecondsSinceEpoch, + ); + } - Future loadFromStorage() async { - final config = _options.getValue(activeConfigKey) as Map; - _lastFetchedConfig = { + RemoteConfigFetchStatus? get lastFetchStatus => + (_storageBox.getValue(lastFetchStatusKey) as String?) + .mapNullable(RemoteConfigFetchStatus.values.byName); + + /// Sets the last fetch status + set lastFetchStatus(RemoteConfigFetchStatus? value) { + _storageBox.putValue(lastFetchStatusKey, value); + } + + Map? get activeConfig { + final config = + _storageBox.getValue(activeConfigKey) as Map?; + if (config == null) { + return null; + } + return { for (final entry in config.entries) if (entry.value != null) entry.key: @@ -31,17 +55,83 @@ class _RemoteConfigStorage { }; } - void update(Map config) { - _lastFetchTime = DateTime.now(); - _lastFetchStatus = RemoteConfigFetchStatus.success; - _lastFetchedConfig = config; - _options.putValue( + set activeConfig(Map? config) { + _storageBox.putValue( activeConfigKey, - {for (final entry in config.entries) entry.key: entry.value.toJson()}, + config == null + ? null + : { + for (final entry in config.entries) + entry.key: entry.value.toJson(), + }, ); } } +/// A memory cache layer over storage to support the SDK's synchronous read requirements. +class _RemoveConfigStorageCache { + _RemoveConfigStorageCache(_RemoteConfigStorage storage) : _storage = storage; + final _RemoteConfigStorage _storage; + + /// Memory caches. + + /// The cached last fetch status + RemoteConfigFetchStatus? get lastFetchStatus => _lastFetchStatus; + RemoteConfigFetchStatus? _lastFetchStatus; + + /// The cached last fetch time + DateTime? get lastFetchTime => _lastFetchTime; + DateTime? _lastFetchTime; + + /// The cached last active config + Map? get activeConfig => _activeConfig; + Map? _activeConfig; + + /// Read-ahead getter + void loadFromStorage() { + // Note: + // 1. we consistently check for null to avoid clobbering defined values + // in memory + // 2. we defer awaiting to improve readability, as opposed to destructuring + // a Promise.all result, for example + + final lastFetchStatus = _storage.lastFetchStatus; + if (lastFetchStatus != null) { + _lastFetchStatus = lastFetchStatus; + } + + final lastFetchTime = _storage.lastFetchTime; + if (lastFetchTime != null) { + _lastFetchTime = lastFetchTime; + } + + final activeConfig = _storage.activeConfig; + if (activeConfig != null) { + _activeConfig = activeConfig; + } + } + + /// Write-through setters + + /// Sets the last fetch time + void setLastFetchTime(DateTime? value) { + _lastFetchTime = value; + _storage.lastFetchTime = value; + } + + /// Sets the last fetch status + void setLastFetchStatus(RemoteConfigFetchStatus? value) { + _lastFetchStatus = value; + _storage.lastFetchStatus = value; + } + + /// Sets the active config + void setActiveConfig(Map config) { + _activeConfig = config; + _storage.activeConfig = config; + } +} + extension _RemoteConfigJson on RemoteConfigValue { static RemoteConfigValue fromJson(Map remoteConfigValue) { return RemoteConfigValue( @@ -56,3 +146,12 @@ extension _RemoteConfigJson on RemoteConfigValue { return {'source': source.name, 'value': asString()}; } } + +extension _MapNullable on T? { + S? mapNullable(S? Function(T) f) { + if (this == null) { + return null; + } + return f(this as T); + } +} diff --git a/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/remote_config_settings.dart b/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/remote_config_settings.dart index 3c0beae0..59d54048 100644 --- a/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/remote_config_settings.dart +++ b/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/remote_config_settings.dart @@ -5,7 +5,7 @@ class RemoteConfigSettings { /// and [minimumFetchInterval]. RemoteConfigSettings({ this.fetchTimeout = const Duration(minutes: 10), - this.minimumFetchInterval = const Duration(minutes: 12), + this.minimumFetchInterval = const Duration(hours: 12), }); /// Maximum Duration to wait for a response when fetching configuration from diff --git a/packages/firebase_remote_config/firebase_remote_config_dart/pubspec.yaml b/packages/firebase_remote_config/firebase_remote_config_dart/pubspec.yaml index f8a2a6a1..318f9a42 100644 --- a/packages/firebase_remote_config/firebase_remote_config_dart/pubspec.yaml +++ b/packages/firebase_remote_config/firebase_remote_config_dart/pubspec.yaml @@ -5,7 +5,7 @@ repository: https://github.com/invertase/flutterfire_dart version: 0.1.0-dev.0 environment: - sdk: '>=2.12.0 <3.0.0' + sdk: '>=2.15.0 <3.0.0' dependencies: collection: ^1.15.0 From 943ed177be66ea17ff28b9ace438195da65c83aa Mon Sep 17 00:00:00 2001 From: Tim Whiting Date: Tue, 26 Apr 2022 18:13:34 -0600 Subject: [PATCH 08/30] finish dart package implementation --- .../lib/firebase_remote_config_dart.dart | 117 +++++++++++++----- .../lib/src/internal/api.dart | 61 +++++---- .../lib/src/internal/storage.dart | 30 +++-- .../lib/src/remote_config_value.dart | 25 ++-- .../firebase_remote_config_dart/pubspec.yaml | 1 + .../pubspec.yaml | 5 +- 6 files changed, 156 insertions(+), 83 deletions(-) diff --git a/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart b/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart index b3e737a4..93e8b4c6 100644 --- a/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart +++ b/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart @@ -4,10 +4,11 @@ library firebase_remote_config_dart; -import 'dart:convert'; +import 'dart:async'; import 'package:firebase_auth_dart/firebase_auth_dart.dart'; import 'package:firebase_core_dart/firebase_core_dart.dart'; +import 'package:firebaseapis/firebaseremoteconfig/v1.dart' as api; import 'package:http/http.dart'; import 'src/remote_config_settings.dart'; @@ -27,10 +28,8 @@ part 'src/internal/storage.dart'; /// [RemoteConfig.instance] is async. // TODO(TimWhiting): Figure out how to introduce ChangeNotifier like class class RemoteConfig { - RemoteConfig._({required this.app}) - : _storage = _RemoveConfigStorageCache( - _RemoteConfigStorage(app.options.appId, app.name, ''), - ); + RemoteConfig._({required this.app, required this.namespace}) + : _storage = _RemoteConfigStorage(app.options.appId, app.name, ''); // Cached instances of [FirebaseRemoteConfig]. static final Map _firebaseRemoteConfigInstances = {}; @@ -45,25 +44,33 @@ class RemoteConfig { /// Returns an instance using the specified [FirebaseApp]. // ignore: prefer_constructors_over_static_methods - static RemoteConfig instanceFor({required FirebaseApp app}) { + static RemoteConfig instanceFor({ + required FirebaseApp app, + String namespace = 'firebase', + }) { return _firebaseRemoteConfigInstances.putIfAbsent(app.name, () { - return RemoteConfig._(app: app); + return RemoteConfig._(app: app, namespace: namespace); }); } // final _api = _RemoteConfigApiClient(); - final _RemoveConfigStorageCache _storage; + late final _RemoteConfigStorageCache _storageCache = + _RemoteConfigStorageCache(_storage); + final _RemoteConfigStorage _storage; + + /// The namespace of the remote config instance + final String namespace; /// Returns the [DateTime] of the last successful fetch. /// /// If no successful fetch has been made a [DateTime] representing /// the epoch (1970-01-01 UTC) is returned. DateTime get lastFetchTime => - _storage.lastFetchTime ?? DateTime.fromMicrosecondsSinceEpoch(0); + _storageCache.lastFetchTime ?? DateTime.fromMicrosecondsSinceEpoch(0); /// Returns the status of the last fetch attempt. RemoteConfigFetchStatus get lastFetchStatus => - _storage.lastFetchStatus ?? RemoteConfigFetchStatus.noFetchYet; + _storageCache.lastFetchStatus ?? RemoteConfigFetchStatus.noFetchYet; /// Returns a copy of the [RemoteConfigSettings] of the current instance. RemoteConfigSettings get settings => RemoteConfigSettings( @@ -73,7 +80,17 @@ class RemoteConfig { RemoteConfigSettings _settings = RemoteConfigSettings(); /// Default parameters set via [setDefaults] - Map _defaultParameters = {}; + Map _defaultConfig = {}; + + /// Api + late final _RemoteConfigApiClient _api = _RemoteConfigApiClient( + app.options.projectId, + namespace, + app.options.apiKey, + app.options.appId, + _storage, + _storageCache, + ); /// Makes the last fetched config available to getters. /// @@ -81,27 +98,52 @@ class RemoteConfig { /// were activated. Returns a [bool] that is false if the /// config parameters were already activated. Future activate() async { - // TODO: Load config from storage - // final bool configChanged - // return configChanged; - - return false; + final lastSuccessfulFetchResponse = + _storage.getLastSuccessfulFetchResponse(); + // final activeConfigEtag = _storage.getActiveConfigEtag(); + if (lastSuccessfulFetchResponse?.parameters == null) { + // lastSuccessfulFetchResponse.eTag == null || + // lastSuccessfulFetchResponse.eTag == activeConfigEtag) { + return false; + } else { + _storageCache.setActiveConfig( + { + for (final entry in lastSuccessfulFetchResponse!.parameters!.entries) + entry.key: RemoteConfigValue.fromApi(entry.value) + }, + ); + // _storage.setActiveConfigEtag(lastSuccessfulFetchResponse.eTag); + return true; + } } + final _initialized = Completer(); + /// Ensures the last activated config are available to getters. Future ensureInitialized() async { - // Unnecessary for desktop because we do synchronous file reads for storage + // Somewhat unnecessary for desktop because we do synchronous file reads for storage // Will be necessary if we ever support pure dart on web + if (!_initialized.isCompleted) { + await _storageCache.loadFromStorage().then((_) { + _initialized.complete(); + }); + } + return _initialized.future; } /// Fetches and caches configuration from the Remote Config service. Future fetch() async { - // TODO: Implement & wrap in try / catch etc - // await _api.fetch(); - // TODO: Update these parameters in storage - // _lastFetchTime = DateTime.now(); - // _lastFetchStatus = RemoteConfigFetchStatus.success; - // _lastFetchedConfig = {}; + try { + await _api + .fetch(cacheMaxAge: settings.minimumFetchInterval) + .timeout(settings.fetchTimeout); + } on TimeoutException { + _storageCache.setLastFetchStatus(RemoteConfigFetchStatus.throttle); + rethrow; // TODO: Throw Firebase Exception + } on Exception { + _storageCache.setLastFetchStatus(RemoteConfigFetchStatus.failure); + rethrow; // TODO: Throw Firebase Exception + } } /// Performs a fetch and activate operation, as a convenience. @@ -114,7 +156,12 @@ class RemoteConfig { /// Returns a Map of all Remote Config parameters. Map? getAll() { - return _storage.activeConfig; + final allKeys = { + ...?_storageCache.activeConfig?.keys, + ..._defaultConfig.keys + }; + // Get the value for each key, respecting the default config + return {for (final key in allKeys) key: getValue(key)}; } /// Gets the value for a given key as a bool. @@ -130,12 +177,17 @@ class RemoteConfig { String getString(String key) => getValue(key).asString(); /// Gets the [RemoteConfigValue] for a given key. - RemoteConfigValue getValue(String key) => - _storage.activeConfig?[key] ?? - RemoteConfigValue( - const Utf8Codec().encode('${_defaultParameters[key]}'), - ValueSource.valueDefault, - ); + RemoteConfigValue getValue(String key) { + assert( + _initialized.isCompleted, + 'Please ensure ensureInitialized is called prior to getting a remote config value', + ); + return _storage.activeConfig?[key] ?? + RemoteConfigValue( + _defaultConfig[key].mapNullable((v) => '$v'), + ValueSource.valueDefault, + ); + } /// Sets the [RemoteConfigSettings] for the current instance. Future setConfigSettings( @@ -152,8 +204,7 @@ class RemoteConfig { } /// Sets the default parameter values for the current instance. - Future setDefaults(Map defaultParameters) async { - // TODO: Copy rather than trusting user to not mutate? - _defaultParameters = defaultParameters; + Future setDefaults(Map defaultConfig) async { + _defaultConfig = {...defaultConfig}; } } diff --git a/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/api.dart b/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/api.dart index 65febcee..967a6cdd 100644 --- a/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/api.dart +++ b/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/api.dart @@ -9,35 +9,48 @@ class _RemoteConfigApiClient { this.namespace, this.apiKey, this.appId, + this.storage, + this.storageCache, ); + final _api = api.FirebaseRemoteConfigApi(Client()); + final _RemoteConfigStorage storage; + final _RemoteConfigStorageCache storageCache; final String projectId; final String appId; final String namespace; final String apiKey; - Future fetch({String? eTag}) async { - // TODO: Firebase installations - final client = Client(); - final response = await client.post( - Uri.parse( - 'https://firebaseremoteconfig.googleapis.com/v1' - '/projects/$projectId/namespaces/$namespace:fetch?key=$apiKey', - ), - headers: { - 'Content-Type': 'application/json', - 'Content-Encoding': 'gzip', - // Deviates from pure decorator by not passing max-age header since we don't currently have - // service behavior using that header. - 'If-None-Match': eTag ?? '*' - }, - body: jsonEncode({ - // 'sdk_version': this.sdkVersion, - // 'app_instance_id': installationId, - // 'app_instance_id_token': installationToken, - 'app_id': appId, - // 'language_code': getUserLanguage() - }), - ); - // TODO: Timeout + + bool isCachedDataFresh( + Duration cacheMaxAge, + DateTime? lastSuccessfulFetchTimestamp, + ) { + if (lastSuccessfulFetchTimestamp == null) { + return false; + } + + final cacheAgeMillis = DateTime.now().millisecondsSinceEpoch - + lastSuccessfulFetchTimestamp.millisecondsSinceEpoch; + return cacheAgeMillis <= cacheMaxAge.inMilliseconds; + } + + Future fetch({ + String? eTag, + required Duration cacheMaxAge, + }) async { + final lastSuccessfulFetchTimestamp = storage.lastFetchTime; + final lastSuccessfulFetchResponse = + storage.getLastSuccessfulFetchResponse(); + + if (lastSuccessfulFetchResponse != null && + isCachedDataFresh(cacheMaxAge, lastSuccessfulFetchTimestamp)) { + return lastSuccessfulFetchResponse; + } + + // TODO: Handle errors in fetch + final remoteConfig = await _api.projects.getRemoteConfig(projectId); + storageCache.setLastFetchTime(DateTime.now()); + storage.setLastSuccessfulFetchResponse(remoteConfig); + return remoteConfig; } } diff --git a/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/storage.dart b/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/storage.dart index 5f52ceb5..be5e3553 100644 --- a/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/storage.dart +++ b/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/storage.dart @@ -11,14 +11,12 @@ class _RemoteConfigStorage { static const lastFetchStatusKey = 'last_fetch_status'; static const lastSuccessfulFetchTimeKey = 'last_successful_fetch_timestamp_millis'; - // TODO: Finish storage - static const activeConfigEtagKey = 'active_config_etag'; static const lastSuccessfulFetchKey = 'last_successful_fetch_response'; - static const settingsKey = 'settings'; - static const throttleMetadataKey = 'throttle_metadata'; - /// The latest cached config loaded from storage or the server - final Map _lastFetchedConfig = {}; + // TODO: Do we need these in storage? + // static const activeConfigEtagKey = 'active_config_etag'; + // static const settingsKey = 'settings'; + // static const throttleMetadataKey = 'throttle_metadata'; DateTime? get lastFetchTime => (_storageBox.getValue(lastSuccessfulFetchTimeKey) as int?) @@ -66,11 +64,21 @@ class _RemoteConfigStorage { }, ); } + + api.RemoteConfig? getLastSuccessfulFetchResponse() { + return _storageBox + .getValue(lastSuccessfulFetchKey) + .mapNullable((v) => api.RemoteConfig.fromJson(v as Map)); + } + + void setLastSuccessfulFetchResponse(api.RemoteConfig remoteConfig) { + _storageBox.putValue(lastSuccessfulFetchKey, remoteConfig.toJson()); + } } /// A memory cache layer over storage to support the SDK's synchronous read requirements. -class _RemoveConfigStorageCache { - _RemoveConfigStorageCache(_RemoteConfigStorage storage) : _storage = storage; +class _RemoteConfigStorageCache { + _RemoteConfigStorageCache(_RemoteConfigStorage storage) : _storage = storage; final _RemoteConfigStorage _storage; /// Memory caches. @@ -88,7 +96,7 @@ class _RemoveConfigStorageCache { Map? _activeConfig; /// Read-ahead getter - void loadFromStorage() { + Future loadFromStorage() async { // Note: // 1. we consistently check for null to avoid clobbering defined values // in memory @@ -135,9 +143,7 @@ class _RemoveConfigStorageCache { extension _RemoteConfigJson on RemoteConfigValue { static RemoteConfigValue fromJson(Map remoteConfigValue) { return RemoteConfigValue( - const Utf8Codec().encode( - remoteConfigValue['value']! as String, - ), + remoteConfigValue['value']! as String, ValueSource.values.byName(remoteConfigValue['source']! as String), ); } diff --git a/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/remote_config_value.dart b/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/remote_config_value.dart index f53beb2f..c1f63593 100644 --- a/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/remote_config_value.dart +++ b/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/remote_config_value.dart @@ -1,6 +1,6 @@ // ignore_for_file: require_trailing_commas -import 'dart:convert'; +import 'package:firebaseapis/firebaseremoteconfig/v1.dart' as api; import 'package:meta/meta.dart'; /// ValueSource defines the possible sources of a config parameter value. @@ -22,6 +22,14 @@ class RemoteConfigValue { @protected RemoteConfigValue(this._value, this.source); + /// Creates a new RemoteConfigValue from the api + factory RemoteConfigValue.fromApi(api.RemoteConfigParameter value) { + return RemoteConfigValue( + value.defaultValue?.value, + ValueSource.valueRemote, + ); + } + /// Default value for String static const String defaultValueForString = ''; @@ -34,7 +42,7 @@ class RemoteConfigValue { /// Default value for Bool static const bool defaultValueForBool = false; - final List? _value; + final String? _value; /// Indicates at which source this value came from. final ValueSource source; @@ -42,17 +50,14 @@ class RemoteConfigValue { /// Decode value to string. String asString() { final value = _value; - return value != null - ? const Utf8Codec().decode(value) - : defaultValueForString; + return value ?? defaultValueForString; } /// Decode value to int. int asInt() { final value = _value; if (value != null) { - final strValue = const Utf8Codec().decode(value); - final intValue = int.tryParse(strValue) ?? defaultValueForInt; + final intValue = int.tryParse(value) ?? defaultValueForInt; return intValue; } else { return defaultValueForInt; @@ -63,8 +68,7 @@ class RemoteConfigValue { double asDouble() { final value = _value; if (value != null) { - final strValue = const Utf8Codec().decode(value); - final doubleValue = double.tryParse(strValue) ?? defaultValueForDouble; + final doubleValue = double.tryParse(value) ?? defaultValueForDouble; return doubleValue; } else { return defaultValueForDouble; @@ -75,8 +79,7 @@ class RemoteConfigValue { bool asBool() { final value = _value; if (value != null) { - final strValue = const Utf8Codec().decode(value); - final lowerCase = strValue.toLowerCase(); + final lowerCase = value.toLowerCase(); return lowerCase == 'true' || lowerCase == '1'; } else { return defaultValueForBool; diff --git a/packages/firebase_remote_config/firebase_remote_config_dart/pubspec.yaml b/packages/firebase_remote_config/firebase_remote_config_dart/pubspec.yaml index 318f9a42..da4a9637 100644 --- a/packages/firebase_remote_config/firebase_remote_config_dart/pubspec.yaml +++ b/packages/firebase_remote_config/firebase_remote_config_dart/pubspec.yaml @@ -11,6 +11,7 @@ dependencies: collection: ^1.15.0 firebase_auth_dart: ^0.1.1 firebase_core_dart: ^0.1.1 + firebaseapis: ^0.1.1 http: ^0.13.4 meta: ^1.7.0 diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/pubspec.yaml b/packages/firebase_remote_config/firebase_remote_config_desktop/pubspec.yaml index 00f5e784..2fb89799 100644 --- a/packages/firebase_remote_config/firebase_remote_config_desktop/pubspec.yaml +++ b/packages/firebase_remote_config/firebase_remote_config_desktop/pubspec.yaml @@ -9,12 +9,11 @@ environment: flutter: '>=1.20.0' dependencies: - firebase_remote_config: ^2.0.5 - firebase_remote_config_platform_interface: any firebase_core: ^1.10.0 firebase_core_dart: ^0.1.1 firebase_core_desktop: ^0.1.1 - firebase_functions_dart: ^0.1.0 + firebase_remote_config: ^2.0.5 + firebase_remote_config_platform_interface: any flutter: sdk: flutter http: ^0.13.4 From 093c6e7bc59da435adc6c701196b7ac37f757e61 Mon Sep 17 00:00:00 2001 From: Tim Whiting Date: Tue, 26 Apr 2022 18:47:32 -0600 Subject: [PATCH 09/30] - move json serialization to dataclass - implement desktop interface --- .../lib/firebase_remote_config_dart.dart | 58 +++++++- .../lib/src/internal/storage.dart | 18 +-- .../lib/src/remote_config_value.dart | 13 ++ .../lib/firebase_remote_config_desktop.dart | 129 ++++++++++++++++-- .../pubspec.yaml | 6 +- 5 files changed, 198 insertions(+), 26 deletions(-) diff --git a/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart b/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart index 93e8b4c6..b8528712 100644 --- a/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart +++ b/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart @@ -26,7 +26,7 @@ part 'src/internal/storage.dart'; /// /// You can get an instance by calling [RemoteConfig.instance]. Note /// [RemoteConfig.instance] is async. -// TODO(TimWhiting): Figure out how to introduce ChangeNotifier like class +// TODO: The flutter implementation uses a ChangeNotifier to let someone listen should we use StateNotifier? class RemoteConfig { RemoteConfig._({required this.app, required this.namespace}) : _storage = _RemoteConfigStorage(app.options.appId, app.name, ''); @@ -155,7 +155,7 @@ class RemoteConfig { } /// Returns a Map of all Remote Config parameters. - Map? getAll() { + Map getAll() { final allKeys = { ...?_storageCache.activeConfig?.keys, ..._defaultConfig.keys @@ -207,4 +207,58 @@ class RemoteConfig { Future setDefaults(Map defaultConfig) async { _defaultConfig = {...defaultConfig}; } + + /// Sets values to be immediately available + void setInitialValues(Map remoteConfigValues) { + final fetchTimeout = Duration(seconds: remoteConfigValues['fetchTimeout']); + final minimumFetchInterval = + Duration(seconds: remoteConfigValues['minimumFetchInterval']); + final lastFetchMillis = remoteConfigValues['lastFetchTime']; + final lastFetchStatus = remoteConfigValues['lastFetchStatus']; + + _settings = RemoteConfigSettings( + fetchTimeout: fetchTimeout, + minimumFetchInterval: minimumFetchInterval, + ); + + _storageCache.setLastFetchTime( + DateTime.fromMillisecondsSinceEpoch(lastFetchMillis), + ); + _storageCache.setLastFetchStatus(_parseFetchStatus(lastFetchStatus)); + _storageCache.setActiveConfig( + _parseParameters(remoteConfigValues['parameters']), + ); + } + + RemoteConfigFetchStatus _parseFetchStatus(String? status) { + try { + return status.mapNullable(RemoteConfigFetchStatus.values.byName) ?? + RemoteConfigFetchStatus.noFetchYet; + } on Exception { + return RemoteConfigFetchStatus.noFetchYet; + } + } + + Map _parseParameters( + Map rawParameters, + ) { + final parameters = {}; + for (final key in rawParameters.keys) { + final rawValue = rawParameters[key] as Map; + parameters[key] = RemoteConfigValue( + rawValue['value'], + _parseValueSource(rawValue['source']), + ); + } + return parameters; + } + + ValueSource _parseValueSource(String? sourceStr) { + try { + return sourceStr.mapNullable(ValueSource.values.byName) ?? + ValueSource.valueStatic; + } on Exception { + return ValueSource.valueStatic; + } + } } diff --git a/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/storage.dart b/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/storage.dart index be5e3553..4776a4a7 100644 --- a/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/storage.dart +++ b/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/storage.dart @@ -48,8 +48,9 @@ class _RemoteConfigStorage { return { for (final entry in config.entries) if (entry.value != null) - entry.key: - _RemoteConfigJson.fromJson(entry.value! as Map) + entry.key: RemoteConfigValue.fromJson( + entry.value! as Map, + ) }; } @@ -140,19 +141,6 @@ class _RemoteConfigStorageCache { } } -extension _RemoteConfigJson on RemoteConfigValue { - static RemoteConfigValue fromJson(Map remoteConfigValue) { - return RemoteConfigValue( - remoteConfigValue['value']! as String, - ValueSource.values.byName(remoteConfigValue['source']! as String), - ); - } - - Map toJson() { - return {'source': source.name, 'value': asString()}; - } -} - extension _MapNullable on T? { S? mapNullable(S? Function(T) f) { if (this == null) { diff --git a/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/remote_config_value.dart b/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/remote_config_value.dart index c1f63593..ad9d9ff2 100644 --- a/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/remote_config_value.dart +++ b/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/remote_config_value.dart @@ -30,6 +30,19 @@ class RemoteConfigValue { ); } + /// Creates a new RemoteConfigValue from json + factory RemoteConfigValue.fromJson(Map remoteConfigValue) { + return RemoteConfigValue( + remoteConfigValue['value']! as String, + ValueSource.values.byName(remoteConfigValue['source']! as String), + ); + } + + /// Converts the RemoteConfigValue to a json map + Map toJson() { + return {'source': source.name, 'value': asString()}; + } + /// Default value for String static const String defaultValueForString = ''; diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/lib/firebase_remote_config_desktop.dart b/packages/firebase_remote_config/firebase_remote_config_desktop/lib/firebase_remote_config_desktop.dart index f1223ec5..42dce7f9 100644 --- a/packages/firebase_remote_config/firebase_remote_config_desktop/lib/firebase_remote_config_desktop.dart +++ b/packages/firebase_remote_config/firebase_remote_config_desktop/lib/firebase_remote_config_desktop.dart @@ -4,9 +4,12 @@ library firebase_remote_config_desktop; +import 'dart:convert'; + import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_core_dart/firebase_core_dart.dart' as core_dart; - +import 'package:firebase_remote_config_dart/firebase_remote_config_dart.dart' + as remote_config; import 'package:firebase_remote_config_platform_interface/firebase_remote_config_platform_interface.dart'; /// Desktop implementation of FirebaseRemoteConfigPlatform for managing FirebaseRemoteConfig @@ -17,10 +20,6 @@ class FirebaseRemoteConfigDesktop extends FirebaseRemoteConfigPlatform { }) : _app = core_dart.Firebase.app(app.name), super(appInstance: app); - FirebaseRemoteConfigDesktop._() - : _app = null, - super(appInstance: null); - /// Called by PluginRegistry to register this plugin as the implementation for Desktop static void registerWith() { FirebaseRemoteConfigPlatform.instance = @@ -32,14 +31,128 @@ class FirebaseRemoteConfigDesktop extends FirebaseRemoteConfigPlatform { /// // ignore: prefer_constructors_over_static_methods static FirebaseRemoteConfigDesktop get instance { - return FirebaseRemoteConfigDesktop._(); + return FirebaseRemoteConfigDesktop(app: Firebase.app()); } - final core_dart.FirebaseApp? _app; - @override FirebaseRemoteConfigPlatform delegateFor({ FirebaseApp? app, }) => FirebaseRemoteConfigDesktop(app: app ?? Firebase.app()); + + final core_dart.FirebaseApp _app; + late final remote_config.RemoteConfig _remoteConfig = + remote_config.RemoteConfig.instanceFor(app: _app); + + /// Sets any initial values on the instance. + /// + /// Platforms with Method Channels can provide constant values to be + /// available before the instance has initialized to prevent unnecessary + /// async calls. + @override + FirebaseRemoteConfigPlatform setInitialValues({ + required Map remoteConfigValues, + }) { + _remoteConfig.setInitialValues(remoteConfigValues); + return this; + } + + @override + + /// Returns the [DateTime] of the last successful fetch. + /// + /// If no successful fetch has been made a [DateTime] representing + /// the epoch (1970-01-01 UTC) is returned. + DateTime get lastFetchTime => _remoteConfig.lastFetchTime; + + @override + + /// Returns the status of the last fetch attempt. + + RemoteConfigFetchStatus get lastFetchStatus => + RemoteConfigFetchStatus.values[_remoteConfig.lastFetchStatus.index]; + + /// Returns the [RemoteConfigSettings] of the current instance. + + @override + RemoteConfigSettings get settings => RemoteConfigSettings( + fetchTimeout: _remoteConfig.settings.fetchTimeout, + minimumFetchInterval: _remoteConfig.settings.minimumFetchInterval, + ); + + /// Makes the last fetched config available to getters. + /// + /// Returns a [bool] that is true if the config parameters + /// were activated. Returns a [bool] that is false if the + /// config parameters were already activated. + @override + Future activate() => _remoteConfig.activate(); + + /// Ensures the last activated config are available to getters. + @override + Future ensureInitialized() => _remoteConfig.ensureInitialized(); + + /// Fetches and caches configuration from the Remote Config service. + @override + Future fetch() => _remoteConfig.fetch(); + + /// Performs a fetch and activate operation, as a convenience. + /// + /// Returns [bool] in the same way that is done for [activate]. + @override + Future fetchAndActivate() => _remoteConfig.fetchAndActivate(); + + /// Returns a Map of all Remote Config parameters. + @override + Map getAll() => { + for (final entry in _remoteConfig.getAll().entries) + entry.key: RemoteConfigValue( + utf8.encode(entry.value.asString()), + ValueSource.values[entry.value.source.index], + ), + }; + + /// Gets the value for a given key as a bool. + @override + bool getBool(String key) => _remoteConfig.getBool(key); + + /// Gets the value for a given key as an int. + @override + int getInt(String key) => _remoteConfig.getInt(key); + + /// Gets the value for a given key as a double. + @override + double getDouble(String key) => _remoteConfig.getDouble(key); + + /// Gets the value for a given key as a String. + @override + String getString(String key) => _remoteConfig.getString(key); + + /// Gets the [RemoteConfigValue] for a given key. + @override + RemoteConfigValue getValue(String key) { + final value = _remoteConfig.getValue(key); + return RemoteConfigValue( + utf8.encode(value.asString()), + ValueSource.values[value.source.index], + ); + } + + /// Sets the [RemoteConfigSettings] for the current instance. + @override + Future setConfigSettings( + RemoteConfigSettings remoteConfigSettings, + ) async { + await _remoteConfig.setConfigSettings( + remote_config.RemoteConfigSettings( + fetchTimeout: remoteConfigSettings.fetchTimeout, + minimumFetchInterval: remoteConfigSettings.minimumFetchInterval, + ), + ); + } + + /// Sets the default parameter values for the current instance. + @override + Future setDefaults(Map defaultParameters) => + _remoteConfig.setDefaults(defaultParameters); } diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/pubspec.yaml b/packages/firebase_remote_config/firebase_remote_config_desktop/pubspec.yaml index 2fb89799..ae909761 100644 --- a/packages/firebase_remote_config/firebase_remote_config_desktop/pubspec.yaml +++ b/packages/firebase_remote_config/firebase_remote_config_desktop/pubspec.yaml @@ -12,7 +12,7 @@ dependencies: firebase_core: ^1.10.0 firebase_core_dart: ^0.1.1 firebase_core_desktop: ^0.1.1 - firebase_remote_config: ^2.0.5 + firebase_remote_config_dart: any firebase_remote_config_platform_interface: any flutter: sdk: flutter @@ -26,6 +26,10 @@ dev_dependencies: mockito: ^5.0.0 plugin_platform_interface: ^2.0.2 +dependency_overrides: + firebase_remote_config_dart: + path: ../firebase_remote_config_dart + flutter: plugin: implements: firebase_remote_config From be121f029255971474e5b41248e398ca116121e9 Mon Sep 17 00:00:00 2001 From: Tim Whiting Date: Wed, 27 Apr 2022 17:01:13 -0600 Subject: [PATCH 10/30] start working on tests --- .../lib/firebase_remote_config_dart.dart | 31 ++++++++++++------- .../firebase_remote_config_dart/pubspec.yaml | 3 +- .../test/firebase_remote_config_test.dart | 19 ++++++++++++ .../lib/firebase_remote_config_desktop.dart | 6 ++-- 4 files changed, 44 insertions(+), 15 deletions(-) create mode 100644 packages/firebase_remote_config/firebase_remote_config_dart/test/firebase_remote_config_test.dart diff --git a/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart b/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart index b8528712..1d57f100 100644 --- a/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart +++ b/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart @@ -10,6 +10,7 @@ import 'package:firebase_auth_dart/firebase_auth_dart.dart'; import 'package:firebase_core_dart/firebase_core_dart.dart'; import 'package:firebaseapis/firebaseremoteconfig/v1.dart' as api; import 'package:http/http.dart'; +import 'package:meta/meta.dart'; import 'src/remote_config_settings.dart'; import 'src/remote_config_status.dart'; @@ -24,32 +25,37 @@ part 'src/internal/storage.dart'; /// The entry point for accessing Remote Config. /// -/// You can get an instance by calling [RemoteConfig.instance]. Note -/// [RemoteConfig.instance] is async. +/// You can get an instance by calling [FirebaseRemoteConfig.instance]. Note +/// [FirebaseRemoteConfig.instance] is async. // TODO: The flutter implementation uses a ChangeNotifier to let someone listen should we use StateNotifier? -class RemoteConfig { - RemoteConfig._({required this.app, required this.namespace}) - : _storage = _RemoteConfigStorage(app.options.appId, app.name, ''); +class FirebaseRemoteConfig { + /// Creates a new instance of FirebaseRemoteConfig + @visibleForTesting + FirebaseRemoteConfig({ + required this.app, + this.namespace = 'firebase', + }) : _storage = _RemoteConfigStorage(app.options.appId, app.name, namespace); // Cached instances of [FirebaseRemoteConfig]. - static final Map _firebaseRemoteConfigInstances = {}; + static final Map + _firebaseRemoteConfigInstances = {}; /// The [FirebaseApp] this instance was initialized with. final FirebaseApp app; /// Returns an instance using the default [FirebaseApp]. - static RemoteConfig get instance { - return RemoteConfig.instanceFor(app: Firebase.app()); + static FirebaseRemoteConfig get instance { + return FirebaseRemoteConfig.instanceFor(app: Firebase.app()); } /// Returns an instance using the specified [FirebaseApp]. // ignore: prefer_constructors_over_static_methods - static RemoteConfig instanceFor({ + static FirebaseRemoteConfig instanceFor({ required FirebaseApp app, String namespace = 'firebase', }) { return _firebaseRemoteConfigInstances.putIfAbsent(app.name, () { - return RemoteConfig._(app: app, namespace: namespace); + return FirebaseRemoteConfig(app: app, namespace: namespace); }); } @@ -209,7 +215,10 @@ class RemoteConfig { } /// Sets values to be immediately available - void setInitialValues(Map remoteConfigValues) { + void setInitialValues({Map? remoteConfigValues}) { + if (remoteConfigValues == null) { + return; + } final fetchTimeout = Duration(seconds: remoteConfigValues['fetchTimeout']); final minimumFetchInterval = Duration(seconds: remoteConfigValues['minimumFetchInterval']); diff --git a/packages/firebase_remote_config/firebase_remote_config_dart/pubspec.yaml b/packages/firebase_remote_config/firebase_remote_config_dart/pubspec.yaml index da4a9637..072e3e2f 100644 --- a/packages/firebase_remote_config/firebase_remote_config_dart/pubspec.yaml +++ b/packages/firebase_remote_config/firebase_remote_config_dart/pubspec.yaml @@ -9,12 +9,13 @@ environment: dependencies: collection: ^1.15.0 - firebase_auth_dart: ^0.1.1 + firebase_auth_dart: ^0.1.2 firebase_core_dart: ^0.1.1 firebaseapis: ^0.1.1 http: ^0.13.4 meta: ^1.7.0 dev_dependencies: + mockito: ^5.1.0 test: ^1.16.0 diff --git a/packages/firebase_remote_config/firebase_remote_config_dart/test/firebase_remote_config_test.dart b/packages/firebase_remote_config/firebase_remote_config_dart/test/firebase_remote_config_test.dart new file mode 100644 index 00000000..d379ed7b --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_dart/test/firebase_remote_config_test.dart @@ -0,0 +1,19 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:firebase_core_dart/firebase_core_dart.dart'; +import 'package:test/test.dart'; +// ignore: import_of_legacy_library_into_null_safe + +FirebaseOptions get firebaseOptions => const FirebaseOptions( + appId: '1:448618578101:ios:0b650370bb29e29cac3efc', + apiKey: 'AIzaSyAgUhHU8wSJgO5MVNy95tMT07NEjzMOfz0', + projectId: 'react-native-firebase-testing', + messagingSenderId: '448618578101', + ); +void main() { + group('Remote Config', () { + test('Test', () {}); + }); +} diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/lib/firebase_remote_config_desktop.dart b/packages/firebase_remote_config/firebase_remote_config_desktop/lib/firebase_remote_config_desktop.dart index 42dce7f9..b521d4d3 100644 --- a/packages/firebase_remote_config/firebase_remote_config_desktop/lib/firebase_remote_config_desktop.dart +++ b/packages/firebase_remote_config/firebase_remote_config_desktop/lib/firebase_remote_config_desktop.dart @@ -41,8 +41,8 @@ class FirebaseRemoteConfigDesktop extends FirebaseRemoteConfigPlatform { FirebaseRemoteConfigDesktop(app: app ?? Firebase.app()); final core_dart.FirebaseApp _app; - late final remote_config.RemoteConfig _remoteConfig = - remote_config.RemoteConfig.instanceFor(app: _app); + late final remote_config.FirebaseRemoteConfig _remoteConfig = + remote_config.FirebaseRemoteConfig.instanceFor(app: _app); /// Sets any initial values on the instance. /// @@ -53,7 +53,7 @@ class FirebaseRemoteConfigDesktop extends FirebaseRemoteConfigPlatform { FirebaseRemoteConfigPlatform setInitialValues({ required Map remoteConfigValues, }) { - _remoteConfig.setInitialValues(remoteConfigValues); + _remoteConfig.setInitialValues(remoteConfigValues: remoteConfigValues); return this; } From 76832c630e7e53f560017a20c760a7ed1402e813 Mon Sep 17 00:00:00 2001 From: Tim Whiting Date: Wed, 27 Apr 2022 22:15:58 -0600 Subject: [PATCH 11/30] basic tests --- .../lib/firebase_remote_config_dart.dart | 82 ++++--- .../lib/src/internal/api.dart | 12 +- .../lib/src/internal/storage.dart | 46 ++-- .../firebase_remote_config_dart/pubspec.yaml | 2 +- .../test/firebase_remote_config_test.dart | 216 +++++++++++++++++- 5 files changed, 295 insertions(+), 63 deletions(-) diff --git a/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart b/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart index 1d57f100..a826a5c9 100644 --- a/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart +++ b/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart @@ -6,11 +6,11 @@ library firebase_remote_config_dart; import 'dart:async'; -import 'package:firebase_auth_dart/firebase_auth_dart.dart'; import 'package:firebase_core_dart/firebase_core_dart.dart'; import 'package:firebaseapis/firebaseremoteconfig/v1.dart' as api; import 'package:http/http.dart'; import 'package:meta/meta.dart'; +import 'package:storagebox/storagebox.dart'; import 'src/remote_config_settings.dart'; import 'src/remote_config_status.dart'; @@ -34,10 +34,10 @@ class FirebaseRemoteConfig { FirebaseRemoteConfig({ required this.app, this.namespace = 'firebase', - }) : _storage = _RemoteConfigStorage(app.options.appId, app.name, namespace); + }) : storage = _RemoteConfigStorage(app.options.appId, app.name, namespace); // Cached instances of [FirebaseRemoteConfig]. - static final Map + static final Map> _firebaseRemoteConfigInstances = {}; /// The [FirebaseApp] this instance was initialized with. @@ -51,18 +51,26 @@ class FirebaseRemoteConfig { /// Returns an instance using the specified [FirebaseApp]. // ignore: prefer_constructors_over_static_methods static FirebaseRemoteConfig instanceFor({ - required FirebaseApp app, + FirebaseApp? app, String namespace = 'firebase', }) { - return _firebaseRemoteConfigInstances.putIfAbsent(app.name, () { - return FirebaseRemoteConfig(app: app, namespace: namespace); - }); + final _app = app ?? Firebase.app(); + if (_firebaseRemoteConfigInstances[_app.name] == null) { + _firebaseRemoteConfigInstances[_app.name] = {}; + } + return _firebaseRemoteConfigInstances[_app.name]!.putIfAbsent( + namespace, + () => FirebaseRemoteConfig(app: _app, namespace: namespace), + ); } - // final _api = _RemoteConfigApiClient(); - late final _RemoteConfigStorageCache _storageCache = - _RemoteConfigStorageCache(_storage); - final _RemoteConfigStorage _storage; + @visibleForTesting + // ignore: library_private_types_in_public_api, public_member_api_docs + late final _RemoteConfigStorageCache storageCache = + _RemoteConfigStorageCache(storage); + @visibleForTesting + // ignore: library_private_types_in_public_api, public_member_api_docs + final _RemoteConfigStorage storage; /// The namespace of the remote config instance final String namespace; @@ -72,11 +80,11 @@ class FirebaseRemoteConfig { /// If no successful fetch has been made a [DateTime] representing /// the epoch (1970-01-01 UTC) is returned. DateTime get lastFetchTime => - _storageCache.lastFetchTime ?? DateTime.fromMicrosecondsSinceEpoch(0); + storageCache.lastFetchTime ?? DateTime.fromMicrosecondsSinceEpoch(0); /// Returns the status of the last fetch attempt. RemoteConfigFetchStatus get lastFetchStatus => - _storageCache.lastFetchStatus ?? RemoteConfigFetchStatus.noFetchYet; + storageCache.lastFetchStatus ?? RemoteConfigFetchStatus.noFetchYet; /// Returns a copy of the [RemoteConfigSettings] of the current instance. RemoteConfigSettings get settings => RemoteConfigSettings( @@ -89,13 +97,14 @@ class FirebaseRemoteConfig { Map _defaultConfig = {}; /// Api - late final _RemoteConfigApiClient _api = _RemoteConfigApiClient( + @visibleForTesting + late RemoteConfigApiClient api = RemoteConfigApiClient( app.options.projectId, namespace, app.options.apiKey, app.options.appId, - _storage, - _storageCache, + storage, + storageCache, ); /// Makes the last fetched config available to getters. @@ -105,20 +114,20 @@ class FirebaseRemoteConfig { /// config parameters were already activated. Future activate() async { final lastSuccessfulFetchResponse = - _storage.getLastSuccessfulFetchResponse(); - // final activeConfigEtag = _storage.getActiveConfigEtag(); + storage.getLastSuccessfulFetchResponse(); + // final activeConfigEtag = storage.getActiveConfigEtag(); if (lastSuccessfulFetchResponse?.parameters == null) { // lastSuccessfulFetchResponse.eTag == null || // lastSuccessfulFetchResponse.eTag == activeConfigEtag) { return false; } else { - _storageCache.setActiveConfig( + storageCache.setActiveConfig( { for (final entry in lastSuccessfulFetchResponse!.parameters!.entries) entry.key: RemoteConfigValue.fromApi(entry.value) }, ); - // _storage.setActiveConfigEtag(lastSuccessfulFetchResponse.eTag); + // storage.setActiveConfigEtag(lastSuccessfulFetchResponse.eTag); return true; } } @@ -130,7 +139,7 @@ class FirebaseRemoteConfig { // Somewhat unnecessary for desktop because we do synchronous file reads for storage // Will be necessary if we ever support pure dart on web if (!_initialized.isCompleted) { - await _storageCache.loadFromStorage().then((_) { + await storageCache.loadFromStorage().then((_) { _initialized.complete(); }); } @@ -140,16 +149,17 @@ class FirebaseRemoteConfig { /// Fetches and caches configuration from the Remote Config service. Future fetch() async { try { - await _api + await api .fetch(cacheMaxAge: settings.minimumFetchInterval) .timeout(settings.fetchTimeout); } on TimeoutException { - _storageCache.setLastFetchStatus(RemoteConfigFetchStatus.throttle); + storageCache.setLastFetchStatus(RemoteConfigFetchStatus.throttle); rethrow; // TODO: Throw Firebase Exception } on Exception { - _storageCache.setLastFetchStatus(RemoteConfigFetchStatus.failure); + storageCache.setLastFetchStatus(RemoteConfigFetchStatus.failure); rethrow; // TODO: Throw Firebase Exception } + storageCache.setLastFetchStatus(RemoteConfigFetchStatus.success); } /// Performs a fetch and activate operation, as a convenience. @@ -163,7 +173,7 @@ class FirebaseRemoteConfig { /// Returns a Map of all Remote Config parameters. Map getAll() { final allKeys = { - ...?_storageCache.activeConfig?.keys, + ...?storageCache.activeConfig?.keys, ..._defaultConfig.keys }; // Get the value for each key, respecting the default config @@ -188,7 +198,7 @@ class FirebaseRemoteConfig { _initialized.isCompleted, 'Please ensure ensureInitialized is called prior to getting a remote config value', ); - return _storage.activeConfig?[key] ?? + return storage.activeConfig?[key] ?? RemoteConfigValue( _defaultConfig[key].mapNullable((v) => '$v'), ValueSource.valueDefault, @@ -230,11 +240,11 @@ class FirebaseRemoteConfig { minimumFetchInterval: minimumFetchInterval, ); - _storageCache.setLastFetchTime( + storageCache.setLastFetchTime( DateTime.fromMillisecondsSinceEpoch(lastFetchMillis), ); - _storageCache.setLastFetchStatus(_parseFetchStatus(lastFetchStatus)); - _storageCache.setActiveConfig( + storageCache.setLastFetchStatus(_parseFetchStatus(lastFetchStatus)); + storageCache.setActiveConfig( _parseParameters(remoteConfigValues['parameters']), ); } @@ -263,11 +273,15 @@ class FirebaseRemoteConfig { } ValueSource _parseValueSource(String? sourceStr) { - try { - return sourceStr.mapNullable(ValueSource.values.byName) ?? - ValueSource.valueStatic; - } on Exception { - return ValueSource.valueStatic; + switch (sourceStr) { + case 'remote': + return ValueSource.valueRemote; + case 'default': + return ValueSource.valueDefault; + case 'static': + return ValueSource.valueStatic; + default: + return ValueSource.valueStatic; } } } diff --git a/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/api.dart b/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/api.dart index 967a6cdd..3a673c3b 100644 --- a/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/api.dart +++ b/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/api.dart @@ -1,10 +1,13 @@ // Copyright 2022 Invertase Limited. All rights reserved. // Use of this source code is governed by a BSD-style license // that can be found in the LICENSE file. +// ignore_for_file: public_member_api_docs, library_private_types_in_public_api + part of '../../firebase_remote_config_dart.dart'; -class _RemoteConfigApiClient { - _RemoteConfigApiClient( +@visibleForTesting +class RemoteConfigApiClient { + RemoteConfigApiClient( this.projectId, this.namespace, this.apiKey, @@ -12,7 +15,10 @@ class _RemoteConfigApiClient { this.storage, this.storageCache, ); - final _api = api.FirebaseRemoteConfigApi(Client()); + Client get httpClient => _httpClient; + final _httpClient = Client(); + + late final _api = api.FirebaseRemoteConfigApi(httpClient); final _RemoteConfigStorage storage; final _RemoteConfigStorageCache storageCache; diff --git a/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/storage.dart b/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/storage.dart index 4776a4a7..2590515e 100644 --- a/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/storage.dart +++ b/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/storage.dart @@ -5,8 +5,7 @@ class _RemoteConfigStorage { final String appId; final String appName; final String namespace; - final StorageBox _storageBox = - StorageBox.instanceOf('firebase_remote_config'); + final StorageBox _storageBox = StorageBox('firebase_remote_config'); static const activeConfigKey = 'active_config'; static const lastFetchStatusKey = 'last_fetch_status'; static const lastSuccessfulFetchTimeKey = @@ -19,29 +18,33 @@ class _RemoteConfigStorage { // static const throttleMetadataKey = 'throttle_metadata'; DateTime? get lastFetchTime => - (_storageBox.getValue(lastSuccessfulFetchTimeKey) as int?) + (_storageBox[lastSuccessfulFetchTimeKey] as int?) .mapNullable(DateTime.fromMicrosecondsSinceEpoch); /// Sets the last fetch time set lastFetchTime(DateTime? value) { - _storageBox.putValue( - lastSuccessfulFetchTimeKey, - value?.microsecondsSinceEpoch, - ); + if (value == null) { + _storageBox.remove(lastSuccessfulFetchKey); + return; + } + _storageBox[lastSuccessfulFetchTimeKey] = value.microsecondsSinceEpoch; } RemoteConfigFetchStatus? get lastFetchStatus => - (_storageBox.getValue(lastFetchStatusKey) as String?) + (_storageBox[lastFetchStatusKey] as String?) .mapNullable(RemoteConfigFetchStatus.values.byName); /// Sets the last fetch status set lastFetchStatus(RemoteConfigFetchStatus? value) { - _storageBox.putValue(lastFetchStatusKey, value); + if (value == null) { + _storageBox.remove(lastFetchStatusKey); + return; + } + _storageBox[lastFetchStatusKey] = value.name; } Map? get activeConfig { - final config = - _storageBox.getValue(activeConfigKey) as Map?; + final config = _storageBox[activeConfigKey] as Map?; if (config == null) { return null; } @@ -55,25 +58,22 @@ class _RemoteConfigStorage { } set activeConfig(Map? config) { - _storageBox.putValue( - activeConfigKey, - config == null - ? null - : { - for (final entry in config.entries) - entry.key: entry.value.toJson(), - }, - ); + if (config == null) { + _storageBox.remove(activeConfigKey); + return; + } + _storageBox[activeConfigKey] = { + for (final entry in config.entries) entry.key: entry.value.toJson(), + }; } api.RemoteConfig? getLastSuccessfulFetchResponse() { - return _storageBox - .getValue(lastSuccessfulFetchKey) + return _storageBox[lastSuccessfulFetchKey] .mapNullable((v) => api.RemoteConfig.fromJson(v as Map)); } void setLastSuccessfulFetchResponse(api.RemoteConfig remoteConfig) { - _storageBox.putValue(lastSuccessfulFetchKey, remoteConfig.toJson()); + _storageBox[lastSuccessfulFetchKey] = remoteConfig.toJson(); } } diff --git a/packages/firebase_remote_config/firebase_remote_config_dart/pubspec.yaml b/packages/firebase_remote_config/firebase_remote_config_dart/pubspec.yaml index 072e3e2f..b3aaa490 100644 --- a/packages/firebase_remote_config/firebase_remote_config_dart/pubspec.yaml +++ b/packages/firebase_remote_config/firebase_remote_config_dart/pubspec.yaml @@ -9,11 +9,11 @@ environment: dependencies: collection: ^1.15.0 - firebase_auth_dart: ^0.1.2 firebase_core_dart: ^0.1.1 firebaseapis: ^0.1.1 http: ^0.13.4 meta: ^1.7.0 + storagebox: ^0.1.0+2 dev_dependencies: mockito: ^5.1.0 diff --git a/packages/firebase_remote_config/firebase_remote_config_dart/test/firebase_remote_config_test.dart b/packages/firebase_remote_config/firebase_remote_config_dart/test/firebase_remote_config_test.dart index d379ed7b..165a9784 100644 --- a/packages/firebase_remote_config/firebase_remote_config_dart/test/firebase_remote_config_test.dart +++ b/packages/firebase_remote_config/firebase_remote_config_dart/test/firebase_remote_config_test.dart @@ -3,6 +3,10 @@ // found in the LICENSE file. import 'package:firebase_core_dart/firebase_core_dart.dart'; +import 'package:firebase_remote_config_dart/firebase_remote_config_dart.dart'; +import 'package:http/http.dart'; +import 'package:http/testing.dart'; +import 'package:storagebox/storagebox.dart'; import 'package:test/test.dart'; // ignore: import_of_legacy_library_into_null_safe @@ -13,7 +17,215 @@ FirebaseOptions get firebaseOptions => const FirebaseOptions( messagingSenderId: '448618578101', ); void main() { - group('Remote Config', () { - test('Test', () {}); + setUpAll(() async { + await Firebase.initializeApp( + options: const FirebaseOptions( + apiKey: '', + appId: '', + messagingSenderId: '', + projectId: '', + ), + ); }); + + setUp(() async {}); + + group('FirebaseRemoteConfig', () { + group('.instance', () { + test('uses the default FirebaseApp instance', () { + expect(FirebaseRemoteConfig.instance.app, isA()); + expect( + FirebaseRemoteConfig.instance.app.name, + equals(defaultFirebaseAppName), + ); + }); + + test('uses the default firebase namespace', () { + expect(FirebaseRemoteConfig.instance.namespace, equals('firebase')); + }); + }); + + group('.instanceFor()', () { + late FirebaseApp secondaryApp; + + setUpAll(() async { + secondaryApp = await Firebase.initializeApp( + name: 'foo', + options: const FirebaseOptions( + apiKey: '123', + appId: '123', + messagingSenderId: '123', + projectId: '123', + ), + ); + }); + + test('accepts a secondary FirebaseApp instance', () async { + final remoteConfigSecondary = + FirebaseRemoteConfig.instanceFor(app: secondaryApp); + expect(remoteConfigSecondary.app, isA()); + expect(remoteConfigSecondary.app.name, secondaryApp.name); + }); + + test('accepts a secondary FirebaseApp instance and custom namespace', + () async { + final remoteConfigSecondary = FirebaseRemoteConfig.instanceFor( + app: secondaryApp, + namespace: 'firebase2', + ); + expect(remoteConfigSecondary.app, isA()); + expect(remoteConfigSecondary.app.name, secondaryApp.name); + expect(remoteConfigSecondary.namespace, equals('firebase2')); + }); + + test('accepts a custom namespace for the default app', () async { + final remoteConfig = + FirebaseRemoteConfig.instanceFor(namespace: 'firebase2'); + expect(remoteConfig.app, isA()); + expect(remoteConfig.app.name, defaultFirebaseAppName); + expect(remoteConfig.namespace, equals('firebase2')); + }); + + test('caches instances by FirebaseApp and namespace', () async { + // Instances using the same namespace and FirebaseApp should be identical. + final remoteConfig1 = + FirebaseRemoteConfig.instanceFor(namespace: 'firebase2'); + final remoteConfig2 = + FirebaseRemoteConfig.instanceFor(namespace: 'firebase2'); + expect(remoteConfig1, same(remoteConfig2)); + + // Instances using the same namespace but a different FirebaseApp should not be identical. + final remoteConfig3 = FirebaseRemoteConfig.instanceFor( + app: secondaryApp, + namespace: 'firebase2', + ); + expect(remoteConfig1, isNot(same(remoteConfig3))); + + // Instances using the same FirebaseApp but a different namespace should not be identical. + final remoteConfig4 = + FirebaseRemoteConfig.instanceFor(namespace: 'firebase1'); + expect(remoteConfig1, isNot(same(remoteConfig4))); + }); + }); + + group('Api', () { + late final FirebaseRemoteConfig rc; + setUpAll(() { + rc = FirebaseRemoteConfig.instance; + + StorageBox('firebase_remote_config').clear(); + }); + test('Fetch updates time, status', () async { + rc.api = FakeConfigClient( + rc.app.options.projectId, + 'firebase', + rc.app.options.apiKey, + rc.app.options.appId, + rc.storage, + rc.storageCache, + ); + final before = rc.lastFetchTime; + expect(before, equals(DateTime.fromMillisecondsSinceEpoch(0))); + expect(rc.lastFetchStatus, equals(RemoteConfigFetchStatus.noFetchYet)); + + await rc.fetch(); + + expect(rc.lastFetchStatus, equals(RemoteConfigFetchStatus.success)); + final after = rc.lastFetchTime; + expect(before, isNot(after)); + expect(() => rc.getValue('key'), throwsA(isA())); + await rc.ensureInitialized(); + expect(rc.getAll(), isEmpty); + expect(rc.storage.getLastSuccessfulFetchResponse(), isNotNull); + }); + + test('Activate updates config', () async { + expect(rc.getAll(), isEmpty); + await rc.activate(); + expect(rc.getAll(), isNotEmpty); + expect(rc.getAll().length, equals(2)); + expect( + rc.getAll().values.every((v) => v.source == ValueSource.valueRemote), + true, + ); + }); + + test('Default config values', () { + rc.setDefaults({ + 'string_key': 'default', + 'number_key': 42, + 'bool_key': true, + 'foo': 'new foo' + }); + + expect(rc.getAll().length, 5); + expect(rc.getString('bar'), equals('bar')); + expect(rc.getString('foo'), equals('real foo')); + expect(rc.getString('string_key'), equals('default')); + expect(rc.getInt('number_key'), equals(42)); + expect(rc.getBool('bool_key'), equals(true)); + expect(rc.getValue('foo').source, equals(ValueSource.valueRemote)); + expect( + rc + .getAll() + .values + .where((v) => v.source == ValueSource.valueRemote) + .length, + equals(2), + ); + expect( + rc + .getAll() + .values + .where((v) => v.source == ValueSource.valueDefault) + .length, + equals(3), + ); + rc.setDefaults({}); + expect(rc.getAll().length, 2); + }); + + test('InitialValues overrides active config', () { + rc.setInitialValues( + remoteConfigValues: { + 'fetchTimeout': 10, + 'minimumFetchInterval': 200, + 'lastFetchTime': DateTime.now().millisecondsSinceEpoch, + 'lastFetchStatus': 'success', + 'parameters': { + 'bar': {'source': 'remote', 'value': 'new bar'}, + } + }, + ); + expect(rc.getAll().length, equals(1)); + }); + }); + }); +} + +class FakeConfigClient extends RemoteConfigApiClient { + FakeConfigClient( + String projectId, + String namespace, + String apiKey, + String appId, + storage, + storageCache, + ) : super(projectId, namespace, apiKey, appId, storage, storageCache); + @override + Client httpClient = MockClient( + (request) => Future.value( + Response( + ''' +{ + "parameters": { + "bar": {"defaultValue": {"value": "bar"}}, + "foo": {"defaultValue": {"value": "real foo"}} + } +}''', + 200, + headers: {'content-type': 'application/json'}, + ), + ), + ); } From dc87e696b1d77d3b08221182f651f8cd60b8f7d6 Mon Sep 17 00:00:00 2001 From: Tim Whiting Date: Wed, 27 Apr 2022 22:38:48 -0600 Subject: [PATCH 12/30] start on example continue testing --- .../example/.gitignore | 47 ++ .../example/.metadata | 42 ++ .../example/README.md | 16 + .../example/analysis_options.yaml | 33 + .../example/lib/main.dart | 107 +++ .../example/macos/.gitignore | 7 + .../macos/Flutter/Flutter-Debug.xcconfig | 2 + .../macos/Flutter/Flutter-Release.xcconfig | 2 + .../macos/Runner.xcodeproj/project.pbxproj | 632 ++++++++++++++++++ .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/xcschemes/Runner.xcscheme | 87 +++ .../contents.xcworkspacedata | 10 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../example/macos/Runner/AppDelegate.swift | 9 + .../AppIcon.appiconset/Contents.json | 68 ++ .../AppIcon.appiconset/app_icon_1024.png | Bin 0 -> 46993 bytes .../AppIcon.appiconset/app_icon_128.png | Bin 0 -> 3276 bytes .../AppIcon.appiconset/app_icon_16.png | Bin 0 -> 1429 bytes .../AppIcon.appiconset/app_icon_256.png | Bin 0 -> 5933 bytes .../AppIcon.appiconset/app_icon_32.png | Bin 0 -> 1243 bytes .../AppIcon.appiconset/app_icon_512.png | Bin 0 -> 14800 bytes .../AppIcon.appiconset/app_icon_64.png | Bin 0 -> 1874 bytes .../macos/Runner/Base.lproj/MainMenu.xib | 343 ++++++++++ .../macos/Runner/Configs/AppInfo.xcconfig | 14 + .../macos/Runner/Configs/Debug.xcconfig | 2 + .../macos/Runner/Configs/Release.xcconfig | 2 + .../macos/Runner/Configs/Warnings.xcconfig | 13 + .../macos/Runner/DebugProfile.entitlements | 12 + .../example/macos/Runner/Info.plist | 32 + .../macos/Runner/MainFlutterWindow.swift | 15 + .../example/macos/Runner/Release.entitlements | 8 + .../example/pubspec.yaml | 30 + .../example/web/favicon.png | Bin 0 -> 917 bytes .../example/web/icons/Icon-192.png | Bin 0 -> 5292 bytes .../example/web/icons/Icon-512.png | Bin 0 -> 8252 bytes .../example/web/icons/Icon-maskable-192.png | Bin 0 -> 5594 bytes .../example/web/icons/Icon-maskable-512.png | Bin 0 -> 20998 bytes .../example/web/index.html | 58 ++ .../example/web/manifest.json | 35 + .../example/windows/.gitignore | 17 + .../example/windows/CMakeLists.txt | 101 +++ .../example/windows/flutter/CMakeLists.txt | 104 +++ .../flutter/generated_plugin_registrant.cc | 11 + .../flutter/generated_plugin_registrant.h | 15 + .../windows/flutter/generated_plugins.cmake | 23 + .../example/windows/runner/CMakeLists.txt | 32 + .../example/windows/runner/Runner.rc | 121 ++++ .../example/windows/runner/flutter_window.cpp | 61 ++ .../example/windows/runner/flutter_window.h | 33 + .../example/windows/runner/main.cpp | 43 ++ .../example/windows/runner/resource.h | 16 + .../windows/runner/resources/app_icon.ico | Bin 0 -> 33772 bytes .../windows/runner/runner.exe.manifest | 20 + .../example/windows/runner/utils.cpp | 64 ++ .../example/windows/runner/utils.h | 19 + .../example/windows/runner/win32_window.cpp | 245 +++++++ .../example/windows/runner/win32_window.h | 98 +++ 57 files changed, 2665 insertions(+) create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/example/.gitignore create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/example/.metadata create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/example/README.md create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/example/analysis_options.yaml create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/example/lib/main.dart create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/.gitignore create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Flutter/Flutter-Debug.xcconfig create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Flutter/Flutter-Release.xcconfig create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner.xcodeproj/project.pbxproj create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner.xcworkspace/contents.xcworkspacedata create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner/AppDelegate.swift create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner/Base.lproj/MainMenu.xib create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner/Configs/AppInfo.xcconfig create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner/Configs/Debug.xcconfig create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner/Configs/Release.xcconfig create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner/Configs/Warnings.xcconfig create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner/DebugProfile.entitlements create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner/Info.plist create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner/MainFlutterWindow.swift create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner/Release.entitlements create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/example/pubspec.yaml create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/example/web/favicon.png create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/example/web/icons/Icon-192.png create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/example/web/icons/Icon-512.png create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/example/web/icons/Icon-maskable-192.png create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/example/web/icons/Icon-maskable-512.png create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/example/web/index.html create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/example/web/manifest.json create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/.gitignore create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/CMakeLists.txt create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/flutter/CMakeLists.txt create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/flutter/generated_plugin_registrant.cc create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/flutter/generated_plugin_registrant.h create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/flutter/generated_plugins.cmake create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/runner/CMakeLists.txt create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/runner/Runner.rc create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/runner/flutter_window.cpp create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/runner/flutter_window.h create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/runner/main.cpp create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/runner/resource.h create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/runner/resources/app_icon.ico create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/runner/runner.exe.manifest create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/runner/utils.cpp create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/runner/utils.h create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/runner/win32_window.cpp create mode 100644 packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/runner/win32_window.h diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/example/.gitignore b/packages/firebase_remote_config/firebase_remote_config_desktop/example/.gitignore new file mode 100644 index 00000000..a8e938c0 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_desktop/example/.gitignore @@ -0,0 +1,47 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +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 +.packages +.pub-cache/ +.pub/ +/build/ + +# Web related +lib/generated_plugin_registrant.dart + +# 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/packages/firebase_remote_config/firebase_remote_config_desktop/example/.metadata b/packages/firebase_remote_config/firebase_remote_config_desktop/example/.metadata new file mode 100644 index 00000000..92da7fc3 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_desktop/example/.metadata @@ -0,0 +1,42 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled. + +version: + revision: 34d84209cf7ece5fb82ac6a114f86a2291754824 + channel: master + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 34d84209cf7ece5fb82ac6a114f86a2291754824 + base_revision: 34d84209cf7ece5fb82ac6a114f86a2291754824 + - platform: android + create_revision: 34d84209cf7ece5fb82ac6a114f86a2291754824 + base_revision: 34d84209cf7ece5fb82ac6a114f86a2291754824 + - platform: ios + create_revision: 34d84209cf7ece5fb82ac6a114f86a2291754824 + base_revision: 34d84209cf7ece5fb82ac6a114f86a2291754824 + - platform: macos + create_revision: 34d84209cf7ece5fb82ac6a114f86a2291754824 + base_revision: 34d84209cf7ece5fb82ac6a114f86a2291754824 + - platform: web + create_revision: 34d84209cf7ece5fb82ac6a114f86a2291754824 + base_revision: 34d84209cf7ece5fb82ac6a114f86a2291754824 + - platform: windows + create_revision: 34d84209cf7ece5fb82ac6a114f86a2291754824 + base_revision: 34d84209cf7ece5fb82ac6a114f86a2291754824 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/example/README.md b/packages/firebase_remote_config/firebase_remote_config_desktop/example/README.md new file mode 100644 index 00000000..2b3fce4c --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_desktop/example/README.md @@ -0,0 +1,16 @@ +# example + +A new Flutter project. + +## 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/packages/firebase_remote_config/firebase_remote_config_desktop/example/analysis_options.yaml b/packages/firebase_remote_config/firebase_remote_config_desktop/example/analysis_options.yaml new file mode 100644 index 00000000..52bd346b --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_desktop/example/analysis_options.yaml @@ -0,0 +1,33 @@ +# 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 + +analyzer: + enable-experiment: + - super-parameters + +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-lang.github.io/linter/lints/index.html. + # + # 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/packages/firebase_remote_config/firebase_remote_config_desktop/example/lib/main.dart b/packages/firebase_remote_config/firebase_remote_config_desktop/example/lib/main.dart new file mode 100644 index 00000000..bba9e207 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_desktop/example/lib/main.dart @@ -0,0 +1,107 @@ +// ignore_for_file: require_trailing_commas, avoid_print +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; + +import 'package:firebase_core/firebase_core.dart'; +import 'package:firebase_remote_config/firebase_remote_config.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +void main() { + WidgetsFlutterBinding.ensureInitialized(); + runApp( + MaterialApp( + title: 'Remote Config Example', + home: FutureBuilder( + future: setupRemoteConfig(), + builder: (BuildContext context, + AsyncSnapshot snapshot) { + return snapshot.hasData + ? WelcomeWidget(remoteConfig: snapshot.requireData) + : Container(); + }, + ), + ), + ); +} + +class WelcomeWidget extends AnimatedWidget { + const WelcomeWidget({ + Key? key, + required this.remoteConfig, + }) : super(listenable: remoteConfig, key: key); + + final FirebaseRemoteConfig remoteConfig; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Remote Config Example'), + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('Welcome ${remoteConfig.getString('welcome')}'), + const SizedBox( + height: 20, + ), + Text('(${remoteConfig.getValue('welcome').source})'), + Text('(${remoteConfig.lastFetchTime})'), + Text('(${remoteConfig.lastFetchStatus})'), + ], + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: () async { + try { + // Using zero duration to force fetching from remote server. + await remoteConfig.setConfigSettings(RemoteConfigSettings( + fetchTimeout: const Duration(seconds: 10), + minimumFetchInterval: Duration.zero, + )); + await remoteConfig.fetchAndActivate(); + } on PlatformException catch (exception) { + // Fetch exception. + print(exception); + } catch (exception) { + print( + 'Unable to fetch remote config. Cached or default values will be ' + 'used'); + print(exception); + } + }, + child: const Icon(Icons.refresh), + ), + ); + } +} + +Future setupRemoteConfig() async { + await Firebase.initializeApp( + options: const FirebaseOptions( + apiKey: 'AIzaSyAgUhHU8wSJgO5MVNy95tMT07NEjzMOfz0', + authDomain: 'react-native-firebase-testing.firebaseapp.com', + databaseURL: 'https://react-native-firebase-testing.firebaseio.com', + projectId: 'react-native-firebase-testing', + storageBucket: 'react-native-firebase-testing.appspot.com', + messagingSenderId: '448618578101', + appId: '1:448618578101:web:772d484dc9eb15e9ac3efc', + measurementId: 'G-0N1G9FLDZE'), + ); + final FirebaseRemoteConfig remoteConfig = FirebaseRemoteConfig.instance; + await remoteConfig.setConfigSettings(RemoteConfigSettings( + fetchTimeout: const Duration(seconds: 10), + minimumFetchInterval: const Duration(hours: 1), + )); + await remoteConfig.setDefaults({ + 'welcome': 'default welcome', + 'hello': 'default hello', + }); + await remoteConfig.fetchAndActivate(); + return remoteConfig; +} diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/.gitignore b/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/.gitignore new file mode 100644 index 00000000..746adbb6 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Flutter/Flutter-Debug.xcconfig b/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 00000000..4b81f9b2 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Flutter/Flutter-Release.xcconfig b/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 00000000..5caa9d15 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner.xcodeproj/project.pbxproj b/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 00000000..cd60f37f --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,632 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 51; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + F7709730E5BF4D3F1A5397CF /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 222E7A3E92C67E9697008298 /* Pods_Runner.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 222E7A3E92C67E9697008298 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 2719D7C507CEF7EE788EE64B /* 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 = ""; }; + 2D9705881FC3AE1BC984F88D /* 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 = ""; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = example.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 75223A6352E15EA78D9ED5D5 /* 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 = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + F7709730E5BF4D3F1A5397CF /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 23D96C61C67E1A82BC254DBC /* Pods */ = { + isa = PBXGroup; + children = ( + 2D9705881FC3AE1BC984F88D /* Pods-Runner.debug.xcconfig */, + 2719D7C507CEF7EE788EE64B /* Pods-Runner.release.xcconfig */, + 75223A6352E15EA78D9ED5D5 /* Pods-Runner.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + 23D96C61C67E1A82BC254DBC /* Pods */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* example.app */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 222E7A3E92C67E9697008298 /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 1B8A3350ADFA1A4213EA1DBB /* [CP] Check Pods Manifest.lock */, + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + 893803F1C4D4533D97F50294 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* example.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1300; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 1B8A3350ADFA1A4213EA1DBB /* [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; + }; + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; + 893803F1C4D4533D97F50294 /* [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 */ + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + 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_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + 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_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + 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_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + 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_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + 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_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + 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_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + 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_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 00000000..fb7259e1 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner.xcworkspace/contents.xcworkspacedata b/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..21a3cc14 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner/AppDelegate.swift b/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner/AppDelegate.swift new file mode 100644 index 00000000..d53ef643 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner/AppDelegate.swift @@ -0,0 +1,9 @@ +import Cocoa +import FlutterMacOS + +@NSApplicationMain +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } +} diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..a2ec33f1 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 0000000000000000000000000000000000000000..3c4935a7ca84f0976aca34b7f2895d65fb94d1ea GIT binary patch literal 46993 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 literal 0 HcmV?d00001 diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 0000000000000000000000000000000000000000..ed4cc16421680a50164ba74381b4b35ceaa0ccfc GIT binary patch literal 3276 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 N;OXk;vd$@?2>?>Ex^Vyi literal 0 HcmV?d00001 diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 0000000000000000000000000000000000000000..bcbf36df2f2aaaa0a63c7dabc94e600184229d0d GIT binary patch literal 5933 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 f$KZHO*s&vt|9^w-6P?|#0pRK8NSwWJ?9znhg z3{`3j3=J&|48MRv4KElNN(~qoUL`OvSj}Ky5HFasE6@fg!ItFh?!xdN1Q+aGJ{c&& zS>O>_%)r1c48n{Iv*t(u1=&kHeO=ifbFy+6aSK)V_AxLppYn8Z42d|rc6w}vOsL55 z`t&mC&y2@JTEyg!eDiFX^k#CC!jq%>erB=yHqUP0XcDOTw6ko}L zX;EmMrq(fKk*eygEuA616;0)>@A{TK|55PV@70 z$OfzS*(VJxQev3J?yY?O=ul(v`fp}?u9z`JK3ugibK>)DyCwImZOF4d{xK%%Ks1*} zv$oa)9anR%lXIBUqYnhLmT>VOzHfNP?ZwJNZ!5$s9M08RynIvaXw>@G^T9@r9^KH1 zVy??F&uuk)bH9Y4pQY!hP58i_H6 znl-NcuCpLV6ZWU;4C zu@9exF&OZi`Bovq_m%T+WhU2kvkz@^_LpycBvqm3bMpLw8X-Or5sL>0AKE1$(k_L=_Zc=CUq#=x1-QZf)G7nHu@fmsQ1eN_N3+nTEz`4HI4Z6uVlE zJH+X&det8JU?tO?upcM4Z=cV!JV;yF>FfL5Q$M|W_2Z!P`S=}Wzp|_1^#d%e?_H`> zV@%vA$+bFVqhw9`U;TfP|5|PD{||OiYdor8P*i??|NJcb%kzT_73*7WE?Ua5hAnR2 z=7WE=PhTlJ#ZeRznjTUb;`E(wkMZrj4e|Hilz-mK>9cZHQY**5TUPw~u}k;u73KI}xAx!0m-)GVia|x^d3p~s_9gh83jA&Ra<8rM%`>U3x69t&NzbwWY}7Ar?)FK#IZ0z|d0H0EkRO w3{9;}4Xg|ebq&m|3=9_N6z8I7$jwj5OsmAL;bP(Gi$Dzwp00i_>zopr02+f8CIA2c literal 0 HcmV?d00001 diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 0000000000000000000000000000000000000000..e71a726136a47ed24125c7efc79d68a4a01961b4 GIT binary patch literal 14800 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner/Configs/AppInfo.xcconfig b/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 00000000..8b42559e --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = example + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.example.example + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2022 com.example. All rights reserved. diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner/Configs/Debug.xcconfig b/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 00000000..36b0fd94 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner/Configs/Release.xcconfig b/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 00000000..dff4f495 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner/Configs/Warnings.xcconfig b/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 00000000..42bcbf47 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner/DebugProfile.entitlements b/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner/DebugProfile.entitlements new file mode 100644 index 00000000..dddb8a30 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + + diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner/Info.plist b/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner/Info.plist new file mode 100644 index 00000000..4789daa6 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner/MainFlutterWindow.swift b/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 00000000..2722837e --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController.init() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner/Release.entitlements b/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner/Release.entitlements new file mode 100644 index 00000000..852fa1a4 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/example/pubspec.yaml b/packages/firebase_remote_config/firebase_remote_config_desktop/example/pubspec.yaml new file mode 100644 index 00000000..6ee6796f --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_desktop/example/pubspec.yaml @@ -0,0 +1,30 @@ +name: firebase_remote_config_desktop_example +description: A new Flutter project. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev +version: 1.0.0+1 + +environment: + sdk: '>=2.17.0-0 <3.0.0' + +dependencies: + flutter: + sdk: flutter + firebase_core: any + firebase_core_desktop: ^0.1.1 + firebase_remote_config: any + firebase_remote_config_desktop: + path: ../ + cupertino_icons: ^1.0.2 + +dependency_overrides: + firebase_remote_config_dart: + path: ../../firebase_remote_config_dart + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^2.0.0 + +# The following section is specific to Flutter packages. +flutter: + uses-material-design: true diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/example/web/favicon.png b/packages/firebase_remote_config/firebase_remote_config_desktop/example/web/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..8aaa46ac1ae21512746f852a42ba87e4165dfdd1 GIT binary patch literal 917 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|I14-?iy0X7 zltGxWVyS%@P(fs7NJL45ua8x7ey(0(N`6wRUPW#JP&EUCO@$SZnVVXYs8ErclUHn2 zVXFjIVFhG^g!Ppaz)DK8ZIvQ?0~DO|i&7O#^-S~(l1AfjnEK zjFOT9D}DX)@^Za$W4-*MbbUihOG|wNBYh(yU7!lx;>x^|#0uTKVr7USFmqf|i<65o z3raHc^AtelCMM;Vme?vOfh>Xph&xL%(-1c06+^uR^q@XSM&D4+Kp$>4P^%3{)XKjo zGZknv$b36P8?Z_gF{nK@`XI}Z90TzwSQO}0J1!f2c(B=V`5aP@1P1a|PZ!4!3&Gl8 zTYqUsf!gYFyJnXpu0!n&N*SYAX-%d(5gVjrHJWqXQshj@!Zm{!01WsQrH~9=kTxW#6SvuapgMqt>$=j#%eyGrQzr zP{L-3gsMA^$I1&gsBAEL+vxi1*Igl=8#8`5?A-T5=z-sk46WA1IUT)AIZHx1rdUrf zVJrJn<74DDw`j)Ki#gt}mIT-Q`XRa2-jQXQoI%w`nb|XblvzK${ZzlV)m-XcwC(od z71_OEC5Bt9GEXosOXaPTYOia#R4ID2TiU~`zVMl08TV_C%DnU4^+HE>9(CE4D6?Fz oujB08i7adh9xk7*FX66dWH6F5TM;?E2b5PlUHx3vIVCg!0Dx9vYXATM literal 0 HcmV?d00001 diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/example/web/icons/Icon-192.png b/packages/firebase_remote_config/firebase_remote_config_desktop/example/web/icons/Icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..b749bfef07473333cf1dd31e9eed89862a5d52aa GIT binary patch literal 5292 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 literal 0 HcmV?d00001 diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/example/web/icons/Icon-512.png b/packages/firebase_remote_config/firebase_remote_config_desktop/example/web/icons/Icon-512.png new file mode 100644 index 0000000000000000000000000000000000000000..88cfd48dff1169879ba46840804b412fe02fefd6 GIT binary patch literal 8252 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 literal 0 HcmV?d00001 diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/example/web/icons/Icon-maskable-192.png b/packages/firebase_remote_config/firebase_remote_config_desktop/example/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000000000000000000000000000000000000..eb9b4d76e525556d5d89141648c724331630325d GIT binary patch literal 5594 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 X>c!)3PgKfL&lX^$Z>Cpu&6)6jvi^Z! literal 0 HcmV?d00001 diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/example/web/icons/Icon-maskable-512.png b/packages/firebase_remote_config/firebase_remote_config_desktop/example/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000000000000000000000000000000000000..d69c56691fbdb0b7efa65097c7cc1edac12a6d3e GIT binary patch literal 20998 zcmeFZ_gj-)&^4Nb2tlbLMU<{!p(#yjqEe+=0IA_oih%ScH9@5#MNp&}Y#;;(h=A0@ zh7{>lT2MkSQ344eAvrhici!td|HJuyvJm#Y_w1Q9Yu3!26dNlO-oxUDK_C#XnW^Co z5C{VN6#{~B0)K2j7}*1Xq(Nqemv23A-6&=ZpEijkVnSwVGqLv40?n0=p;k3-U5e5+ z+z3>aS`u9DS=!wg8ROu?X4TFoW6CFLL&{GzoVT)ldhLekLM|+j3tIxRd|*5=c{=s&*vfPdBr(Fyj(v@%eQj1Soy7m4^@VRl1~@-PV7y+c!xz$8436WBn$t{=}mEdK#k`aystimGgI{(IBx$!pAwFoE9Y`^t^;> zKAD)C(Dl^s%`?q5$P|fZf8Xymrtu^Pv(7D`rn>Z-w$Ahs!z9!94WNVxrJuXfHAaxg zC6s@|Z1$7R$(!#t%Jb{{s6(Y?NoQXDYq)!}X@jKPhe`{9KQ@sAU8y-5`xt?S9$jKH zoi}6m5PcG*^{kjvt+kwPpyQzVg4o)a>;LK`aaN2x4@itBD3Aq?yWTM20VRn1rrd+2 zKO=P0rMjEGq_UqpMa`~7B|p?xAN1SCoCp}QxAv8O`jLJ5CVh@umR%c%i^)6!o+~`F zaalSTQcl5iwOLC&H)efzd{8(88mo`GI(56T<(&p7>Qd^;R1hn1Y~jN~tApaL8>##U zd65bo8)79CplWxr#z4!6HvLz&N7_5AN#x;kLG?zQ(#p|lj<8VUlKY=Aw!ATqeL-VG z42gA!^cMNPj>(`ZMEbCrnkg*QTsn*u(nQPWI9pA{MQ=IsPTzd7q5E#7+z>Ch=fx$~ z;J|?(5jTo5UWGvsJa(Sx0?S#56+8SD!I^tftyeh_{5_31l6&Hywtn`bbqYDqGZXI( zCG7hBgvksX2ak8+)hB4jnxlO@A32C_RM&g&qDSb~3kM&)@A_j1*oTO@nicGUyv+%^ z=vB)4(q!ykzT==Z)3*3{atJ5}2PV*?Uw+HhN&+RvKvZL3p9E?gHjv{6zM!A|z|UHK z-r6jeLxbGn0D@q5aBzlco|nG2tr}N@m;CJX(4#Cn&p&sLKwzLFx1A5izu?X_X4x8r@K*d~7>t1~ zDW1Mv5O&WOxbzFC`DQ6yNJ(^u9vJdj$fl2dq`!Yba_0^vQHXV)vqv1gssZYzBct!j zHr9>ydtM8wIs}HI4=E}qAkv|BPWzh3^_yLH(|kdb?x56^BlDC)diWyPd*|f!`^12_U>TD^^94OCN0lVv~Sgvs94ecpE^}VY$w`qr_>Ue zTfH~;C<3H<0dS5Rkf_f@1x$Gms}gK#&k()IC0zb^QbR!YLoll)c$Agfi6MKI0dP_L z=Uou&u~~^2onea2%XZ@>`0x^L8CK6=I{ge;|HXMj)-@o~h&O{CuuwBX8pVqjJ*o}5 z#8&oF_p=uSo~8vn?R0!AMWvcbZmsrj{ZswRt(aEdbi~;HeVqIe)-6*1L%5u$Gbs}| zjFh?KL&U(rC2izSGtwP5FnsR@6$-1toz?RvLD^k~h9NfZgzHE7m!!7s6(;)RKo2z} zB$Ci@h({l?arO+vF;s35h=|WpefaOtKVx>l399}EsX@Oe3>>4MPy%h&^3N_`UTAHJ zI$u(|TYC~E4)|JwkWW3F!Tib=NzjHs5ii2uj0^m|Qlh-2VnB#+X~RZ|`SA*}}&8j9IDv?F;(Y^1=Z0?wWz;ikB zewU>MAXDi~O7a~?jx1x=&8GcR-fTp>{2Q`7#BE#N6D@FCp`?ht-<1|y(NArxE_WIu zP+GuG=Qq>SHWtS2M>34xwEw^uvo4|9)4s|Ac=ud?nHQ>ax@LvBqusFcjH0}{T3ZPQ zLO1l<@B_d-(IS682}5KA&qT1+{3jxKolW+1zL4inqBS-D>BohA!K5++41tM@ z@xe<-qz27}LnV#5lk&iC40M||JRmZ*A##K3+!j93eouU8@q-`W0r%7N`V$cR&JV;iX(@cS{#*5Q>~4BEDA)EikLSP@>Oo&Bt1Z~&0d5)COI%3$cLB_M?dK# z{yv2OqW!al-#AEs&QFd;WL5zCcp)JmCKJEdNsJlL9K@MnPegK23?G|O%v`@N{rIRa zi^7a}WBCD77@VQ-z_v{ZdRsWYrYgC$<^gRQwMCi6);%R~uIi31OMS}=gUTE(GKmCI z$zM>mytL{uNN+a&S38^ez(UT=iSw=l2f+a4)DyCA1Cs_N-r?Q@$3KTYosY!;pzQ0k zzh1G|kWCJjc(oZVBji@kN%)UBw(s{KaYGy=i{g3{)Z+&H8t2`^IuLLKWT6lL<-C(! zSF9K4xd-|VO;4}$s?Z7J_dYqD#Mt)WCDnsR{Kpjq275uUq6`v0y*!PHyS(}Zmv)_{>Vose9-$h8P0|y;YG)Bo}$(3Z%+Gs0RBmFiW!^5tBmDK-g zfe5%B*27ib+7|A*Fx5e)2%kIxh7xWoc3pZcXS2zik!63lAG1;sC1ja>BqH7D zODdi5lKW$$AFvxgC-l-)!c+9@YMC7a`w?G(P#MeEQ5xID#<}W$3bSmJ`8V*x2^3qz zVe<^^_8GHqYGF$nIQm0Xq2kAgYtm#UC1A(=&85w;rmg#v906 zT;RyMgbMpYOmS&S9c38^40oUp?!}#_84`aEVw;T;r%gTZkWeU;;FwM@0y0adt{-OK z(vGnPSlR=Nv2OUN!2=xazlnHPM9EWxXg2EKf0kI{iQb#FoP>xCB<)QY>OAM$Dcdbm zU6dU|%Mo(~avBYSjRc13@|s>axhrPl@Sr81{RSZUdz4(=|82XEbV*JAX6Lfbgqgz584lYgi0 z2-E{0XCVON$wHfvaLs;=dqhQJ&6aLn$D#0i(FkAVrXG9LGm3pSTf&f~RQb6|1_;W> z?n-;&hrq*~L=(;u#jS`*Yvh@3hU-33y_Kv1nxqrsf>pHVF&|OKkoC)4DWK%I!yq?P z=vXo8*_1iEWo8xCa{HJ4tzxOmqS0&$q+>LroMKI*V-rxhOc%3Y!)Y|N6p4PLE>Yek>Y(^KRECg8<|%g*nQib_Yc#A5q8Io z6Ig&V>k|~>B6KE%h4reAo*DfOH)_01tE0nWOxX0*YTJgyw7moaI^7gW*WBAeiLbD?FV9GSB zPv3`SX*^GRBM;zledO`!EbdBO_J@fEy)B{-XUTVQv}Qf~PSDpK9+@I`7G7|>Dgbbu z_7sX9%spVo$%qwRwgzq7!_N;#Td08m5HV#?^dF-EV1o)Q=Oa+rs2xH#g;ykLbwtCh znUnA^dW!XjspJ;otq$yV@I^s9Up(5k7rqhQd@OLMyyxVLj_+$#Vc*}Usevp^I(^vH zmDgHc0VMme|K&X?9&lkN{yq_(If)O`oUPW8X}1R5pSVBpfJe0t{sPA(F#`eONTh_) zxeLqHMfJX#?P(@6w4CqRE@Eiza; z;^5)Kk=^5)KDvd9Q<`=sJU8rjjxPmtWMTmzcH={o$U)j=QBuHarp?=}c??!`3d=H$nrJMyr3L-& zA#m?t(NqLM?I3mGgWA_C+0}BWy3-Gj7bR+d+U?n*mN$%5P`ugrB{PeV>jDUn;eVc- zzeMB1mI4?fVJatrNyq|+zn=!AiN~<}eoM#4uSx^K?Iw>P2*r=k`$<3kT00BE_1c(02MRz4(Hq`L^M&xt!pV2 zn+#U3@j~PUR>xIy+P>51iPayk-mqIK_5rlQMSe5&tDkKJk_$i(X&;K(11YGpEc-K= zq4Ln%^j>Zi_+Ae9eYEq_<`D+ddb8_aY!N;)(&EHFAk@Ekg&41ABmOXfWTo)Z&KotA zh*jgDGFYQ^y=m)<_LCWB+v48DTJw*5dwMm_YP0*_{@HANValf?kV-Ic3xsC}#x2h8 z`q5}d8IRmqWk%gR)s~M}(Qas5+`np^jW^oEd-pzERRPMXj$kS17g?H#4^trtKtq;C?;c ztd|%|WP2w2Nzg@)^V}!Gv++QF2!@FP9~DFVISRW6S?eP{H;;8EH;{>X_}NGj^0cg@ z!2@A>-CTcoN02^r6@c~^QUa={0xwK0v4i-tQ9wQq^=q*-{;zJ{Qe%7Qd!&X2>rV@4 z&wznCz*63_vw4>ZF8~%QCM?=vfzW0r_4O^>UA@otm_!N%mH)!ERy&b!n3*E*@?9d^ zu}s^By@FAhG(%?xgJMuMzuJw2&@$-oK>n z=UF}rt%vuaP9fzIFCYN-1&b#r^Cl6RDFIWsEsM|ROf`E?O(cy{BPO2Ie~kT+^kI^i zp>Kbc@C?}3vy-$ZFVX#-cx)Xj&G^ibX{pWggtr(%^?HeQL@Z( zM-430g<{>vT*)jK4aY9(a{lSy{8vxLbP~n1MXwM527ne#SHCC^F_2@o`>c>>KCq9c(4c$VSyMl*y3Nq1s+!DF| z^?d9PipQN(mw^j~{wJ^VOXDCaL$UtwwTpyv8IAwGOg<|NSghkAR1GSNLZ1JwdGJYm zP}t<=5=sNNUEjc=g(y)1n5)ynX(_$1-uGuDR*6Y^Wgg(LT)Jp><5X|}bt z_qMa&QP?l_n+iVS>v%s2Li_;AIeC=Ca^v1jX4*gvB$?H?2%ndnqOaK5-J%7a} zIF{qYa&NfVY}(fmS0OmXA70{znljBOiv5Yod!vFU{D~*3B3Ka{P8?^ zfhlF6o7aNT$qi8(w<}OPw5fqA7HUje*r*Oa(YV%*l0|9FP9KW@U&{VSW{&b0?@y)M zs%4k1Ax;TGYuZ9l;vP5@?3oQsp3)rjBeBvQQ>^B;z5pc=(yHhHtq6|0m(h4envn_j787fizY@V`o(!SSyE7vlMT zbo=Z1c=atz*G!kwzGB;*uPL$Ei|EbZLh8o+1BUMOpnU(uX&OG1MV@|!&HOOeU#t^x zr9=w2ow!SsTuJWT7%Wmt14U_M*3XiWBWHxqCVZI0_g0`}*^&yEG9RK9fHK8e+S^m? zfCNn$JTswUVbiC#>|=wS{t>-MI1aYPLtzO5y|LJ9nm>L6*wpr_m!)A2Fb1RceX&*|5|MwrvOk4+!0p99B9AgP*9D{Yt|x=X}O% zgIG$MrTB=n-!q%ROT|SzH#A$Xm;|ym)0>1KR}Yl0hr-KO&qMrV+0Ej3d@?FcgZ+B3 ztEk16g#2)@x=(ko8k7^Tq$*5pfZHC@O@}`SmzT1(V@x&NkZNM2F#Q-Go7-uf_zKC( zB(lHZ=3@dHaCOf6C!6i8rDL%~XM@rVTJbZL09?ht@r^Z_6x}}atLjvH^4Vk#Ibf(^LiBJFqorm?A=lE zzFmwvp4bT@Nv2V>YQT92X;t9<2s|Ru5#w?wCvlhcHLcsq0TaFLKy(?nzezJ>CECqj zggrI~Hd4LudM(m{L@ezfnpELsRFVFw>fx;CqZtie`$BXRn#Ns%AdoE$-Pf~{9A8rV zf7FbgpKmVzmvn-z(g+&+-ID=v`;6=)itq8oM*+Uz**SMm_{%eP_c0{<%1JGiZS19o z@Gj7$Se~0lsu}w!%;L%~mIAO;AY-2i`9A*ZfFs=X!LTd6nWOZ7BZH2M{l2*I>Xu)0 z`<=;ObglnXcVk!T>e$H?El}ra0WmPZ$YAN0#$?|1v26^(quQre8;k20*dpd4N{i=b zuN=y}_ew9SlE~R{2+Rh^7%PA1H5X(p8%0TpJ=cqa$65XL)$#ign-y!qij3;2>j}I; ziO@O|aYfn&up5F`YtjGw68rD3{OSGNYmBnl?zdwY$=RFsegTZ=kkzRQ`r7ZjQP!H( zp4>)&zf<*N!tI00xzm-ME_a{_I!TbDCr;8E;kCH4LlL-tqLxDuBn-+xgPk37S&S2^ z2QZumkIimwz!c@!r0)j3*(jPIs*V!iLTRl0Cpt_UVNUgGZzdvs0(-yUghJfKr7;=h zD~y?OJ-bWJg;VdZ^r@vlDoeGV&8^--!t1AsIMZ5S440HCVr%uk- z2wV>!W1WCvFB~p$P$$_}|H5>uBeAe>`N1FI8AxM|pq%oNs;ED8x+tb44E) zTj{^fbh@eLi%5AqT?;d>Es5D*Fi{Bpk)q$^iF!!U`r2hHAO_?#!aYmf>G+jHsES4W zgpTKY59d?hsb~F0WE&dUp6lPt;Pm zcbTUqRryw^%{ViNW%Z(o8}dd00H(H-MmQmOiTq{}_rnwOr*Ybo7*}3W-qBT!#s0Ie z-s<1rvvJx_W;ViUD`04%1pra*Yw0BcGe)fDKUK8aF#BwBwMPU;9`!6E(~!043?SZx z13K%z@$$#2%2ovVlgFIPp7Q6(vO)ud)=*%ZSucL2Dh~K4B|%q4KnSpj#n@(0B})!9 z8p*hY@5)NDn^&Pmo;|!>erSYg`LkO?0FB@PLqRvc>4IsUM5O&>rRv|IBRxi(RX(gJ ztQ2;??L~&Mv;aVr5Q@(?y^DGo%pO^~zijld41aA0KKsy_6FeHIn?fNHP-z>$OoWer zjZ5hFQTy*-f7KENRiCE$ZOp4|+Wah|2=n@|W=o}bFM}Y@0e62+_|#fND5cwa3;P{^pEzlJbF1Yq^}>=wy8^^^$I2M_MH(4Dw{F6hm+vrWV5!q;oX z;tTNhz5`-V={ew|bD$?qcF^WPR{L(E%~XG8eJx(DoGzt2G{l8r!QPJ>kpHeOvCv#w zr=SSwMDaUX^*~v%6K%O~i)<^6`{go>a3IdfZ8hFmz&;Y@P%ZygShQZ2DSHd`m5AR= zx$wWU06;GYwXOf(%MFyj{8rPFXD};JCe85Bdp4$YJ2$TzZ7Gr#+SwCvBI1o$QP0(c zy`P51FEBV2HTisM3bHqpmECT@H!Y2-bv2*SoSPoO?wLe{M#zDTy@ujAZ!Izzky~3k zRA1RQIIoC*Mej1PH!sUgtkR0VCNMX(_!b65mo66iM*KQ7xT8t2eev$v#&YdUXKwGm z7okYAqYF&bveHeu6M5p9xheRCTiU8PFeb1_Rht0VVSbm%|1cOVobc8mvqcw!RjrMRM#~=7xibH&Fa5Imc|lZ{eC|R__)OrFg4@X_ ze+kk*_sDNG5^ELmHnZ7Ue?)#6!O)#Nv*Dl2mr#2)w{#i-;}0*_h4A%HidnmclH#;Q zmQbq+P4DS%3}PpPm7K_K3d2s#k~x+PlTul7+kIKol0@`YN1NG=+&PYTS->AdzPv!> zQvzT=)9se*Jr1Yq+C{wbK82gAX`NkbXFZ)4==j4t51{|-v!!$H8@WKA={d>CWRW+g z*`L>9rRucS`vbXu0rzA1#AQ(W?6)}1+oJSF=80Kf_2r~Qm-EJ6bbB3k`80rCv(0d` zvCf3;L2ovYG_TES%6vSuoKfIHC6w;V31!oqHM8-I8AFzcd^+_86!EcCOX|Ta9k1!s z_Vh(EGIIsI3fb&dF$9V8v(sTBC%!#<&KIGF;R+;MyC0~}$gC}}= zR`DbUVc&Bx`lYykFZ4{R{xRaUQkWCGCQlEc;!mf=+nOk$RUg*7 z;kP7CVLEc$CA7@6VFpsp3_t~m)W0aPxjsA3e5U%SfY{tp5BV5jH-5n?YX7*+U+Zs%LGR>U- z!x4Y_|4{gx?ZPJobISy991O znrmrC3otC;#4^&Rg_iK}XH(XX+eUHN0@Oe06hJk}F?`$)KmH^eWz@@N%wEc)%>?Ft z#9QAroDeyfztQ5Qe{m*#R#T%-h*&XvSEn@N$hYRTCMXS|EPwzF3IIysD2waj`vQD{ zv_#^Pgr?s~I*NE=acf@dWVRNWTr(GN0wrL)Z2=`Dr>}&ZDNX|+^Anl{Di%v1Id$_p zK5_H5`RDjJx`BW7hc85|> zHMMsWJ4KTMRHGu+vy*kBEMjz*^K8VtU=bXJYdhdZ-?jTXa$&n)C?QQIZ7ln$qbGlr zS*TYE+ppOrI@AoPP=VI-OXm}FzgXRL)OPvR$a_=SsC<3Jb+>5makX|U!}3lx4tX&L z^C<{9TggZNoeX!P1jX_K5HkEVnQ#s2&c#umzV6s2U-Q;({l+j^?hi7JnQ7&&*oOy9 z(|0asVTWUCiCnjcOnB2pN0DpuTglKq;&SFOQ3pUdye*eT<2()7WKbXp1qq9=bhMWlF-7BHT|i3TEIT77AcjD(v=I207wi-=vyiw5mxgPdTVUC z&h^FEUrXwWs9en2C{ywZp;nvS(Mb$8sBEh-*_d-OEm%~p1b2EpcwUdf<~zmJmaSTO zSX&&GGCEz-M^)G$fBvLC2q@wM$;n4jp+mt0MJFLuJ%c`tSp8$xuP|G81GEd2ci$|M z4XmH{5$j?rqDWoL4vs!}W&!?!rtj=6WKJcE>)?NVske(p;|#>vL|M_$as=mi-n-()a*OU3Okmk0wC<9y7t^D(er-&jEEak2!NnDiOQ99Wx8{S8}=Ng!e0tzj*#T)+%7;aM$ z&H}|o|J1p{IK0Q7JggAwipvHvko6>Epmh4RFRUr}$*2K4dz85o7|3#Bec9SQ4Y*;> zXWjT~f+d)dp_J`sV*!w>B%)#GI_;USp7?0810&3S=WntGZ)+tzhZ+!|=XlQ&@G@~3 z-dw@I1>9n1{+!x^Hz|xC+P#Ab`E@=vY?3%Bc!Po~e&&&)Qp85!I|U<-fCXy*wMa&t zgDk!l;gk;$taOCV$&60z+}_$ykz=Ea*)wJQ3-M|p*EK(cvtIre0Pta~(95J7zoxBN zS(yE^3?>88AL0Wfuou$BM{lR1hkrRibz=+I9ccwd`ZC*{NNqL)3pCcw^ygMmrG^Yp zn5f}Xf>%gncC=Yq96;rnfp4FQL#{!Y*->e82rHgY4Zwy{`JH}b9*qr^VA{%~Z}jtp z_t$PlS6}5{NtTqXHN?uI8ut8rOaD#F1C^ls73S=b_yI#iZDOGz3#^L@YheGd>L;<( z)U=iYj;`{>VDNzIxcjbTk-X3keXR8Xbc`A$o5# zKGSk-7YcoBYuAFFSCjGi;7b<;n-*`USs)IX z=0q6WZ=L!)PkYtZE-6)azhXV|+?IVGTOmMCHjhkBjfy@k1>?yFO3u!)@cl{fFAXnRYsWk)kpT?X{_$J=|?g@Q}+kFw|%n!;Zo}|HE@j=SFMvT8v`6Y zNO;tXN^036nOB2%=KzxB?n~NQ1K8IO*UE{;Xy;N^ZNI#P+hRZOaHATz9(=)w=QwV# z`z3+P>9b?l-@$@P3<;w@O1BdKh+H;jo#_%rr!ute{|YX4g5}n?O7Mq^01S5;+lABE+7`&_?mR_z7k|Ja#8h{!~j)| zbBX;*fsbUak_!kXU%HfJ2J+G7;inu#uRjMb|8a){=^))y236LDZ$$q3LRlat1D)%7K0!q5hT5V1j3qHc7MG9 z_)Q=yQ>rs>3%l=vu$#VVd$&IgO}Za#?aN!xY>-<3PhzS&q!N<=1Q7VJBfHjug^4|) z*fW^;%3}P7X#W3d;tUs3;`O&>;NKZBMR8au6>7?QriJ@gBaorz-+`pUWOP73DJL=M z(33uT6Gz@Sv40F6bN|H=lpcO z^AJl}&=TIjdevuDQ!w0K*6oZ2JBOhb31q!XDArFyKpz!I$p4|;c}@^bX{>AXdt7Bm zaLTk?c%h@%xq02reu~;t@$bv`b3i(P=g}~ywgSFpM;}b$zAD+=I!7`V~}ARB(Wx0C(EAq@?GuxOL9X+ffbkn3+Op0*80TqmpAq~EXmv%cq36celXmRz z%0(!oMp&2?`W)ALA&#|fu)MFp{V~~zIIixOxY^YtO5^FSox8v$#d0*{qk0Z)pNTt0QVZ^$`4vImEB>;Lo2!7K05TpY-sl#sWBz_W-aDIV`Ksabi zvpa#93Svo!70W*Ydh)Qzm{0?CU`y;T^ITg-J9nfWeZ-sbw)G@W?$Eomf%Bg2frfh5 zRm1{|E0+(4zXy){$}uC3%Y-mSA2-^I>Tw|gQx|7TDli_hB>``)Q^aZ`LJC2V3U$SABP}T)%}9g2pF9dT}aC~!rFFgkl1J$ z`^z{Arn3On-m%}r}TGF8KQe*OjSJ=T|caa_E;v89A{t@$yT^(G9=N9F?^kT*#s3qhJq!IH5|AhnqFd z0B&^gm3w;YbMNUKU>naBAO@fbz zqw=n!@--}o5;k6DvTW9pw)IJVz;X}ncbPVrmH>4x);8cx;q3UyiML1PWp%bxSiS|^ zC5!kc4qw%NSOGQ*Kcd#&$30=lDvs#*4W4q0u8E02U)7d=!W7+NouEyuF1dyH$D@G& zaFaxo9Ex|ZXA5y{eZT*i*dP~INSMAi@mvEX@q5i<&o&#sM}Df?Og8n8Ku4vOux=T% zeuw~z1hR}ZNwTn8KsQHKLwe2>p^K`YWUJEdVEl|mO21Bov!D0D$qPoOv=vJJ`)|%_ z>l%`eexY7t{BlVKP!`a^U@nM?#9OC*t76My_E_<16vCz1x_#82qj2PkWiMWgF8bM9 z(1t4VdHcJ;B~;Q%x01k_gQ0>u2*OjuEWNOGX#4}+N?Gb5;+NQMqp}Puqw2HnkYuKA zzKFWGHc&K>gwVgI1Sc9OT1s6fq=>$gZU!!xsilA$fF`kLdGoX*^t}ao@+^WBpk>`8 z4v_~gK|c2rCq#DZ+H)$3v~Hoi=)=1D==e3P zpKrRQ+>O^cyTuWJ%2}__0Z9SM_z9rptd*;-9uC1tDw4+A!=+K%8~M&+Zk#13hY$Y$ zo-8$*8dD5@}XDi19RjK6T^J~DIXbF5w&l?JLHMrf0 zLv0{7*G!==o|B%$V!a=EtVHdMwXLtmO~vl}P6;S(R2Q>*kTJK~!}gloxj)m|_LYK{ zl(f1cB=EON&wVFwK?MGn^nWuh@f95SHatPs(jcwSY#Dnl1@_gkOJ5=f`%s$ZHljRH0 z+c%lrb=Gi&N&1>^L_}#m>=U=(oT^vTA&3!xXNyqi$pdW1BDJ#^{h|2tZc{t^vag3& zAD7*8C`chNF|27itjBUo^CCDyEpJLX3&u+(L;YeeMwnXEoyN(ytoEabcl$lSgx~Ltatn}b$@j_yyMrBb03)shJE*$;Mw=;mZd&8e>IzE+4WIoH zCSZE7WthNUL$|Y#m!Hn?x7V1CK}V`KwW2D$-7&ODy5Cj;!_tTOOo1Mm%(RUt)#$@3 zhurA)t<7qik%%1Et+N1?R#hdBB#LdQ7{%-C zn$(`5e0eFh(#c*hvF>WT*07fk$N_631?W>kfjySN8^XC9diiOd#s?4tybICF;wBjp zIPzilX3{j%4u7blhq)tnaOBZ_`h_JqHXuI7SuIlNTgBk9{HIS&3|SEPfrvcE<@}E` zKk$y*nzsqZ{J{uWW9;#n=de&&h>m#A#q)#zRonr(?mDOYU&h&aQWD;?Z(22wY?t$U3qo`?{+amA$^TkxL+Ex2dh`q7iR&TPd0Ymwzo#b? zP$#t=elB5?k$#uE$K>C$YZbYUX_JgnXA`oF_Ifz4H7LEOW~{Gww&3s=wH4+j8*TU| zSX%LtJWqhr-xGNSe{;(16kxnak6RnZ{0qZ^kJI5X*It_YuynSpi(^-}Lolr{)#z_~ zw!(J-8%7Ybo^c3(mED`Xz8xecP35a6M8HarxRn%+NJBE;dw>>Y2T&;jzRd4FSDO3T zt*y+zXCtZQ0bP0yf6HRpD|WmzP;DR^-g^}{z~0x~z4j8m zucTe%k&S9Nt-?Jb^gYW1w6!Y3AUZ0Jcq;pJ)Exz%7k+mUOm6%ApjjSmflfKwBo6`B zhNb@$NHTJ>guaj9S{@DX)!6)b-Shav=DNKWy(V00k(D!v?PAR0f0vDNq*#mYmUp6> z76KxbFDw5U{{qx{BRj(>?|C`82ICKbfLxoldov-M?4Xl+3;I4GzLHyPOzYw7{WQST zPNYcx5onA%MAO9??41Po*1zW(Y%Zzn06-lUp{s<3!_9vv9HBjT02On0Hf$}NP;wF) zP<`2p3}A^~1YbvOh{ePMx$!JGUPX-tbBzp3mDZMY;}h;sQ->!p97GA)9a|tF(Gh{1$xk7 zUw?ELkT({Xw!KIr);kTRb1b|UL`r2_`a+&UFVCdJ)1T#fdh;71EQl9790Br0m_`$x z9|ZANuchFci8GNZ{XbP=+uXSJRe(;V5laQz$u18#?X*9}x7cIEbnr%<=1cX3EIu7$ zhHW6pe5M(&qEtsqRa>?)*{O;OJT+YUhG5{km|YI7I@JL_3Hwao9aXneiSA~a* z|Lp@c-oMNyeAEuUz{F?kuou3x#C*gU?lon!RC1s37gW^0Frc`lqQWH&(J4NoZg3m8 z;Lin#8Q+cFPD7MCzj}#|ws7b@?D9Q4dVjS4dpco=4yX5SSH=A@U@yqPdp@?g?qeia zH=Tt_9)G=6C2QIPsi-QipnK(mc0xXIN;j$WLf@n8eYvMk;*H-Q4tK%(3$CN}NGgO8n}fD~+>?<3UzvsrMf*J~%i;VKQHbF%TPalFi=#sgj)(P#SM^0Q=Tr>4kJVw8X3iWsP|e8tj}NjlMdWp z@2+M4HQu~3!=bZpjh;;DIDk&X}=c8~kn)FWWH z2KL1w^rA5&1@@^X%MjZ7;u(kH=YhH2pJPFQe=hn>tZd5RC5cfGYis8s9PKaxi*}-s6*W zRA^PwR=y^5Z){!(4D9-KC;0~;b*ploznFOaU`bJ_7U?qAi#mTo!&rIECRL$_y@yI27x2?W+zqDBD5~KCVYKFZLK+>ABC(Kj zeAll)KMgIlAG`r^rS{loBrGLtzhHY8$)<_S<(Dpkr(Ym@@vnQ&rS@FC*>2@XCH}M+an74WcRDcoQ+a3@A z9tYhl5$z7bMdTvD2r&jztBuo37?*k~wcU9GK2-)MTFS-lux-mIRYUuGUCI~V$?s#< z?1qAWb(?ZLm(N>%S%y10COdaq_Tm5c^%ooIxpR=`3e4C|@O5wY+eLik&XVi5oT7oe zmxH)Jd*5eo@!7t`x8!K=-+zJ-Sz)B_V$)s1pW~CDU$=q^&ABvf6S|?TOMB-RIm@CoFg>mjIQE)?+A1_3s6zmFU_oW&BqyMz1mY*IcP_2knjq5 zqw~JK(cVsmzc7*EvTT2rvpeqhg)W=%TOZ^>f`rD4|7Z5fq*2D^lpCttIg#ictgqZ$P@ru6P#f$x#KfnfTZj~LG6U_d-kE~`;kU_X)`H5so@?C zWmb!7x|xk@0L~0JFall*@ltyiL^)@3m4MqC7(7H0sH!WidId1#f#6R{Q&A!XzO1IAcIx;$k66dumt6lpUw@nL2MvqJ5^kbOVZ<^2jt5-njy|2@`07}0w z;M%I1$FCoLy`8xp8Tk)bFr;7aJeQ9KK6p=O$U0-&JYYy8woV*>b+FB?xLX`=pirYM z5K$BA(u)+jR{?O2r$c_Qvl?M{=Ar{yQ!UVsVn4k@0!b?_lA;dVz9uaQUgBH8Oz(Sb zrEs;&Ey>_ex8&!N{PmQjp+-Hlh|OA&wvDai#GpU=^-B70V0*LF=^bi+Nhe_o|azZ%~ZZ1$}LTmWt4aoB1 zPgccm$EwYU+jrdBaQFxQfn5gd(gM`Y*Ro1n&Zi?j=(>T3kmf94vdhf?AuS8>$Va#P zGL5F+VHpxdsCUa}+RqavXCobI-@B;WJbMphpK2%6t=XvKWWE|ruvREgM+|V=i6;;O zx$g=7^`$XWn0fu!gF=Xe9cMB8Z_SelD>&o&{1XFS`|nInK3BXlaeD*rc;R-#osyIS zWv&>~^TLIyBB6oDX+#>3<_0+2C4u2zK^wmHXXDD9_)kmLYJ!0SzM|%G9{pi)`X$uf zW}|%%#LgyK7m(4{V&?x_0KEDq56tk|0YNY~B(Sr|>WVz-pO3A##}$JCT}5P7DY+@W z#gJv>pA5>$|E3WO2tV7G^SuymB?tY`ooKcN3!vaQMnBNk-WATF{-$#}FyzgtJ8M^; zUK6KWSG)}6**+rZ&?o@PK3??uN{Q)#+bDP9i1W&j)oaU5d0bIWJ_9T5ac!qc?x66Q z$KUSZ`nYY94qfN_dpTFr8OW~A?}LD;Yty-BA)-be5Z3S#t2Io%q+cAbnGj1t$|qFR z9o?8B7OA^KjCYL=-!p}w(dkC^G6Nd%_I=1))PC0w5}ZZGJxfK)jP4Fwa@b-SYBw?% zdz9B-<`*B2dOn(N;mcTm%Do)rIvfXRNFX&1h`?>Rzuj~Wx)$p13nrDlS8-jwq@e@n zNIj_|8or==8~1h*Ih?w*8K7rYkGlwlTWAwLKc5}~dfz3y`kM&^Q|@C%1VAp_$wnw6zG~W4O+^ z>i?NY?oXf^Puc~+fDM$VgRNBpOZj{2cMP~gCqWAX4 z7>%$ux8@a&_B(pt``KSt;r+sR-$N;jdpY>|pyvPiN)9ohd*>mVST3wMo)){`B(&eX z1?zZJ-4u9NZ|~j1rdZYq4R$?swf}<6(#ex%7r{kh%U@kT)&kWuAszS%oJts=*OcL9 zaZwK<5DZw%1IFHXgFplP6JiL^dk8+SgM$D?8X+gE4172hXh!WeqIO>}$I9?Nry$*S zQ#f)RuH{P7RwA3v9f<-w>{PSzom;>(i&^l{E0(&Xp4A-*q-@{W1oE3K;1zb{&n28dSC2$N+6auXe0}e4b z)KLJ?5c*>@9K#I^)W;uU_Z`enquTUxr>mNq z1{0_puF-M7j${rs!dxxo3EelGodF1TvjV;Zpo;s{5f1pyCuRp=HDZ?s#IA4f?h|-p zGd|Mq^4hDa@Bh!c4ZE?O&x&XZ_ptZGYK4$9F4~{%R!}G1leCBx`dtNUS|K zL-7J5s4W@%mhXg1!}a4PD%!t&Qn%f_oquRajn3@C*)`o&K9o7V6DwzVMEhjVdDJ1fjhr#@=lp#@4EBqi=CCQ>73>R(>QKPNM&_Jpe5G`n4wegeC`FYEPJ{|vwS>$-`fuRSp3927qOv|NC3T3G-0 zA{K`|+tQy1yqE$ShWt8ny&5~)%ITb@^+x$w0)f&om;P8B)@}=Wzy59BwUfZ1vqw87 za2lB8J(&*l#(V}Id8SyQ0C(2amzkz3EqG&Ed0Jq1)$|&>4_|NIe=5|n=3?siFV0fI z{As5DLW^gs|B-b4C;Hd(SM-S~GQhzb>HgF2|2Usww0nL^;x@1eaB)=+Clj+$fF@H( z-fqP??~QMT$KI-#m;QC*&6vkp&8699G3)Bq0*kFZXINw=b9OVaed(3(3kS|IZ)CM? zJdnW&%t8MveBuK21uiYj)_a{Fnw0OErMzMN?d$QoPwkhOwcP&p+t>P)4tHlYw-pPN z^oJ=uc$Sl>pv@fZH~ZqxSvdhF@F1s=oZawpr^-#l{IIOGG=T%QXjtwPhIg-F@k@uIlr?J->Ia zpEUQ*=4g|XYn4Gez&aHr*;t$u3oODPmc2Ku)2Og|xjc%w;q!Zz+zY)*3{7V8bK4;& zYV82FZ+8?v)`J|G1w4I0fWdKg|2b#iaazCv;|?(W-q}$o&Y}Q5d@BRk^jL7#{kbCK zSgkyu;=DV+or2)AxCBgq-nj5=@n^`%T#V+xBGEkW4lCqrE)LMv#f;AvD__cQ@Eg3`~x| zW+h9mofSXCq5|M)9|ez(#X?-sxB%Go8};sJ?2abp(Y!lyi>k)|{M*Z$c{e1-K4ky` MPgg&ebxsLQ025IeI{*Lx literal 0 HcmV?d00001 diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/example/web/index.html b/packages/firebase_remote_config/firebase_remote_config_desktop/example/web/index.html new file mode 100644 index 00000000..41b3bc33 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_desktop/example/web/index.html @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + example + + + + + + + + + + diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/example/web/manifest.json b/packages/firebase_remote_config/firebase_remote_config_desktop/example/web/manifest.json new file mode 100644 index 00000000..096edf8f --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_desktop/example/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "example", + "short_name": "example", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/.gitignore b/packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/.gitignore new file mode 100644 index 00000000..d492d0d9 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/CMakeLists.txt b/packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/CMakeLists.txt new file mode 100644 index 00000000..c0270746 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/CMakeLists.txt @@ -0,0 +1,101 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(example LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "example") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/flutter/CMakeLists.txt b/packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/flutter/CMakeLists.txt new file mode 100644 index 00000000..930d2071 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/flutter/CMakeLists.txt @@ -0,0 +1,104 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + windows-x64 $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/flutter/generated_plugin_registrant.cc b/packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 00000000..8b6d4680 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void RegisterPlugins(flutter::PluginRegistry* registry) { +} diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/flutter/generated_plugin_registrant.h b/packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 00000000..dc139d85 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/flutter/generated_plugins.cmake b/packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/flutter/generated_plugins.cmake new file mode 100644 index 00000000..b93c4c30 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/flutter/generated_plugins.cmake @@ -0,0 +1,23 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/runner/CMakeLists.txt b/packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/runner/CMakeLists.txt new file mode 100644 index 00000000..b9e550fb --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/runner/CMakeLists.txt @@ -0,0 +1,32 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/runner/Runner.rc b/packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/runner/Runner.rc new file mode 100644 index 00000000..5fdea291 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#ifdef FLUTTER_BUILD_NUMBER +#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER +#else +#define VERSION_AS_NUMBER 1,0,0 +#endif + +#ifdef FLUTTER_BUILD_NAME +#define VERSION_AS_STRING #FLUTTER_BUILD_NAME +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.example" "\0" + VALUE "FileDescription", "example" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "example" "\0" + VALUE "LegalCopyright", "Copyright (C) 2022 com.example. All rights reserved." "\0" + VALUE "OriginalFilename", "example.exe" "\0" + VALUE "ProductName", "example" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/runner/flutter_window.cpp b/packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/runner/flutter_window.cpp new file mode 100644 index 00000000..b43b9095 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/runner/flutter_window.cpp @@ -0,0 +1,61 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/runner/flutter_window.h b/packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/runner/flutter_window.h new file mode 100644 index 00000000..6da0652f --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/runner/main.cpp b/packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/runner/main.cpp new file mode 100644 index 00000000..bcb57b0e --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.CreateAndShow(L"example", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/runner/resource.h b/packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/runner/resource.h new file mode 100644 index 00000000..66a65d1e --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/runner/resources/app_icon.ico b/packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/runner/resources/app_icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..c04e20caf6370ebb9253ad831cc31de4a9c965f6 GIT binary patch literal 33772 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 literal 0 HcmV?d00001 diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/runner/runner.exe.manifest b/packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/runner/runner.exe.manifest new file mode 100644 index 00000000..c977c4a4 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/runner/runner.exe.manifest @@ -0,0 +1,20 @@ + + + + + PerMonitorV2 + + + + + + + + + + + + + + + diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/runner/utils.cpp b/packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/runner/utils.cpp new file mode 100644 index 00000000..f5bf9fa0 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/runner/utils.cpp @@ -0,0 +1,64 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr); + std::string utf8_string; + if (target_length == 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, utf8_string.data(), + target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/runner/utils.h b/packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/runner/utils.h new file mode 100644 index 00000000..3879d547 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/runner/win32_window.cpp b/packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/runner/win32_window.cpp new file mode 100644 index 00000000..c10f08dc --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/runner/win32_window.cpp @@ -0,0 +1,245 @@ +#include "win32_window.h" + +#include + +#include "resource.h" + +namespace { + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + FreeLibrary(user32_module); + } +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::CreateAndShow(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + return OnCreate(); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/runner/win32_window.h b/packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/runner/win32_window.h new file mode 100644 index 00000000..17ba4311 --- /dev/null +++ b/packages/firebase_remote_config/firebase_remote_config_desktop/example/windows/runner/win32_window.h @@ -0,0 +1,98 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates and shows a win32 window with |title| and position and size using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size to will treat the width height passed in to this function + // as logical pixels and scale to appropriate for the default monitor. Returns + // true if the window was created successfully. + bool CreateAndShow(const std::wstring& title, + const Point& origin, + const Size& size); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responsponds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_ From 11c44dd9bc6bc8a269e79108c13d4e27418749f7 Mon Sep 17 00:00:00 2001 From: Tim Whiting Date: Fri, 13 May 2022 21:39:46 -0600 Subject: [PATCH 13/30] get working with example app --- .../lib/firebase_remote_config_dart.dart | 17 +++++---- .../lib/src/internal/api.dart | 30 +++++++++++++-- .../lib/src/internal/storage.dart | 9 ++--- .../lib/src/remote_config_settings.dart | 4 ++ .../lib/src/remote_config_value.dart | 5 +++ .../example/lib/main.dart | 38 +++++++++++-------- .../macos/Runner/DebugProfile.entitlements | 2 + .../lib/firebase_remote_config_desktop.dart | 23 ++++++++++- 8 files changed, 94 insertions(+), 34 deletions(-) diff --git a/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart b/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart index a826a5c9..f5b82be6 100644 --- a/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart +++ b/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart @@ -5,9 +5,9 @@ library firebase_remote_config_dart; import 'dart:async'; +import 'dart:convert'; import 'package:firebase_core_dart/firebase_core_dart.dart'; -import 'package:firebaseapis/firebaseremoteconfig/v1.dart' as api; import 'package:http/http.dart'; import 'package:meta/meta.dart'; import 'package:storagebox/storagebox.dart'; @@ -115,18 +115,19 @@ class FirebaseRemoteConfig { Future activate() async { final lastSuccessfulFetchResponse = storage.getLastSuccessfulFetchResponse(); + // final activeConfigEtag = storage.getActiveConfigEtag(); - if (lastSuccessfulFetchResponse?.parameters == null) { + if (lastSuccessfulFetchResponse?['entries'] == null) { // lastSuccessfulFetchResponse.eTag == null || // lastSuccessfulFetchResponse.eTag == activeConfigEtag) { return false; } else { - storageCache.setActiveConfig( - { - for (final entry in lastSuccessfulFetchResponse!.parameters!.entries) - entry.key: RemoteConfigValue.fromApi(entry.value) - }, - ); + final newConfig = { + for (final entry + in (lastSuccessfulFetchResponse!['entries'] as Map).entries) + entry.key: RemoteConfigValue(entry.value, ValueSource.valueRemote) + }; + storageCache.setActiveConfig(newConfig); // storage.setActiveConfigEtag(lastSuccessfulFetchResponse.eTag); return true; } diff --git a/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/api.dart b/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/api.dart index 3a673c3b..02ff5ead 100644 --- a/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/api.dart +++ b/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/api.dart @@ -18,8 +18,6 @@ class RemoteConfigApiClient { Client get httpClient => _httpClient; final _httpClient = Client(); - late final _api = api.FirebaseRemoteConfigApi(httpClient); - final _RemoteConfigStorage storage; final _RemoteConfigStorageCache storageCache; final String projectId; @@ -40,7 +38,7 @@ class RemoteConfigApiClient { return cacheAgeMillis <= cacheMaxAge.inMilliseconds; } - Future fetch({ + Future fetch({ String? eTag, required Duration cacheMaxAge, }) async { @@ -54,8 +52,32 @@ class RemoteConfigApiClient { } // TODO: Handle errors in fetch - final remoteConfig = await _api.projects.getRemoteConfig(projectId); + final response = await _httpClient.post( + Uri.parse( + 'https://firebaseremoteconfig.googleapis.com/v1/projects/$projectId/namespaces/firebase:fetch?key=$apiKey', + ), + headers: { + 'Content-Type': 'application/json', + 'Content-Encoding': 'gzip', + 'If-None-Match': eTag ?? '*' + }, + body: json.encode({ + // TODO: Sync this with pubspec.yaml + 'sdk_version': '0.1.0', + // TODO: Replace this with installation id + 'app_instance_id': '1', + 'app_id': appId, + }), + ); + if (response.statusCode != 200) { + throw Exception( + 'Failed to fetch remote config: ${response.statusCode} ${response.body}', + ); + } + + final remoteConfig = json.decode(response.body); storageCache.setLastFetchTime(DateTime.now()); + storage.setLastSuccessfulFetchResponse(remoteConfig); return remoteConfig; } diff --git a/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/storage.dart b/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/storage.dart index 2590515e..2613dc21 100644 --- a/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/storage.dart +++ b/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/storage.dart @@ -67,13 +67,12 @@ class _RemoteConfigStorage { }; } - api.RemoteConfig? getLastSuccessfulFetchResponse() { - return _storageBox[lastSuccessfulFetchKey] - .mapNullable((v) => api.RemoteConfig.fromJson(v as Map)); + Map? getLastSuccessfulFetchResponse() { + return _storageBox[lastSuccessfulFetchKey].mapNullable((v) => v as Map); } - void setLastSuccessfulFetchResponse(api.RemoteConfig remoteConfig) { - _storageBox[lastSuccessfulFetchKey] = remoteConfig.toJson(); + void setLastSuccessfulFetchResponse(Map remoteConfig) { + _storageBox[lastSuccessfulFetchKey] = remoteConfig; } } diff --git a/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/remote_config_settings.dart b/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/remote_config_settings.dart index 59d54048..59194012 100644 --- a/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/remote_config_settings.dart +++ b/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/remote_config_settings.dart @@ -15,4 +15,8 @@ class RemoteConfigSettings { /// Maximum age of a cached config before it is considered stale. Defaults /// to twelve hours. Duration minimumFetchInterval; + + @override + String toString() => + 'RemoteConfigSettings(fetchTimeout: $fetchTimeout, minimumFetchInterval: $minimumFetchInterval)'; } diff --git a/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/remote_config_value.dart b/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/remote_config_value.dart index ad9d9ff2..a6b0ce4d 100644 --- a/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/remote_config_value.dart +++ b/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/remote_config_value.dart @@ -98,4 +98,9 @@ class RemoteConfigValue { return defaultValueForBool; } } + + @override + String toString() { + return 'RemoteConfigValue(value: $_value, source: $source)'; + } } diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/example/lib/main.dart b/packages/firebase_remote_config/firebase_remote_config_desktop/example/lib/main.dart index bba9e207..334e69a6 100644 --- a/packages/firebase_remote_config/firebase_remote_config_desktop/example/lib/main.dart +++ b/packages/firebase_remote_config/firebase_remote_config_desktop/example/lib/main.dart @@ -21,7 +21,7 @@ void main() { AsyncSnapshot snapshot) { return snapshot.hasData ? WelcomeWidget(remoteConfig: snapshot.requireData) - : Container(); + : const Center(child: CircularProgressIndicator()); }, ), ), @@ -47,10 +47,14 @@ class WelcomeWidget extends AnimatedWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ Text('Welcome ${remoteConfig.getString('welcome')}'), - const SizedBox( - height: 20, - ), - Text('(${remoteConfig.getValue('welcome').source})'), + const SizedBox(height: 20), + const Text('All Config'), + Text(remoteConfig + .getAll() + .entries + .map((e) => '${e.key}: "${e.value.asString()}"') + .join('\n')), + const SizedBox(height: 20), Text('(${remoteConfig.lastFetchTime})'), Text('(${remoteConfig.lastFetchStatus})'), ], @@ -70,8 +74,9 @@ class WelcomeWidget extends AnimatedWidget { print(exception); } catch (exception) { print( - 'Unable to fetch remote config. Cached or default values will be ' - 'used'); + 'Unable to fetch remote config.' + 'Cached or default values will be used', + ); print(exception); } }, @@ -84,16 +89,18 @@ class WelcomeWidget extends AnimatedWidget { Future setupRemoteConfig() async { await Firebase.initializeApp( options: const FirebaseOptions( - apiKey: 'AIzaSyAgUhHU8wSJgO5MVNy95tMT07NEjzMOfz0', - authDomain: 'react-native-firebase-testing.firebaseapp.com', - databaseURL: 'https://react-native-firebase-testing.firebaseio.com', - projectId: 'react-native-firebase-testing', - storageBucket: 'react-native-firebase-testing.appspot.com', - messagingSenderId: '448618578101', - appId: '1:448618578101:web:772d484dc9eb15e9ac3efc', - measurementId: 'G-0N1G9FLDZE'), + apiKey: 'AIzaSyAgUhHU8wSJgO5MVNy95tMT07NEjzMOfz0', + authDomain: 'react-native-firebase-testing.firebaseapp.com', + databaseURL: 'https://react-native-firebase-testing.firebaseio.com', + projectId: 'react-native-firebase-testing', + storageBucket: 'react-native-firebase-testing.appspot.com', + messagingSenderId: '448618578101', + appId: '1:448618578101:web:772d484dc9eb15e9ac3efc', + measurementId: 'G-0N1G9FLDZE', + ), ); final FirebaseRemoteConfig remoteConfig = FirebaseRemoteConfig.instance; + await remoteConfig.ensureInitialized(); await remoteConfig.setConfigSettings(RemoteConfigSettings( fetchTimeout: const Duration(seconds: 10), minimumFetchInterval: const Duration(hours: 1), @@ -102,6 +109,7 @@ Future setupRemoteConfig() async { 'welcome': 'default welcome', 'hello': 'default hello', }); + await remoteConfig.fetchAndActivate(); return remoteConfig; } diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner/DebugProfile.entitlements b/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner/DebugProfile.entitlements index dddb8a30..08c3ab17 100644 --- a/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner/DebugProfile.entitlements +++ b/packages/firebase_remote_config/firebase_remote_config_desktop/example/macos/Runner/DebugProfile.entitlements @@ -8,5 +8,7 @@ com.apple.security.network.server + com.apple.security.network.client + diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/lib/firebase_remote_config_desktop.dart b/packages/firebase_remote_config/firebase_remote_config_desktop/lib/firebase_remote_config_desktop.dart index b521d4d3..3b68807b 100644 --- a/packages/firebase_remote_config/firebase_remote_config_desktop/lib/firebase_remote_config_desktop.dart +++ b/packages/firebase_remote_config/firebase_remote_config_desktop/lib/firebase_remote_config_desktop.dart @@ -20,6 +20,10 @@ class FirebaseRemoteConfigDesktop extends FirebaseRemoteConfigPlatform { }) : _app = core_dart.Firebase.app(app.name), super(appInstance: app); + FirebaseRemoteConfigDesktop._() + : _app = null, + super(appInstance: null); + /// Called by PluginRegistry to register this plugin as the implementation for Desktop static void registerWith() { FirebaseRemoteConfigPlatform.instance = @@ -31,7 +35,7 @@ class FirebaseRemoteConfigDesktop extends FirebaseRemoteConfigPlatform { /// // ignore: prefer_constructors_over_static_methods static FirebaseRemoteConfigDesktop get instance { - return FirebaseRemoteConfigDesktop(app: Firebase.app()); + return FirebaseRemoteConfigDesktop._(); } @override @@ -40,7 +44,7 @@ class FirebaseRemoteConfigDesktop extends FirebaseRemoteConfigPlatform { }) => FirebaseRemoteConfigDesktop(app: app ?? Firebase.app()); - final core_dart.FirebaseApp _app; + final core_dart.FirebaseApp? _app; late final remote_config.FirebaseRemoteConfig _remoteConfig = remote_config.FirebaseRemoteConfig.instanceFor(app: _app); @@ -53,6 +57,21 @@ class FirebaseRemoteConfigDesktop extends FirebaseRemoteConfigPlatform { FirebaseRemoteConfigPlatform setInitialValues({ required Map remoteConfigValues, }) { + if (!remoteConfigValues.containsKey('fetchTimeout')) { + remoteConfigValues['fetchTimeout'] = 60; + } + if (!remoteConfigValues.containsKey('minimumFetchInterval')) { + remoteConfigValues['minimumFetchInterval'] = 12 * 60; + } + if (!remoteConfigValues.containsKey('lastFetchStatus')) { + remoteConfigValues['lastFetchStatus'] = 'noFetchYet'; + } + if (!remoteConfigValues.containsKey('lastFetchTime')) { + remoteConfigValues['lastFetchTime'] = 0; + } + if (!remoteConfigValues.containsKey('parameters')) { + remoteConfigValues['parameters'] = {}; + } _remoteConfig.setInitialValues(remoteConfigValues: remoteConfigValues); return this; } From 529978191c15ca231004ee637fb7e7faa02daa50 Mon Sep 17 00:00:00 2001 From: Tim Whiting Date: Fri, 13 May 2022 21:43:02 -0600 Subject: [PATCH 14/30] remove firebaseapis --- .../firebase_remote_config_dart/pubspec.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/firebase_remote_config/firebase_remote_config_dart/pubspec.yaml b/packages/firebase_remote_config/firebase_remote_config_dart/pubspec.yaml index b3aaa490..bfe00687 100644 --- a/packages/firebase_remote_config/firebase_remote_config_dart/pubspec.yaml +++ b/packages/firebase_remote_config/firebase_remote_config_dart/pubspec.yaml @@ -10,7 +10,6 @@ environment: dependencies: collection: ^1.15.0 firebase_core_dart: ^0.1.1 - firebaseapis: ^0.1.1 http: ^0.13.4 meta: ^1.7.0 storagebox: ^0.1.0+2 From 56401b6b7689d9a26e7506809d55d4b386e4c867 Mon Sep 17 00:00:00 2001 From: Tim Whiting Date: Fri, 13 May 2022 21:45:48 -0600 Subject: [PATCH 15/30] remove reference to firebaseapis --- .../lib/src/remote_config_value.dart | 9 --------- 1 file changed, 9 deletions(-) diff --git a/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/remote_config_value.dart b/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/remote_config_value.dart index a6b0ce4d..acca1730 100644 --- a/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/remote_config_value.dart +++ b/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/remote_config_value.dart @@ -1,6 +1,5 @@ // ignore_for_file: require_trailing_commas -import 'package:firebaseapis/firebaseremoteconfig/v1.dart' as api; import 'package:meta/meta.dart'; /// ValueSource defines the possible sources of a config parameter value. @@ -22,14 +21,6 @@ class RemoteConfigValue { @protected RemoteConfigValue(this._value, this.source); - /// Creates a new RemoteConfigValue from the api - factory RemoteConfigValue.fromApi(api.RemoteConfigParameter value) { - return RemoteConfigValue( - value.defaultValue?.value, - ValueSource.valueRemote, - ); - } - /// Creates a new RemoteConfigValue from json factory RemoteConfigValue.fromJson(Map remoteConfigValue) { return RemoteConfigValue( From ecdeec22af37044d54ca9caec5d2ba521f98e364 Mon Sep 17 00:00:00 2001 From: pr_Mais Date: Tue, 26 Apr 2022 00:55:22 +0300 Subject: [PATCH 16/30] chore: upgrade `desktop_webview_auth` to v0.0.7 --- .../firebase_auth/firebase_auth_desktop/example/pubspec.yaml | 2 +- packages/firebase_auth/firebase_auth_desktop/pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/firebase_auth/firebase_auth_desktop/example/pubspec.yaml b/packages/firebase_auth/firebase_auth_desktop/example/pubspec.yaml index 027e94b8..8798f497 100644 --- a/packages/firebase_auth/firebase_auth_desktop/example/pubspec.yaml +++ b/packages/firebase_auth/firebase_auth_desktop/example/pubspec.yaml @@ -22,7 +22,7 @@ environment: dependencies: crypto: ^3.0.1 - desktop_webview_auth: ^0.0.6 + desktop_webview_auth: ^0.0.7 firebase_auth: ^3.3.7 firebase_auth_desktop: ^0.1.1 firebase_core: ^1.12.0 diff --git a/packages/firebase_auth/firebase_auth_desktop/pubspec.yaml b/packages/firebase_auth/firebase_auth_desktop/pubspec.yaml index dfd93ae3..d1ce617d 100644 --- a/packages/firebase_auth/firebase_auth_desktop/pubspec.yaml +++ b/packages/firebase_auth/firebase_auth_desktop/pubspec.yaml @@ -9,7 +9,7 @@ environment: flutter: '>=1.20.0' dependencies: - desktop_webview_auth: ^0.0.6 + desktop_webview_auth: ^0.0.7 firebase_auth: ^3.3.7 firebase_auth_dart: ^0.1.1 firebase_auth_platform_interface: ^6.1.4 From 0d775b0f7ce88d9d263256a244a1391d30caea2d Mon Sep 17 00:00:00 2001 From: pr_Mais Date: Tue, 26 Apr 2022 00:55:31 +0300 Subject: [PATCH 17/30] chore(release): publish packages - firebase_auth_dart@0.1.2 - firebase_auth_desktop@0.1.2 - firebase_functions_dart@0.1.0+1 - firebase_functions_desktop@0.1.0+1 --- packages/firebase_auth/firebase_auth_dart/CHANGELOG.md | 7 +++++++ .../firebase_auth/firebase_auth_dart/example/pubspec.yaml | 2 +- packages/firebase_auth/firebase_auth_dart/pubspec.yaml | 2 +- packages/firebase_auth/firebase_auth_desktop/CHANGELOG.md | 6 ++++++ .../firebase_auth_desktop/example/pubspec.yaml | 2 +- packages/firebase_auth/firebase_auth_desktop/pubspec.yaml | 4 ++-- .../firebase_functions_dart/CHANGELOG.md | 4 ++++ .../firebase_functions_dart/pubspec.yaml | 4 ++-- .../firebase_functions_desktop/CHANGELOG.md | 4 ++++ .../firebase_functions_desktop/pubspec.yaml | 4 ++-- 10 files changed, 30 insertions(+), 9 deletions(-) diff --git a/packages/firebase_auth/firebase_auth_dart/CHANGELOG.md b/packages/firebase_auth/firebase_auth_dart/CHANGELOG.md index 6c69fbc0..16c6c4eb 100644 --- a/packages/firebase_auth/firebase_auth_dart/CHANGELOG.md +++ b/packages/firebase_auth/firebase_auth_dart/CHANGELOG.md @@ -1,3 +1,10 @@ +## 0.1.2 + + - **REFACTOR**: errors (#64). + - **REFACTOR**: 🔨 identity toolkit api layer (#61). + - **FEAT**: OAuth providers support (#65). + - **DOCS**: fix typo in import. + ## 0.1.1 - Graduate package to a stable release. See pre-releases prior to this version for changelog entries. diff --git a/packages/firebase_auth/firebase_auth_dart/example/pubspec.yaml b/packages/firebase_auth/firebase_auth_dart/example/pubspec.yaml index 8790f2ee..026a42b7 100644 --- a/packages/firebase_auth/firebase_auth_dart/example/pubspec.yaml +++ b/packages/firebase_auth/firebase_auth_dart/example/pubspec.yaml @@ -13,7 +13,7 @@ dependencies: ansicolor: ^2.0.1 args: ^2.3.0 cli_util: ^0.3.5 - firebase_auth_dart: ^0.1.1 + firebase_auth_dart: ^0.1.2 firebase_core_dart: ^0.1.1 dependency_overrides: firebase_auth_dart: diff --git a/packages/firebase_auth/firebase_auth_dart/pubspec.yaml b/packages/firebase_auth/firebase_auth_dart/pubspec.yaml index d263eb89..a2d8eb17 100644 --- a/packages/firebase_auth/firebase_auth_dart/pubspec.yaml +++ b/packages/firebase_auth/firebase_auth_dart/pubspec.yaml @@ -2,7 +2,7 @@ name: firebase_auth_dart description: TODO homepage: https://firebase.flutter.dev repository: https://github.com/invertase/flutterfire_dart -version: 0.1.1 +version: 0.1.2 environment: sdk: '>=2.12.0 <3.0.0' diff --git a/packages/firebase_auth/firebase_auth_desktop/CHANGELOG.md b/packages/firebase_auth/firebase_auth_desktop/CHANGELOG.md index 60cd6b1a..d0d10090 100644 --- a/packages/firebase_auth/firebase_auth_desktop/CHANGELOG.md +++ b/packages/firebase_auth/firebase_auth_desktop/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.1.2 + + - **REFACTOR**: 🔨 identity toolkit api layer (#61). + - **FEAT**: OAuth providers support (#65). + - **FEAT**: support for signInWithCustomToken (#62). + ## 0.1.1 - Graduate package to a stable release. See pre-releases prior to this version for changelog entries. diff --git a/packages/firebase_auth/firebase_auth_desktop/example/pubspec.yaml b/packages/firebase_auth/firebase_auth_desktop/example/pubspec.yaml index 8798f497..e4afc3ab 100644 --- a/packages/firebase_auth/firebase_auth_desktop/example/pubspec.yaml +++ b/packages/firebase_auth/firebase_auth_desktop/example/pubspec.yaml @@ -24,7 +24,7 @@ dependencies: crypto: ^3.0.1 desktop_webview_auth: ^0.0.7 firebase_auth: ^3.3.7 - firebase_auth_desktop: ^0.1.1 + firebase_auth_desktop: ^0.1.2 firebase_core: ^1.12.0 firebase_core_desktop: ^0.1.1 flutter: diff --git a/packages/firebase_auth/firebase_auth_desktop/pubspec.yaml b/packages/firebase_auth/firebase_auth_desktop/pubspec.yaml index d1ce617d..a255ed76 100644 --- a/packages/firebase_auth/firebase_auth_desktop/pubspec.yaml +++ b/packages/firebase_auth/firebase_auth_desktop/pubspec.yaml @@ -2,7 +2,7 @@ name: firebase_auth_desktop description: Desktop implementation of firebase_auth homepage: https://firebase.flutter.dev repository: https://github.com/invertase/flutterfire_dart -version: 0.1.1 +version: 0.1.2 environment: sdk: '>=2.12.0 <3.0.0' @@ -11,7 +11,7 @@ environment: dependencies: desktop_webview_auth: ^0.0.7 firebase_auth: ^3.3.7 - firebase_auth_dart: ^0.1.1 + firebase_auth_dart: ^0.1.2 firebase_auth_platform_interface: ^6.1.4 firebase_core: ^1.12.0 firebase_core_dart: ^0.1.1 diff --git a/packages/firebase_functions/firebase_functions_dart/CHANGELOG.md b/packages/firebase_functions/firebase_functions_dart/CHANGELOG.md index 380b62bf..7f6d1cb6 100644 --- a/packages/firebase_functions/firebase_functions_dart/CHANGELOG.md +++ b/packages/firebase_functions/firebase_functions_dart/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.0+1 + + - Update a dependency to the latest release. + ## 0.1.0 - Graduate package to a stable release. See pre-releases prior to this version for changelog entries. diff --git a/packages/firebase_functions/firebase_functions_dart/pubspec.yaml b/packages/firebase_functions/firebase_functions_dart/pubspec.yaml index 462bde56..850be83b 100644 --- a/packages/firebase_functions/firebase_functions_dart/pubspec.yaml +++ b/packages/firebase_functions/firebase_functions_dart/pubspec.yaml @@ -2,14 +2,14 @@ name: firebase_functions_dart description: Pure Dart implementation of FlutterFire CloudFunctions API. homepage: https://firebase.flutter.dev repository: https://github.com/invertase/flutterfire_dart -version: 0.1.0 +version: 0.1.0+1 environment: sdk: '>=2.12.0 <3.0.0' dependencies: collection: ^1.15.0 - firebase_auth_dart: ^0.1.1 + firebase_auth_dart: ^0.1.2 firebase_core_dart: ^0.1.1 http: ^0.13.4 meta: ^1.7.0 diff --git a/packages/firebase_functions/firebase_functions_desktop/CHANGELOG.md b/packages/firebase_functions/firebase_functions_desktop/CHANGELOG.md index 380b62bf..7f6d1cb6 100644 --- a/packages/firebase_functions/firebase_functions_desktop/CHANGELOG.md +++ b/packages/firebase_functions/firebase_functions_desktop/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.0+1 + + - Update a dependency to the latest release. + ## 0.1.0 - Graduate package to a stable release. See pre-releases prior to this version for changelog entries. diff --git a/packages/firebase_functions/firebase_functions_desktop/pubspec.yaml b/packages/firebase_functions/firebase_functions_desktop/pubspec.yaml index 28888cde..91c98b6d 100644 --- a/packages/firebase_functions/firebase_functions_desktop/pubspec.yaml +++ b/packages/firebase_functions/firebase_functions_desktop/pubspec.yaml @@ -2,7 +2,7 @@ name: firebase_functions_desktop description: Desktop implementation of cloud_functions homepage: https://firebase.flutter.dev repository: https://github.com/invertase/flutterfire_dart -version: 0.1.0 +version: 0.1.0+1 environment: sdk: '>=2.12.0 <3.0.0' @@ -14,7 +14,7 @@ dependencies: firebase_core: ^1.10.0 firebase_core_dart: ^0.1.1 firebase_core_desktop: ^0.1.1 - firebase_functions_dart: ^0.1.0 + firebase_functions_dart: ^0.1.0+1 flutter: sdk: flutter http: ^0.13.4 From 112e1f1a0242d7d4dd647611d55a2d973d4a1add Mon Sep 17 00:00:00 2001 From: Mais Alheraki Date: Thu, 28 Apr 2022 10:54:59 +0300 Subject: [PATCH 18/30] feat(firebase_auth_dart): web support (#72) --- .../lib/firebase_auth_dart.dart | 3 +- .../firebase_auth_dart/lib/src/api/api.dart | 8 +- .../lib/src/api/authentication/sms.dart | 2 +- .../lib/src/api/authentication/token.dart | 12 +- .../lib/src/firebase_auth.dart | 19 +-- .../lib/src/utils/persistence.dart | 114 ------------------ .../firebase_auth_dart/pubspec.yaml | 1 + .../test/firebase_auth_dart_test.dart | 60 ++++----- .../firebase_functions_dart/pubspec.yaml | 1 + .../firebase_functions_integration_test.dart | 8 +- .../test/firebase_functions_test.dart | 14 ++- 11 files changed, 72 insertions(+), 170 deletions(-) delete mode 100644 packages/firebase_auth/firebase_auth_dart/lib/src/utils/persistence.dart diff --git a/packages/firebase_auth/firebase_auth_dart/lib/firebase_auth_dart.dart b/packages/firebase_auth/firebase_auth_dart/lib/firebase_auth_dart.dart index 49f5b0fd..73637de6 100644 --- a/packages/firebase_auth/firebase_auth_dart/lib/firebase_auth_dart.dart +++ b/packages/firebase_auth/firebase_auth_dart/lib/firebase_auth_dart.dart @@ -7,13 +7,13 @@ library firebase_auth_dart; import 'dart:async'; -import 'dart:convert'; import 'dart:developer'; import 'dart:io'; import 'package:firebase_core_dart/firebase_core_dart.dart'; import 'package:http/http.dart' as http; import 'package:meta/meta.dart'; +import 'package:storagebox/storagebox.dart'; import 'src/api/api.dart'; import 'src/api/errors.dart'; @@ -46,4 +46,3 @@ part 'src/user.dart'; part 'src/user_credential.dart'; part 'src/user_info.dart'; part 'src/user_metadata.dart'; -part 'src/utils/persistence.dart'; diff --git a/packages/firebase_auth/firebase_auth_dart/lib/src/api/api.dart b/packages/firebase_auth/firebase_auth_dart/lib/src/api/api.dart index 2c61f7ad..86a91060 100644 --- a/packages/firebase_auth/firebase_auth_dart/lib/src/api/api.dart +++ b/packages/firebase_auth/firebase_auth_dart/lib/src/api/api.dart @@ -38,7 +38,7 @@ part 'authentication/sms.dart'; part 'authentication/token.dart'; part 'emulator.dart'; -/// All API classes calling to IDP API must extend this template. +/// All API classes calling to Identity Toolkit API must extend this template. abstract class APIDelegate { /// Construct a new [APIDelegate]. const APIDelegate(this.api); @@ -46,7 +46,7 @@ abstract class APIDelegate { /// The [API] instance containing required configurations to make the requests. final API api; - /// Convert [DetailedApiRequestError] thrown by idp to [FirebaseAuthException]. + /// Convert [DetailedApiRequestError] thrown by Identity Toolkit to [FirebaseAuthException]. FirebaseAuthException makeAuthException(DetailedApiRequestError apiError) { try { final json = apiError.jsonResponse; @@ -83,7 +83,7 @@ abstract class APIDelegate { } } -/// Configurations necessary for making all idp requests. +/// Configurations necessary for making all Identity Toolkit requests. @protected class APIConfig { /// Construct [APIConfig]. @@ -133,7 +133,7 @@ abstract class SignInResponse { } } -/// A return type from Idp authentication requests, must be extended by any other response +/// A return type from Identity Toolkit authentication requests, must be extended by any other response /// type for any operation that requires idToken. @protected abstract class IdTokenResponse { diff --git a/packages/firebase_auth/firebase_auth_dart/lib/src/api/authentication/sms.dart b/packages/firebase_auth/firebase_auth_dart/lib/src/api/authentication/sms.dart index 68288a7e..539cc94b 100644 --- a/packages/firebase_auth/firebase_auth_dart/lib/src/api/authentication/sms.dart +++ b/packages/firebase_auth/firebase_auth_dart/lib/src/api/authentication/sms.dart @@ -3,7 +3,7 @@ part of api; /// A return type from Idp phone authentication requests. @internal class SignInWithPhoneNumberResponse extends SignInResponse { - /// Construct a new [IdTokenResponse]. + /// Construct a new [SignInWithPhoneNumberResponse]. SignInWithPhoneNumberResponse._({ required String idToken, required String refreshToken, diff --git a/packages/firebase_auth/firebase_auth_dart/lib/src/api/authentication/token.dart b/packages/firebase_auth/firebase_auth_dart/lib/src/api/authentication/token.dart index 34304b90..f6d9d45b 100644 --- a/packages/firebase_auth/firebase_auth_dart/lib/src/api/authentication/token.dart +++ b/packages/firebase_auth/firebase_auth_dart/lib/src/api/authentication/token.dart @@ -7,14 +7,20 @@ class IdToken extends APIDelegate { // ignore: public_member_api_docs IdToken(API api) : super(api); - /// Refresh a user ID token using the refreshToken, + /// Refresh a user's IdToken using a `refreshToken`, /// will refresh even if the token hasn't expired. /// + /// Common error codes: + /// - `TOKEN_EXPIRED`: The user's credential is no longer valid. The user must sign in again. + /// - `USER_DISABLED`: The user account has been disabled by an administrator. + /// - `USER_NOT_FOUND`: The user corresponding to the refresh token was not found. It is likely the user was deleted. + /// - `INVALID_REFRESH_TOKEN`: An invalid refresh token is provided. + /// - `INVALID_GRANT_TYPE`: the grant type specified is invalid. + /// - `MISSING_REFRESH_TOKEN`: no refresh token provided. + /// - API key not valid. Please pass a valid API key. (invalid API key provided) Future refreshIdToken(String? refreshToken) async { try { return await _exchangeRefreshWithIdToken(refreshToken); - } on HttpException catch (_) { - rethrow; } catch (_) { rethrow; } diff --git a/packages/firebase_auth/firebase_auth_dart/lib/src/firebase_auth.dart b/packages/firebase_auth/firebase_auth_dart/lib/src/firebase_auth.dart index 4df0f3b3..bfae069a 100644 --- a/packages/firebase_auth/firebase_auth_dart/lib/src/firebase_auth.dart +++ b/packages/firebase_auth/firebase_auth_dart/lib/src/firebase_auth.dart @@ -55,13 +55,15 @@ class FirebaseAuth { _api.client = client; } - StorageBox get _userStorage => - StorageBox.instanceOf(app.options.projectId); + StorageBox get _userStorage => StorageBox( + app.options.projectId, + configPathPrefix: '.firebase-auth', + ); Map? _localUser() { try { - return (_userStorage.getValue('${app.options.apiKey}:${app.name}') - as Map)['currentUser']; + return (_userStorage['${app.options.apiKey}:${app.name}'] + as Map?)?['currentUser']; } catch (e) { return null; } @@ -109,10 +111,11 @@ class FirebaseAuth { @protected void _updateCurrentUserAndEvents(User? user, [bool authStateChanged = false]) { - _userStorage.putValue( - '${app.options.apiKey}:${app.name}', - {'currentUser': user?.toMap()}, - ); + _userStorage.addAll({ + '${app.options.apiKey}:${app.name}': { + 'currentUser': user?.toMap(), + }, + }); _currentUser = user; diff --git a/packages/firebase_auth/firebase_auth_dart/lib/src/utils/persistence.dart b/packages/firebase_auth/firebase_auth_dart/lib/src/utils/persistence.dart deleted file mode 100644 index b47dc1bb..00000000 --- a/packages/firebase_auth/firebase_auth_dart/lib/src/utils/persistence.dart +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright 2021 Invertase Limited. All rights reserved. -// Use of this source code is governed by a BSD-style license -// that can be found in the LICENSE file. - -// ignore_for_file: require_trailing_commas - -part of firebase_auth_dart; - -/// A storage box is a container for key-value pairs of data -/// which exists in one box with specific name. -/// -/// To get or create a new box, first initialize an instance -/// with a name, then use the methods provided for that box, -/// if the box doesn't exist, it will be created, if it exists -/// data will be written to the existing one. -class StorageBox { - // ignore: public_member_api_docs - StorageBox._(this._name); - - /// Get a [StorageBox] instance for a given name. - static StorageBox instanceOf([String name = 'user']) { - if (!_instances.containsKey(name)) { - _instances.addAll({name: StorageBox._(name)}); - } - return _instances[name]!; - } - - static final Map _instances = {}; - - /// The name of the box which you want to create or get. - final String _name; - - File get _file { - /// `APPDATA` for windows, `HOME` for linux and mac - final _env = Platform.environment; - final _sep = Platform.pathSeparator; - - late String _home; - - if (Platform.isLinux || Platform.isMacOS) { - // ignore: cast_nullable_to_non_nullable - _home = _env['HOME'] as String; - } - - if (Platform.isWindows) { - // ignore: cast_nullable_to_non_nullable - _home = _env['APPDATA'] as String; - } - - final _path = '$_home$_sep.firebase-auth$_sep$_name.json'; - - return File(_path); - } - - /// Store the key-value pair in the box with [_name], if key already - /// exists the value will be overwritten. - void putValue(String key, T? value) { - if (!_file.existsSync()) { - _file.createSync(recursive: true); - } - final contentMap = _readFile(); - - if (value != null) { - contentMap[key] = value; - } else { - contentMap.remove(key); - } - - if (contentMap.isEmpty) { - _file.deleteSync(recursive: true); - } else { - _file.writeAsStringSync(jsonEncode(contentMap)); - } - } - - /// Get the value for a specific key, if no such key exists, or no such box with [_name] - /// [StorageBoxException] will be thrown. - T getValue(String key) { - try { - final contentText = _file.readAsStringSync(); - final Map content = jsonDecode(contentText); - - if (!content.containsKey(key)) { - throw StorageBoxException('Key $key does not exist.'); - } - - return content[key]; - } on FileSystemException { - throw StorageBoxException('Box $_name does not exist, ' - 'to create one, add some value using "putValue".'); - } - } - - Map _readFile() { - final contentText = _file.readAsStringSync(); - - if (contentText.isEmpty) { - return {}; - } - - final content = json.decode(contentText) as Map; - - return content; - } -} - -/// Throw when there's an error with [StorageBox] methods. -class StorageBoxException implements Exception { - // ignore: public_member_api_docs - StorageBoxException([this.message]); - - /// Message describing the error. - final String? message; -} diff --git a/packages/firebase_auth/firebase_auth_dart/pubspec.yaml b/packages/firebase_auth/firebase_auth_dart/pubspec.yaml index a2d8eb17..ef3981d5 100644 --- a/packages/firebase_auth/firebase_auth_dart/pubspec.yaml +++ b/packages/firebase_auth/firebase_auth_dart/pubspec.yaml @@ -13,6 +13,7 @@ dependencies: googleapis_auth: ^1.1.0 http: ^0.13.4 meta: ^1.3.0 + storagebox: ^0.1.0+2 dev_dependencies: async: ^2.8.1 diff --git a/packages/firebase_auth/firebase_auth_dart/test/firebase_auth_dart_test.dart b/packages/firebase_auth/firebase_auth_dart/test/firebase_auth_dart_test.dart index f5f283e9..66224858 100644 --- a/packages/firebase_auth/firebase_auth_dart/test/firebase_auth_dart_test.dart +++ b/packages/firebase_auth/firebase_auth_dart/test/firebase_auth_dart_test.dart @@ -178,35 +178,35 @@ void main() { }); }); }); - group('StorageBox ', () { - test('put a new value.', () { - final box = StorageBox.instanceOf('box'); - box.putValue('key', '123'); - - expect(box.getValue('key'), '123'); - }); - test('put a null value does not add the value.', () { - final box = StorageBox.instanceOf('box'); - box.putValue('key_2', null); - expect( - () => box.getValue('key_2'), - throwsA(isA()), - ); - }); - test('get a key that does not exist.', () { - final box = StorageBox.instanceOf('box'); - expect( - () => box.getValue('random_key'), - throwsA(isA()), - ); - }); - test('get a key from a box that does not exist.', () { - final box = StorageBox.instanceOf('box_'); - expect( - () => box.getValue('key'), - throwsA(isA()), - ); - }); - }); + // group('StorageBox ', () { + // test('put a new value.', () { + // final box = StorageBox.instanceOf('box'); + // box.putValue('key', '123'); + + // expect(box.getValue('key'), '123'); + // }); + // test('put a null value does not add the value.', () { + // final box = StorageBox.instanceOf('box'); + // box.putValue('key_2', null); + // expect( + // () => box.getValue('key_2'), + // throwsA(isA()), + // ); + // }); + // test('get a key that does not exist.', () { + // final box = StorageBox.instanceOf('box'); + // expect( + // () => box.getValue('random_key'), + // throwsA(isA()), + // ); + // }); + // test('get a key from a box that does not exist.', () { + // final box = StorageBox.instanceOf('box_'); + // expect( + // () => box.getValue('key'), + // throwsA(isA()), + // ); + // }); + // }); }); } diff --git a/packages/firebase_functions/firebase_functions_dart/pubspec.yaml b/packages/firebase_functions/firebase_functions_dart/pubspec.yaml index 850be83b..13a80565 100644 --- a/packages/firebase_functions/firebase_functions_dart/pubspec.yaml +++ b/packages/firebase_functions/firebase_functions_dart/pubspec.yaml @@ -13,6 +13,7 @@ dependencies: firebase_core_dart: ^0.1.1 http: ^0.13.4 meta: ^1.7.0 + storagebox: ^0.1.0+1 dev_dependencies: test: ^1.16.0 diff --git a/packages/firebase_functions/firebase_functions_dart/test/firebase_functions_integration_test.dart b/packages/firebase_functions/firebase_functions_dart/test/firebase_functions_integration_test.dart index 0621db19..0ca9c3b4 100644 --- a/packages/firebase_functions/firebase_functions_dart/test/firebase_functions_integration_test.dart +++ b/packages/firebase_functions/firebase_functions_dart/test/firebase_functions_integration_test.dart @@ -8,6 +8,7 @@ import 'package:firebase_auth_dart/firebase_auth_dart.dart'; import 'package:firebase_core_dart/firebase_core_dart.dart'; import 'package:firebase_functions_dart/firebase_functions_dart.dart'; import 'package:http/http.dart' as http; +import 'package:storagebox/storagebox.dart'; import 'package:test/test.dart'; import 'data.dart' as data; @@ -154,11 +155,11 @@ Future main() async { functions.useFunctionsEmulator('localhost', 5001); httpsCallable = functions.httpsCallable('testFunctionAuthorized'); }); - tearDown(() { + final config = StorageBox(app.options.projectId); + // Clear auth storage - StorageBox.instanceOf(app.options.projectId) - .putValue('${app.options.apiKey}:${app.name}', null); + config.remove('${app.options.apiKey}:${app.name}'); }); test('unauthorized access throws', () async { expect( @@ -167,7 +168,6 @@ Future main() async { .having((e) => e.code, 'code', contains('unauthenticated'))), ); }); - test('authorized access succeeds', () async { final auth = FirebaseAuth.instanceFor(app: app); await auth.useAuthEmulator(); diff --git a/packages/firebase_functions/firebase_functions_dart/test/firebase_functions_test.dart b/packages/firebase_functions/firebase_functions_dart/test/firebase_functions_test.dart index 74f5d4e2..e608d308 100644 --- a/packages/firebase_functions/firebase_functions_dart/test/firebase_functions_test.dart +++ b/packages/firebase_functions/firebase_functions_dart/test/firebase_functions_test.dart @@ -11,6 +11,7 @@ import 'package:firebase_core_dart/firebase_core_dart.dart'; import 'package:firebase_functions_dart/firebase_functions_dart.dart'; import 'package:http/http.dart' as http; import 'package:http/testing.dart'; +import 'package:storagebox/storagebox.dart'; import 'package:test/test.dart'; import 'data.dart' as data; @@ -379,9 +380,12 @@ Future main() async { options: firebaseOptions, name: 'auth_functions', ); + + // Clear auth storage + final config = StorageBox(app.options.projectId); + // Clear auth storage - StorageBox.instanceOf(app.options.projectId) - .putValue('${app.options.apiKey}:${app.name}', null); + config.remove('${app.options.apiKey}:${app.name}'); functions = FirebaseFunctions.instanceFor(app: app); functions.setApiClient(MockClient(_authCheck)); httpsCallable = functions.httpsCallable('testFunctionAuthorized'); @@ -389,8 +393,10 @@ Future main() async { tearDown(() async { // Clear auth storage - StorageBox.instanceOf(app.options.projectId) - .putValue('${app.options.apiKey}:${app.name}', null); + final config = StorageBox(app.options.projectId); + + // Clear auth storage + config.remove('${app.options.apiKey}:${app.name}'); }); test('unauthorized access throws', () async { From fe8d56b85206237cca3845876f00f22fb912af8b Mon Sep 17 00:00:00 2001 From: Mais Alheraki Date: Fri, 6 May 2022 11:01:14 +0300 Subject: [PATCH 19/30] feat(firebase_auth): Github auth provider (#74) --- .../lib/firebase_auth_dart.dart | 1 + .../lib/src/providers/facebook_auth.dart | 4 +- .../lib/src/providers/github_auth.dart | 88 +++ .../example/assets/github.png | Bin 0 -> 1571 bytes .../example/lib/auth.dart | 572 ++++++++---------- .../example/lib/auth_service.dart | 277 +++++++++ .../example/lib/profile.dart | 53 +- .../example/pubspec.yaml | 7 +- .../lib/src/utils/desktop_utils.dart | 4 + .../firebase_auth_desktop/pubspec.yaml | 2 +- 10 files changed, 645 insertions(+), 363 deletions(-) create mode 100644 packages/firebase_auth/firebase_auth_dart/lib/src/providers/github_auth.dart create mode 100644 packages/firebase_auth/firebase_auth_desktop/example/assets/github.png create mode 100644 packages/firebase_auth/firebase_auth_desktop/example/lib/auth_service.dart diff --git a/packages/firebase_auth/firebase_auth_dart/lib/firebase_auth_dart.dart b/packages/firebase_auth/firebase_auth_dart/lib/firebase_auth_dart.dart index 73637de6..4ed3d886 100644 --- a/packages/firebase_auth/firebase_auth_dart/lib/firebase_auth_dart.dart +++ b/packages/firebase_auth/firebase_auth_dart/lib/firebase_auth_dart.dart @@ -32,6 +32,7 @@ export 'src/auth_provider.dart'; export 'src/firebase_auth_exception.dart'; export 'src/providers/email_auth.dart'; export 'src/providers/facebook_auth.dart'; +export 'src/providers/github_auth.dart'; export 'src/providers/google_auth.dart'; export 'src/providers/oauth.dart'; export 'src/providers/phone_auth.dart'; diff --git a/packages/firebase_auth/firebase_auth_dart/lib/src/providers/facebook_auth.dart b/packages/firebase_auth/firebase_auth_dart/lib/src/providers/facebook_auth.dart index f420df50..f209087f 100644 --- a/packages/firebase_auth/firebase_auth_dart/lib/src/providers/facebook_auth.dart +++ b/packages/firebase_auth/firebase_auth_dart/lib/src/providers/facebook_auth.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license // that can be found in the LICENSE file. -// ignore_for_file: require_trailing_commas, non_constant_identifier_names, prefer_relative_imports +// ignore_for_file: require_trailing_commas, non_constant_identifier_names, prefer_relative_imports, avoid_returning_this import 'package:firebase_auth_dart/firebase_auth_dart.dart'; @@ -71,7 +71,6 @@ class FacebookAuthProvider extends AuthProvider { } /// Adds Facebook OAuth scope. - // ignore: avoid_returning_this FacebookAuthProvider addScope(String scope) { _scopes.add(scope); return this; @@ -79,7 +78,6 @@ class FacebookAuthProvider extends AuthProvider { /// Sets the OAuth custom parameters to pass in a Facebook OAuth /// request for popup and redirect sign-in operations. - // ignore: avoid_returning_this FacebookAuthProvider setCustomParameters( Map customOAuthParameters, ) { diff --git a/packages/firebase_auth/firebase_auth_dart/lib/src/providers/github_auth.dart b/packages/firebase_auth/firebase_auth_dart/lib/src/providers/github_auth.dart new file mode 100644 index 00000000..a4ffe6de --- /dev/null +++ b/packages/firebase_auth/firebase_auth_dart/lib/src/providers/github_auth.dart @@ -0,0 +1,88 @@ +// Copyright 2021 Invertase Limited. All rights reserved. +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file. + +// ignore_for_file: require_trailing_commas, non_constant_identifier_names, prefer_relative_imports, avoid_returning_this + +import 'package:firebase_auth_dart/firebase_auth_dart.dart'; + +const _kProviderId = 'github.com'; + +/// This class should be used to either create a new GitHub credential with an +/// access code, or use the provider to trigger user authentication flows. +/// +/// If authenticating with GitHub via a 3rd party, use the returned +/// `accessToken` to sign-in or link the user with the created credential, for +/// example: +/// +/// ```dart +/// String accessToken = '...'; // From 3rd party provider +/// var githubAuthCredential = GithubAuthProvider.credential(accessToken); +/// +/// FirebaseAuth.instance.signInWithCredential(githubAuthCredential) +/// .then(...); +/// ``` +class GithubAuthProvider extends AuthProvider { + /// Creates a new instance. + GithubAuthProvider() : super(_kProviderId); + + /// Create a new [GithubAuthCredential] from a provided [accessToken]; + static OAuthCredential credential(String accessToken) { + return GithubAuthCredential._credential( + accessToken, + ); + } + + /// This corresponds to the sign-in method identifier. + static String get GITHUB_SIGN_IN_METHOD { + return _kProviderId; + } + + // ignore: public_member_api_docs + static String get PROVIDER_ID { + return _kProviderId; + } + + final List _scopes = []; + Map _parameters = {}; + + /// Returns the currently assigned scopes to this provider instance. + List get scopes { + return _scopes; + } + + /// Returns the parameters for this provider instance. + Map get parameters { + return _parameters; + } + + /// Adds GitHub OAuth scope. + GithubAuthProvider addScope(String scope) { + _scopes.add(scope); + return this; + } + + /// Sets the OAuth custom parameters to pass in a GitHub OAuth + /// request for popup and redirect sign-in operations. + GithubAuthProvider setCustomParameters( + Map customOAuthParameters, + ) { + _parameters = customOAuthParameters; + return this; + } +} + +/// The auth credential returned from calling +/// [GithubAuthProvider.credential]. +class GithubAuthCredential extends OAuthCredential { + GithubAuthCredential._({ + required String accessToken, + }) : super( + providerId: _kProviderId, + signInMethod: _kProviderId, + accessToken: accessToken); + + factory GithubAuthCredential._credential(String accessToken) { + return GithubAuthCredential._(accessToken: accessToken); + } +} diff --git a/packages/firebase_auth/firebase_auth_desktop/example/assets/github.png b/packages/firebase_auth/firebase_auth_desktop/example/assets/github.png new file mode 100644 index 0000000000000000000000000000000000000000..628da97c70890c73e59204f5b140c4e67671e92d GIT binary patch literal 1571 zcmaJ>c~BE~6izDPQq)#Nu*KOf(n^(VHY9;fiINM65``pc+9*v(mL$bwfCjbc%v9V{8r9iX|O%>Nr%pLD2qT{mty}c=LVleeamv znz3SOSm@kP8jThvOOq(56Yzh*fz(booe!uZij=BJC6+_lbvQ~B8nA2>kXdv_RDtRY z`5QXWWEySCe6vbTs^#f?J!WC*{1~RgVx!nJTJjQyO{dRANgx|FnymtGbD9%JmCh9^y)##j7{Dcqfn*1ta$rG89pJF6w-S7Z037$rr|y0;1Onp_ zGFJdT6Q!1C0AdVB0WOmpuV=AgAQ550Tn+-mivTtYPJmz*#75#_n9oV%!#rSOfmAfy zki%C~=fTp1{O#BLpJ|0jj#m6#|LRWit-vq3PE1z9ZqyvET4sX$-Icqy7t z<=aq5ff86AuBZBu6EjJsYWM0uejufWFTwPA7Su}0Bm$7KFb!q{Um_8~A{LUG#1l(l zSehUda@kU8LIRg9fkk2tZ;~ss5~R+mM<==F7hLHpxqLB>>PQS%Vc7b~?q!%T5+h8Q z4G=4Nzyi5WZ?^gkasJ{?Xhm`JC#WG6$1K2jb@=9&D3EgD#3UhGh#*21rJjulVXjCF zvp76q62jt0zzMG5C7DlfMgPl%C^3+~wf|}Lq=}jz|MmIcQjh1Ok6NjD$Em^Iv26D> z8tt_TnM9~^Tt8mflRGPOrrX|HtT3gG4LEuuk{g2Rn}QgJIa?gZo))!!=o_l9bvD%A zZ`aHajl8#~u?!4f7F#*b*->A=R2L)6!>saz?h>#wTXT-I(XmQ zx{84skS>k=i~i`(6k4C7;Zpfx%dCPVjPayMf8pugtGM=~s=Id1l#8MZJ1-73wV#Q3 zR3>v3%}jbQs1f_Z0xo;%=LILlA+nTpKI4ha%xWW}uqHrNao~&T4AY6m`P$_n-6h*g zhoX+e4n%~gl_lhe#s+AMb7d{5WzvYTa%6Q~si@@4{;s(0zU|H&P3fE+t{7X`S#Cj@ zC#vd}^4pcBD*77Ny5=j$h8EL2_t$O38$SQiJ6fPjJMimypr~MB2(&P0aI|h}$64<0 z>_~duqNjaT=DM^6+N{&B_lED;F2wrl?!4Lk*2((x!fmrcsw+=cI^qttuZ9C}-m~5E z-ryYVpL%^xR#&(0YI5hz<(}F7-p)?FPcyJO-zVO>%9ZDXJH8pnY;GJYFDQ>vd#j_* zRrd}L(r=!g+1#nQwsO?kpS`Qq8`NxE+Zy{gf7*_7J*U2V_|NpLo{iasj7VCg_V9&| ShohtYzipXxh2)4xTk this == AuthMode.login ? 'Sign in' @@ -62,6 +49,23 @@ extension on AuthMode { : 'Register'; } +extension on SocialOAuthProvider { + Buttons get button { + switch (this) { + case SocialOAuthProvider.google: + return Buttons.Google; + case SocialOAuthProvider.facebook: + return Buttons.Facebook; + case SocialOAuthProvider.twitter: + return Buttons.Twitter; + case SocialOAuthProvider.github: + return Buttons.GitHub; + case SocialOAuthProvider.apple: + return Buttons.Apple; + } + } +} + /// Entrypoint example for various sign-in flows with Firebase. class AuthGate extends StatefulWidget { const AuthGate({Key? key}) : super(key: key); @@ -71,6 +75,8 @@ class AuthGate extends StatefulWidget { } class _AuthGateState extends State { + final authService = AuthService(); + TextEditingController emailController = TextEditingController(); TextEditingController passwordController = TextEditingController(); TextEditingController phoneController = TextEditingController(); @@ -96,10 +102,11 @@ class _AuthGateState extends State { } } - Future _resetPassword() async { + Future _resetPassword() async { resetError(); String? email; + await showDialog( context: context, builder: (context) { @@ -131,7 +138,7 @@ class _AuthGateState extends State { if (email != null) { try { - await FirebaseAuth.instance.sendPasswordResetEmail(email: email!); + await authService.resetPassword(email!); ScaffoldSnackbar.of(context).show('Password reset email is sent'); } catch (e) { ScaffoldSnackbar.of(context).show('Error resetting'); @@ -139,26 +146,18 @@ class _AuthGateState extends State { } } - Future _emailAuth() async { + Future _emailAuth() async { resetError(); if (formKey.currentState?.validate() ?? false) { setIsLoading(); try { - if (mode == AuthMode.login) { - await _auth.signInWithEmailAndPassword( - email: emailController.text, - password: passwordController.text, - ); - } else if (mode == AuthMode.register) { - await _auth.createUserWithEmailAndPassword( - email: emailController.text, - password: passwordController.text, - ); - } else { - await _onPhoneAuth(); - } + await authService.emailAuth( + mode, + email: emailController.text, + password: passwordController.text, + ); } on FirebaseAuthException catch (e) { setIsLoading(); @@ -175,7 +174,7 @@ class _AuthGateState extends State { setIsLoading(); try { - await _auth.signInAnonymously(); + await authService.anonymousAuth(); } on FirebaseAuthException catch (e) { setState(() { error = '${e.message}'; @@ -189,353 +188,302 @@ class _AuthGateState extends State { } } - Future _onPhoneAuth() async { + Future _phoneAuth() async { resetError(); - if (mode != AuthMode.phone) { + try { + setIsLoading(); + await authService.phoneAuth( + phoneNumber: phoneController.text, + smsCode: () { + return ExampleDialog.of(context).show('SMS Code:', 'Sign in'); + }, + ); + } catch (e) { setState(() { - mode = AuthMode.phone; + error = '$e'; }); - } else { - try { - final confirmationResult = await FirebaseAuth.instance - .signInWithPhoneNumber(phoneController.text); - - final smsCode = - await ExampleDialog.of(context).show('SMS Code:', 'Sign in'); - - if (smsCode != null) { - await confirmationResult.confirm(smsCode); - } - } catch (e) { - setState(() { - error = '$e'; - }); - } finally { - setIsLoading(); - } + } finally { + setIsLoading(); } } - Future _onGoogleSignIn() async { + Future _googleSignIn() async { resetError(); try { - final result = await DesktopWebviewAuth.signIn( - GoogleSignInArgs( - clientId: - '448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com', - redirectUri: redirectUri, - scope: 'https://www.googleapis.com/auth/userinfo.email', - ), - ); - - if (result != null) { - // Create a new credential - final credential = GoogleAuthProvider.credential( - idToken: result.idToken, - accessToken: result.accessToken, - ); - - // Once signed in, return the UserCredential - await _auth.signInWithCredential(credential); - } + setIsLoading(); + await authService.googleSignIn(); } on FirebaseAuthException catch (e) { setState(() { error = '${e.message}'; }); + } finally { + setIsLoading(); } } - Future _onTwitterSignIn() async { + Future _twitterSignIn() async { resetError(); try { - final result = await DesktopWebviewAuth.signIn( - TwitterSignInArgs( - apiKey: twitterApiKey, - apiSecretKey: twitterApiSecretKey, - redirectUri: redirectUri, - ), - ); - - if (result != null) { - // Create a new credential - final credential = TwitterAuthProvider.credential( - secret: result.tokenSecret!, - accessToken: result.accessToken!, - ); - - // Once signed in, return the UserCredential - await _auth.signInWithCredential(credential); - } + setIsLoading(); + await authService.twitterSignIn(); } on FirebaseAuthException catch (e) { setState(() { error = '${e.message}'; }); + } finally { + setIsLoading(); } } - Future _onFacebookSignIn() async { + Future _facebookSignIn() async { resetError(); try { - final result = await DesktopWebviewAuth.signIn( - FacebookSignInArgs( - clientId: facebookClientId, - redirectUri: redirectUri, - ), - ); - - if (result != null) { - // Create a new credential - final credential = FacebookAuthProvider.credential(result.accessToken!); - - // Once signed in, return the UserCredential - await _auth.signInWithCredential(credential); - } + setIsLoading(); + await authService.facebookSignIn(); } on FirebaseAuthException catch (e) { setState(() { error = '${e.message}'; }); + } finally { + setIsLoading(); } } - /// Generates a cryptographically secure random nonce, to be included in a - /// credential request. - String generateNonce([int length = 32]) { - const charset = - '0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._'; - final random = Random.secure(); - return List.generate(length, (_) => charset[random.nextInt(charset.length)]) - .join(); - } + Future _githubSignIn() async { + resetError(); - /// Returns the sha256 hash of [input] in hex notation. - String sha256ofString(String input) { - final bytes = utf8.encode(input); - final digest = sha256.convert(bytes); - return digest.toString(); + try { + setIsLoading(); + await authService.githubSignIn(); + } on FirebaseAuthException catch (e) { + setState(() { + error = '${e.message}'; + }); + } finally { + setIsLoading(); + } } - Future _onAppleSignIn() async { + Future _appleSignIn() async { try { - final rawNonce = generateNonce(); - final nonce = sha256ofString(rawNonce); - - final credential = await SignInWithApple.getAppleIDCredential( - scopes: [ - AppleIDAuthorizationScopes.email, - AppleIDAuthorizationScopes.fullName, - ], - ); - - debugPrint('${credential.state}'); - - if (credential.identityToken != null) { - // Create an `OAuthCredential` from the credential returned by Apple. - final oauthCredential = OAuthProvider('apple.com').credential( - idToken: credential.identityToken, - rawNonce: nonce, - ); - - // Sign in the user with Firebase. If the nonce we generated earlier does - // not match the nonce in `appleCredential.identityToken`, sign in will fail. - return await FirebaseAuth.instance - .signInWithCredential(oauthCredential); - } + setIsLoading(); + await authService.appleSignIn(); } on FirebaseAuthException catch (e) { setState(() { error = '${e.message}'; }); + } finally { + setIsLoading(); } } @override Widget build(BuildContext context) { return Scaffold( - body: SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(20), - child: Center( - child: SizedBox( - width: 400, - child: Form( - key: formKey, - autovalidateMode: AutovalidateMode.onUserInteraction, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - AnimatedError(text: error, show: error.isNotEmpty), - const SizedBox(height: 20), - if (mode != AuthMode.phone) - Column( - children: [ - TextFormField( - controller: emailController, - decoration: - const InputDecoration(hintText: 'Email'), - validator: (value) => - value != null && value.isNotEmpty - ? null - : 'Required', + body: Stack( + children: [ + SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(20), + child: Center( + child: SizedBox( + width: 400, + child: Form( + key: formKey, + autovalidateMode: AutovalidateMode.onUserInteraction, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + AnimatedError(text: error, show: error.isNotEmpty), + const SizedBox(height: 20), + if (mode != AuthMode.phone) + Column( + children: [ + TextFormField( + controller: emailController, + decoration: + const InputDecoration(hintText: 'Email'), + validator: (value) => + value != null && value.isNotEmpty + ? null + : 'Required', + ), + const SizedBox(height: 20), + TextFormField( + controller: passwordController, + obscureText: true, + decoration: + const InputDecoration(hintText: 'Password'), + validator: (value) => + value != null && value.isNotEmpty + ? null + : 'Required', + ), + ], + ), + const SizedBox(height: 10), + if (mode != AuthMode.phone) + TextButton( + onPressed: _resetPassword, + child: const Text('Forgot password?'), ), - const SizedBox(height: 20), + if (mode == AuthMode.phone) TextFormField( - controller: passwordController, - obscureText: true, - decoration: - const InputDecoration(hintText: 'Password'), + controller: phoneController, + decoration: const InputDecoration( + hintText: '+16505550101', + labelText: 'Phone number', + ), validator: (value) => value != null && value.isNotEmpty ? null : 'Required', ), - ], - ), - const SizedBox(height: 10), - if (mode != AuthMode.phone) - TextButton( - onPressed: _resetPassword, - child: const Text('Forgot password?'), - ), - if (mode == AuthMode.phone) - TextFormField( - controller: phoneController, - decoration: const InputDecoration( - hintText: '+16505550101', - labelText: 'Phone number', - ), - validator: (value) => value != null && value.isNotEmpty - ? null - : 'Required', - ), - const SizedBox(height: 20), - SizedBox( - width: double.infinity, - height: 50, - child: ElevatedButton( - onPressed: isLoading ? null : _emailAuth, - child: isLoading - ? const CircularProgressIndicator.adaptive() - : Text(mode.label), - ), - ), - const SizedBox(height: 20), - SizedBox( - width: double.infinity, - height: 50, - child: SignInButton( - Theme.of(context).brightness == Brightness.dark - ? Buttons.Google - : Buttons.GoogleDark, - onPressed: _onGoogleSignIn, - ), - ), - const SizedBox(height: 20), - SizedBox( - width: double.infinity, - height: 50, - child: SignInButton( - Buttons.Twitter, - onPressed: _onTwitterSignIn, - ), - ), - const SizedBox(height: 20), - SizedBox( - width: double.infinity, - height: 50, - child: SignInButton( - Buttons.FacebookNew, - onPressed: _onFacebookSignIn, - ), - ), - const SizedBox(height: 20), - if (defaultTargetPlatform == TargetPlatform.macOS) - SizedBox( - width: double.infinity, - height: 50, - child: SignInButton( - Buttons.Apple, - onPressed: _onAppleSignIn, + const SizedBox(height: 20), + SizedBox( + width: double.infinity, + height: 50, + child: ElevatedButton( + onPressed: isLoading + ? null + : mode == AuthMode.phone + ? _phoneAuth + : _emailAuth, + child: Text(mode.label), + ), ), - ), - const SizedBox(height: 20), - SizedBox( - width: double.infinity, - height: 50, - child: OutlinedButton( - onPressed: isLoading - ? null - : () { - if (mode != AuthMode.phone) { - setState(() { - mode = AuthMode.phone; - }); - } else { - setState(() { - mode = AuthMode.login; - }); - } - }, - child: isLoading - ? const CircularProgressIndicator.adaptive() - : Text( - mode != AuthMode.phone - ? 'Sign in with Phone Number' - : 'sign in with Email and Password', + const SizedBox(height: 20), + ...SocialOAuthProvider.values + .skipWhile( + (provider) => defaultTargetPlatform == + TargetPlatform.macOS + ? provider == SocialOAuthProvider.apple + : defaultTargetPlatform != TargetPlatform.macOS, + ) + .map((provider) { + return Padding( + padding: const EdgeInsets.only(bottom: 10), + child: SizedBox( + width: double.infinity, + height: 50, + child: SignInButton( + provider.button, + onPressed: () { + if (!isLoading) { + switch (provider) { + case SocialOAuthProvider.google: + _googleSignIn(); + break; + case SocialOAuthProvider.facebook: + _facebookSignIn(); + break; + case SocialOAuthProvider.twitter: + _twitterSignIn(); + break; + case SocialOAuthProvider.github: + _githubSignIn(); + break; + case SocialOAuthProvider.apple: + _appleSignIn(); + break; + } + } + }, ), - ), - ), - const SizedBox(height: 20), - if (mode != AuthMode.phone) - RichText( - text: TextSpan( - style: Theme.of(context).textTheme.bodyText1, - children: [ - TextSpan( - text: mode == AuthMode.login - ? "Don't have an account? " - : 'You have an account? ', ), - TextSpan( - text: mode == AuthMode.login - ? 'Register now' - : 'Click to login', - style: const TextStyle(color: Colors.blue), - recognizer: TapGestureRecognizer() - ..onTap = () { - setState(() { - mode = mode == AuthMode.login - ? AuthMode.register - : AuthMode.login; - }); - }, + ); + }).toList(), + const SizedBox(height: 20), + SizedBox( + width: double.infinity, + height: 50, + child: OutlinedButton( + onPressed: isLoading + ? null + : () { + if (mode != AuthMode.phone) { + setState(() { + mode = AuthMode.phone; + }); + } else { + setState(() { + mode = AuthMode.login; + }); + } + }, + child: Text( + mode != AuthMode.phone + ? 'Sign in with Phone Number' + : 'sign in with Email and Password', ), - ], + ), ), - ), - const SizedBox(height: 10), - RichText( - text: TextSpan( - style: Theme.of(context).textTheme.bodyText1, - children: [ - const TextSpan(text: 'Or '), - TextSpan( - text: 'continue as guest', - style: const TextStyle(color: Colors.blue), - recognizer: TapGestureRecognizer() - ..onTap = _anonymousAuth, + const SizedBox(height: 20), + if (mode != AuthMode.phone) + RichText( + text: TextSpan( + style: Theme.of(context).textTheme.bodyText1, + children: [ + TextSpan( + text: mode == AuthMode.login + ? "Don't have an account? " + : 'You have an account? ', + ), + TextSpan( + text: mode == AuthMode.login + ? 'Register now' + : 'Click to login', + style: const TextStyle(color: Colors.blue), + recognizer: TapGestureRecognizer() + ..onTap = () { + setState(() { + mode = mode == AuthMode.login + ? AuthMode.register + : AuthMode.login; + }); + }, + ), + ], + ), ), - ], - ), + const SizedBox(height: 10), + RichText( + text: TextSpan( + style: Theme.of(context).textTheme.bodyText1, + children: [ + const TextSpan(text: 'Or '), + TextSpan( + text: 'continue as guest', + style: const TextStyle(color: Colors.blue), + recognizer: TapGestureRecognizer() + ..onTap = _anonymousAuth, + ), + ], + ), + ), + ], ), - ], + ), ), ), ), ), - ), + AnimatedSwitcher( + duration: const Duration(milliseconds: 200), + child: isLoading + ? Container( + color: Colors.black.withOpacity(0.8), + child: const Center( + child: CircularProgressIndicator.adaptive(), + ), + ) + : const SizedBox(), + ) + ], ), ); } diff --git a/packages/firebase_auth/firebase_auth_desktop/example/lib/auth_service.dart b/packages/firebase_auth/firebase_auth_desktop/example/lib/auth_service.dart new file mode 100644 index 00000000..e31fe4eb --- /dev/null +++ b/packages/firebase_auth/firebase_auth_desktop/example/lib/auth_service.dart @@ -0,0 +1,277 @@ +// ignore_for_file: public_member_api_docs + +import 'dart:convert'; +import 'dart:math'; + +import 'package:crypto/crypto.dart'; +import 'package:desktop_webview_auth/desktop_webview_auth.dart'; +import 'package:desktop_webview_auth/facebook.dart'; +import 'package:desktop_webview_auth/github.dart'; +import 'package:desktop_webview_auth/google.dart'; +import 'package:desktop_webview_auth/twitter.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/material.dart'; +import 'package:sign_in_with_apple/sign_in_with_apple.dart'; + +import 'auth.dart'; + +const _redirectUri = + 'https://react-native-firebase-testing.firebaseapp.com/__/auth/handler'; +const _googleClientId = + '448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com'; +const _twitterApiKey = 'YEXSiWv5UeCHyy0c61O2LBC3B'; +const _twitterApiSecretKey = + 'DOd9dCCRFgtnqMDQT7A68YuGZtvcO4WP1mEFS4mEJAUooM4yaE'; +const _facebookClientId = '128693022464535'; +const _githubClientId = '582d07c80a9afae77406'; +const _githubClientSecret = '2d60f5e850bc178dfa6b7f6c6e37a65b175172d3'; + +/// Provide authentication services with [FirebaseAuth]. +class AuthService { + final _auth = FirebaseAuth.instance; + + Future emailAuth( + AuthMode mode, { + required String email, + required String password, + }) { + assert(mode != AuthMode.phone); + + try { + if (mode == AuthMode.login) { + return _auth.signInWithEmailAndPassword( + email: email, + password: password, + ); + } else { + return _auth.createUserWithEmailAndPassword( + email: email, + password: password, + ); + } + } catch (e) { + rethrow; + } + } + + Future anonymousAuth() { + try { + return _auth.signInAnonymously(); + } catch (e) { + rethrow; + } + } + + Future phoneAuth({ + required String phoneNumber, + required Future Function() smsCode, + }) async { + try { + final confirmationResult = + await FirebaseAuth.instance.signInWithPhoneNumber(phoneNumber); + + final _smsCode = await smsCode.call(); + + if (_smsCode != null) { + await confirmationResult.confirm(_smsCode); + } else { + return; + } + } catch (e) { + rethrow; + } + } + + Future googleSignIn() async { + try { + // Handle login by a third-party provider. + final result = await DesktopWebviewAuth.signIn( + GoogleSignInArgs( + clientId: + '448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com', + redirectUri: _redirectUri, + scope: 'https://www.googleapis.com/auth/userinfo.email', + ), + ); + + if (result != null) { + // Create a new credential + final credential = GoogleAuthProvider.credential( + idToken: result.idToken, + accessToken: result.accessToken, + ); + + // Once signed in, return the UserCredential + await _auth.signInWithCredential(credential); + } else { + return; + } + } on FirebaseAuthException catch (_) { + rethrow; + } + } + + Future twitterSignIn() async { + try { + // Handle login by a third-party provider. + final result = await DesktopWebviewAuth.signIn( + TwitterSignInArgs( + apiKey: _twitterApiKey, + apiSecretKey: _twitterApiSecretKey, + redirectUri: _redirectUri, + ), + ); + + if (result != null) { + // Create a new credential + final credential = TwitterAuthProvider.credential( + secret: result.tokenSecret!, + accessToken: result.accessToken!, + ); + + // Once signed in, return the UserCredential + await _auth.signInWithCredential(credential); + } else { + return; + } + } on FirebaseAuthException catch (_) { + rethrow; + } + } + + Future facebookSignIn() async { + try { + // Handle login by a third-party provider. + final result = await DesktopWebviewAuth.signIn( + FacebookSignInArgs( + clientId: _facebookClientId, + redirectUri: _redirectUri, + ), + ); + + if (result != null) { + // Create a new credential + final credential = FacebookAuthProvider.credential(result.accessToken!); + + // Once signed in, return the UserCredential + await _auth.signInWithCredential(credential); + } else { + return; + } + } on FirebaseAuthException catch (_) { + rethrow; + } + } + + Future githubSignIn() async { + try { + // Handle login by a third-party provider. + final result = await DesktopWebviewAuth.signIn( + GitHubSignInArgs( + clientId: _githubClientId, + clientSecret: _githubClientSecret, + redirectUri: _redirectUri, + ), + ); + + if (result != null) { + // Create a new credential + final credential = GithubAuthProvider.credential(result.accessToken!); + + // Once signed in, return the UserCredential + await _auth.signInWithCredential(credential); + } else { + return; + } + } on FirebaseAuthException catch (_) { + rethrow; + } + } + + /// Generates a cryptographically secure random nonce, to be included in a + /// credential request. + String _generateNonce([int length = 32]) { + const charset = + '0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._'; + final random = Random.secure(); + return List.generate(length, (_) => charset[random.nextInt(charset.length)]) + .join(); + } + + /// Returns the sha256 hash of [input] in hex notation. + String _sha256ofString(String input) { + final bytes = utf8.encode(input); + final digest = sha256.convert(bytes); + return digest.toString(); + } + + Future appleSignIn() async { + try { + final rawNonce = _generateNonce(); + final nonce = _sha256ofString(rawNonce); + + final credential = await SignInWithApple.getAppleIDCredential( + scopes: [ + AppleIDAuthorizationScopes.email, + AppleIDAuthorizationScopes.fullName, + ], + ); + + debugPrint('${credential.state}'); + + if (credential.identityToken != null) { + // Create an `OAuthCredential` from the credential returned by Apple. + final oauthCredential = OAuthProvider('apple.com').credential( + idToken: credential.identityToken, + rawNonce: nonce, + ); + + // Sign in the user with Firebase. If the nonce we generated earlier does + // not match the nonce in `appleCredential.identityToken`, sign in will fail. + await FirebaseAuth.instance.signInWithCredential(oauthCredential); + } else { + return; + } + } on FirebaseAuthException catch (_) { + rethrow; + } + } + + Future resetPassword(String email) { + try { + return FirebaseAuth.instance.sendPasswordResetEmail(email: email); + } catch (e) { + rethrow; + } + } + + Future linkWithGoogle() async { + try { + final result = await DesktopWebviewAuth.signIn( + GoogleSignInArgs( + clientId: _googleClientId, + redirectUri: _redirectUri, + scope: 'https://www.googleapis.com/auth/userinfo.email', + ), + ); + + if (result != null) { + // Create a new credential + final credential = GoogleAuthProvider.credential( + accessToken: result.accessToken, + idToken: result.idToken, + ); + + // Once signed in, return the UserCredential + await _auth.currentUser?.linkWithCredential(credential); + } + } on FirebaseAuthException catch (_) { + rethrow; + } + } + + /// Sign the Firebase user out. + Future signOut() async { + await _auth.signOut(); + } +} diff --git a/packages/firebase_auth/firebase_auth_desktop/example/lib/profile.dart b/packages/firebase_auth/firebase_auth_desktop/example/lib/profile.dart index cf202a53..3ff69956 100644 --- a/packages/firebase_auth/firebase_auth_desktop/example/lib/profile.dart +++ b/packages/firebase_auth/firebase_auth_desktop/example/lib/profile.dart @@ -1,11 +1,10 @@ import 'dart:developer'; -import 'package:desktop_webview_auth/desktop_webview_auth.dart'; -import 'package:desktop_webview_auth/google.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/material.dart'; import 'auth.dart'; +import 'auth_service.dart'; import 'sms_dialog.dart'; /// Displayed as a profile image if the user doesn't have one. @@ -23,6 +22,8 @@ class ProfilePage extends StatefulWidget { } class _ProfilePageState extends State { + final authService = AuthService(); + late User user; late TextEditingController controller; @@ -106,26 +107,6 @@ class _ProfilePageState extends State { user.photoURL ?? placeholderImage, ), ), - // TODO once storage is supported - // Positioned.directional( - // textDirection: Directionality.of(context), - // end: 0, - // bottom: 0, - // child: Material( - // clipBehavior: Clip.antiAlias, - // color: Theme.of(context).colorScheme.secondary, - // borderRadius: BorderRadius.circular(40), - // child: InkWell( - // onTap: () {}, - // radius: 50, - // child: const SizedBox( - // width: 35, - // height: 35, - // child: Icon(Icons.edit), - // ), - // ), - // ), - // ) ], ), const SizedBox(height: 10), @@ -159,6 +140,11 @@ class _ProfilePageState extends State { 'https://upload.wikimedia.org/wikipedia/commons/0/09/IOS_Google_icon.png', ), ), + if (userProviders.contains('github.com')) + SizedBox( + width: 24, + child: Image.asset('assets/github.png'), + ), ], ), const SizedBox(height: 40), @@ -209,25 +195,7 @@ class _ProfilePageState extends State { Future _linkWithOAuth() async { try { - final result = await DesktopWebviewAuth.signIn( - GoogleSignInArgs( - clientId: - '448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com', - redirectUri: redirectUri, - scope: 'https://www.googleapis.com/auth/userinfo.email', - ), - ); - - if (result != null) { - // Create a new credential - final credential = GoogleAuthProvider.credential( - accessToken: result.accessToken, - idToken: result.idToken, - ); - - // Once signed in, return the UserCredential - await user.linkWithCredential(credential); - } + await authService.linkWithGoogle(); } on FirebaseAuthException catch (e) { ScaffoldSnackbar.of(context).show('${e.message}'); log('$e'); @@ -293,8 +261,7 @@ class _ProfilePageState extends State { } } - /// Example code for sign out. Future _signOut() async { - await FirebaseAuth.instance.signOut(); + await authService.signOut(); } } diff --git a/packages/firebase_auth/firebase_auth_desktop/example/pubspec.yaml b/packages/firebase_auth/firebase_auth_desktop/example/pubspec.yaml index e4afc3ab..d58e9372 100644 --- a/packages/firebase_auth/firebase_auth_desktop/example/pubspec.yaml +++ b/packages/firebase_auth/firebase_auth_desktop/example/pubspec.yaml @@ -22,7 +22,7 @@ environment: dependencies: crypto: ^3.0.1 - desktop_webview_auth: ^0.0.7 + desktop_webview_auth: ^0.0.8 firebase_auth: ^3.3.7 firebase_auth_desktop: ^0.1.2 firebase_core: ^1.12.0 @@ -55,9 +55,8 @@ flutter: # 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 + assets: + - assets/ # 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 diff --git a/packages/firebase_auth/firebase_auth_desktop/lib/src/utils/desktop_utils.dart b/packages/firebase_auth/firebase_auth_desktop/lib/src/utils/desktop_utils.dart index 5dbac059..e3d63ce5 100644 --- a/packages/firebase_auth/firebase_auth_desktop/lib/src/utils/desktop_utils.dart +++ b/packages/firebase_auth/firebase_auth_desktop/lib/src/utils/desktop_utils.dart @@ -28,6 +28,8 @@ auth_dart.AuthCredential mapAuthCredentialFromPlatform( ); } else if (credential is FacebookAuthCredential) { return auth_dart.FacebookAuthProvider.credential(credential.accessToken!); + } else if (credential is GithubAuthCredential) { + return auth_dart.GithubAuthProvider.credential(credential.accessToken!); } else if (credential is OAuthCredential) { return auth_dart.OAuthProvider(credential.providerId).credential( accessToken: credential.accessToken, @@ -71,6 +73,8 @@ AuthCredential mapAuthCredentialFromDart(auth_dart.AuthCredential credential) { ); } else if (credential is auth_dart.FacebookAuthCredential) { return FacebookAuthProvider.credential(credential.accessToken!); + } else if (credential is auth_dart.GithubAuthCredential) { + return GithubAuthProvider.credential(credential.accessToken!); } else if (credential is auth_dart.OAuthCredential) { return OAuthProvider(credential.providerId).credential( accessToken: credential.accessToken, diff --git a/packages/firebase_auth/firebase_auth_desktop/pubspec.yaml b/packages/firebase_auth/firebase_auth_desktop/pubspec.yaml index a255ed76..b27090cb 100644 --- a/packages/firebase_auth/firebase_auth_desktop/pubspec.yaml +++ b/packages/firebase_auth/firebase_auth_desktop/pubspec.yaml @@ -9,7 +9,7 @@ environment: flutter: '>=1.20.0' dependencies: - desktop_webview_auth: ^0.0.7 + desktop_webview_auth: ^0.0.8 firebase_auth: ^3.3.7 firebase_auth_dart: ^0.1.2 firebase_auth_platform_interface: ^6.1.4 From b71229a0904e17f6ce6e476b07d9e54c60a104df Mon Sep 17 00:00:00 2001 From: pr_Mais Date: Fri, 6 May 2022 11:03:56 +0300 Subject: [PATCH 20/30] chore(release): publish packages - firebase_auth_dart@0.1.3 - firebase_auth_desktop@0.1.3 - firebase_functions_dart@0.1.1 - firebase_functions_desktop@0.1.0+2 --- packages/firebase_auth/firebase_auth_dart/CHANGELOG.md | 5 +++++ .../firebase_auth/firebase_auth_dart/example/pubspec.yaml | 2 +- packages/firebase_auth/firebase_auth_dart/pubspec.yaml | 2 +- packages/firebase_auth/firebase_auth_desktop/CHANGELOG.md | 4 ++++ .../firebase_auth/firebase_auth_desktop/example/pubspec.yaml | 2 +- packages/firebase_auth/firebase_auth_desktop/pubspec.yaml | 4 ++-- .../firebase_functions/firebase_functions_dart/CHANGELOG.md | 4 ++++ .../firebase_functions/firebase_functions_dart/pubspec.yaml | 4 ++-- .../firebase_functions_desktop/CHANGELOG.md | 4 ++++ .../firebase_functions_desktop/pubspec.yaml | 4 ++-- 10 files changed, 26 insertions(+), 9 deletions(-) diff --git a/packages/firebase_auth/firebase_auth_dart/CHANGELOG.md b/packages/firebase_auth/firebase_auth_dart/CHANGELOG.md index 16c6c4eb..a0a5fba7 100644 --- a/packages/firebase_auth/firebase_auth_dart/CHANGELOG.md +++ b/packages/firebase_auth/firebase_auth_dart/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.1.3 + + - **FEAT**: Github auth provider (#74). + - **FEAT**: web support (#72). + ## 0.1.2 - **REFACTOR**: errors (#64). diff --git a/packages/firebase_auth/firebase_auth_dart/example/pubspec.yaml b/packages/firebase_auth/firebase_auth_dart/example/pubspec.yaml index 026a42b7..41c2dddf 100644 --- a/packages/firebase_auth/firebase_auth_dart/example/pubspec.yaml +++ b/packages/firebase_auth/firebase_auth_dart/example/pubspec.yaml @@ -13,7 +13,7 @@ dependencies: ansicolor: ^2.0.1 args: ^2.3.0 cli_util: ^0.3.5 - firebase_auth_dart: ^0.1.2 + firebase_auth_dart: ^0.1.3 firebase_core_dart: ^0.1.1 dependency_overrides: firebase_auth_dart: diff --git a/packages/firebase_auth/firebase_auth_dart/pubspec.yaml b/packages/firebase_auth/firebase_auth_dart/pubspec.yaml index ef3981d5..25584706 100644 --- a/packages/firebase_auth/firebase_auth_dart/pubspec.yaml +++ b/packages/firebase_auth/firebase_auth_dart/pubspec.yaml @@ -2,7 +2,7 @@ name: firebase_auth_dart description: TODO homepage: https://firebase.flutter.dev repository: https://github.com/invertase/flutterfire_dart -version: 0.1.2 +version: 0.1.3 environment: sdk: '>=2.12.0 <3.0.0' diff --git a/packages/firebase_auth/firebase_auth_desktop/CHANGELOG.md b/packages/firebase_auth/firebase_auth_desktop/CHANGELOG.md index d0d10090..bb822186 100644 --- a/packages/firebase_auth/firebase_auth_desktop/CHANGELOG.md +++ b/packages/firebase_auth/firebase_auth_desktop/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.3 + + - **FEAT**: Github auth provider (#74). + ## 0.1.2 - **REFACTOR**: 🔨 identity toolkit api layer (#61). diff --git a/packages/firebase_auth/firebase_auth_desktop/example/pubspec.yaml b/packages/firebase_auth/firebase_auth_desktop/example/pubspec.yaml index d58e9372..6956c131 100644 --- a/packages/firebase_auth/firebase_auth_desktop/example/pubspec.yaml +++ b/packages/firebase_auth/firebase_auth_desktop/example/pubspec.yaml @@ -24,7 +24,7 @@ dependencies: crypto: ^3.0.1 desktop_webview_auth: ^0.0.8 firebase_auth: ^3.3.7 - firebase_auth_desktop: ^0.1.2 + firebase_auth_desktop: ^0.1.3 firebase_core: ^1.12.0 firebase_core_desktop: ^0.1.1 flutter: diff --git a/packages/firebase_auth/firebase_auth_desktop/pubspec.yaml b/packages/firebase_auth/firebase_auth_desktop/pubspec.yaml index b27090cb..5ff44503 100644 --- a/packages/firebase_auth/firebase_auth_desktop/pubspec.yaml +++ b/packages/firebase_auth/firebase_auth_desktop/pubspec.yaml @@ -2,7 +2,7 @@ name: firebase_auth_desktop description: Desktop implementation of firebase_auth homepage: https://firebase.flutter.dev repository: https://github.com/invertase/flutterfire_dart -version: 0.1.2 +version: 0.1.3 environment: sdk: '>=2.12.0 <3.0.0' @@ -11,7 +11,7 @@ environment: dependencies: desktop_webview_auth: ^0.0.8 firebase_auth: ^3.3.7 - firebase_auth_dart: ^0.1.2 + firebase_auth_dart: ^0.1.3 firebase_auth_platform_interface: ^6.1.4 firebase_core: ^1.12.0 firebase_core_dart: ^0.1.1 diff --git a/packages/firebase_functions/firebase_functions_dart/CHANGELOG.md b/packages/firebase_functions/firebase_functions_dart/CHANGELOG.md index 7f6d1cb6..ecaf7a0e 100644 --- a/packages/firebase_functions/firebase_functions_dart/CHANGELOG.md +++ b/packages/firebase_functions/firebase_functions_dart/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.1 + + - **FEAT**: web support (#72). + ## 0.1.0+1 - Update a dependency to the latest release. diff --git a/packages/firebase_functions/firebase_functions_dart/pubspec.yaml b/packages/firebase_functions/firebase_functions_dart/pubspec.yaml index 13a80565..33598781 100644 --- a/packages/firebase_functions/firebase_functions_dart/pubspec.yaml +++ b/packages/firebase_functions/firebase_functions_dart/pubspec.yaml @@ -2,14 +2,14 @@ name: firebase_functions_dart description: Pure Dart implementation of FlutterFire CloudFunctions API. homepage: https://firebase.flutter.dev repository: https://github.com/invertase/flutterfire_dart -version: 0.1.0+1 +version: 0.1.1 environment: sdk: '>=2.12.0 <3.0.0' dependencies: collection: ^1.15.0 - firebase_auth_dart: ^0.1.2 + firebase_auth_dart: ^0.1.3 firebase_core_dart: ^0.1.1 http: ^0.13.4 meta: ^1.7.0 diff --git a/packages/firebase_functions/firebase_functions_desktop/CHANGELOG.md b/packages/firebase_functions/firebase_functions_desktop/CHANGELOG.md index 7f6d1cb6..2be10620 100644 --- a/packages/firebase_functions/firebase_functions_desktop/CHANGELOG.md +++ b/packages/firebase_functions/firebase_functions_desktop/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.0+2 + + - Update a dependency to the latest release. + ## 0.1.0+1 - Update a dependency to the latest release. diff --git a/packages/firebase_functions/firebase_functions_desktop/pubspec.yaml b/packages/firebase_functions/firebase_functions_desktop/pubspec.yaml index 91c98b6d..7fa8e0ce 100644 --- a/packages/firebase_functions/firebase_functions_desktop/pubspec.yaml +++ b/packages/firebase_functions/firebase_functions_desktop/pubspec.yaml @@ -2,7 +2,7 @@ name: firebase_functions_desktop description: Desktop implementation of cloud_functions homepage: https://firebase.flutter.dev repository: https://github.com/invertase/flutterfire_dart -version: 0.1.0+1 +version: 0.1.0+2 environment: sdk: '>=2.12.0 <3.0.0' @@ -14,7 +14,7 @@ dependencies: firebase_core: ^1.10.0 firebase_core_dart: ^0.1.1 firebase_core_desktop: ^0.1.1 - firebase_functions_dart: ^0.1.0+1 + firebase_functions_dart: ^0.1.1 flutter: sdk: flutter http: ^0.13.4 From e771a3f2451c50d8de3d4106cad5a5a2f7a062f4 Mon Sep 17 00:00:00 2001 From: pr_Mais Date: Mon, 9 May 2022 15:40:00 +0300 Subject: [PATCH 21/30] refactor!: remove macOS support The support for macOS is removed, it's already in official FlutterFire. --- .../Classes/FirebaseAuthDesktopPlugin.swift | 19 ------------------- .../firebase_auth_desktop/pubspec.yaml | 7 ++++--- .../firebase_core_desktop/pubspec.yaml | 7 ++++--- .../FirebaseFunctionsDesktopPlugin.swift | 19 ------------------- .../firebase_functions_desktop/pubspec.yaml | 7 ++++--- 5 files changed, 12 insertions(+), 47 deletions(-) delete mode 100644 packages/firebase_auth/firebase_auth_desktop/macos/Classes/FirebaseAuthDesktopPlugin.swift delete mode 100644 packages/firebase_functions/firebase_functions_desktop/macos/Classes/FirebaseFunctionsDesktopPlugin.swift diff --git a/packages/firebase_auth/firebase_auth_desktop/macos/Classes/FirebaseAuthDesktopPlugin.swift b/packages/firebase_auth/firebase_auth_desktop/macos/Classes/FirebaseAuthDesktopPlugin.swift deleted file mode 100644 index 1ca6f669..00000000 --- a/packages/firebase_auth/firebase_auth_desktop/macos/Classes/FirebaseAuthDesktopPlugin.swift +++ /dev/null @@ -1,19 +0,0 @@ -import Cocoa -import FlutterMacOS - -public class FirebaseAuthDesktopPlugin: NSObject, FlutterPlugin { - public static func register(with registrar: FlutterPluginRegistrar) { - let channel = FlutterMethodChannel(name: "firebase_auth_desktop", binaryMessenger: registrar.messenger) - let instance = FirebaseAuthDesktopPlugin() - registrar.addMethodCallDelegate(instance, channel: channel) - } - - public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { - switch call.method { - case "getPlatformVersion": - result("macOS " + ProcessInfo.processInfo.operatingSystemVersionString) - default: - result(FlutterMethodNotImplemented) - } - } -} diff --git a/packages/firebase_auth/firebase_auth_desktop/pubspec.yaml b/packages/firebase_auth/firebase_auth_desktop/pubspec.yaml index 5ff44503..1604aaaa 100644 --- a/packages/firebase_auth/firebase_auth_desktop/pubspec.yaml +++ b/packages/firebase_auth/firebase_auth_desktop/pubspec.yaml @@ -31,9 +31,10 @@ flutter: plugin: implements: firebase_auth platforms: - macos: - dartPluginClass: FirebaseAuthDesktop - pluginClass: none + # TODO when doing development on macOS, uncomment this. + # macos: + # dartPluginClass: FirebaseAuthDesktop + # pluginClass: none linux: dartPluginClass: FirebaseAuthDesktop pluginClass: none diff --git a/packages/firebase_core/firebase_core_desktop/pubspec.yaml b/packages/firebase_core/firebase_core_desktop/pubspec.yaml index 1dada1f5..9a1a045c 100644 --- a/packages/firebase_core/firebase_core_desktop/pubspec.yaml +++ b/packages/firebase_core/firebase_core_desktop/pubspec.yaml @@ -26,9 +26,10 @@ flutter: plugin: implements: firebase_core platforms: - macos: - dartPluginClass: FirebaseCore - pluginClass: none + # TODO when doing development on macOS, uncomment this. + # macos: + # dartPluginClass: FirebaseCore + # pluginClass: none linux: dartPluginClass: FirebaseCore pluginClass: none diff --git a/packages/firebase_functions/firebase_functions_desktop/macos/Classes/FirebaseFunctionsDesktopPlugin.swift b/packages/firebase_functions/firebase_functions_desktop/macos/Classes/FirebaseFunctionsDesktopPlugin.swift deleted file mode 100644 index 3746ad05..00000000 --- a/packages/firebase_functions/firebase_functions_desktop/macos/Classes/FirebaseFunctionsDesktopPlugin.swift +++ /dev/null @@ -1,19 +0,0 @@ -import Cocoa -import FlutterMacOS - -public class FirebaseFunctionsDesktopPlugin: NSObject, FlutterPlugin { - public static func register(with registrar: FlutterPluginRegistrar) { - let channel = FlutterMethodChannel(name: "firebase_functions_desktop", binaryMessenger: registrar.messenger) - let instance = FirebaseFunctionsDesktopPlugin() - registrar.addMethodCallDelegate(instance, channel: channel) - } - - public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { - switch call.method { - case "getPlatformVersion": - result("macOS " + ProcessInfo.processInfo.operatingSystemVersionString) - default: - result(FlutterMethodNotImplemented) - } - } -} diff --git a/packages/firebase_functions/firebase_functions_desktop/pubspec.yaml b/packages/firebase_functions/firebase_functions_desktop/pubspec.yaml index 7fa8e0ce..0fc79c4d 100644 --- a/packages/firebase_functions/firebase_functions_desktop/pubspec.yaml +++ b/packages/firebase_functions/firebase_functions_desktop/pubspec.yaml @@ -31,9 +31,10 @@ flutter: plugin: implements: cloud_functions platforms: - macos: - dartPluginClass: FirebaseFunctionsDesktop - pluginClass: none + # TODO when doing development on macOS, uncomment this. + # macos: + # dartPluginClass: FirebaseFunctionsDesktop + # pluginClass: none linux: dartPluginClass: FirebaseFunctionsDesktop pluginClass: none From 9bbe72be2ea330225b909bc29a4092270c38b608 Mon Sep 17 00:00:00 2001 From: pr_Mais Date: Mon, 9 May 2022 15:43:09 +0300 Subject: [PATCH 22/30] chore(release): publish packages - firebase_auth_desktop@0.2.0 - firebase_core_desktop@0.2.0 - firebase_functions_desktop@0.2.0 --- packages/firebase_auth/firebase_auth_desktop/CHANGELOG.md | 6 ++++++ .../firebase_auth_desktop/example/pubspec.yaml | 4 ++-- packages/firebase_auth/firebase_auth_desktop/pubspec.yaml | 2 +- packages/firebase_core/firebase_core_desktop/CHANGELOG.md | 6 ++++++ .../firebase_core_desktop/example/pubspec.yaml | 2 +- packages/firebase_core/firebase_core_desktop/pubspec.yaml | 2 +- .../firebase_functions_desktop/CHANGELOG.md | 6 ++++++ .../firebase_functions_desktop/pubspec.yaml | 4 ++-- 8 files changed, 25 insertions(+), 7 deletions(-) diff --git a/packages/firebase_auth/firebase_auth_desktop/CHANGELOG.md b/packages/firebase_auth/firebase_auth_desktop/CHANGELOG.md index bb822186..6f50ec8a 100644 --- a/packages/firebase_auth/firebase_auth_desktop/CHANGELOG.md +++ b/packages/firebase_auth/firebase_auth_desktop/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.2.0 + +> Note: This release has breaking changes. + + - **BREAKING** **REFACTOR**: remove macOS support. + ## 0.1.3 - **FEAT**: Github auth provider (#74). diff --git a/packages/firebase_auth/firebase_auth_desktop/example/pubspec.yaml b/packages/firebase_auth/firebase_auth_desktop/example/pubspec.yaml index 6956c131..9bb76d9b 100644 --- a/packages/firebase_auth/firebase_auth_desktop/example/pubspec.yaml +++ b/packages/firebase_auth/firebase_auth_desktop/example/pubspec.yaml @@ -24,9 +24,9 @@ dependencies: crypto: ^3.0.1 desktop_webview_auth: ^0.0.8 firebase_auth: ^3.3.7 - firebase_auth_desktop: ^0.1.3 + firebase_auth_desktop: ^0.2.0 firebase_core: ^1.12.0 - firebase_core_desktop: ^0.1.1 + firebase_core_desktop: ^0.2.0 flutter: sdk: flutter flutter_lints: ^1.0.4 diff --git a/packages/firebase_auth/firebase_auth_desktop/pubspec.yaml b/packages/firebase_auth/firebase_auth_desktop/pubspec.yaml index 1604aaaa..281ac902 100644 --- a/packages/firebase_auth/firebase_auth_desktop/pubspec.yaml +++ b/packages/firebase_auth/firebase_auth_desktop/pubspec.yaml @@ -2,7 +2,7 @@ name: firebase_auth_desktop description: Desktop implementation of firebase_auth homepage: https://firebase.flutter.dev repository: https://github.com/invertase/flutterfire_dart -version: 0.1.3 +version: 0.2.0 environment: sdk: '>=2.12.0 <3.0.0' diff --git a/packages/firebase_core/firebase_core_desktop/CHANGELOG.md b/packages/firebase_core/firebase_core_desktop/CHANGELOG.md index 0b18b4e8..6471b26a 100644 --- a/packages/firebase_core/firebase_core_desktop/CHANGELOG.md +++ b/packages/firebase_core/firebase_core_desktop/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.2.0 + +> Note: This release has breaking changes. + + - **BREAKING** **REFACTOR**: remove macOS support. + ## 0.1.1 - Graduate package to a stable release. See pre-releases prior to this version for changelog entries. diff --git a/packages/firebase_core/firebase_core_desktop/example/pubspec.yaml b/packages/firebase_core/firebase_core_desktop/example/pubspec.yaml index d8d76edc..ffb49aa2 100644 --- a/packages/firebase_core/firebase_core_desktop/example/pubspec.yaml +++ b/packages/firebase_core/firebase_core_desktop/example/pubspec.yaml @@ -22,7 +22,7 @@ environment: dependencies: firebase_core: ^1.9.0 - firebase_core_desktop: ^0.1.1 + firebase_core_desktop: ^0.2.0 flutter: sdk: flutter diff --git a/packages/firebase_core/firebase_core_desktop/pubspec.yaml b/packages/firebase_core/firebase_core_desktop/pubspec.yaml index 9a1a045c..2349535b 100644 --- a/packages/firebase_core/firebase_core_desktop/pubspec.yaml +++ b/packages/firebase_core/firebase_core_desktop/pubspec.yaml @@ -2,7 +2,7 @@ name: firebase_core_desktop description: Desktop implementation of firebase_core homepage: https://firebase.flutter.dev repository: https://github.com/invertase/flutterfire_dart -version: 0.1.1 +version: 0.2.0 environment: sdk: '>=2.12.0 <3.0.0' diff --git a/packages/firebase_functions/firebase_functions_desktop/CHANGELOG.md b/packages/firebase_functions/firebase_functions_desktop/CHANGELOG.md index 2be10620..6a0121b3 100644 --- a/packages/firebase_functions/firebase_functions_desktop/CHANGELOG.md +++ b/packages/firebase_functions/firebase_functions_desktop/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.2.0 + +> Note: This release has breaking changes. + + - **BREAKING** **REFACTOR**: remove macOS support. + ## 0.1.0+2 - Update a dependency to the latest release. diff --git a/packages/firebase_functions/firebase_functions_desktop/pubspec.yaml b/packages/firebase_functions/firebase_functions_desktop/pubspec.yaml index 0fc79c4d..3ab57684 100644 --- a/packages/firebase_functions/firebase_functions_desktop/pubspec.yaml +++ b/packages/firebase_functions/firebase_functions_desktop/pubspec.yaml @@ -2,7 +2,7 @@ name: firebase_functions_desktop description: Desktop implementation of cloud_functions homepage: https://firebase.flutter.dev repository: https://github.com/invertase/flutterfire_dart -version: 0.1.0+2 +version: 0.2.0 environment: sdk: '>=2.12.0 <3.0.0' @@ -13,7 +13,7 @@ dependencies: cloud_functions_platform_interface: ^5.0.14 firebase_core: ^1.10.0 firebase_core_dart: ^0.1.1 - firebase_core_desktop: ^0.1.1 + firebase_core_desktop: ^0.2.0 firebase_functions_dart: ^0.1.1 flutter: sdk: flutter From b45df7dcb2d3404b94969e8c6699a203c9a4bb84 Mon Sep 17 00:00:00 2001 From: pr_Mais Date: Mon, 9 May 2022 15:45:15 +0300 Subject: [PATCH 23/30] ci: remove macos tests job --- .github/workflows/validate.yaml | 104 ++++++++++++++++---------------- 1 file changed, 52 insertions(+), 52 deletions(-) diff --git a/.github/workflows/validate.yaml b/.github/workflows/validate.yaml index b3e70466..2c5e9302 100644 --- a/.github/workflows/validate.yaml +++ b/.github/workflows/validate.yaml @@ -89,55 +89,55 @@ jobs: working-directory: tests run: cmd /c flutter drive -d windows --no-pub --target=./test_driver/driver_e2e.dart --dart-define=CI=true - test_macos: - runs-on: macos-latest - timeout-minutes: 20 - steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - uses: actions/setup-node@v2 - name: Install Node.js 12 - with: - node-version: '12' - - uses: hendrikmuhs/ccache-action@v1 - name: Xcode Compile Cache - with: - key: ${{ runner.os }}-macos-v2 - max-size: 700M - - uses: actions/cache@v2 - name: Pods Cache - id: pods-cache - with: - path: tests/macos/Pods - key: ${{ runner.os }}-pods-v2-${{ hashFiles('tests/macos/Podfile.lock') }} - restore-keys: ${{ runner.os }}-macos-pods-v1 - - name: Cache Firebase Emulator - uses: actions/cache@v2 - with: - path: ~/.cache/firebase/emulators - key: firebase-emulators-v1-${{ github.run_id }} - restore-keys: firebase-emulators-v1 - - name: 'Install Flutter' - run: ./.github/workflows/scripts/install-flutter.sh stable - - name: 'Install Tools' - run: | - ./.github/workflows/scripts/install-tools.sh - flutter config --enable-macos-desktop - sudo npm i -g firebase-tools - - name: 'Build Application' - working-directory: tests - run: | - export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH" - export CCACHE_SLOPPINESS=clang_index_store,file_stat_matches,include_file_ctime,include_file_mtime,ivfsoverlay,pch_defines,modules,system_headers,time_macros - export CCACHE_FILECLONE=true - export CCACHE_DEPEND=true - export CCACHE_INODECACHE=true - ccache -s - flutter build macos --debug --target=./test_driver/driver_e2e.dart --device-id=macos --dart-define=CI=true - ccache -s - - name: Start Firebase Emulator - run: cd ./.github/workflows/scripts && ./start-firebase-emulator.sh - - name: 'Run Tests' - working-directory: tests - run: flutter drive -d macos --no-pub --target=./test_driver/driver_e2e.dart --dart-define=CI=true + # test_macos: + # runs-on: macos-latest + # timeout-minutes: 20 + # steps: + # - uses: actions/checkout@v2 + # with: + # fetch-depth: 0 + # - uses: actions/setup-node@v2 + # name: Install Node.js 12 + # with: + # node-version: '12' + # - uses: hendrikmuhs/ccache-action@v1 + # name: Xcode Compile Cache + # with: + # key: ${{ runner.os }}-macos-v2 + # max-size: 700M + # - uses: actions/cache@v2 + # name: Pods Cache + # id: pods-cache + # with: + # path: tests/macos/Pods + # key: ${{ runner.os }}-pods-v2-${{ hashFiles('tests/macos/Podfile.lock') }} + # restore-keys: ${{ runner.os }}-macos-pods-v1 + # - name: Cache Firebase Emulator + # uses: actions/cache@v2 + # with: + # path: ~/.cache/firebase/emulators + # key: firebase-emulators-v1-${{ github.run_id }} + # restore-keys: firebase-emulators-v1 + # - name: 'Install Flutter' + # run: ./.github/workflows/scripts/install-flutter.sh stable + # - name: 'Install Tools' + # run: | + # ./.github/workflows/scripts/install-tools.sh + # flutter config --enable-macos-desktop + # sudo npm i -g firebase-tools + # - name: 'Build Application' + # working-directory: tests + # run: | + # export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH" + # export CCACHE_SLOPPINESS=clang_index_store,file_stat_matches,include_file_ctime,include_file_mtime,ivfsoverlay,pch_defines,modules,system_headers,time_macros + # export CCACHE_FILECLONE=true + # export CCACHE_DEPEND=true + # export CCACHE_INODECACHE=true + # ccache -s + # flutter build macos --debug --target=./test_driver/driver_e2e.dart --device-id=macos --dart-define=CI=true + # ccache -s + # - name: Start Firebase Emulator + # run: cd ./.github/workflows/scripts && ./start-firebase-emulator.sh + # - name: 'Run Tests' + # working-directory: tests + # run: flutter drive -d macos --no-pub --target=./test_driver/driver_e2e.dart --dart-define=CI=true From 1bfb1310740e1d9b207af5aed14ea274083bf529 Mon Sep 17 00:00:00 2001 From: pr_Mais Date: Wed, 11 May 2022 12:09:45 +0300 Subject: [PATCH 24/30] fix(firebase_core): return existing app if options are matching if the same app is initialized twice --- .../lib/src/internal/firebase_core_delegate.dart | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/firebase_core/firebase_core_dart/lib/src/internal/firebase_core_delegate.dart b/packages/firebase_core/firebase_core_dart/lib/src/internal/firebase_core_delegate.dart index fb0914bc..d65edb88 100644 --- a/packages/firebase_core/firebase_core_dart/lib/src/internal/firebase_core_delegate.dart +++ b/packages/firebase_core/firebase_core_dart/lib/src/internal/firebase_core_delegate.dart @@ -28,7 +28,17 @@ class FirebaseCoreDelegate { final _name = name ?? defaultFirebaseAppName; if (_apps.containsKey(_name)) { - throw duplicateApp(_name); + final existingApp = _apps[name]!; + if (options.apiKey != existingApp.options.apiKey || + (options.databaseURL != null && + options.databaseURL != existingApp.options.databaseURL) || + (options.storageBucket != null && + options.storageBucket != existingApp.options.storageBucket)) { + // Options are different; throw. + throw duplicateApp(_name); + } else { + return existingApp; + } } final _delegate = _FirebaseAppDelegete(this, _name, options); From 6f6741cc1145ffeb5fe32d9c6b1c27b008638f4f Mon Sep 17 00:00:00 2001 From: pr_Mais Date: Wed, 11 May 2022 18:33:51 +0300 Subject: [PATCH 25/30] chore(release): publish packages - firebase_core_dart@1.0.0 - firebase_auth_dart@1.0.0 - firebase_core_desktop@1.0.0 - firebase_auth_desktop@1.0.0 - firebase_functions_desktop@0.2.0+1 - firebase_functions_dart@0.1.1+1 --- packages/firebase_auth/firebase_auth_dart/CHANGELOG.md | 4 ++++ .../firebase_auth/firebase_auth_dart/example/pubspec.yaml | 4 ++-- packages/firebase_auth/firebase_auth_dart/pubspec.yaml | 4 ++-- packages/firebase_auth/firebase_auth_desktop/CHANGELOG.md | 4 ++++ .../firebase_auth_desktop/example/pubspec.yaml | 4 ++-- packages/firebase_auth/firebase_auth_desktop/pubspec.yaml | 6 +++--- packages/firebase_core/firebase_core_dart/CHANGELOG.md | 6 ++++++ packages/firebase_core/firebase_core_dart/pubspec.yaml | 2 +- packages/firebase_core/firebase_core_desktop/CHANGELOG.md | 4 ++++ .../firebase_core_desktop/example/pubspec.yaml | 2 +- packages/firebase_core/firebase_core_desktop/pubspec.yaml | 4 ++-- .../firebase_functions_dart/CHANGELOG.md | 4 ++++ .../firebase_functions_dart/pubspec.yaml | 6 +++--- .../firebase_functions_desktop/CHANGELOG.md | 4 ++++ .../firebase_functions_desktop/pubspec.yaml | 8 ++++---- 15 files changed, 46 insertions(+), 20 deletions(-) diff --git a/packages/firebase_auth/firebase_auth_dart/CHANGELOG.md b/packages/firebase_auth/firebase_auth_dart/CHANGELOG.md index a0a5fba7..b75ba916 100644 --- a/packages/firebase_auth/firebase_auth_dart/CHANGELOG.md +++ b/packages/firebase_auth/firebase_auth_dart/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.0.0 + + - First stable release. + ## 0.1.3 - **FEAT**: Github auth provider (#74). diff --git a/packages/firebase_auth/firebase_auth_dart/example/pubspec.yaml b/packages/firebase_auth/firebase_auth_dart/example/pubspec.yaml index 41c2dddf..3dd63213 100644 --- a/packages/firebase_auth/firebase_auth_dart/example/pubspec.yaml +++ b/packages/firebase_auth/firebase_auth_dart/example/pubspec.yaml @@ -13,8 +13,8 @@ dependencies: ansicolor: ^2.0.1 args: ^2.3.0 cli_util: ^0.3.5 - firebase_auth_dart: ^0.1.3 - firebase_core_dart: ^0.1.1 + firebase_auth_dart: ^1.0.0 + firebase_core_dart: ^1.0.0 dependency_overrides: firebase_auth_dart: path: ../ diff --git a/packages/firebase_auth/firebase_auth_dart/pubspec.yaml b/packages/firebase_auth/firebase_auth_dart/pubspec.yaml index 25584706..8b44f21e 100644 --- a/packages/firebase_auth/firebase_auth_dart/pubspec.yaml +++ b/packages/firebase_auth/firebase_auth_dart/pubspec.yaml @@ -2,13 +2,13 @@ name: firebase_auth_dart description: TODO homepage: https://firebase.flutter.dev repository: https://github.com/invertase/flutterfire_dart -version: 0.1.3 +version: 1.0.0 environment: sdk: '>=2.12.0 <3.0.0' dependencies: - firebase_core_dart: ^0.1.1 + firebase_core_dart: ^1.0.0 firebaseapis: ^0.1.1 googleapis_auth: ^1.1.0 http: ^0.13.4 diff --git a/packages/firebase_auth/firebase_auth_desktop/CHANGELOG.md b/packages/firebase_auth/firebase_auth_desktop/CHANGELOG.md index 6f50ec8a..e971a3b7 100644 --- a/packages/firebase_auth/firebase_auth_desktop/CHANGELOG.md +++ b/packages/firebase_auth/firebase_auth_desktop/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.0.0 + + - First stable release. + ## 0.2.0 > Note: This release has breaking changes. diff --git a/packages/firebase_auth/firebase_auth_desktop/example/pubspec.yaml b/packages/firebase_auth/firebase_auth_desktop/example/pubspec.yaml index 9bb76d9b..5b3f9790 100644 --- a/packages/firebase_auth/firebase_auth_desktop/example/pubspec.yaml +++ b/packages/firebase_auth/firebase_auth_desktop/example/pubspec.yaml @@ -24,9 +24,9 @@ dependencies: crypto: ^3.0.1 desktop_webview_auth: ^0.0.8 firebase_auth: ^3.3.7 - firebase_auth_desktop: ^0.2.0 + firebase_auth_desktop: ^1.0.0 firebase_core: ^1.12.0 - firebase_core_desktop: ^0.2.0 + firebase_core_desktop: ^1.0.0 flutter: sdk: flutter flutter_lints: ^1.0.4 diff --git a/packages/firebase_auth/firebase_auth_desktop/pubspec.yaml b/packages/firebase_auth/firebase_auth_desktop/pubspec.yaml index 281ac902..941e637c 100644 --- a/packages/firebase_auth/firebase_auth_desktop/pubspec.yaml +++ b/packages/firebase_auth/firebase_auth_desktop/pubspec.yaml @@ -2,7 +2,7 @@ name: firebase_auth_desktop description: Desktop implementation of firebase_auth homepage: https://firebase.flutter.dev repository: https://github.com/invertase/flutterfire_dart -version: 0.2.0 +version: 1.0.0 environment: sdk: '>=2.12.0 <3.0.0' @@ -11,10 +11,10 @@ environment: dependencies: desktop_webview_auth: ^0.0.8 firebase_auth: ^3.3.7 - firebase_auth_dart: ^0.1.3 + firebase_auth_dart: ^1.0.0 firebase_auth_platform_interface: ^6.1.4 firebase_core: ^1.12.0 - firebase_core_dart: ^0.1.1 + firebase_core_dart: ^1.0.0 flutter: sdk: flutter meta: ^1.3.0 diff --git a/packages/firebase_core/firebase_core_dart/CHANGELOG.md b/packages/firebase_core/firebase_core_dart/CHANGELOG.md index 4f1a2536..4aae956d 100644 --- a/packages/firebase_core/firebase_core_dart/CHANGELOG.md +++ b/packages/firebase_core/firebase_core_dart/CHANGELOG.md @@ -1,3 +1,9 @@ +## 1.0.0 + + - First stable release. + + - **FIX**: return existing app if options are matching if the same app is initialized twice. + ## 0.1.1 - Graduate package to a stable release. See pre-releases prior to this version for changelog entries. diff --git a/packages/firebase_core/firebase_core_dart/pubspec.yaml b/packages/firebase_core/firebase_core_dart/pubspec.yaml index 43ba122d..2a30e722 100644 --- a/packages/firebase_core/firebase_core_dart/pubspec.yaml +++ b/packages/firebase_core/firebase_core_dart/pubspec.yaml @@ -2,7 +2,7 @@ name: firebase_core_dart description: Pure Dart implementation of FlutterFire core API. homepage: https://firebase.flutter.dev repository: https://github.com/invertase/flutterfire_dart -version: 0.1.1 +version: 1.0.0 environment: sdk: '>=2.12.0 <3.0.0' diff --git a/packages/firebase_core/firebase_core_desktop/CHANGELOG.md b/packages/firebase_core/firebase_core_desktop/CHANGELOG.md index 6471b26a..b2309a9c 100644 --- a/packages/firebase_core/firebase_core_desktop/CHANGELOG.md +++ b/packages/firebase_core/firebase_core_desktop/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.0.0 + + - First stable release. + ## 0.2.0 > Note: This release has breaking changes. diff --git a/packages/firebase_core/firebase_core_desktop/example/pubspec.yaml b/packages/firebase_core/firebase_core_desktop/example/pubspec.yaml index ffb49aa2..01bf9c60 100644 --- a/packages/firebase_core/firebase_core_desktop/example/pubspec.yaml +++ b/packages/firebase_core/firebase_core_desktop/example/pubspec.yaml @@ -22,7 +22,7 @@ environment: dependencies: firebase_core: ^1.9.0 - firebase_core_desktop: ^0.2.0 + firebase_core_desktop: ^1.0.0 flutter: sdk: flutter diff --git a/packages/firebase_core/firebase_core_desktop/pubspec.yaml b/packages/firebase_core/firebase_core_desktop/pubspec.yaml index 2349535b..1fcc7113 100644 --- a/packages/firebase_core/firebase_core_desktop/pubspec.yaml +++ b/packages/firebase_core/firebase_core_desktop/pubspec.yaml @@ -2,14 +2,14 @@ name: firebase_core_desktop description: Desktop implementation of firebase_core homepage: https://firebase.flutter.dev repository: https://github.com/invertase/flutterfire_dart -version: 0.2.0 +version: 1.0.0 environment: sdk: '>=2.12.0 <3.0.0' flutter: '>=1.20.0' dependencies: - firebase_core_dart: ^0.1.1 + firebase_core_dart: ^1.0.0 firebase_core_platform_interface: ^4.1.0 flutter: sdk: flutter diff --git a/packages/firebase_functions/firebase_functions_dart/CHANGELOG.md b/packages/firebase_functions/firebase_functions_dart/CHANGELOG.md index ecaf7a0e..c0d7aa22 100644 --- a/packages/firebase_functions/firebase_functions_dart/CHANGELOG.md +++ b/packages/firebase_functions/firebase_functions_dart/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.1+1 + + - Update a dependency to the latest release. + ## 0.1.1 - **FEAT**: web support (#72). diff --git a/packages/firebase_functions/firebase_functions_dart/pubspec.yaml b/packages/firebase_functions/firebase_functions_dart/pubspec.yaml index 33598781..4b73575d 100644 --- a/packages/firebase_functions/firebase_functions_dart/pubspec.yaml +++ b/packages/firebase_functions/firebase_functions_dart/pubspec.yaml @@ -2,15 +2,15 @@ name: firebase_functions_dart description: Pure Dart implementation of FlutterFire CloudFunctions API. homepage: https://firebase.flutter.dev repository: https://github.com/invertase/flutterfire_dart -version: 0.1.1 +version: 0.1.1+1 environment: sdk: '>=2.12.0 <3.0.0' dependencies: collection: ^1.15.0 - firebase_auth_dart: ^0.1.3 - firebase_core_dart: ^0.1.1 + firebase_auth_dart: ^1.0.0 + firebase_core_dart: ^1.0.0 http: ^0.13.4 meta: ^1.7.0 storagebox: ^0.1.0+1 diff --git a/packages/firebase_functions/firebase_functions_desktop/CHANGELOG.md b/packages/firebase_functions/firebase_functions_desktop/CHANGELOG.md index 6a0121b3..335b4495 100644 --- a/packages/firebase_functions/firebase_functions_desktop/CHANGELOG.md +++ b/packages/firebase_functions/firebase_functions_desktop/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.2.0+1 + + - Update a dependency to the latest release. + ## 0.2.0 > Note: This release has breaking changes. diff --git a/packages/firebase_functions/firebase_functions_desktop/pubspec.yaml b/packages/firebase_functions/firebase_functions_desktop/pubspec.yaml index 3ab57684..ed1b86e7 100644 --- a/packages/firebase_functions/firebase_functions_desktop/pubspec.yaml +++ b/packages/firebase_functions/firebase_functions_desktop/pubspec.yaml @@ -2,7 +2,7 @@ name: firebase_functions_desktop description: Desktop implementation of cloud_functions homepage: https://firebase.flutter.dev repository: https://github.com/invertase/flutterfire_dart -version: 0.2.0 +version: 0.2.0+1 environment: sdk: '>=2.12.0 <3.0.0' @@ -12,9 +12,9 @@ dependencies: cloud_functions: ^3.1.1 cloud_functions_platform_interface: ^5.0.14 firebase_core: ^1.10.0 - firebase_core_dart: ^0.1.1 - firebase_core_desktop: ^0.2.0 - firebase_functions_dart: ^0.1.1 + firebase_core_dart: ^1.0.0 + firebase_core_desktop: ^1.0.0 + firebase_functions_dart: ^0.1.1+1 flutter: sdk: flutter http: ^0.13.4 From f09fe110328ac04c576587ada922ac9e7b74da6a Mon Sep 17 00:00:00 2001 From: Tim Whiting Date: Wed, 18 May 2022 20:18:41 -0600 Subject: [PATCH 26/30] firebaseapis --- .../lib/firebase_remote_config_dart.dart | 2 +- .../lib/src/internal/api.dart | 41 +++++++------------ .../firebase_remote_config_dart/pubspec.yaml | 1 + tests/linux/flutter/generated_plugins.cmake | 8 ++++ tests/windows/flutter/generated_plugins.cmake | 8 ++++ 5 files changed, 33 insertions(+), 27 deletions(-) diff --git a/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart b/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart index f5b82be6..adc206a6 100644 --- a/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart +++ b/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart @@ -5,9 +5,9 @@ library firebase_remote_config_dart; import 'dart:async'; -import 'dart:convert'; import 'package:firebase_core_dart/firebase_core_dart.dart'; +import 'package:firebaseapis/firebaseremoteconfig/v1.dart' as api; import 'package:http/http.dart'; import 'package:meta/meta.dart'; import 'package:storagebox/storagebox.dart'; diff --git a/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/api.dart b/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/api.dart index 02ff5ead..bfa4a976 100644 --- a/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/api.dart +++ b/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/api.dart @@ -15,8 +15,7 @@ class RemoteConfigApiClient { this.storage, this.storageCache, ); - Client get httpClient => _httpClient; - final _httpClient = Client(); + final remoteConfigClient = api.FirebaseRemoteConfigApi(Client()); final _RemoteConfigStorage storage; final _RemoteConfigStorageCache storageCache; @@ -51,34 +50,24 @@ class RemoteConfigApiClient { return lastSuccessfulFetchResponse; } - // TODO: Handle errors in fetch - final response = await _httpClient.post( - Uri.parse( - 'https://firebaseremoteconfig.googleapis.com/v1/projects/$projectId/namespaces/firebase:fetch?key=$apiKey', + print('Calling api'); + final response = await remoteConfigClient.projects.namespaces.fetch( + api.FetchRemoteConfigRequest( + appId: appId, + appInstanceId: '1', // TODO: get from installations + sdkVersion: '0.1.0', // TODO: Sync with pubspec ), - headers: { - 'Content-Type': 'application/json', - 'Content-Encoding': 'gzip', - 'If-None-Match': eTag ?? '*' - }, - body: json.encode({ - // TODO: Sync this with pubspec.yaml - 'sdk_version': '0.1.0', - // TODO: Replace this with installation id - 'app_instance_id': '1', - 'app_id': appId, - }), + projectId, + 'firebase', ); - if (response.statusCode != 200) { - throw Exception( - 'Failed to fetch remote config: ${response.statusCode} ${response.body}', - ); - } + // print('Calling api'); + // final response = await remoteConfigClient.projects.namespaces + // .getRemoteConfig('projects/$projectId'); - final remoteConfig = json.decode(response.body); storageCache.setLastFetchTime(DateTime.now()); + print('Got ${response.entries}'); - storage.setLastSuccessfulFetchResponse(remoteConfig); - return remoteConfig; + storage.setLastSuccessfulFetchResponse({}); + return {}; } } diff --git a/packages/firebase_remote_config/firebase_remote_config_dart/pubspec.yaml b/packages/firebase_remote_config/firebase_remote_config_dart/pubspec.yaml index bfe00687..f12d7a05 100644 --- a/packages/firebase_remote_config/firebase_remote_config_dart/pubspec.yaml +++ b/packages/firebase_remote_config/firebase_remote_config_dart/pubspec.yaml @@ -10,6 +10,7 @@ environment: dependencies: collection: ^1.15.0 firebase_core_dart: ^0.1.1 + firebaseapis: ^0.1.2 http: ^0.13.4 meta: ^1.7.0 storagebox: ^0.1.0+2 diff --git a/tests/linux/flutter/generated_plugins.cmake b/tests/linux/flutter/generated_plugins.cmake index 5254f4fa..e5bf8b2e 100644 --- a/tests/linux/flutter/generated_plugins.cmake +++ b/tests/linux/flutter/generated_plugins.cmake @@ -6,6 +6,9 @@ list(APPEND FLUTTER_PLUGIN_LIST desktop_webview_auth ) +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) @@ -14,3 +17,8 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST}) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/tests/windows/flutter/generated_plugins.cmake b/tests/windows/flutter/generated_plugins.cmake index 08ec9fbf..67972319 100644 --- a/tests/windows/flutter/generated_plugins.cmake +++ b/tests/windows/flutter/generated_plugins.cmake @@ -6,6 +6,9 @@ list(APPEND FLUTTER_PLUGIN_LIST desktop_webview_auth ) +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) @@ -14,3 +17,8 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST}) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) From fb8efa7b0937ed0d4ee0885a4399d34eb7d77487 Mon Sep 17 00:00:00 2001 From: Tim Whiting Date: Wed, 18 May 2022 20:44:44 -0600 Subject: [PATCH 27/30] some more attempts at fixing --- .../lib/firebase_remote_config_dart.dart | 6 ++++-- .../lib/src/internal/api.dart | 11 ++++------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart b/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart index adc206a6..19a666a8 100644 --- a/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart +++ b/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart @@ -153,10 +153,12 @@ class FirebaseRemoteConfig { await api .fetch(cacheMaxAge: settings.minimumFetchInterval) .timeout(settings.fetchTimeout); - } on TimeoutException { + } on TimeoutException catch (e) { + print(e); storageCache.setLastFetchStatus(RemoteConfigFetchStatus.throttle); rethrow; // TODO: Throw Firebase Exception - } on Exception { + } on Exception catch (e) { + print(e); storageCache.setLastFetchStatus(RemoteConfigFetchStatus.failure); rethrow; // TODO: Throw Firebase Exception } diff --git a/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/api.dart b/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/api.dart index bfa4a976..43fae4fd 100644 --- a/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/api.dart +++ b/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/api.dart @@ -15,7 +15,7 @@ class RemoteConfigApiClient { this.storage, this.storageCache, ); - final remoteConfigClient = api.FirebaseRemoteConfigApi(Client()); + late final remoteConfigClient = api.FirebaseRemoteConfigApi(Client()); final _RemoteConfigStorage storage; final _RemoteConfigStorageCache storageCache; @@ -58,16 +58,13 @@ class RemoteConfigApiClient { sdkVersion: '0.1.0', // TODO: Sync with pubspec ), projectId, - 'firebase', + namespace, ); - // print('Calling api'); - // final response = await remoteConfigClient.projects.namespaces - // .getRemoteConfig('projects/$projectId'); storageCache.setLastFetchTime(DateTime.now()); print('Got ${response.entries}'); - storage.setLastSuccessfulFetchResponse({}); - return {}; + storage.setLastSuccessfulFetchResponse(response.entries ?? {}); + return response.entries ?? {}; } } From 3478130051c0b827dfdcbadda256244f6911e813 Mon Sep 17 00:00:00 2001 From: Tim Whiting Date: Fri, 20 May 2022 11:39:56 -0600 Subject: [PATCH 28/30] fix using clientViaApiKey --- .../lib/firebase_remote_config_dart.dart | 11 +++-------- .../lib/src/internal/api.dart | 5 ++--- .../firebase_remote_config_dart/pubspec.yaml | 1 + 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart b/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart index 19a666a8..b5d60db5 100644 --- a/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart +++ b/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart @@ -8,7 +8,7 @@ import 'dart:async'; import 'package:firebase_core_dart/firebase_core_dart.dart'; import 'package:firebaseapis/firebaseremoteconfig/v1.dart' as api; -import 'package:http/http.dart'; +import 'package:googleapis_auth/auth_io.dart'; import 'package:meta/meta.dart'; import 'package:storagebox/storagebox.dart'; @@ -116,19 +116,14 @@ class FirebaseRemoteConfig { final lastSuccessfulFetchResponse = storage.getLastSuccessfulFetchResponse(); - // final activeConfigEtag = storage.getActiveConfigEtag(); - if (lastSuccessfulFetchResponse?['entries'] == null) { - // lastSuccessfulFetchResponse.eTag == null || - // lastSuccessfulFetchResponse.eTag == activeConfigEtag) { + if (lastSuccessfulFetchResponse == null) { return false; } else { final newConfig = { - for (final entry - in (lastSuccessfulFetchResponse!['entries'] as Map).entries) + for (final entry in lastSuccessfulFetchResponse.entries) entry.key: RemoteConfigValue(entry.value, ValueSource.valueRemote) }; storageCache.setActiveConfig(newConfig); - // storage.setActiveConfigEtag(lastSuccessfulFetchResponse.eTag); return true; } } diff --git a/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/api.dart b/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/api.dart index 43fae4fd..90e3b170 100644 --- a/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/api.dart +++ b/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/api.dart @@ -15,7 +15,8 @@ class RemoteConfigApiClient { this.storage, this.storageCache, ); - late final remoteConfigClient = api.FirebaseRemoteConfigApi(Client()); + late final remoteConfigClient = + api.FirebaseRemoteConfigApi(clientViaApiKey(apiKey)); final _RemoteConfigStorage storage; final _RemoteConfigStorageCache storageCache; @@ -50,7 +51,6 @@ class RemoteConfigApiClient { return lastSuccessfulFetchResponse; } - print('Calling api'); final response = await remoteConfigClient.projects.namespaces.fetch( api.FetchRemoteConfigRequest( appId: appId, @@ -62,7 +62,6 @@ class RemoteConfigApiClient { ); storageCache.setLastFetchTime(DateTime.now()); - print('Got ${response.entries}'); storage.setLastSuccessfulFetchResponse(response.entries ?? {}); return response.entries ?? {}; diff --git a/packages/firebase_remote_config/firebase_remote_config_dart/pubspec.yaml b/packages/firebase_remote_config/firebase_remote_config_dart/pubspec.yaml index f12d7a05..0f0c4192 100644 --- a/packages/firebase_remote_config/firebase_remote_config_dart/pubspec.yaml +++ b/packages/firebase_remote_config/firebase_remote_config_dart/pubspec.yaml @@ -14,6 +14,7 @@ dependencies: http: ^0.13.4 meta: ^1.7.0 storagebox: ^0.1.0+2 + googleapis_auth: ^1.3.1 dev_dependencies: mockito: ^5.1.0 From 841ccdb8333e0c3a93ab954c4da5ddae1ffe8953 Mon Sep 17 00:00:00 2001 From: Tim Whiting Date: Fri, 20 May 2022 11:43:35 -0600 Subject: [PATCH 29/30] fix analyzer errors and pubspec --- .../lib/firebase_remote_config_dart.dart | 6 ++---- .../firebase_remote_config_dart/pubspec.yaml | 4 ++-- .../firebase_remote_config_desktop/pubspec.yaml | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart b/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart index b5d60db5..8bcc7d0d 100644 --- a/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart +++ b/packages/firebase_remote_config/firebase_remote_config_dart/lib/firebase_remote_config_dart.dart @@ -148,12 +148,10 @@ class FirebaseRemoteConfig { await api .fetch(cacheMaxAge: settings.minimumFetchInterval) .timeout(settings.fetchTimeout); - } on TimeoutException catch (e) { - print(e); + } on TimeoutException { storageCache.setLastFetchStatus(RemoteConfigFetchStatus.throttle); rethrow; // TODO: Throw Firebase Exception - } on Exception catch (e) { - print(e); + } on Exception { storageCache.setLastFetchStatus(RemoteConfigFetchStatus.failure); rethrow; // TODO: Throw Firebase Exception } diff --git a/packages/firebase_remote_config/firebase_remote_config_dart/pubspec.yaml b/packages/firebase_remote_config/firebase_remote_config_dart/pubspec.yaml index 0f0c4192..554bdbf1 100644 --- a/packages/firebase_remote_config/firebase_remote_config_dart/pubspec.yaml +++ b/packages/firebase_remote_config/firebase_remote_config_dart/pubspec.yaml @@ -1,7 +1,7 @@ name: firebase_remote_config_dart description: Pure Dart implementation of FlutterFire RemoteConfig API. homepage: https://firebase.flutter.dev -repository: https://github.com/invertase/flutterfire_dart +repository: https://github.com/invertase/flutterfire_desktop version: 0.1.0-dev.0 environment: @@ -11,10 +11,10 @@ dependencies: collection: ^1.15.0 firebase_core_dart: ^0.1.1 firebaseapis: ^0.1.2 + googleapis_auth: ^1.3.1 http: ^0.13.4 meta: ^1.7.0 storagebox: ^0.1.0+2 - googleapis_auth: ^1.3.1 dev_dependencies: mockito: ^5.1.0 diff --git a/packages/firebase_remote_config/firebase_remote_config_desktop/pubspec.yaml b/packages/firebase_remote_config/firebase_remote_config_desktop/pubspec.yaml index ae909761..18254c53 100644 --- a/packages/firebase_remote_config/firebase_remote_config_desktop/pubspec.yaml +++ b/packages/firebase_remote_config/firebase_remote_config_desktop/pubspec.yaml @@ -1,7 +1,7 @@ name: firebase_remote_config_desktop description: Desktop implementation of remote_config homepage: https://firebase.flutter.dev -repository: https://github.com/invertase/flutterfire_dart +repository: https://github.com/invertase/flutterfire_desktop version: 0.1.0-dev.0 environment: From cf4c71bbd178befb0b3c6605bb315ce9f0c76cbf Mon Sep 17 00:00:00 2001 From: Tim Whiting Date: Sun, 22 May 2022 14:25:05 -0600 Subject: [PATCH 30/30] start on fixing tests --- .../lib/src/internal/api.dart | 4 ++-- .../test/firebase_remote_config_test.dart | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/api.dart b/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/api.dart index 90e3b170..bfea5b55 100644 --- a/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/api.dart +++ b/packages/firebase_remote_config/firebase_remote_config_dart/lib/src/internal/api.dart @@ -15,8 +15,8 @@ class RemoteConfigApiClient { this.storage, this.storageCache, ); - late final remoteConfigClient = - api.FirebaseRemoteConfigApi(clientViaApiKey(apiKey)); + late final httpClient = clientViaApiKey(apiKey); + late final remoteConfigClient = api.FirebaseRemoteConfigApi(httpClient); final _RemoteConfigStorage storage; final _RemoteConfigStorageCache storageCache; diff --git a/packages/firebase_remote_config/firebase_remote_config_dart/test/firebase_remote_config_test.dart b/packages/firebase_remote_config/firebase_remote_config_dart/test/firebase_remote_config_test.dart index 165a9784..0104c648 100644 --- a/packages/firebase_remote_config/firebase_remote_config_dart/test/firebase_remote_config_test.dart +++ b/packages/firebase_remote_config/firebase_remote_config_dart/test/firebase_remote_config_test.dart @@ -213,19 +213,19 @@ class FakeConfigClient extends RemoteConfigApiClient { storageCache, ) : super(projectId, namespace, apiKey, appId, storage, storageCache); @override - Client httpClient = MockClient( - (request) => Future.value( - Response( - ''' + Client get httpClient => MockClient( + (request) => Future.value( + Response( + ''' { "parameters": { "bar": {"defaultValue": {"value": "bar"}}, "foo": {"defaultValue": {"value": "real foo"}} } }''', - 200, - headers: {'content-type': 'application/json'}, - ), - ), - ); + 200, + headers: {'content-type': 'application/json'}, + ), + ), + ); }