1
0
mirror of https://github.com/bitwarden/mobile.git synced 2024-12-25 16:47:55 +01:00

custom toast implementations

This commit is contained in:
Kyle Spearrin 2017-12-22 15:00:11 -05:00
parent 45ab6d47de
commit 9f23f4ead7
22 changed files with 101 additions and 69 deletions

View File

@ -4375,17 +4375,17 @@ namespace Bit.Android
// aapt resource value: 0x7f090051
public const int ApplicationName = 2131296337;
// aapt resource value: 0x7f0900b2
public const int AutoFillServiceDescription = 2131296434;
// aapt resource value: 0x7f0900ab
public const int AutoFillServiceDescription = 2131296427;
// aapt resource value: 0x7f0900b1
public const int AutoFillServiceSummary = 2131296433;
// aapt resource value: 0x7f0900aa
public const int AutoFillServiceSummary = 2131296426;
// aapt resource value: 0x7f090050
public const int Hello = 2131296336;
// aapt resource value: 0x7f0900b3
public const int MyVault = 2131296435;
// aapt resource value: 0x7f0900ac
public const int MyVault = 2131296428;
// aapt resource value: 0x7f090027
public const int abc_action_bar_home_description = 2131296295;
@ -4540,27 +4540,6 @@ namespace Bit.Android
// aapt resource value: 0x7f09000f
public const int common_signin_button_text_long = 2131296271;
// aapt resource value: 0x7f0900ac
public const int default_web_client_id = 2131296428;
// aapt resource value: 0x7f0900ad
public const int firebase_database_url = 2131296429;
// aapt resource value: 0x7f0900aa
public const int gcm_defaultSenderId = 2131296426;
// aapt resource value: 0x7f0900ae
public const int google_api_key = 2131296430;
// aapt resource value: 0x7f0900ab
public const int google_app_id = 2131296427;
// aapt resource value: 0x7f0900af
public const int google_crash_reporting_api_key = 2131296431;
// aapt resource value: 0x7f0900b0
public const int google_storage_bucket = 2131296432;
// aapt resource value: 0x7f090052
public const int hockeyapp_crash_dialog_app_name_fallback = 2131296338;

View File

