Commit:
e7bada9Parent:
1131460Add OneDrive sign-in, folder picker, and cached note list screen
Sign in with a personal Microsoft account via MSAL, pick a OneDrive root folder for notes, and show a chat-style note list that renders instantly from a local JSON cache and refreshes in the background (paginated listing + batched preview fetch to handle large libraries). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Directory.Packages.props
+10
-0
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 48f3df1..c9ddc2a 100644
@@ -9,4 +9,14 @@
<PrivateAssets>all</PrivateAssets>
</GlobalPackageReference>
</ItemGroup>
<ItemGroup>
<PackageVersion Include="Microsoft.Identity.Client" Version="4.87.0" />
<PackageVersion Include="Microsoft.Graph" Version="6.2.0" />
<PackageVersion Include="Microsoft.Graph.Core" Version="4.0.1" />
<!-- MSAL's Android binding references these AndroidX libraries directly but doesn't
declare them as NuGet dependencies, so they must be added explicitly. -->
<PackageVersion Include="Xamarin.AndroidX.Core" Version="1.19.0.1" />
<PackageVersion Include="Xamarin.AndroidX.Browser" Version="1.10.0.1" />
<PackageVersion Include="Xamarin.AndroidX.RecyclerView" Version="1.4.0.6" />
</ItemGroup>
</Project>
\ No newline at end of file
src/Activities/FolderPickerActivity.cs
+99
-0
diff --git a/src/Activities/FolderPickerActivity.cs b/src/Activities/FolderPickerActivity.cs
new file mode 100644
index 0000000..584b1bf
@@ -0,0 +1,99 @@
using Android.Content;
using Android.Views;
using AndroidX.RecyclerView.Widget;
using NotesApp.Adapters;
using NotesApp.Auth;
using NotesApp.Graph;
using NotesApp.Models;
namespace NotesApp.Activities;
[Activity(Label = "@string/choose_folder_title", Exported = false)]
public class FolderPickerActivity : Activity
{
private const int SelectFolderRequestCode = 100;
public const string ExtraFolderId = "folder_id";
public const string ExtraFolderPath = "folder_path";
private ProgressBar _progressBar = null!;
private FolderListAdapter _adapter = null!;
private GraphService _graphService = null!;
private string? _folderId;
private string _folderPath = "/";
protected override void OnCreate(Bundle? savedInstanceState)
{
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.activity_folder_picker);
_folderId = Intent?.GetStringExtra(ExtraFolderId);
_folderPath = Intent?.GetStringExtra(ExtraFolderPath) ?? "/";
FindViewById<TextView>(Resource.Id.folder_path_label)!.Text = _folderPath;
_progressBar = FindViewById<ProgressBar>(Resource.Id.folder_progress)!;
var recyclerView = FindViewById<RecyclerView>(Resource.Id.folder_recycler_view)!;
recyclerView.SetLayoutManager(new LinearLayoutManager(this));
_adapter = new FolderListAdapter(OnFolderTapped);
recyclerView.SetAdapter(_adapter);
FindViewById<Button>(Resource.Id.select_folder_button)!.Click += (_, _) =>
SelectThisFolder();
_graphService = new GraphService(this);
_ = LoadFoldersAsync();
}
protected override void OnActivityResult(int requestCode, Result resultCode, Intent? data)
{
base.OnActivityResult(requestCode, resultCode, data);
AuthService.HandleActivityResult(requestCode, resultCode, data);
if (requestCode == SelectFolderRequestCode && resultCode == Result.Ok && data != null)
{
// A nested picker (a subfolder browsed into) selected a folder; forward the
// result up the recursive chain and close this level too.
SetResult(Result.Ok, data);
Finish();
}
}
private async Task LoadFoldersAsync()
{
_progressBar.Visibility = ViewStates.Visible;
try
{
var folders = await _graphService.ListChildFoldersAsync(_folderId);
_adapter.SetItems(folders);
}
catch (Exception ex)
{
Toast.MakeText(this, ex.Message, ToastLength.Long)?.Show();
}
finally
{
_progressBar.Visibility = ViewStates.Gone;
}
}
private void OnFolderTapped(DriveFolderItem folder)
{
var intent = new Intent(this, typeof(FolderPickerActivity));
intent.PutExtra(ExtraFolderId, folder.Id);
intent.PutExtra(ExtraFolderPath, _folderPath.TrimEnd('/') + "/" + folder.Name);
StartActivityForResult(intent, SelectFolderRequestCode);
}
private void SelectThisFolder()
{
var data = new Intent();
data.PutExtra(ExtraFolderId, _folderId ?? "root");
data.PutExtra(ExtraFolderPath, _folderPath);
SetResult(Result.Ok, data);
Finish();
}
}
src/Activities/NoteListActivity.cs
+101
-0
diff --git a/src/Activities/NoteListActivity.cs b/src/Activities/NoteListActivity.cs
new file mode 100644
index 0000000..9f8e794
@@ -0,0 +1,101 @@
using Android.Views;
using AndroidX.RecyclerView.Widget;
using NotesApp.Adapters;
using NotesApp.Graph;
using NotesApp.Models;
using NotesApp.Storage;
namespace NotesApp.Activities;
[Activity(Label = "@string/app_name", Exported = false)]
public class NoteListActivity : Activity
{
private RecyclerView _recyclerView = null!;
private ProgressBar _progressBar = null!;
private NoteListAdapter _adapter = null!;
private GraphService _graphService = null!;
private bool _hasScrolledToBottom;
protected override void OnCreate(Bundle? savedInstanceState)
{
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.activity_note_list);
_recyclerView = FindViewById<RecyclerView>(Resource.Id.note_recycler_view)!;
_recyclerView.SetLayoutManager(new LinearLayoutManager(this));
_adapter = new NoteListAdapter();
_recyclerView.SetAdapter(_adapter);
_progressBar = FindViewById<ProgressBar>(Resource.Id.note_refresh_progress)!;
_graphService = new GraphService(this);
// Cache renders immediately, scrolled to the bottom, before any network call.
BindItems(NoteCacheStore.Load());
_ = RefreshAsync();
}
private async Task RefreshAsync()
{
var folderId = AppSettings.RootFolderId;
if (folderId is null)
return;
_progressBar.Visibility = ViewStates.Visible;
try
{
var cacheById = NoteCacheStore.Load().ToDictionary(e => e.Id);
var remoteNotes = await _graphService.ListMarkdownFilesAsync(folderId);
var changedIds = remoteNotes
.Where(n =>
!cacheById.TryGetValue(n.Id, out var existing) || existing.ETag != n.ETag
)
.Select(n => n.Id)
.ToList();
var freshPreviews = await _graphService.GetPreviewsAsync(changedIds);
var merged = remoteNotes
.Select(n => new NoteCacheEntry(
n.Id,
n.Name,
n.ETag,
n.LastModifiedUtc,
freshPreviews.TryGetValue(n.Id, out var preview) ? preview
: cacheById.TryGetValue(n.Id, out var existing) ? existing.Preview
: "",
n.Size
))
.OrderBy(e => e.Name, StringComparer.OrdinalIgnoreCase)
.ToList();
NoteCacheStore.Save(merged);
BindItems(merged);
}
catch (Exception ex)
{
Toast.MakeText(this, ex.Message, ToastLength.Long)?.Show();
}
finally
{
_progressBar.Visibility = ViewStates.Gone;
}
}
private void BindItems(List<NoteCacheEntry> entries)
{
_adapter.SetItems(entries);
// Only force scroll-to-bottom on the initial (cache-empty) load; don't yank the
// scroll position if the user is already reading older notes when a refresh lands.
if (!_hasScrolledToBottom && entries.Count > 0)
{
_recyclerView.ScrollToPosition(entries.Count - 1);
_hasScrolledToBottom = true;
}
}
}
src/Activities/SettingsActivity.cs
+83
-0
diff --git a/src/Activities/SettingsActivity.cs b/src/Activities/SettingsActivity.cs
new file mode 100644
index 0000000..9c828e1
@@ -0,0 +1,83 @@
using Android.Content;
using NotesApp.Auth;
using NotesApp.Storage;
namespace NotesApp.Activities;
[Activity(Label = "@string/settings_title", Exported = false)]
public class SettingsActivity : Activity
{
private const int ChooseFolderRequestCode = 200;
private TextView _accountStatus = null!;
private Button _signInButton = null!;
private TextView _folderStatus = null!;
protected override void OnCreate(Bundle? savedInstanceState)
{
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.activity_settings);
_accountStatus = FindViewById<TextView>(Resource.Id.account_status)!;
_signInButton = FindViewById<Button>(Resource.Id.sign_in_button)!;
_folderStatus = FindViewById<TextView>(Resource.Id.folder_status)!;
_signInButton.Click += async (_, _) => await SignInAsync();
FindViewById<Button>(Resource.Id.choose_folder_button)!.Click += (_, _) =>
OpenFolderPicker();
RefreshFolderStatus();
_ = RefreshAccountStatusAsync();
}
protected override void OnActivityResult(int requestCode, Result resultCode, Intent? data)
{
base.OnActivityResult(requestCode, resultCode, data);
AuthService.HandleActivityResult(requestCode, resultCode, data);
if (requestCode == ChooseFolderRequestCode && resultCode == Result.Ok && data != null)
{
AppSettings.RootFolderId = data.GetStringExtra(FolderPickerActivity.ExtraFolderId);
AppSettings.RootFolderPath = data.GetStringExtra(FolderPickerActivity.ExtraFolderPath);
RefreshFolderStatus();
}
}
private async Task SignInAsync()
{
_signInButton.Enabled = false;
try
{
await AuthService.GetAccessTokenAsync(this);
await RefreshAccountStatusAsync();
}
catch (Exception ex)
{
var message = string.Format(GetString(Resource.String.sign_in_failed), ex.Message);
Toast.MakeText(this, message, ToastLength.Long)?.Show();
}
finally
{
_signInButton.Enabled = true;
}
}
private async Task RefreshAccountStatusAsync()
{
var signedIn = await AuthService.IsSignedInAsync();
_accountStatus.Text = GetString(
signedIn ? Resource.String.signed_in : Resource.String.not_signed_in
);
}
private void OpenFolderPicker() =>
StartActivityForResult(
new Intent(this, typeof(FolderPickerActivity)),
ChooseFolderRequestCode
);
private void RefreshFolderStatus() =>
_folderStatus.Text = AppSettings.RootFolderPath is { } path
? string.Format(GetString(Resource.String.folder_selected_format), path)
: GetString(Resource.String.no_folder_selected);
}
src/Adapters/FolderListAdapter.cs
+54
-0
diff --git a/src/Adapters/FolderListAdapter.cs b/src/Adapters/FolderListAdapter.cs
new file mode 100644
index 0000000..9a8bfa9
@@ -0,0 +1,54 @@
using Android.Views;
using AndroidX.RecyclerView.Widget;
using NotesApp.Models;
namespace NotesApp.Adapters;
public class FolderListAdapter(Action<DriveFolderItem> onFolderTapped) : RecyclerView.Adapter
{
private List<DriveFolderItem> _items = [];
public override int ItemCount => _items.Count;
public void SetItems(List<DriveFolderItem> items)
{
_items = items;
NotifyDataSetChanged();
}
public override RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup parent, int viewType)
{
var view = LayoutInflater
.From(parent.Context)!
.Inflate(Resource.Layout.row_folder, parent, false)!;
return new FolderViewHolder(view, onFolderTapped);
}
public override void OnBindViewHolder(RecyclerView.ViewHolder holder, int position) =>
((FolderViewHolder)holder).Bind(_items[position]);
private class FolderViewHolder : RecyclerView.ViewHolder
{
private readonly TextView _nameView;
private readonly Action<DriveFolderItem> _onFolderTapped;
private DriveFolderItem? _item;
public FolderViewHolder(View itemView, Action<DriveFolderItem> onFolderTapped)
: base(itemView)
{
_nameView = itemView.FindViewById<TextView>(Resource.Id.folder_name)!;
_onFolderTapped = onFolderTapped;
itemView.Click += (_, _) =>
{
if (_item != null)
_onFolderTapped(_item);
};
}
public void Bind(DriveFolderItem item)
{
_item = item;
_nameView.Text = item.Name;
}
}
}
src/Adapters/NoteListAdapter.cs
+48
-0
diff --git a/src/Adapters/NoteListAdapter.cs b/src/Adapters/NoteListAdapter.cs
new file mode 100644
index 0000000..295a733
@@ -0,0 +1,48 @@
using Android.Views;
using AndroidX.RecyclerView.Widget;
using NotesApp.Models;
namespace NotesApp.Adapters;
public class NoteListAdapter : RecyclerView.Adapter
{
private List<NoteCacheEntry> _items = [];
public override int ItemCount => _items.Count;
public void SetItems(List<NoteCacheEntry> items)
{
_items = items;
NotifyDataSetChanged();
}
public override RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup parent, int viewType)
{
var view = LayoutInflater
.From(parent.Context)!
.Inflate(Resource.Layout.row_note, parent, false)!;
return new NoteViewHolder(view);
}
public override void OnBindViewHolder(RecyclerView.ViewHolder holder, int position) =>
((NoteViewHolder)holder).Bind(_items[position]);
private class NoteViewHolder : RecyclerView.ViewHolder
{
private readonly TextView _nameView;
private readonly TextView _previewView;
public NoteViewHolder(View itemView)
: base(itemView)
{
_nameView = itemView.FindViewById<TextView>(Resource.Id.note_name)!;
_previewView = itemView.FindViewById<TextView>(Resource.Id.note_preview)!;
}
public void Bind(NoteCacheEntry entry)
{
_nameView.Text = entry.Name;
_previewView.Text = entry.Preview;
}
}
}
src/AndroidManifest.xml
+14
-1
diff --git a/src/AndroidManifest.xml b/src/AndroidManifest.xml
index 9687c82..1409521 100644
@@ -6,6 +6,19 @@
android:label="@string/app_name"
android:roundIcon="@mipmap/appicon_round"
android:supportsRtl="true"
></application>
>
<activity android:name=".Activities.SettingsActivity" android:exported="false" />
<activity android:name=".Activities.FolderPickerActivity" android:exported="false" />
<activity android:name=".Activities.NoteListActivity" android:exported="false" />
<activity android:name="microsoft.identity.client.BrowserTabActivity" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="@string/msal_redirect_scheme" android:host="auth" />
</intent-filter>
</activity>
</application>
<uses-permission android:name="android.permission.INTERNET" />
</manifest>
src/Auth/AuthService.cs
+67
-0
diff --git a/src/Auth/AuthService.cs b/src/Auth/AuthService.cs
new file mode 100644
index 0000000..dcd8e69
@@ -0,0 +1,67 @@
using Android.Content;
using Microsoft.Identity.Client;
namespace NotesApp.Auth;
public static class AuthService
{
private static readonly string[] Scopes = ["Files.ReadWrite", "User.Read"];
private static IPublicClientApplication? _pca;
private static IPublicClientApplication GetOrCreatePca()
{
if (_pca != null)
return _pca;
var context = Application.Context;
var clientId = context.GetString(Resource.String.msal_client_id);
_pca = PublicClientApplicationBuilder
.Create(clientId)
.WithAuthority(
AzureCloudInstance.AzurePublic,
AadAuthorityAudience.PersonalMicrosoftAccount
)
.WithRedirectUri($"msal{clientId}://auth")
.Build();
return _pca;
}
public static async Task<AuthenticationResult> GetAccessTokenAsync(Activity activity)
{
var pca = GetOrCreatePca();
var accounts = await pca.GetAccountsAsync();
var account = accounts.FirstOrDefault();
if (account != null)
{
try
{
return await pca.AcquireTokenSilent(Scopes, account).ExecuteAsync();
}
catch (MsalUiRequiredException)
{
// Falls through to interactive sign-in below.
}
}
return await pca.AcquireTokenInteractive(Scopes)
.WithParentActivityOrWindow(activity)
.ExecuteAsync();
}
public static async Task<bool> IsSignedInAsync()
{
var accounts = await GetOrCreatePca().GetAccountsAsync();
return accounts.Any();
}
public static void HandleActivityResult(int requestCode, Result resultCode, Intent? data) =>
AuthenticationContinuationHelper.SetAuthenticationContinuationEventArgs(
requestCode,
resultCode,
data
);
}
src/Graph/GraphAccessTokenProvider.cs
+19
-0
diff --git a/src/Graph/GraphAccessTokenProvider.cs b/src/Graph/GraphAccessTokenProvider.cs
new file mode 100644
index 0000000..4b3e830
@@ -0,0 +1,19 @@
using Microsoft.Kiota.Abstractions.Authentication;
using NotesApp.Auth;
namespace NotesApp.Graph;
public class GraphAccessTokenProvider(Activity activity) : IAccessTokenProvider
{
public AllowedHostsValidator AllowedHostsValidator { get; } = new(["graph.microsoft.com"]);
public async Task<string> GetAuthorizationTokenAsync(
Uri uri,
Dictionary<string, object>? additionalAuthenticationContext = null,
CancellationToken cancellationToken = default
)
{
var result = await AuthService.GetAccessTokenAsync(activity);
return result.AccessToken;
}
}
src/Graph/GraphService.cs
+193
-0
diff --git a/src/Graph/GraphService.cs b/src/Graph/GraphService.cs
new file mode 100644
index 0000000..f867cd8
@@ -0,0 +1,193 @@
using System.Text;
using Microsoft.Graph;
using Microsoft.Graph.Models;
using Microsoft.Kiota.Abstractions.Authentication;
using NotesApp.Models;
namespace NotesApp.Graph;
public class GraphService
{
private readonly GraphServiceClient _client;
private string? _driveId;
public GraphService(Activity activity)
{
_client = new GraphServiceClient(
new BaseBearerTokenAuthenticationProvider(new GraphAccessTokenProvider(activity))
);
}
// Me.Drive only exposes the drive resource itself in this SDK; navigating into
// root/items/children requires going through Drives[driveId] instead.
private async Task<string> GetDriveIdAsync(CancellationToken ct)
{
if (_driveId != null)
return _driveId;
var drive = await _client.Me.Drive.GetAsync(cancellationToken: ct);
_driveId =
drive?.Id
?? throw new InvalidOperationException("Could not resolve OneDrive drive id.");
return _driveId;
}
public async Task<List<DriveFolderItem>> ListChildFoldersAsync(
string? folderId,
CancellationToken ct = default
)
{
var driveId = await GetDriveIdAsync(ct);
var itemId = folderId ?? "root"; // Graph accepts the literal id "root" as an alias.
var page = await _client
.Drives[driveId]
.Items[itemId]
.Children.GetAsync(cfg => cfg.QueryParameters.Select = ["id", "name", "folder"], ct);
var results = new List<DriveFolderItem>();
if (page is null)
return results;
var iterator = PageIterator<DriveItem, DriveItemCollectionResponse>.CreatePageIterator(
_client,
page,
item =>
{
if (item.Folder != null && item.Id != null && item.Name != null)
results.Add(new DriveFolderItem(item.Id, item.Name));
return true;
}
);
await iterator.IterateAsync(ct);
return results.OrderBy(f => f.Name, StringComparer.OrdinalIgnoreCase).ToList();
}
public async Task<List<DriveNoteMetadata>> ListMarkdownFilesAsync(
string folderId,
CancellationToken ct = default
)
{
var driveId = await GetDriveIdAsync(ct);
var page = await _client
.Drives[driveId]
.Items[folderId]
.Children.GetAsync(
cfg =>
{
cfg.QueryParameters.Select =
[
"id",
"name",
"eTag",
"lastModifiedDateTime",
"size",
"file",
];
cfg.QueryParameters.Top = 200;
},
ct
);
var results = new List<DriveNoteMetadata>();
if (page is null)
return results;
// With 1000+ notes this always spans multiple pages; PageIterator follows
// @odata.nextLink automatically until the whole folder has been walked.
var iterator = PageIterator<DriveItem, DriveItemCollectionResponse>.CreatePageIterator(
_client,
page,
item =>
{
if (
item.File != null
&& item.Id != null
&& item.Name != null
&& item.Name.EndsWith(".md", StringComparison.OrdinalIgnoreCase)
)
{
results.Add(
new DriveNoteMetadata(
item.Id,
item.Name,
item.ETag ?? "",
item.LastModifiedDateTime,
item.Size
)
);
}
return true;
}
);
await iterator.IterateAsync(ct);
return results.OrderBy(n => n.Name, StringComparer.OrdinalIgnoreCase).ToList();
}
private const int PreviewByteRange = 1024;
private const int BatchSize = 20; // Graph's JSON $batch endpoint allows at most 20 sub-requests.
// Fetches only the first ~1KB of each note's content so the list preview doesn't require
// downloading full note bodies, and batches requests so a first-time sync of a large
// library doesn't take one HTTP round trip per note.
public async Task<Dictionary<string, string>> GetPreviewsAsync(
IReadOnlyList<string> itemIds,
CancellationToken ct = default
)
{
var previews = new Dictionary<string, string>();
if (itemIds.Count == 0)
return previews;
var driveId = await GetDriveIdAsync(ct);
foreach (var chunk in itemIds.Chunk(BatchSize))
{
var batch = new BatchRequestContentCollection(_client);
var requestIds = new List<(string RequestId, string ItemId)>();
foreach (var itemId in chunk)
{
var requestInfo = _client
.Drives[driveId]
.Items[itemId]
.Content.ToGetRequestInformation();
requestInfo.Headers.Add("Range", [$"bytes=0-{PreviewByteRange - 1}"]);
var requestId = await batch.AddBatchRequestStepAsync(requestInfo, itemId);
requestIds.Add((requestId, itemId));
}
var response = await _client.Batch.PostAsync(batch, ct);
foreach (var (requestId, itemId) in requestIds)
{
try
{
var stream = await response.GetResponseStreamByIdAsync(requestId);
using var reader = new StreamReader(stream, Encoding.UTF8);
var raw = await reader.ReadToEndAsync(ct);
previews[itemId] = TrimPreview(raw);
}
catch
{
// Leave this item out; its ETag will still differ from the cache next
// refresh, so it's retried automatically rather than silently dropped.
}
}
}
return previews;
}
private static string TrimPreview(string raw)
{
// A byte range can cut mid-line or mid-character; drop the trailing partial line.
var lastNewline = raw.LastIndexOf('\n');
var trimmed = lastNewline > 0 ? raw[..lastNewline] : raw;
const int maxLength = 300;
return trimmed.Length > maxLength ? trimmed[..maxLength] : trimmed;
}
}
src/MainActivity.cs
+9
-2
diff --git a/src/MainActivity.cs b/src/MainActivity.cs
index cdebb7f..6d764a0 100644
@@ -1,3 +1,7 @@
using Android.Content;
using NotesApp.Activities;
using NotesApp.Storage;
namespace NotesApp.Android;
[Activity(Label = "@string/app_name", MainLauncher = true)]
@@ -7,7 +11,10 @@ public class MainActivity : Activity
{
base.OnCreate(savedInstanceState);
// Set our view from the "main" layout resource
SetContentView(Resource.Layout.activity_main);
var destination = AppSettings.HasRootFolder
? typeof(NoteListActivity)
: typeof(SettingsActivity);
StartActivity(new Intent(this, destination));
Finish();
}
}
src/Models/DriveFolderItem.cs
+3
-0
diff --git a/src/Models/DriveFolderItem.cs b/src/Models/DriveFolderItem.cs
new file mode 100644
index 0000000..3b9eea2
@@ -0,0 +1,3 @@
namespace NotesApp.Models;
public record DriveFolderItem(string Id, string Name);
src/Models/DriveNoteMetadata.cs
+9
-0
diff --git a/src/Models/DriveNoteMetadata.cs b/src/Models/DriveNoteMetadata.cs
new file mode 100644
index 0000000..21ed3fb
@@ -0,0 +1,9 @@
namespace NotesApp.Models;
public record DriveNoteMetadata(
string Id,
string Name,
string ETag,
DateTimeOffset? LastModifiedUtc,
long? Size
);
src/Models/NoteCacheEntry.cs
+10
-0
diff --git a/src/Models/NoteCacheEntry.cs b/src/Models/NoteCacheEntry.cs
new file mode 100644
index 0000000..42ec8c4
@@ -0,0 +1,10 @@
namespace NotesApp.Models;
public record NoteCacheEntry(
string Id,
string Name,
string ETag,
DateTimeOffset? LastModifiedUtc,
string Preview,
long? Size
);
src/NotesApp.csproj
+8
-0
diff --git a/src/NotesApp.csproj b/src/NotesApp.csproj
index eab9485..60b571e 100644
@@ -10,4 +10,12 @@
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
<TrimMode>full</TrimMode>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Identity.Client" />
<PackageReference Include="Microsoft.Graph" />
<PackageReference Include="Microsoft.Graph.Core" />
<PackageReference Include="Xamarin.AndroidX.Core" />
<PackageReference Include="Xamarin.AndroidX.Browser" />
<PackageReference Include="Xamarin.AndroidX.RecyclerView" />
</ItemGroup>
</Project>
src/Resources/layout/activity_folder_picker.xml
+39
-0
diff --git a/src/Resources/layout/activity_folder_picker.xml b/src/Resources/layout/activity_folder_picker.xml
new file mode 100644
index 0000000..fa13a43
@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<TextView
android:id="@+id/folder_path_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="12dp"
android:textStyle="bold"
/>
<Button
android:id="@+id/select_folder_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/select_this_folder"
/>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/folder_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
<ProgressBar
android:id="@+id/folder_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="gone"
/>
</FrameLayout>
</LinearLayout>
src/Resources/layout/activity_note_list.xml
+20
-0
diff --git a/src/Resources/layout/activity_note_list.xml b/src/Resources/layout/activity_note_list.xml
new file mode 100644
index 0000000..0c4d756
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/note_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
<ProgressBar
android:id="@+id/note_refresh_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:layout_marginBottom="16dp"
android:visibility="gone"
/>
</FrameLayout>
src/Resources/layout/activity_settings.xml
+39
-0
diff --git a/src/Resources/layout/activity_settings.xml b/src/Resources/layout/activity_settings.xml
new file mode 100644
index 0000000..3a24a44
@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="24dp"
>
<TextView
android:id="@+id/account_status"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/not_signed_in"
android:textSize="16sp"
android:paddingBottom="16dp"
/>
<Button
android:id="@+id/sign_in_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/sign_in_button"
/>
<TextView
android:id="@+id/folder_status"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/no_folder_selected"
android:textSize="16sp"
android:paddingTop="32dp"
android:paddingBottom="16dp"
/>
<Button
android:id="@+id/choose_folder_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/choose_folder_button"
/>
</LinearLayout>
src/Resources/layout/row_folder.xml
+12
-0
diff --git a/src/Resources/layout/row_folder.xml b/src/Resources/layout/row_folder.xml
new file mode 100644
index 0000000..015235f
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/folder_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:textSize="16sp"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
/>
src/Resources/layout/row_note.xml
+28
-0
diff --git a/src/Resources/layout/row_note.xml b/src/Resources/layout/row_note.xml
new file mode 100644
index 0000000..3bf9ee6
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
>
<TextView
android:id="@+id/note_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textStyle="bold"
android:fontFamily="monospace"
/>
<TextView
android:id="@+id/note_preview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="monospace"
android:maxLines="3"
android:ellipsize="end"
android:paddingTop="4dp"
/>
</LinearLayout>
src/Resources/values/strings.xml
+17
-0
diff --git a/src/Resources/values/strings.xml b/src/Resources/values/strings.xml
index 6a06690..975ccbc 100644
@@ -1,4 +1,21 @@
<resources>
<string name="app_name">NotesApp</string>
<string name="app_text">Hello, Notes!</string>
<!-- Replace both with the Application (client) ID from your Azure AD app registration.
msal_redirect_scheme must be "msal" followed by the same client ID (no "://auth" suffix, that part is in the manifest). -->
<string name="msal_client_id" translatable="false">REPLACE_WITH_YOUR_CLIENT_ID</string>
<string name="msal_redirect_scheme" translatable="false">msalREPLACE_WITH_YOUR_CLIENT_ID</string>
<string name="settings_title">Settings</string>
<string name="not_signed_in">Not signed in</string>
<string name="signed_in">Signed in</string>
<string name="sign_in_button">Sign in with Microsoft account</string>
<string name="sign_in_failed">Sign-in failed: {0}</string>
<string name="choose_folder_title">Choose folder</string>
<string name="select_this_folder">Select this folder</string>
<string name="no_folder_selected">No notes folder selected</string>
<string name="folder_selected_format">Notes folder: {0}</string>
<string name="choose_folder_button">Choose OneDrive folder</string>
</resources>
src/Storage/AppSettings.cs
+27
-0
diff --git a/src/Storage/AppSettings.cs b/src/Storage/AppSettings.cs
new file mode 100644
index 0000000..a4c7885
@@ -0,0 +1,27 @@
using Android.Content;
namespace NotesApp.Storage;
public static class AppSettings
{
private const string PrefsName = "notes_app_settings";
private const string RootFolderIdKey = "root_folder_id";
private const string RootFolderPathKey = "root_folder_path";
private static ISharedPreferences Prefs =>
Application.Context.GetSharedPreferences(PrefsName, FileCreationMode.Private)!;
public static string? RootFolderId
{
get => Prefs.GetString(RootFolderIdKey, null);
set => Prefs.Edit()!.PutString(RootFolderIdKey, value)!.Apply();
}
public static string? RootFolderPath
{
get => Prefs.GetString(RootFolderPathKey, null);
set => Prefs.Edit()!.PutString(RootFolderPathKey, value)!.Apply();
}
public static bool HasRootFolder => RootFolderId != null;
}
src/Storage/NoteCacheJsonContext.cs
+9
-0
diff --git a/src/Storage/NoteCacheJsonContext.cs b/src/Storage/NoteCacheJsonContext.cs
new file mode 100644
index 0000000..588c0d0
@@ -0,0 +1,9 @@
using System.Text.Json.Serialization;
using NotesApp.Models;
namespace NotesApp.Storage;
// Source-generated serialization avoids reflection-based System.Text.Json, which is a
// common trim-breakage source under this project's TrimMode=full.
[JsonSerializable(typeof(List<NoteCacheEntry>))]
public partial class NoteCacheJsonContext : JsonSerializerContext;
src/Storage/NoteCacheStore.cs
+38
-0
diff --git a/src/Storage/NoteCacheStore.cs b/src/Storage/NoteCacheStore.cs
new file mode 100644
index 0000000..1ef4c1a
@@ -0,0 +1,38 @@
using System.Text.Json;
using NotesApp.Models;
namespace NotesApp.Storage;
public static class NoteCacheStore
{
private const string FileName = "notes_cache.json";
private static string FilePath =>
Path.Combine(Application.Context.FilesDir!.AbsolutePath, FileName);
public static List<NoteCacheEntry> Load()
{
try
{
if (!File.Exists(FilePath))
return [];
using var stream = File.OpenRead(FilePath);
return JsonSerializer.Deserialize(
stream,
NoteCacheJsonContext.Default.ListNoteCacheEntry
) ?? [];
}
catch
{
// Corrupt or unreadable cache; treat as empty and let the next refresh rebuild it.
return [];
}
}
public static void Save(List<NoteCacheEntry> entries)
{
using var stream = File.Create(FilePath);
JsonSerializer.Serialize(stream, entries, NoteCacheJsonContext.Default.ListNoteCacheEntry);
}
}
src/packages.lock.json
+734
-0
diff --git a/src/packages.lock.json b/src/packages.lock.json
index dad1588..58c22c5 100644
@@ -8,11 +8,745 @@
"resolved": "1.3.0",
"contentHash": "jzzLpmz7F6LyBh5FTZy8/4dEYVcAG9VjBPKryZ82LIoUuupcBkCSXzaeS/4oYcp/rBC8yVLcDstJ8CIv9n2IFw=="
},
"Microsoft.Graph": {
"type": "Direct",
"requested": "[6.2.0, )",
"resolved": "6.2.0",
"contentHash": "s7Flvk+r/f/x64I4D4FiYc8sPVcrSjXmCZblTeBFVqBr0RXJOGg5SDMbiHtCqxiGoYtEvLp/fs8FN1G1nopl0g==",
"dependencies": {
"Microsoft.Graph.Core": "4.0.1"
}
},
"Microsoft.Graph.Core": {
"type": "Direct",
"requested": "[4.0.1, )",
"resolved": "4.0.1",
"contentHash": "hWfGglaCLUZsOs0VHA2vkwBCRy0WOEfH4Wh1Y7yb1Q5nSbiyiPJWtHOuEfuEa2jqU0tPKOQ7eN8HRTSOMMQNfw==",
"dependencies": {
"Microsoft.IdentityModel.Protocols.OpenIdConnect": "8.18.0",
"Microsoft.IdentityModel.Validators": "8.18.0",
"Microsoft.Kiota.Abstractions": "2.0.0",
"Microsoft.Kiota.Authentication.Azure": "2.0.0",
"Microsoft.Kiota.Http.HttpClientLibrary": "2.0.0",
"Microsoft.Kiota.Serialization.Form": "2.0.0",
"Microsoft.Kiota.Serialization.Json": "2.0.0",
"Microsoft.Kiota.Serialization.Multipart": "2.0.0",
"Microsoft.Kiota.Serialization.Text": "2.0.0"
}
},
"Microsoft.Identity.Client": {
"type": "Direct",
"requested": "[4.87.0, )",
"resolved": "4.87.0",
"contentHash": "ERObBpS3b6kc92D/Z86zSd/MpWvdJu97BNamRzg5V4y59KwnxzfhM762tgiNnKoIwTbnIBOcwGPFGQ4DvShLvw==",
"dependencies": {
"Microsoft.IdentityModel.Abstractions": "8.14.0"
}
},
"Microsoft.NET.ILLink.Tasks": {
"type": "Direct",
"requested": "[10.0.10, )",
"resolved": "10.0.10",
"contentHash": "f5VCIE7AJpd5YvzNTeMGVzQIgyE9tX+AreTYwQF+REbu+DZo/2Ae+jNSwhPEYrVz6RRkd7y8ubXjk6Nn6Ka+Cg=="
},
"Xamarin.AndroidX.Browser": {
"type": "Direct",
"requested": "[1.10.0.1, )",
"resolved": "1.10.0.1",
"contentHash": "FNkV3m7Z+97k+j9qWPRZHnwuqf8Q19DXmE0PsVuIZXuFh2ifWKKYjJS9gVeuTtjySFK1Y253tEQCT7zg/RrnoA==",
"dependencies": {
"Xamarin.AndroidX.Activity": "1.13.0.1",
"Xamarin.AndroidX.Annotation": "1.10.0.1",
"Xamarin.AndroidX.Annotation.Experimental": "1.6.0.1",
"Xamarin.AndroidX.Collection": "1.6.0.1",
"Xamarin.AndroidX.Concurrent.Futures": "1.3.0.4",
"Xamarin.AndroidX.Core": "1.19.0.1",
"Xamarin.AndroidX.Interpolator": "1.0.0.37",
"Xamarin.Google.Guava.ListenableFuture": "1.0.0.32",
"Xamarin.JSpecify": "1.0.0.7",
"Xamarin.Kotlin.StdLib": "2.4.0.1"
}
},
"Xamarin.AndroidX.Core": {
"type": "Direct",
"requested": "[1.19.0.1, )",
"resolved": "1.19.0.1",
"contentHash": "o99imtQhC8lAEzxbvoeh+Tk4Gr2yHOE5LlV0YJK0n561PZlFvQYBSGrM+ZYL3pivXBO2IjAcWZdRGy67DZI0oA==",
"dependencies": {
"Xamarin.AndroidX.Annotation": "1.10.0.1",
"Xamarin.AndroidX.Annotation.Experimental": "1.6.0.1",
"Xamarin.AndroidX.Collection": "1.6.0.1",
"Xamarin.AndroidX.Concurrent.Futures": "1.3.0.4",
"Xamarin.AndroidX.Core.ViewTree": "1.0.0.6",
"Xamarin.AndroidX.Interpolator": "1.0.0.37",
"Xamarin.AndroidX.Lifecycle.Common": "2.11.0.1",
"Xamarin.AndroidX.Lifecycle.Runtime": "2.11.0.1",
"Xamarin.AndroidX.Tracing.Tracing": "1.3.0.4",
"Xamarin.AndroidX.VersionedParcelable": "1.2.1.6",
"Xamarin.Google.Guava.ListenableFuture": "1.0.0.32",
"Xamarin.JSpecify": "1.0.0.7",
"Xamarin.Kotlin.StdLib": "2.4.0.1",
"Xamarin.KotlinX.Coroutines.Core": "1.11.0.1"
}
},
"Xamarin.AndroidX.RecyclerView": {
"type": "Direct",
"requested": "[1.4.0.6, )",
"resolved": "1.4.0.6",
"contentHash": "Kh/DaEm1pkMdfOghcwa2mTfMRCwmyGLX+DgFmN4I7gxzdFYEyJdYthIwLfoF9RWO8derP+5VinOZLkxwqCrs+A==",
"dependencies": {
"Xamarin.AndroidX.Annotation": "1.10.0.1",
"Xamarin.AndroidX.Collection": "1.6.0.1",
"Xamarin.AndroidX.Core": "1.19.0.1",
"Xamarin.AndroidX.CustomView": "1.2.0.4",
"Xamarin.AndroidX.CustomView.PoolingContainer": "1.1.0.4",
"Xamarin.AndroidX.ProfileInstaller.ProfileInstaller": "1.4.1.8"
}
},
"Azure.Core": {
"type": "Transitive",
"resolved": "1.50.0",
"contentHash": "GBNKZEhdIbTXxedvD3R7I/yDVFX9jJJEz02kCziFSJxspSQ5RMHc3GktulJ1s7+ffXaXD7kMgrtdQTaggyInLw==",
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "8.0.0",
"System.ClientModel": "1.8.0",
"System.Memory.Data": "8.0.1"
}
},
"Microsoft.Bcl.AsyncInterfaces": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "3WA9q9yVqJp222P3x1wYIGDAkpjAku0TMUaaQV22g6L67AI0LdOIrVS7Ht2vJfLHGSPVuqN94vIr15qn+HEkHw=="
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "8.0.2",
"contentHash": "3iE7UF7MQkCv1cxzCahz+Y/guQbTqieyxyaWKhrRO91itI9cOKO76OHeQDahqG4MmW5umr3CcCvGmK92lWNlbg=="
},
"Microsoft.Extensions.Logging.Abstractions": {
"type": "Transitive",
"resolved": "8.0.3",
"contentHash": "dL0QGToTxggRLMYY4ZYX5AMwBb+byQBd/5dMiZE07Nv73o6I5Are3C7eQTh7K2+A4ct0PVISSr7TZANbiNb2yQ==",
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2"
}
},
"Microsoft.IdentityModel.Abstractions": {
"type": "Transitive",
"resolved": "8.18.0",
"contentHash": "8VUcDy66uw1GUC/ytyRJAUgGxydPu2rLtUbUAiniCHd5SMB/01Q28XgqFyxIqb3srz6HWTgSsZdDbkdVJr3LXQ=="
},
"Microsoft.IdentityModel.JsonWebTokens": {
"type": "Transitive",
"resolved": "8.18.0",
"contentHash": "ZUMJt3r1zOi67AVSfnh3u9hg9KCq06roOIX5gs7FqsucSZ/VTsI89DI9h2gHyU0xOtj/qVZV2ugWS6JlLMTwHQ==",
"dependencies": {
"Microsoft.IdentityModel.Tokens": "8.18.0"
}
},
"Microsoft.IdentityModel.Logging": {
"type": "Transitive",
"resolved": "8.18.0",
"contentHash": "c2l/VEtW1XI/ifcu49xzDwgrZZ0a0aX/TwCPC7mEHFQk/KixDgtSdjB5eDhYyCO38GJiRUjeRTz9aWCy1t55ww==",
"dependencies": {
"Microsoft.IdentityModel.Abstractions": "8.18.0"
}
},
"Microsoft.IdentityModel.Protocols": {
"type": "Transitive",
"resolved": "8.18.0",
"contentHash": "kKimlJy4j5m9ZkX/RTp12+gvxzM+QU5H3h7zzdroTt0v10/7AQsHG4tqi3ixvNjx5PPY0JnbpBsNXC/Xb1+sgQ==",
"dependencies": {
"Microsoft.IdentityModel.Tokens": "8.18.0"
}
},
"Microsoft.IdentityModel.Protocols.OpenIdConnect": {
"type": "Transitive",
"resolved": "8.18.0",
"contentHash": "suO9qM+gTjIMmHiEwZFitx1Z0BROQwTT3BYJJCDM/+djBAolhXHVpZzTzIFAxkjgakJRNCRlZi9ecZ2g68Pn5w==",
"dependencies": {
"Microsoft.IdentityModel.Protocols": "8.18.0",
"System.IdentityModel.Tokens.Jwt": "8.18.0"
}
},
"Microsoft.IdentityModel.Tokens": {
"type": "Transitive",
"resolved": "8.18.0",
"contentHash": "c6ksXXFj5oPPsl8pfsui5zv8Gs7uxrGetXCTc1p7k7Nue/C8iBMtAVgtRrH7Esqe596QWD7KS3exKYY1FJG2iw==",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "8.0.0",
"Microsoft.IdentityModel.Logging": "8.18.0"
}
},
"Microsoft.IdentityModel.Validators": {
"type": "Transitive",
"resolved": "8.18.0",
"contentHash": "JjqoKHMlPTFk4DkLvN/eO8scc5Am568Rc9juN9YGB6isHCuIllQNOCZqCZzk1fvrfnw4EL4mP57U0936KRcMGQ==",
"dependencies": {
"Microsoft.IdentityModel.Protocols": "8.18.0",
"Microsoft.IdentityModel.Protocols.OpenIdConnect": "8.18.0",
"Microsoft.IdentityModel.Tokens": "8.18.0",
"System.IdentityModel.Tokens.Jwt": "8.18.0"
}
},
"Microsoft.IO.RecyclableMemoryStream": {
"type": "Transitive",
"resolved": "3.0.1",
"contentHash": "s/s20YTVY9r9TPfTrN5g8zPF1YhwxyqO6PxUkrYTGI2B+OGPe9AdajWZrLhFqXIvqIW23fnUE4+ztrUWNU1+9g=="
},
"Microsoft.Kiota.Abstractions": {
"type": "Transitive",
"resolved": "2.0.0",
"contentHash": "cb0KA6QGvqMR1xNIjmErb5CiEbQJ/6odKcuPn2GTWD+aRMuldKDqRqRWMDT7MOBcUk9waJyJsgDG+ItQA01JmA==",
"dependencies": {
"Std.UriTemplate": "2.0.8"
}
},
"Microsoft.Kiota.Authentication.Azure": {
"type": "Transitive",
"resolved": "2.0.0",
"contentHash": "O890EE/4mIwfZbAeayZ0dXMSJVZWwUDcvOeH2GEmUbEQlSn+w37OHhuPClhIN4UO7EcqNC5N9ynyQWhOF+FWCg==",
"dependencies": {
"Azure.Core": "1.50.0",
"Microsoft.Kiota.Abstractions": "2.0.0"
}
},
"Microsoft.Kiota.Http.HttpClientLibrary": {
"type": "Transitive",
"resolved": "2.0.0",
"contentHash": "oZ3ZR84InWEST6GyO9EBZMHweeprzlMiVsGy5dt9ec8Py1d6fgs4Yyyv0LJ56WoPc3Og5mM84pz3qD4hpiCT9w==",
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0",
"Microsoft.Kiota.Abstractions": "2.0.0"
}
},
"Microsoft.Kiota.Serialization.Form": {
"type": "Transitive",
"resolved": "2.0.0",
"contentHash": "Sm7dLRzwJRx80o/U5LUI0ICX4k0hT3tZgLAfXbO/UehsX4YVu6yiuOwCNdXlvE9tnC5LWcQdi9RRCSo3fQgI+A==",
"dependencies": {
"Microsoft.IO.RecyclableMemoryStream": "3.0.1",
"Microsoft.Kiota.Abstractions": "2.0.0"
}
},
"Microsoft.Kiota.Serialization.Json": {
"type": "Transitive",
"resolved": "2.0.0",
"contentHash": "0QB6RWSA3u7kQEDyaY3a3dVA98Fk2UwuYpusvX2aXmkQLPFijW4XKujQazHMoalx26fmSg6RF85ZJBqGzmgucA==",
"dependencies": {
"Microsoft.IO.RecyclableMemoryStream": "3.0.1",
"Microsoft.Kiota.Abstractions": "2.0.0"
}
},
"Microsoft.Kiota.Serialization.Multipart": {
"type": "Transitive",
"resolved": "2.0.0",
"contentHash": "IR27DQpYul43E+2Nw2mzEMFIzwiRjQCnCOfylIE/dlFra3QjoT/s5bhYv9ZN182U0ajkiQYYT/UORvpgGau4mw==",
"dependencies": {
"Microsoft.IO.RecyclableMemoryStream": "3.0.1",
"Microsoft.Kiota.Abstractions": "2.0.0"
}
},
"Microsoft.Kiota.Serialization.Text": {
"type": "Transitive",
"resolved": "2.0.0",
"contentHash": "yuQZ8VIWc3NCpK439zG105sAfyFkq8kA+fvxfXI1DA76gTGKNLBJtX8ljyuhMhcYCl/Uu9AOm/vfbbI/P5TapQ==",
"dependencies": {
"Microsoft.IO.RecyclableMemoryStream": "3.0.1",
"Microsoft.Kiota.Abstractions": "2.0.0"
}
},
"Std.UriTemplate": {
"type": "Transitive",
"resolved": "2.0.8",
"contentHash": "IAR4gJlLVD4r/FsU/rDb1+9sbB3/tCFVZz5IzlbM2yHoiUisBi5Ek/KXRUF7RcqNARka79EoaOz9INnXvQ/O6w=="
},
"System.ClientModel": {
"type": "Transitive",
"resolved": "1.8.0",
"contentHash": "AqRzhn0v29GGGLj/Z6gKq4lGNtvPHT4nHdG5PDJh9IfVjv/nYUVmX11hwwws1vDFeIAzrvmn0dPu8IjLtu6fAw==",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "8.0.3",
"System.Memory.Data": "8.0.1"
}
},
"System.IdentityModel.Tokens.Jwt": {
"type": "Transitive",
"resolved": "8.18.0",
"contentHash": "JxKlBca3/eXeH4koAGjEXOwrBdtwyDXwHXdYT5TQunp64TcVBl4wBCfmlGwhSG017Q/Qrz24leLxV6v2eknnJg==",
"dependencies": {
"Microsoft.IdentityModel.JsonWebTokens": "8.18.0",
"Microsoft.IdentityModel.Tokens": "8.18.0"
}
},
"System.Memory.Data": {
"type": "Transitive",
"resolved": "8.0.1",
"contentHash": "BVYuec3jV23EMRDeR7Dr1/qhx7369dZzJ9IWy2xylvb4YfXsrUxspWc4UWYid/tj4zZK58uGZqn2WQiaDMhmAg=="
},
"Xamarin.AndroidX.Activity": {
"type": "Transitive",
"resolved": "1.13.0.1",
"contentHash": "W2Tvk9UOvP6i/cCJrv0vQyAFzaxejfzSQDvjj9BoHtbM7fCVSXUngHTSaWptRcgUQQJyRNmKmTJ2YipndCZijw==",
"dependencies": {
"Xamarin.AndroidX.Annotation": "1.10.0.1",
"Xamarin.AndroidX.Core.Core.Ktx": "1.19.0.1",
"Xamarin.AndroidX.Core.ViewTree": "1.0.0.6",
"Xamarin.AndroidX.Lifecycle.Common": "2.11.0.1",
"Xamarin.AndroidX.Lifecycle.Runtime": "2.11.0.1",
"Xamarin.AndroidX.Lifecycle.ViewModel": "2.11.0.1",
"Xamarin.AndroidX.Lifecycle.ViewModelSavedState": "2.11.0.1",
"Xamarin.AndroidX.NavigationEvent": "1.1.2.1",
"Xamarin.AndroidX.ProfileInstaller.ProfileInstaller": "1.4.1.8",
"Xamarin.AndroidX.SavedState": "1.5.0.1",
"Xamarin.AndroidX.Tracing.Tracing": "1.3.0.4",
"Xamarin.Kotlin.StdLib": "2.4.0.1",
"Xamarin.KotlinX.Coroutines.Core": "1.11.0.1"
}
},
"Xamarin.AndroidX.Annotation": {
"type": "Transitive",
"resolved": "1.10.0.1",
"contentHash": "OFFhDPUvfvyAaYU1s32hkiRt460zLkNxBgW1l2QUZDa2sSAQSGdrdpSZSpeXGXI0r1lgXPLIv8PWNmStIDr+Hw==",
"dependencies": {
"Xamarin.AndroidX.Annotation.Jvm": "1.10.0.1",
"Xamarin.Kotlin.StdLib": "2.4.0.1"
}
},
"Xamarin.AndroidX.Annotation.Experimental": {
"type": "Transitive",
"resolved": "1.6.0.1",
"contentHash": "sZSJtoivC3uBRIZVkRoBeYtpRcs1MxBxRoW9hcHeNwbQO9yPxBBpUsQyztZT4DrUNXvbLVAjA+fS4kvy7rUjbA==",
"dependencies": {
"Xamarin.Kotlin.StdLib": "2.4.0.1"
}
},
"Xamarin.AndroidX.Annotation.Jvm": {
"type": "Transitive",
"resolved": "1.10.0.1",
"contentHash": "TwAZDaPB2TYKAfRnrkh59rqrk0KaCKAqkMomZ84WuFJ5elkVzO1ttZjAHY7oD6XkmnaZyOkGhBacxmu4ApTh0g==",
"dependencies": {
"Xamarin.Kotlin.StdLib": "2.4.0.1"
}
},
"Xamarin.AndroidX.Arch.Core.Common": {
"type": "Transitive",
"resolved": "2.2.0.21",
"contentHash": "K3/VTkAn9aKFPjMQZnMyXwU3jA3hj5RZps/KoQGzGBeg+4KtBpx+aZd8n6bSJQGRg0YF0Viz8bocSoCKfylvtw==",
"dependencies": {
"Xamarin.AndroidX.Annotation": "1.10.0.1"
}
},
"Xamarin.AndroidX.Arch.Core.Runtime": {
"type": "Transitive",
"resolved": "2.2.0.21",
"contentHash": "d+xgYu109rmZRpRA3QjdOxHWFwJBndtAudu/XbfeqQR+OTaW7dpFYNAKLK2bGlihQqVszOuVdUdn3crpbZeUGw==",
"dependencies": {
"Xamarin.AndroidX.Annotation": "1.10.0.1",
"Xamarin.AndroidX.Arch.Core.Common": "[2.2.0.21, 2.2.1)"
}
},
"Xamarin.AndroidX.Collection": {
"type": "Transitive",
"resolved": "1.6.0.1",
"contentHash": "2KWXJOSjKTcJRIUrayYj5Kic+UP7TsTCieDBgiyImMhft5cEvdHUJjhseH7WIrKRVkpNoj5PAIlZgRimVSbayA==",
"dependencies": {
"Xamarin.AndroidX.Annotation": "1.10.0.1",
"Xamarin.AndroidX.Collection.Jvm": "1.6.0.1",
"Xamarin.Kotlin.StdLib": "2.4.0.1"
}
},
"Xamarin.AndroidX.Collection.Jvm": {
"type": "Transitive",
"resolved": "1.6.0.1",
"contentHash": "qmUjU7y9SOuhBwVOVtsJogWE8xuDQIRGN+IQtL+JK4du6BDKrpY4sKSBaPH//+9SAzo+l6V8EgGXd/KhJQM0dg==",
"dependencies": {
"Xamarin.AndroidX.Annotation.Jvm": "1.10.0.1",
"Xamarin.JSpecify": "1.0.0.7",
"Xamarin.Kotlin.StdLib": "2.4.0.1"
}
},
"Xamarin.AndroidX.Compose.Runtime.Annotation": {
"type": "Transitive",
"resolved": "1.11.3.1",
"contentHash": "IP+ct5uaVhAp0f2Spe62kXgaoGwLuogBeG42q5ta6Nj7mQlHgxetJC5dwauMXjKnT8hwoZthpP4lvagpCZZdnA==",
"dependencies": {
"Xamarin.Kotlin.StdLib": "2.4.0.1"
}
},
"Xamarin.AndroidX.Compose.Runtime.Annotation.Android": {
"type": "Transitive",
"resolved": "1.11.3.1",
"contentHash": "NCz5fp215Nj4lE6SQd8EXjJ6vTztoLCa3rtrOYmb39Tlv96NdDNHTR/jl5ARL8+RjcLuwxwL/q1Na1aVPjFDGA==",
"dependencies": {
"Xamarin.Kotlin.StdLib": "2.4.0.1"
}
},
"Xamarin.AndroidX.Concurrent.Futures": {
"type": "Transitive",
"resolved": "1.3.0.4",
"contentHash": "KiRIKChlF+ZUSmwF7WsBXl5ACfr/dxaCzk8o3xRqLaUxuY5BFK7mrG1zGahkl1WZFSvXkMIqzA88DShr7EdnXQ==",
"dependencies": {
"Xamarin.AndroidX.Annotation": "1.10.0.1",
"Xamarin.Google.Guava.ListenableFuture": "1.0.0.32",
"Xamarin.JSpecify": "1.0.0.7"
}
},
"Xamarin.AndroidX.Core.Core.Ktx": {
"type": "Transitive",
"resolved": "1.19.0.1",
"contentHash": "oYMa8qwe+Rs4v3Rs1SXA0UJ3DLyFWJtpjwSeFeh1+jlS+SF1xHbOt9G5zKqz193zPP+mDJkaYmdJpv8ahhAjlA==",
"dependencies": {
"Xamarin.AndroidX.Core": "1.19.0.1",
"Xamarin.Kotlin.StdLib": "2.4.0.1"
}
},
"Xamarin.AndroidX.Core.ViewTree": {
"type": "Transitive",
"resolved": "1.0.0.6",
"contentHash": "BJP0aUy9fLExMVrrjqvzSj5MKU9RFVfT+fxW/5oUrlkmSewDZyfJfHo4MwtmbUDQGDDG+ARpPIABOalhWqYJdA==",
"dependencies": {
"Xamarin.Kotlin.StdLib": "2.4.0.1"
}
},
"Xamarin.AndroidX.CustomView": {
"type": "Transitive",
"resolved": "1.2.0.4",
"contentHash": "AuBRKJ1l2NwVze0pnkCflJM2rWortwoQ3Vz3ZUniYYEfVInDyQVRkNxwlDJqnxCxg/MPYK+120dV/fEQDGOOHg==",
"dependencies": {
"Xamarin.AndroidX.Annotation": "1.10.0.1",
"Xamarin.AndroidX.Collection": "1.6.0.1",
"Xamarin.AndroidX.Core": "1.19.0.1",
"Xamarin.JSpecify": "1.0.0.7"
}
},
"Xamarin.AndroidX.CustomView.PoolingContainer": {
"type": "Transitive",
"resolved": "1.1.0.4",
"contentHash": "qP27ERJqtyg+AxOy2tqqqJ0L4IVTCwEZRbSZNdBKa/1a5iy5gTt/IIOs9Ba4tg+JuW9iaGJ/UjFnEOZvg7FsAg==",
"dependencies": {
"Xamarin.AndroidX.Core.Core.Ktx": "1.19.0.1",
"Xamarin.Kotlin.StdLib": "2.4.0.1"
}
},
"Xamarin.AndroidX.Interpolator": {
"type": "Transitive",
"resolved": "1.0.0.37",
"contentHash": "uBGc0kgh8v/SDiSWanClynPGz7sa6pQzkGgISn87MsiUqS8wtYlB+4HmXh7kEE449spgwPTf3ziEIEq/uJd99g==",
"dependencies": {
"Xamarin.AndroidX.Annotation": "1.10.0.1"
}
},
"Xamarin.AndroidX.Lifecycle.Common": {
"type": "Transitive",
"resolved": "2.11.0.1",
"contentHash": "fCKqf0sAnuA0J4AqusRBWfMSoLQPEt9uDNTB9X2ZM2oed1JKyt8ipdOTvX46pes2HsyCT+0etu4oaJh4Agcdvw==",
"dependencies": {
"Xamarin.AndroidX.Annotation": "1.10.0.1",
"Xamarin.AndroidX.Lifecycle.Common.Jvm": "2.11.0.1",
"Xamarin.Kotlin.StdLib": "2.4.0.1",
"Xamarin.KotlinX.Coroutines.Core": "1.11.0.1"
}
},
"Xamarin.AndroidX.Lifecycle.Common.Jvm": {
"type": "Transitive",
"resolved": "2.11.0.1",
"contentHash": "wnp6hF14kO3zO9LFdH1SJJMIY6Vm0kIY+/goCRbtLMuF2/ZLnAW4SHGPqNrB467+YAVjNCNkc+CxLWP2ER7EIg==",
"dependencies": {
"Xamarin.AndroidX.Annotation.Jvm": "1.10.0.1",
"Xamarin.JSpecify": "1.0.0.7",
"Xamarin.Kotlin.StdLib": "2.4.0.1",
"Xamarin.KotlinX.Coroutines.Core": "1.11.0.1"
}
},
"Xamarin.AndroidX.Lifecycle.LiveData.Core": {
"type": "Transitive",
"resolved": "2.11.0.1",
"contentHash": "oPRlEve2Cnngh15/6OoEzVE3mwYSo9XAfuphB37ya54+mxSrINO/deTuIR7zgwjG5iTUYKDAHlya848h1/VSug==",
"dependencies": {
"Xamarin.AndroidX.Arch.Core.Common": "2.2.0.21",
"Xamarin.AndroidX.Arch.Core.Runtime": "2.2.0.21",
"Xamarin.AndroidX.Lifecycle.Common": "[2.11.0.1, 2.11.1)",
"Xamarin.JSpecify": "1.0.0.7",
"Xamarin.Kotlin.StdLib": "2.4.0.1"
}
},
"Xamarin.AndroidX.Lifecycle.Runtime": {
"type": "Transitive",
"resolved": "2.11.0.1",
"contentHash": "Xb/G30kVZVClyHiylVzxG+5Rhce9Zk4BBo1qNzrh8vGztLVg8c1dof1zodB3DOaCE/Kz08xe+azYdeuzgvKwmw==",
"dependencies": {
"Xamarin.AndroidX.Annotation": "1.10.0.1",
"Xamarin.AndroidX.Arch.Core.Common": "2.2.0.21",
"Xamarin.AndroidX.Collection": "1.6.0.1",
"Xamarin.AndroidX.Lifecycle.Common": "[2.11.0.1, 2.11.1)",
"Xamarin.AndroidX.Lifecycle.Runtime.Android": "2.11.0.1",
"Xamarin.Kotlin.StdLib": "2.4.0.1"
}
},
"Xamarin.AndroidX.Lifecycle.Runtime.Android": {
"type": "Transitive",
"resolved": "2.11.0.1",
"contentHash": "XdoCaH+dVCnr5ya24MIgasaMyZV2mnUg812Ns+WO2gocOOtSjIxYzbXqihkFTaDUlWk0A+OFRIXTKYZ3ec6Viw==",
"dependencies": {
"Xamarin.AndroidX.Annotation.Jvm": "1.10.0.1",
"Xamarin.AndroidX.Arch.Core.Common": "2.2.0.21",
"Xamarin.AndroidX.Arch.Core.Runtime": "2.2.0.21",
"Xamarin.AndroidX.Collection.Jvm": "1.6.0.1",
"Xamarin.AndroidX.Core.ViewTree": "1.0.0.6",
"Xamarin.AndroidX.Lifecycle.Common.Jvm": "[2.11.0.1, 2.11.1)",
"Xamarin.AndroidX.ProfileInstaller.ProfileInstaller": "1.4.1.8",
"Xamarin.JSpecify": "1.0.0.7",
"Xamarin.Kotlin.StdLib": "2.4.0.1",
"Xamarin.KotlinX.Coroutines.Android": "1.11.0.1"
}
},
"Xamarin.AndroidX.Lifecycle.ViewModel": {
"type": "Transitive",
"resolved": "2.11.0.1",
"contentHash": "XhweDAL6Gehaep9XhmrJCC5y4xNsHAR1+yWcvjreRnLWbSFVc8+qcBRI6emvYC6pzb0y2XjhFaFYEbgmJi3cNA==",
"dependencies": {
"Xamarin.AndroidX.Annotation": "1.10.0.1",
"Xamarin.AndroidX.Collection": "1.6.0.1",
"Xamarin.AndroidX.Lifecycle.ViewModel.Android": "2.11.0.1",
"Xamarin.Kotlin.StdLib": "2.4.0.1",
"Xamarin.KotlinX.AtomicFU": "0.33.0.1",
"Xamarin.KotlinX.Coroutines.Core": "1.11.0.1"
}
},
"Xamarin.AndroidX.Lifecycle.ViewModel.Android": {
"type": "Transitive",
"resolved": "2.11.0.1",
"contentHash": "Yy0/eissXgeHSeyL6x4qeB4XbUy6H007c4xHMtpCP2ujhV7smebva2RbwMB4ikTAON/C715amv1mVfVeo3/JEA==",
"dependencies": {
"Xamarin.AndroidX.Annotation.Jvm": "1.10.0.1",
"Xamarin.AndroidX.Collection.Jvm": "1.6.0.1",
"Xamarin.AndroidX.Core.ViewTree": "1.0.0.6",
"Xamarin.Kotlin.StdLib": "2.4.0.1",
"Xamarin.KotlinX.Coroutines.Android": "1.11.0.1",
"Xamarin.KotlinX.Coroutines.Core": "1.11.0.1"
}
},
"Xamarin.AndroidX.Lifecycle.ViewModelSavedState": {
"type": "Transitive",
"resolved": "2.11.0.1",
"contentHash": "vjN7bimU3WXfJc12fN5uQV1X2iYbFiB7b46v2i7N4qpCHgnkP/3Ae2fY+7OPEMsLu32N/keO8ZWGX52mGb4ZQw==",
"dependencies": {
"Xamarin.AndroidX.Annotation": "1.10.0.1",
"Xamarin.AndroidX.Collection": "1.6.0.1",
"Xamarin.AndroidX.Lifecycle.Common": "[2.11.0.1, 2.11.1)",
"Xamarin.AndroidX.Lifecycle.ViewModel": "[2.11.0.1, 2.11.1)",
"Xamarin.AndroidX.Lifecycle.ViewModelSavedState.Android": "2.11.0.1",
"Xamarin.AndroidX.SavedState": "1.5.0.1",
"Xamarin.Kotlin.StdLib": "2.4.0.1",
"Xamarin.KotlinX.Coroutines.Core": "1.11.0.1",
"Xamarin.KotlinX.Serialization.Core": "1.11.0.1"
}
},
"Xamarin.AndroidX.Lifecycle.ViewModelSavedState.Android": {
"type": "Transitive",
"resolved": "2.11.0.1",
"contentHash": "f+dRGHiNQSAYmSFi78uZ57ZqcgaRK5C6vsA7oyGCeM/yi0S29lWRX1wAlEjcaDal+XB7IE+4AT3gh4gK6RmbFg==",
"dependencies": {
"Xamarin.AndroidX.Annotation.Jvm": "1.10.0.1",
"Xamarin.AndroidX.Collection.Jvm": "1.6.0.1",
"Xamarin.AndroidX.Core.Core.Ktx": "1.19.0.1",
"Xamarin.AndroidX.Lifecycle.Common.Jvm": "[2.11.0.1, 2.11.1)",
"Xamarin.AndroidX.Lifecycle.LiveData.Core": "[2.11.0.1, 2.11.1)",
"Xamarin.AndroidX.Lifecycle.ViewModel.Android": "[2.11.0.1, 2.11.1)",
"Xamarin.AndroidX.SavedState.SavedState.Android": "1.5.0.1",
"Xamarin.Kotlin.StdLib": "2.4.0.1",
"Xamarin.KotlinX.Coroutines.Android": "1.11.0.1",
"Xamarin.KotlinX.Coroutines.Core": "1.11.0.1",
"Xamarin.KotlinX.Serialization.Core.Jvm": "1.11.0.1"
}
},
"Xamarin.AndroidX.NavigationEvent": {
"type": "Transitive",
"resolved": "1.1.2.1",
"contentHash": "BuHoykG9rBTQg8WwXhwRl3NQPkroyksciRyuHVuYiO3gtU+M4MQwKj5JUW228yso8psjJhnhNg/79N2ExRF+og==",
"dependencies": {
"Xamarin.AndroidX.Annotation": "1.10.0.1",
"Xamarin.AndroidX.Collection": "1.6.0.1",
"Xamarin.AndroidX.Compose.Runtime.Annotation": "1.11.3.1",
"Xamarin.AndroidX.NavigationEvent.Android": "1.1.2.1",
"Xamarin.Kotlin.StdLib": "2.4.0.1",
"Xamarin.KotlinX.AtomicFU": "0.33.0.1",
"Xamarin.KotlinX.Coroutines.Core": "1.11.0.1"
}
},
"Xamarin.AndroidX.NavigationEvent.Android": {
"type": "Transitive",
"resolved": "1.1.2.1",
"contentHash": "rRcewSbsor2c/RS/kSCGvahu0boa9jvrNjLCOek8WeQJNyjhMqji7YECbv1PqT9jwHUXcx1FB4ULvjJnqVEtxQ==",
"dependencies": {
"Xamarin.AndroidX.Annotation.Jvm": "1.10.0.1",
"Xamarin.AndroidX.Collection.Jvm": "1.6.0.1",
"Xamarin.AndroidX.Compose.Runtime.Annotation.Android": "1.11.3.1",
"Xamarin.AndroidX.Core.ViewTree": "1.0.0.6",
"Xamarin.Kotlin.StdLib": "2.4.0.1",
"Xamarin.KotlinX.Coroutines.Android": "1.11.0.1",
"Xamarin.KotlinX.Coroutines.Core": "1.11.0.1"
}
},
"Xamarin.AndroidX.ProfileInstaller.ProfileInstaller": {
"type": "Transitive",
"resolved": "1.4.1.8",
"contentHash": "zBwJUR3ipSDdFs8yQCSHZVRc1Ngz6LRcdojEAF3Fn3YeXYjGEpQIWeGaruZi95bePWwk0L5628y3aqznAKfccQ==",
"dependencies": {
"Xamarin.AndroidX.Annotation": "1.10.0.1",
"Xamarin.AndroidX.Concurrent.Futures": "1.3.0.4",
"Xamarin.AndroidX.Startup.StartupRuntime": "1.2.0.8",
"Xamarin.Google.Guava.ListenableFuture": "1.0.0.32"
}
},
"Xamarin.AndroidX.SavedState": {
"type": "Transitive",
"resolved": "1.5.0.1",
"contentHash": "H9wF2dchEuakH4gj4vnPpjgCcZ/f3WU+g1CCO+abTmYmZs3qUYzE4nekVoqNJHQ6PNYc7kMMo7hzM97nSXmS2Q==",
"dependencies": {
"Xamarin.AndroidX.Annotation": "1.10.0.1",
"Xamarin.AndroidX.Collection": "1.6.0.1",
"Xamarin.AndroidX.Lifecycle.Common": "2.11.0.1",
"Xamarin.AndroidX.SavedState.SavedState.Android": "1.5.0.1",
"Xamarin.Kotlin.StdLib": "2.4.0.1",
"Xamarin.KotlinX.AtomicFU": "0.33.0.1",
"Xamarin.KotlinX.Coroutines.Core": "1.11.0.1",
"Xamarin.KotlinX.Serialization.Core": "1.11.0.1"
}
},
"Xamarin.AndroidX.SavedState.SavedState.Android": {
"type": "Transitive",
"resolved": "1.5.0.1",
"contentHash": "7XfPayLSlP2RRYcpvvEgbQG/t/g5o/RWtAEVE+sO0NBPNJWlQ/zvEzWjH7JeZE+ttRGov78AiwKfeWVX1ptPwQ==",
"dependencies": {
"Xamarin.AndroidX.Annotation.Jvm": "1.10.0.1",
"Xamarin.AndroidX.Collection.Jvm": "1.6.0.1",
"Xamarin.AndroidX.Core.Core.Ktx": "1.19.0.1",
"Xamarin.AndroidX.Core.ViewTree": "1.0.0.6",
"Xamarin.AndroidX.Lifecycle.Common.Jvm": "2.11.0.1",
"Xamarin.Kotlin.StdLib": "2.4.0.1",
"Xamarin.KotlinX.Coroutines.Core": "1.11.0.1",
"Xamarin.KotlinX.Serialization.Core.Jvm": "1.11.0.1"
}
},
"Xamarin.AndroidX.Startup.StartupRuntime": {
"type": "Transitive",
"resolved": "1.2.0.8",
"contentHash": "HKloS4O57Oniv/5VF0fkHh6zEWRwToK7VyW2ajyWZ7+l+bkxUFZy9tHIHS/TCGvDYR6NVM8yA/Mz2x+TrWwPmg==",
"dependencies": {
"Xamarin.AndroidX.Annotation": "1.10.0.1",
"Xamarin.AndroidX.Tracing.Tracing": "1.3.0.4"
}
},
"Xamarin.AndroidX.Tracing.Tracing": {
"type": "Transitive",
"resolved": "1.3.0.4",
"contentHash": "x0Oig3saZ/b2zk0R8Um713vT15I428kKm0jh/cW2t1XDbk/LF9Cnrsbo6/0p8SFPyqQch7JJACbnvsH+xEunZQ==",
"dependencies": {
"Xamarin.AndroidX.Tracing.Tracing.Android": "1.3.0.4",
"Xamarin.Kotlin.StdLib": "2.4.0.1"
}
},
"Xamarin.AndroidX.Tracing.Tracing.Android": {
"type": "Transitive",
"resolved": "1.3.0.4",
"contentHash": "qQNpa9D6q6CgtgGRJGfaEZUVFH+0pzA+58V2z8CYosv6K/mKwJys9InR+0xNmMDshm1IuGhX4mgJ3Lf54D2BQg==",
"dependencies": {
"Xamarin.AndroidX.Annotation.Jvm": "1.10.0.1",
"Xamarin.Kotlin.StdLib": "2.4.0.1"
}
},
"Xamarin.AndroidX.VersionedParcelable": {
"type": "Transitive",
"resolved": "1.2.1.6",
"contentHash": "RxZnmS3nBqOGHUUegHWBqAU4qlApR8s1nhCZ4CnCJBwvfXJQ1Y9aschXn2fXrYgV86xtwRR31+TN5KufIIvINw==",
"dependencies": {
"Xamarin.AndroidX.Annotation": "1.10.0.1",
"Xamarin.AndroidX.Collection": "1.6.0.1",
"Xamarin.JSpecify": "1.0.0.7"
}
},
"Xamarin.Google.Guava.ListenableFuture": {
"type": "Transitive",
"resolved": "1.0.0.32",
"contentHash": "2jv4/HoPHk9PG8TGEuAwYMUJWf+OwxANCNASr0i0sWOAbvVQsNk9Mnu4BlYZkmJ5+KnEp0vtiHWDSkxm4kSabw=="
},
"Xamarin.Jetbrains.Annotations": {
"type": "Transitive",
"resolved": "26.1.0.2",
"contentHash": "mwKW1QGbVFT649s9fJ/tTX1kkekI8Mt+n4sWS4kP39J78FjaatbNFu7bdyzxHYMbBB61S4cU6XRuaGAw2mhQCQ=="
},
"Xamarin.JSpecify": {
"type": "Transitive",
"resolved": "1.0.0.7",
"contentHash": "pX+mTpwy0SkHGBxi3YPvQb0xLdl31GZD/VH+ebwXr97d5j7a+DGEyLWRqzC+hgZNK+bCOnlYlnUYYLD4TfSXSA=="
},
"Xamarin.Kotlin.StdLib": {
"type": "Transitive",
"resolved": "2.4.0.1",
"contentHash": "ZGskPNF6lIQWzkwfhoRLuf9isUSOmjfTZVOVXXfDP/vL/xLNC4H4h/NC63AcNoscOAReRic0po3VZsqfvLsgZQ==",
"dependencies": {
"Xamarin.Jetbrains.Annotations": "26.1.0.2"
}
},
"Xamarin.KotlinX.AtomicFU": {
"type": "Transitive",
"resolved": "0.33.0.1",
"contentHash": "9khTuFz9CvgZJc6pzKXNlmtTZF9zun1TCRHIlWpHddqmLoFjiN2Xy/Qfwj8+KK8EJZEBLPETOu0XsagKlm//SQ==",
"dependencies": {
"Xamarin.KotlinX.AtomicFU.Jvm": "0.33.0.1"
}
},
"Xamarin.KotlinX.AtomicFU.Jvm": {
"type": "Transitive",
"resolved": "0.33.0.1",
"contentHash": "4HAO14g7RTJm+WsahmXwzZBhBAEixKZuyiE+oxdQKAHCOfZzbh5uQphkcbp6YPpV3YVw/oOxE+vgL3Hn4ZPNUA==",
"dependencies": {
"Xamarin.Kotlin.StdLib": "2.4.0.1"
}
},
"Xamarin.KotlinX.Coroutines.Android": {
"type": "Transitive",
"resolved": "1.11.0.1",
"contentHash": "C+N1XS1s7bOD6ydON8DYjucwIsH/y1+zVRNQdwSqAG/6NEQPeTTxww2PpMnnIft9zxrpuCRBmnbb58BvL6j7+g==",
"dependencies": {
"Xamarin.Kotlin.StdLib": "2.4.0.1",
"Xamarin.KotlinX.Coroutines.Core.Jvm": "1.11.0.1"
}
},
"Xamarin.KotlinX.Coroutines.Core": {
"type": "Transitive",
"resolved": "1.11.0.1",
"contentHash": "7zMBcmtyOqguwRLVZ8q6/GkWQ5RMu26W+me5KUJZU2yamHjdTfrOHIrnd0zmx3+3wwgohEmzjFfwwyJf3QOSwg==",
"dependencies": {
"Xamarin.KotlinX.Coroutines.Core.Jvm": "1.11.0.1"
}
},
"Xamarin.KotlinX.Coroutines.Core.Jvm": {
"type": "Transitive",
"resolved": "1.11.0.1",
"contentHash": "PfimvxVnQSf3E6eXnWIw3ZW60cSDCNAFNnouf0Bw19/GoHUFnDRIjUrOccnX5TR+V7rvfGP2c3nblijKyPxM+w==",
"dependencies": {
"Xamarin.Jetbrains.Annotations": "26.1.0.2",
"Xamarin.Kotlin.StdLib": "2.4.0.1"
}
},
"Xamarin.KotlinX.Serialization.Core": {
"type": "Transitive",
"resolved": "1.11.0.1",
"contentHash": "Isdm4DA4BNRzQr+bX9p31taWWrf0pG0icOpq7kIeXb6qOR96kI6wi1nOlYt46mFtTpbJ2UyY5hkhJC+C+NknwA==",
"dependencies": {
"Xamarin.KotlinX.Serialization.Core.Jvm": "1.11.0.1"
}
},
"Xamarin.KotlinX.Serialization.Core.Jvm": {
"type": "Transitive",
"resolved": "1.11.0.1",
"contentHash": "UKb1C/+LxDhbLU2uQlR/4hEnbMbVir02j6jZE78H2TfP24E7NzXhIlCniFw6qp1nrSRCI4ZSs0dpxHvTANNoZQ==",
"dependencies": {
"Xamarin.Kotlin.StdLib": "2.4.0.1"
}
}
},
"net10.0-android36.0/android-arm64": {},