diff --git a/.gitignore b/.gitignore
index 6272ab93..73dcc4f5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -325,3 +325,7 @@ ASALocalRun/
# MFractors (Xamarin productivity tool) working folder
.mfractor/
+
+# Firebase
+*google-services.json
+*GoogleService-Info.plist
diff --git a/src/app/ApplicationTemplate.Business/ForcedUpdates/UpdateRequiredService.cs b/src/app/ApplicationTemplate.Business/ForcedUpdates/UpdateRequiredService.cs
index 5882edd6..f2c23db2 100644
--- a/src/app/ApplicationTemplate.Business/ForcedUpdates/UpdateRequiredService.cs
+++ b/src/app/ApplicationTemplate.Business/ForcedUpdates/UpdateRequiredService.cs
@@ -18,7 +18,13 @@ public sealed class UpdateRequiredService : IUpdateRequiredService, IDisposable
public UpdateRequiredService(IMinimumVersionReposiory minimumVersionReposiory)
{
_subscription = minimumVersionReposiory.MinimumVersionObservable
- .Subscribe(_ => UpdateRequired?.Invoke(this, EventArgs.Empty));
+ .Subscribe(version =>
+ {
+ if (version > new Version("1.1.0"))
+ {
+ UpdateRequired?.Invoke(this, EventArgs.Empty);
+ }
+ });
}
///
diff --git a/src/app/ApplicationTemplate.Mobile/ApplicationTemplate.Mobile.csproj b/src/app/ApplicationTemplate.Mobile/ApplicationTemplate.Mobile.csproj
index 6678ad4b..cb9d5bdf 100644
--- a/src/app/ApplicationTemplate.Mobile/ApplicationTemplate.Mobile.csproj
+++ b/src/app/ApplicationTemplate.Mobile/ApplicationTemplate.Mobile.csproj
@@ -15,6 +15,9 @@
True
partial
+
+
+
@@ -100,8 +103,11 @@
-
+
+
+
+
@@ -149,10 +155,19 @@
$(MtouchExtraArgs) --registrar:static
$(MtouchExtraArgs) --marshal-objectivec-exceptions:disable
+
+ $(MtouchExtraArgs) --gcc_flags -L/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/iphoneos
+ $(MtouchExtraArgs) --gcc_flags -L/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/iphonesimulator/
iOS\Entitlements.plist
true
+
+
+
+ GoogleService-Info.plist
+
+
@@ -201,6 +216,7 @@
+
@@ -268,4 +284,8 @@
+
+
+
+
\ No newline at end of file
diff --git a/src/app/ApplicationTemplate.Mobile/LinkWithSwiftSystemLibrariesWorkaround.targets b/src/app/ApplicationTemplate.Mobile/LinkWithSwiftSystemLibrariesWorkaround.targets
new file mode 100644
index 00000000..9497a7ae
--- /dev/null
+++ b/src/app/ApplicationTemplate.Mobile/LinkWithSwiftSystemLibrariesWorkaround.targets
@@ -0,0 +1,18 @@
+
+
+
+
+
+ <_SwiftPlatform Condition="$(RuntimeIdentifier.StartsWith('iossimulator-'))">iphonesimulator
+ <_SwiftPlatform Condition="$(RuntimeIdentifier.StartsWith('ios-'))">iphoneos
+
+
+ <_CustomLinkFlags Include="-L" />
+ <_CustomLinkFlags Include="/usr/lib/swift" />
+ <_CustomLinkFlags Include="-L" />
+ <_CustomLinkFlags Include="$(_SdkDevPath)/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/$(_SwiftPlatform)" />
+ <_CustomLinkFlags Include="-Wl,-rpath" />
+ <_CustomLinkFlags Include="-Wl,/usr/lib/swift" />
+
+
+
\ No newline at end of file
diff --git a/src/app/ApplicationTemplate.Presentation/Configuration/ApiConfiguration.cs b/src/app/ApplicationTemplate.Presentation/Configuration/ApiConfiguration.cs
index 1bab32ae..0f622457 100644
--- a/src/app/ApplicationTemplate.Presentation/Configuration/ApiConfiguration.cs
+++ b/src/app/ApplicationTemplate.Presentation/Configuration/ApiConfiguration.cs
@@ -42,8 +42,6 @@ public static IServiceCollection AddApi(this IServiceCollection services, IConfi
.AddAuthentication()
.AddPosts(configuration)
.AddUserProfile()
- .AddMinimumVersion()
- .AddKillSwitch()
.AddDadJokes(configuration);
return services;
@@ -55,18 +53,6 @@ private static IServiceCollection AddUserProfile(this IServiceCollection service
return services.AddSingleton();
}
- private static IServiceCollection AddMinimumVersion(this IServiceCollection services)
- {
- // This one doesn't have an actual remote API yet. It's always a mock implementation.
- return services.AddSingleton();
- }
-
- private static IServiceCollection AddKillSwitch(this IServiceCollection services)
- {
- // This one doesn't have an actual remote API yet. It's always a mock implementation.
- return services.AddSingleton();
- }
-
private static IServiceCollection AddAuthentication(this IServiceCollection services)
{
// This one doesn't have an actual remote API yet. It's always a mock implementation.
diff --git a/src/app/ApplicationTemplate.Shared.Views/ApplicationTemplate.Shared.Views.projitems b/src/app/ApplicationTemplate.Shared.Views/ApplicationTemplate.Shared.Views.projitems
index 059e75fd..9da107ed 100644
--- a/src/app/ApplicationTemplate.Shared.Views/ApplicationTemplate.Shared.Views.projitems
+++ b/src/app/ApplicationTemplate.Shared.Views/ApplicationTemplate.Shared.Views.projitems
@@ -118,6 +118,8 @@
+
+
Shell.xaml
diff --git a/src/app/ApplicationTemplate.Shared.Views/Configuration/ViewServicesConfiguration.cs b/src/app/ApplicationTemplate.Shared.Views/Configuration/ViewServicesConfiguration.cs
index b6d91d4d..ff128d4c 100644
--- a/src/app/ApplicationTemplate.Shared.Views/Configuration/ViewServicesConfiguration.cs
+++ b/src/app/ApplicationTemplate.Shared.Views/Configuration/ViewServicesConfiguration.cs
@@ -1,4 +1,5 @@
using System.Reactive.Concurrency;
+using ApplicationTemplate.DataAccess;
using Chinook.DynamicMvvm;
using MessageDialogService;
using Microsoft.Extensions.DependencyInjection;
@@ -34,6 +35,14 @@ public static IServiceCollection AddViewServices(this IServiceCollection service
.AddSingleton()
.AddSingleton()
.AddSingleton()
+#if __ANDROID__ || __IOS__
+ .AddSingleton()
+ .AddSingleton(s => s.GetRequiredService())
+ .AddSingleton(s => s.GetRequiredService())
+#else
+ .AddSingleton()
+ .AddSingleton()
+#endif
.AddMessageDialog();
}
diff --git a/src/app/ApplicationTemplate.Shared.Views/RemoteConfigRepository.Android.cs b/src/app/ApplicationTemplate.Shared.Views/RemoteConfigRepository.Android.cs
new file mode 100644
index 00000000..3f02c45c
--- /dev/null
+++ b/src/app/ApplicationTemplate.Shared.Views/RemoteConfigRepository.Android.cs
@@ -0,0 +1,114 @@
+#if __ANDROID__
+using System;
+using System.Reactive.Subjects;
+using ApplicationTemplate.DataAccess;
+using Firebase.RemoteConfig;
+
+namespace ApplicationTemplate.Views;
+
+///
+/// RemoteConfigRepository is a repository for Firebase Remote Config.
+///
+public sealed class RemoteConfigRepository : IKillSwitchRepository, IMinimumVersionReposiory, IDisposable
+{
+ private Subject _killSwitchSubject = new Subject();
+ private Subject _versionSubject = new Subject();
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public RemoteConfigRepository()
+ {
+ FirebaseRemoteConfig mFirebaseRemoteConfig = FirebaseRemoteConfig.Instance;
+ FirebaseRemoteConfigSettings configSettings = new FirebaseRemoteConfigSettings.Builder()
+ .SetMinimumFetchIntervalInSeconds(3600)
+ .Build();
+ mFirebaseRemoteConfig.SetConfigSettingsAsync(configSettings);
+
+ FetchRemoteConfig();
+ ListenForRealTimeChanges();
+ }
+
+ private void FetchRemoteConfig()
+ {
+ FirebaseRemoteConfig mFirebaseRemoteConfig = FirebaseRemoteConfig.Instance;
+ mFirebaseRemoteConfig.FetchAndActivate()
+ .AddOnCompleteListener(new FetchCompleteListener(this));
+ }
+
+ private void ListenForRealTimeChanges()
+ {
+ FirebaseRemoteConfig mFirebaseRemoteConfig = FirebaseRemoteConfig.Instance;
+
+ mFirebaseRemoteConfig.AddOnConfigUpdateListener(new ConfigUpdateListener(this));
+ }
+
+ ///
+ public IObservable MinimumVersionObservable => _versionSubject;
+
+ ///
+ public void CheckMinimumVersion()
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ public IObservable ObserveKillSwitchActivation()
+ {
+ return _killSwitchSubject;
+ }
+
+ ///
+ public void Dispose()
+ {
+ _killSwitchSubject.Dispose();
+ _versionSubject.Dispose();
+ }
+
+ private sealed class FetchCompleteListener : Java.Lang.Object, Android.Gms.Tasks.IOnCompleteListener
+ {
+ private readonly RemoteConfigRepository _repository;
+
+ public FetchCompleteListener(RemoteConfigRepository repository)
+ {
+ _repository = repository;
+ }
+
+ public void OnComplete(Android.Gms.Tasks.Task task)
+ {
+ if (task.IsSuccessful)
+ {
+ _repository._killSwitchSubject.OnNext(FirebaseRemoteConfig.Instance.GetBoolean("kill_switch"));
+ _repository._versionSubject.OnNext(new Version(FirebaseRemoteConfig.Instance.GetString("minimum_version")));
+ }
+ }
+ }
+
+ private sealed class ConfigUpdateListener : Java.Lang.Object, IConfigUpdateListener
+ {
+ private readonly RemoteConfigRepository _repository;
+
+ public ConfigUpdateListener(RemoteConfigRepository repository)
+ {
+ _repository = repository;
+ }
+
+ public void OnError(FirebaseRemoteConfigException p0)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void OnUpdate(ConfigUpdate p0)
+ {
+ var instance = FirebaseRemoteConfig.Instance;
+
+ instance.FetchAndActivate();
+ var isKillSwitchActivated = FirebaseRemoteConfig.Instance.GetBoolean("kill_switch");
+ var minimumVersion = new Version(FirebaseRemoteConfig.Instance.GetString("minimum_version"));
+
+ _repository._killSwitchSubject.OnNext(isKillSwitchActivated);
+ _repository._versionSubject.OnNext(minimumVersion);
+ }
+ }
+}
+#endif
diff --git a/src/app/ApplicationTemplate.Shared.Views/RemoteConfigRepository.Ios.cs b/src/app/ApplicationTemplate.Shared.Views/RemoteConfigRepository.Ios.cs
new file mode 100644
index 00000000..4374777d
--- /dev/null
+++ b/src/app/ApplicationTemplate.Shared.Views/RemoteConfigRepository.Ios.cs
@@ -0,0 +1,96 @@
+#if __IOS__
+using System;
+using System.Reactive.Subjects;
+using ApplicationTemplate.DataAccess;
+using Firebase.RemoteConfig;
+using Foundation;
+
+namespace ApplicationTemplate.Views;
+
+///
+/// Implementation of the killswitch repository and the minimum version with Firebase Remote Config for iOS.
+///
+public sealed class RemoteConfigRepository : IKillSwitchRepository, IMinimumVersionReposiory, IDisposable
+{
+ private BehaviorSubject _killSwitchSubject = new BehaviorSubject(default);
+ private BehaviorSubject _versionSubject = new BehaviorSubject(default);
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public RemoteConfigRepository()
+ {
+ Firebase.Core.App.Configure();
+ // Enabling developer mode, allows for frequent refreshes of the cache
+ RemoteConfig.SharedInstance.ConfigSettings = new RemoteConfigSettings();
+
+ object[] values = { false, "1.0.0" };
+ object[] keys = { "kill_switch", "minimum_version" };
+ var defaultValues = NSDictionary.FromObjectsAndKeys(values, keys, keys.Length);
+ RemoteConfig.SharedInstance.SetDefaults(defaultValues);
+
+ // CacheExpirationSeconds is set to CacheExpiration here, indicating that any previously
+ // fetched and cached config would be considered expired because it would have been fetched
+ // more than CacheExpiration seconds ago. Thus the next fetch would go to the server unless
+ // throttling is in progress. The default expiration duration is 43200 (12 hours).
+ RemoteConfig.SharedInstance.Fetch(10, (status, error) =>
+ {
+ switch (status)
+ {
+ case RemoteConfigFetchStatus.Success:
+ Console.WriteLine("Config Fetched!");
+
+ // Call this method to make fetched parameter values available to your app
+ RemoteConfig.SharedInstance.Activate();
+
+ _killSwitchSubject.OnNext(RemoteConfig.SharedInstance["kill_switch"].BoolValue);
+ _versionSubject.OnNext(new Version(RemoteConfig.SharedInstance["minimum_version"].StringValue));
+ break;
+
+ case RemoteConfigFetchStatus.Throttled:
+ case RemoteConfigFetchStatus.NoFetchYet:
+ case RemoteConfigFetchStatus.Failure:
+ Console.WriteLine("Config not fetched...");
+ break;
+ }
+ });
+
+ RemoteConfig.SharedInstance.AddObserver(new NSString("kill_switch"), NSKeyValueObservingOptions.New, (nSObservedChange) =>
+ {
+ if (nSObservedChange.NewValue is NSNumber nSNumber)
+ {
+ _killSwitchSubject.OnNext(nSNumber.BoolValue);
+ }
+ });
+ RemoteConfig.SharedInstance.AddObserver(new NSString("minimum_version"), NSKeyValueObservingOptions.New, (nSObservedChange) =>
+ {
+ if (nSObservedChange.NewValue is NSString nSString)
+ {
+ _versionSubject.OnNext(new Version(nSString));
+ }
+ });
+ }
+
+ ///
+ public IObservable MinimumVersionObservable => _versionSubject;
+
+ ///
+ public void CheckMinimumVersion()
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ public IObservable ObserveKillSwitchActivation()
+ {
+ return _killSwitchSubject;
+ }
+
+ ///
+ public void Dispose()
+ {
+ _killSwitchSubject.Dispose();
+ _versionSubject.Dispose();
+ }
+}
+#endif