@ -45,6 +45,12 @@ namespace Bit.Android.Services
private Context CurrentContext => CrossCurrentActivity.Current.Activity;
public void Toast(string text, bool longDuration = false)
{
global::Android.Widget.Toast.MakeText(CurrentContext, text,
longDuration ? global::Android.Widget.ToastLength.Long : global::Android.Widget.ToastLength.Short).Show();
}
public void CopyToClipboard(string text)
{
var clipboardManager = (ClipboardManager)CurrentContext.GetSystemService(Context.ClipboardService);

View File

@ -5,6 +5,7 @@ namespace Bit.App.Abstractions
{
public interface IDeviceActionService
{
void Toast(string text, bool longDuration = false);
void CopyToClipboard(string text);
bool OpenFile(byte[] fileData, string id, string fileName);
bool CanOpenFile(string fileName);

View File

@ -14,6 +14,7 @@ namespace Bit.App.Pages
{
private IAppSettingsService _appSettings;
private IUserDialogs _userDialogs;
private IDeviceActionService _deviceActionService;
private IGoogleAnalyticsService _googleAnalyticsService;
public EnvironmentPage()
@ -21,6 +22,7 @@ namespace Bit.App.Pages
{
_appSettings = Resolver.Resolve<IAppSettingsService>();
_userDialogs = Resolver.Resolve<IUserDialogs>();
_deviceActionService = Resolver.Resolve<IDeviceActionService>();
_googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
Init();
@ -238,7 +240,7 @@ namespace Bit.App.Pages
_appSettings.IdentityUrl = IdentityUrlCell.Entry.Text;
_appSettings.ApiUrl = ApiUrlCell.Entry.Text;
_appSettings.WebVaultUrl = WebVaultUrlCell.Entry.Text;
_userDialogs.Toast(AppResources.EnvironmentSaved);
_deviceActionService.Toast(AppResources.EnvironmentSaved);
_googleAnalyticsService.TrackAppEvent("SetEnvironmentUrls");
await Navigation.PopForDeviceAsync();
}

View File

@ -14,15 +14,15 @@ namespace Bit.App.Pages
public class HomePage : ExtendedContentPage
{
private readonly IAuthService _authService;
private readonly IUserDialogs _userDialogs;
private readonly ISettings _settings;
private readonly IDeviceActionService _deviceActionService;
private DateTime? _lastAction;
public HomePage()
: base(updateActivity: false)
{
_authService = Resolver.Resolve<IAuthService>();
_userDialogs = Resolver.Resolve<IUserDialogs>();
_deviceActionService = Resolver.Resolve<IDeviceActionService>();
_settings = Resolver.Resolve<ISettings>();
Init();
@ -130,7 +130,7 @@ namespace Bit.App.Pages
{
await Navigation.PopForDeviceAsync();
await Navigation.PushForDeviceAsync(new LoginPage(email));
_userDialogs.Toast(AppResources.AccountCreated);
_deviceActionService.Toast(AppResources.AccountCreated);
}
public async Task SettingsAsync()

View File

@ -373,7 +373,7 @@ namespace Bit.App.Pages
if(response.Succeeded && doToast)
{
_userDialogs.Toast(AppResources.VerificationEmailSent);
_deviceActionService.Toast(AppResources.VerificationEmailSent);
}
else if(!response.Succeeded)
{

View File

@ -16,6 +16,7 @@ namespace Bit.App.Pages
{
private readonly IFolderService _folderService;
private readonly IUserDialogs _userDialogs;
private readonly IDeviceActionService _deviceActionService;
private readonly IConnectivity _connectivity;
private readonly IGoogleAnalyticsService _googleAnalyticsService;
private DateTime? _lastAction;
@ -24,6 +25,7 @@ namespace Bit.App.Pages
{
_folderService = Resolver.Resolve<IFolderService>();
_userDialogs = Resolver.Resolve<IUserDialogs>();
_deviceActionService = Resolver.Resolve<IDeviceActionService>();
_connectivity = Resolver.Resolve<IConnectivity>();
_googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
@ -89,7 +91,7 @@ namespace Bit.App.Pages
if(saveResult.Succeeded)
{
_userDialogs.Toast(AppResources.FolderCreated);
_deviceActionService.Toast(AppResources.FolderCreated);
_googleAnalyticsService.TrackAppEvent("CreatedFolder");
await Navigation.PopForDeviceAsync();
}

View File

@ -16,6 +16,7 @@ namespace Bit.App.Pages
private readonly string _folderId;
private readonly IFolderService _folderService;
private readonly IUserDialogs _userDialogs;
private readonly IDeviceActionService _deviceActionService;
private readonly IConnectivity _connectivity;
private readonly IGoogleAnalyticsService _googleAnalyticsService;
private DateTime? _lastAction;
@ -25,6 +26,7 @@ namespace Bit.App.Pages
_folderId = folderId;
_folderService = Resolver.Resolve<IFolderService>();
_userDialogs = Resolver.Resolve<IUserDialogs>();
_deviceActionService = Resolver.Resolve<IDeviceActionService>();
_connectivity = Resolver.Resolve<IConnectivity>();
_googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
@ -103,7 +105,7 @@ namespace Bit.App.Pages
if(saveResult.Succeeded)
{
_userDialogs.Toast(AppResources.FolderUpdated);
_deviceActionService.Toast(AppResources.FolderUpdated);
_googleAnalyticsService.TrackAppEvent("EditedFolder");
await Navigation.PopForDeviceAsync();
}
@ -166,7 +168,7 @@ namespace Bit.App.Pages
if(deleteTask.Succeeded)
{
_userDialogs.Toast(AppResources.FolderDeleted);
_deviceActionService.Toast(AppResources.FolderDeleted);
await Navigation.PopForDeviceAsync();
}
else if(deleteTask.Errors.Count() > 0)

View File

@ -15,6 +15,7 @@ namespace Bit.App.Pages
{
private readonly ISyncService _syncService;
private readonly IUserDialogs _userDialogs;
private readonly IDeviceActionService _deviceActionService;
private readonly IConnectivity _connectivity;
private readonly ISettings _settings;
private readonly IGoogleAnalyticsService _googleAnalyticsService;
@ -23,6 +24,7 @@ namespace Bit.App.Pages
{
_syncService = Resolver.Resolve<ISyncService>();
_userDialogs = Resolver.Resolve<IUserDialogs>();
_deviceActionService = Resolver.Resolve<IDeviceActionService>();
_connectivity = Resolver.Resolve<IConnectivity>();
_settings = Resolver.Resolve<ISettings>();
_googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
@ -107,12 +109,12 @@ namespace Bit.App.Pages
_userDialogs.HideLoading();
if(succeeded)
{
_userDialogs.Toast(AppResources.SyncingComplete);
_deviceActionService.Toast(AppResources.SyncingComplete);
_googleAnalyticsService.TrackAppEvent("Synced");
}
else
{
_userDialogs.Toast(AppResources.SyncingFailed);
_deviceActionService.Toast(AppResources.SyncingFailed);
}
SetLastSync();

View File

@ -13,20 +13,18 @@ namespace Bit.App.Pages
{
public class ToolsPasswordGeneratorPage : ExtendedContentPage
{
private readonly IUserDialogs _userDialogs;
private readonly IPasswordGenerationService _passwordGenerationService;
private readonly ISettings _settings;
private readonly IDeviceActionService _clipboardService;
private readonly IDeviceActionService _deviceActionService;
private readonly IGoogleAnalyticsService _googleAnalyticsService;
private readonly Action<string> _passwordValueAction;
private readonly bool _fromAutofill;
public ToolsPasswordGeneratorPage(Action<string> passwordValueAction = null, bool fromAutofill = false)
{
_userDialogs = Resolver.Resolve<IUserDialogs>();
_passwordGenerationService = Resolver.Resolve<IPasswordGenerationService>();
_settings = Resolver.Resolve<ISettings>();
_clipboardService = Resolver.Resolve<IDeviceActionService>();
_deviceActionService = Resolver.Resolve<IDeviceActionService>();
_googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
_passwordValueAction = passwordValueAction;
_fromAutofill = fromAutofill;
@ -278,8 +276,8 @@ namespace Bit.App.Pages
{
_googleAnalyticsService.TrackAppEvent("CopiedGeneratedPassword");
}
_clipboardService.CopyToClipboard(Password.Text);
_userDialogs.Toast(string.Format(AppResources.ValueHasBeenCopied, AppResources.Password));
_deviceActionService.CopyToClipboard(Password.Text);
_deviceActionService.Toast(string.Format(AppResources.ValueHasBeenCopied, AppResources.Password));
}
private void AvoidAmbiguousCell_OnChanged(object sender, ToggledEventArgs e)

View File

@ -335,7 +335,7 @@ namespace Bit.App.Pages
if(!string.IsNullOrWhiteSpace(key))
{
LoginTotpCell.Entry.Text = key;
_userDialogs.Toast(AppResources.AuthenticatorKeyAdded);
_deviceActionService.Toast(AppResources.AuthenticatorKeyAdded);
}
else
{
@ -352,7 +352,7 @@ namespace Bit.App.Pages
var page = new ToolsPasswordGeneratorPage((password) =>
{
LoginPasswordCell.Entry.Text = password;
_userDialogs.Toast(AppResources.PasswordGenerated);
_deviceActionService.Toast(AppResources.PasswordGenerated);
}, _fromAutofill);
await Navigation.PushForDeviceAsync(page);
}
@ -750,7 +750,7 @@ namespace Bit.App.Pages
if(saveTask.Succeeded)
{
_userDialogs.Toast(AppResources.NewItemCreated);
_deviceActionService.Toast(AppResources.NewItemCreated);
if(_fromAutofill)
{
_googleAnalyticsService.TrackExtensionEvent("CreatedCipher");

View File

@ -19,7 +19,7 @@ namespace Bit.App.Pages
private readonly ICipherService _cipherService;
private readonly IUserDialogs _userDialogs;
private readonly IConnectivity _connectivity;
private readonly IDeviceActionService _deviceActiveService;
private readonly IDeviceActionService _deviceActionService;
private readonly IGoogleAnalyticsService _googleAnalyticsService;
private readonly ITokenService _tokenService;
private readonly ICryptoService _cryptoService;
@ -36,7 +36,7 @@ namespace Bit.App.Pages
_cipherService = Resolver.Resolve<ICipherService>();
_connectivity = Resolver.Resolve<IConnectivity>();
_userDialogs = Resolver.Resolve<IUserDialogs>();
_deviceActiveService = Resolver.Resolve<IDeviceActionService>();
_deviceActionService = Resolver.Resolve<IDeviceActionService>();
_googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
_tokenService = Resolver.Resolve<ITokenService>();
_cryptoService = Resolver.Resolve<ICryptoService>();
@ -61,7 +61,7 @@ namespace Bit.App.Pages
var selectButton = new ExtendedButton
{
Text = AppResources.ChooseFile,
Command = new Command(async () => await _deviceActiveService.SelectFileAsync()),
Command = new Command(async () => await _deviceActionService.SelectFileAsync()),
Style = (Style)Application.Current.Resources["btn-primaryAccent"],
FontSize = Device.GetNamedSize(NamedSize.Medium, typeof(Button))
};
@ -169,7 +169,7 @@ namespace Bit.App.Pages
{
_fileBytes = null;
FileLabel.Text = AppResources.NoFileChosen;
_userDialogs.Toast(AppResources.AttachementAdded);
_deviceActionService.Toast(AppResources.AttachementAdded);
_googleAnalyticsService.TrackAppEvent("AddedAttachment");
await LoadAttachmentsAsync();
}
@ -272,7 +272,7 @@ namespace Bit.App.Pages
if(saveTask.Succeeded)
{
_userDialogs.Toast(AppResources.AttachmentDeleted);
_deviceActionService.Toast(AppResources.AttachmentDeleted);
_googleAnalyticsService.TrackAppEvent("DeletedAttachment");
await LoadAttachmentsAsync();
}

View File

@ -21,7 +21,6 @@ namespace Bit.App.Pages
{
private readonly ICipherService _cipherService;
private readonly IDeviceInfoService _deviceInfoService;
private readonly IDeviceActionService _deviceActionService;
private readonly ISettingsService _settingsService;
private readonly IAppSettingsService _appSettingsService;
private CancellationTokenSource _filterResultsCancellationTokenSource;
@ -45,7 +44,7 @@ namespace Bit.App.Pages
_cipherService = Resolver.Resolve<ICipherService>();
_deviceInfoService = Resolver.Resolve<IDeviceInfoService>();
_deviceActionService = Resolver.Resolve<IDeviceActionService>();
DeviceActionService = Resolver.Resolve<IDeviceActionService>();
_settingsService = Resolver.Resolve<ISettingsService>();
UserDialogs = Resolver.Resolve<IUserDialogs>();
_appSettingsService = Resolver.Resolve<IAppSettingsService>();
@ -63,6 +62,7 @@ namespace Bit.App.Pages
private AddCipherToolBarItem AddCipherItem { get; set; }
private IGoogleAnalyticsService GoogleAnalyticsService { get; set; }
private IUserDialogs UserDialogs { get; set; }
private IDeviceActionService DeviceActionService { get; set; }
private string Uri { get; set; }
private void Init()
@ -143,7 +143,7 @@ namespace Bit.App.Pages
protected override bool OnBackButtonPressed()
{
GoogleAnalyticsService.TrackExtensionEvent("BackClosed", Uri.StartsWith("http") ? "Website" : "App");
_deviceActionService.CloseAutofill();
DeviceActionService.CloseAutofill();
return true;
}
@ -245,7 +245,7 @@ namespace Bit.App.Pages
if(doAutofill)
{
GoogleAnalyticsService.TrackExtensionEvent("AutoFilled", Uri.StartsWith("http") ? "Website" : "App");
_deviceActionService.Autofill(cipher);
DeviceActionService.Autofill(cipher);
}
}
@ -294,8 +294,8 @@ namespace Bit.App.Pages
_page.GoogleAnalyticsService.TrackExtensionEvent("CloseToSearch",
_page.Uri.StartsWith("http") ? "Website" : "App");
Application.Current.MainPage = new ExtendedNavigationPage(new VaultListCiphersPage(uri: _page.Uri));
_page.UserDialogs.Toast(string.Format(AppResources.BitwardenAutofillServiceSearch, _page._name),
TimeSpan.FromSeconds(10));
_page.DeviceActionService.Toast(string.Format(AppResources.BitwardenAutofillServiceSearch, _page._name),
true);
}
}
}

View File

@ -18,6 +18,7 @@ namespace Bit.App.Pages
{
private readonly ICipherService _cipherService;
private readonly IUserDialogs _userDialogs;
private readonly IDeviceActionService _deviceActionService;
private readonly IConnectivity _connectivity;
private readonly IGoogleAnalyticsService _googleAnalyticsService;
private readonly string _cipherId;
@ -31,6 +32,7 @@ namespace Bit.App.Pages
_cipherService = Resolver.Resolve<ICipherService>();
_connectivity = Resolver.Resolve<IConnectivity>();
_userDialogs = Resolver.Resolve<IUserDialogs>();
_deviceActionService = Resolver.Resolve<IDeviceActionService>();
_googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
Init();
@ -120,7 +122,7 @@ namespace Bit.App.Pages
if(saveTask.Succeeded)
{
_userDialogs.Toast(AppResources.CustomFieldsUpdated);
_deviceActionService.Toast(AppResources.CustomFieldsUpdated);
_googleAnalyticsService.TrackAppEvent("UpdatedCustomFields");
await Navigation.PopForDeviceAsync();
}

View File

@ -20,6 +20,7 @@ namespace Bit.App.Pages
private readonly ICipherService _cipherService;
private readonly IFolderService _folderService;
private readonly IUserDialogs _userDialogs;
private readonly IDeviceActionService _deviceActionService;
private readonly IConnectivity _connectivity;
private readonly IDeviceInfoService _deviceInfo;
private readonly IGoogleAnalyticsService _googleAnalyticsService;
@ -31,6 +32,7 @@ namespace Bit.App.Pages
_cipherService = Resolver.Resolve<ICipherService>();
_folderService = Resolver.Resolve<IFolderService>();
_userDialogs = Resolver.Resolve<IUserDialogs>();
_deviceActionService = Resolver.Resolve<IDeviceActionService>();
_connectivity = Resolver.Resolve<IConnectivity>();
_deviceInfo = Resolver.Resolve<IDeviceInfoService>();
_googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
@ -618,7 +620,7 @@ namespace Bit.App.Pages
if(saveTask.Succeeded)
{
_userDialogs.Toast(AppResources.ItemUpdated);
_deviceActionService.Toast(AppResources.ItemUpdated);
_googleAnalyticsService.TrackAppEvent("EditedCipher");
await Navigation.PopForDeviceAsync();
}
@ -804,7 +806,7 @@ namespace Bit.App.Pages
if(!string.IsNullOrWhiteSpace(key))
{
LoginTotpCell.Entry.Text = key;
_userDialogs.Toast(AppResources.AuthenticatorKeyAdded);
_deviceActionService.Toast(AppResources.AuthenticatorKeyAdded);
}
else
{
@ -827,7 +829,7 @@ namespace Bit.App.Pages
var page = new ToolsPasswordGeneratorPage((password) =>
{
LoginPasswordCell.Entry.Text = password;
_userDialogs.Toast(AppResources.PasswordGenerated);
_deviceActionService.Toast(AppResources.PasswordGenerated);
});
await Navigation.PushForDeviceAsync(page);
}
@ -863,7 +865,7 @@ namespace Bit.App.Pages
if(deleteTask.Succeeded)
{
_userDialogs.Toast(AppResources.ItemDeleted);
_deviceActionService.Toast(AppResources.ItemDeleted);
_googleAnalyticsService.TrackAppEvent("DeletedCipher");
await Navigation.PopForDeviceAsync();
}

View File

@ -475,7 +475,7 @@ namespace Bit.App.Pages
private void Copy(string copyText, string alertLabel)
{
_deviceActionService.CopyToClipboard(copyText);
_userDialogs.Toast(string.Format(AppResources.ValueHasBeenCopied, alertLabel));
_deviceActionService.Toast(string.Format(AppResources.ValueHasBeenCopied, alertLabel));
}
private void TotpTick(string totpKey)

View File

@ -223,7 +223,7 @@ namespace Bit.App.Services
Device.BeginInvokeOnMainThread(() => Application.Current.MainPage = new ExtendedNavigationPage(new HomePage()));
if(!string.IsNullOrWhiteSpace(logoutMessage))
{
Resolver.Resolve<IUserDialogs>()?.Toast(logoutMessage);
Resolver.Resolve<IDeviceActionService>()?.Toast(logoutMessage);
}
}

View File

@ -157,8 +157,9 @@ namespace Bit.App.Utilities
public static void CipherCopy(string copyText, string alertLabel)
{
Resolver.Resolve<IDeviceActionService>().CopyToClipboard(copyText);
Resolver.Resolve<IUserDialogs>().Toast(string.Format(AppResources.ValueHasBeenCopied, alertLabel));
var daService = Resolver.Resolve<IDeviceActionService>();
daService.CopyToClipboard(copyText);
daService.Toast(string.Format(AppResources.ValueHasBeenCopied, alertLabel));
}
public static async void AddCipher(Page page, string folderId)

View File

@ -1,5 +1,6 @@
using Bit.App.Abstractions;
using Bit.App.Models.Page;
using Coding4Fun.Toolkit.Controls;
using System;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
@ -8,8 +9,10 @@ using Windows.ApplicationModel.Core;
using Windows.ApplicationModel.DataTransfer;
using Windows.Storage;
using Windows.System;
using Windows.UI;
using Windows.UI.Core;
using Xamarin.Forms;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Media;
namespace Bit.UWP.Services
{
@ -78,7 +81,7 @@ namespace Bit.UWP.Services
private async Task SelectFileResult(StorageFile file)
{
var buffer = await FileIO.ReadBufferAsync(file);
MessagingCenter.Send(Application.Current, "SelectFileResult",
Xamarin.Forms.MessagingCenter.Send(Xamarin.Forms.Application.Current, "SelectFileResult",
new Tuple<byte[], string>(buffer.ToArray(), file.Name));
}
@ -121,5 +124,24 @@ namespace Bit.UWP.Services
{
throw new NotImplementedException();
}
public void Toast(string text, bool longDuration = false)
{
new ToastPrompt
{
Message = text,
TextWrapping = TextWrapping.Wrap,
MillisecondsUntilHidden = Convert.ToInt32(longDuration ? 5 : 2) * 1000,
Background = new SolidColorBrush(Color.FromArgb(255, 73, 73, 73)),
Foreground = new SolidColorBrush(Colors.White),
Margin = new Thickness(0, 0, 0, 100),
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Bottom,
HorizontalContentAlignment = HorizontalAlignment.Center,
VerticalContentAlignment = VerticalAlignment.Center,
Stretch = Stretch.Uniform,
IsHitTestVisible = false
}.Show();
}
}
}

View File

@ -192,7 +192,7 @@
<Version>6.0.5</Version>
</PackageReference>
<PackageReference Include="Microsoft.Toolkit.Uwp">
<Version>2.1.0</Version>
<Version>2.1.1</Version>
</PackageReference>
<PackageReference Include="SimpleInjector">
<Version>4.0.12</Version>

View File

@ -26,6 +26,16 @@ namespace Bit.iOS.Services
_deviceInfoService = deviceInfoService;
}
public void Toast(string text, bool longDuration = false)
{
var snackbar = new TTGSnackBar.TTGSnackbar(text)
{
Duration = TimeSpan.FromSeconds(longDuration ? 5 : 2),
AnimationType = TTGSnackBar.TTGSnackbarAnimationType.FadeInFadeOut
};
snackbar.Show();
}
public void CopyToClipboard(string text)
{
UIPasteboard clipboard = UIPasteboard.General;

View File

@ -704,6 +704,9 @@
<PackageReference Include="SimpleInjector">
<Version>4.0.12</Version>
</PackageReference>
<PackageReference Include="TTGSnackbar">
<Version>1.3.4</Version>
</PackageReference>
<PackageReference Include="Xamarin.Google.iOS.Analytics">
<Version>3.17.0.1</Version>
</PackageReference>