Cant get android URI permissions to work...

This commit is contained in:
2026-01-06 23:18:07 -06:00
parent ab800124e8
commit ebc2e3a187
8 changed files with 176 additions and 24 deletions

View File

@@ -1,9 +1,16 @@
using System;
using System.Collections.Generic;
using Android;
using Android.App;
using Android.Content.PM;
using Android.OS;
using AndroidX.Core.App;
using Avalonia;
using Avalonia.Android;
using Avalonia.Controls.ApplicationLifetimes;
using Java.Security;
using Environment = System.Environment;
using Permission = Android.Content.PM.Permission;
namespace CritterFolio.Android;
@@ -21,6 +28,12 @@ public class MainActivity : AvaloniaMainActivity<App>
.WithInterFont();
}
protected override void OnCreate(Bundle? savedInstanceState)
{
base.OnCreate(savedInstanceState);
RequestStorageAccess();
}
public override void OnBackPressed()
{
if (Sys.Navigation != null && Sys.Navigation.IsLastPage())
@@ -38,4 +51,39 @@ public class MainActivity : AvaloniaMainActivity<App>
Sys.Navigation?.PopPage();
}
}
public void RequestStorageAccess()
{
string[] permissions;
if (Build.VERSION.SdkInt >= BuildVersionCodes.Tiramisu)
{
permissions = new string[]
{
Manifest.Permission.ReadMediaImages,
Manifest.Permission.ReadMediaVideo
};
}
else
{
permissions = new string[] { Manifest.Permission.ReadExternalStorage };
}
List<string> permissionsToRequest = new List<string>();
foreach (var permission in permissions)
{
if (CheckSelfPermission(permission) != Permission.Granted)
{
permissionsToRequest.Add(permission);
}
}
if (permissionsToRequest.Count > 0)
{
RequestPermissions(permissionsToRequest.ToArray(), 1000);
}
}
}

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:installLocation="auto">
<!-- <uses-permission android:name="android.permission.INTERNET" />-->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application android:label="CritterFolio" android:icon="@drawable/Icon" />
</manifest>

View File

@@ -40,6 +40,7 @@ public partial class App : Application
// Other init stuff here
Directory.CreateDirectory(Sys.UserDataPath);
Directory.CreateDirectory(Path.Combine(Sys.UserDataPath, "ImageCache"));
Directory.CreateDirectory(Path.Combine(Sys.UserDataPath, "DocumentCache"));
base.OnFrameworkInitializationCompleted();

View File

@@ -2,7 +2,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
mc:Ignorable="d" d:DesignWidth="720" d:DesignHeight="1280"
x:Class="CritterFolio.Controls.DocumentItem">
<Grid HorizontalAlignment="Stretch" Height="50"
Background="{DynamicResource AccentBackground}"
@@ -20,7 +20,9 @@
Content="Document"
Foreground="{DynamicResource PrimaryForeground}"
VerticalAlignment="Center"
FontSize="20" />
FontSize="20"
MaxWidth="150"
/>
</StackPanel>
<StackPanel Grid.Column="2" Orientation="Horizontal">
<Button x:Name="ViewButton" Classes="icon" Content="&#xe220;" />

View File

@@ -13,6 +13,8 @@ namespace CritterFolio.Controls;
public partial class DocumentItem : UserControl
{
private Document? _document;
public event EventHandler? DocumentWasDeleted;
public DocumentItem()
{
@@ -35,7 +37,10 @@ public partial class DocumentItem : UserControl
if (File.Exists(_document.Path))
{
var result = await DialogHelper.ShowConfirmationDialog("Are you sure you want to delete this document?");
if (result) File.Delete(_document.Path);
if (!result) return;
await DatabaseService.DeleteDocument(_document.Id);
File.Delete(_document.Path);
DocumentWasDeleted?.Invoke(this, EventArgs.Empty);
}
else
{
@@ -46,28 +51,48 @@ public partial class DocumentItem : UserControl
private async void ViewButtonOnClick(object? sender, RoutedEventArgs e)
{
if (_document is null) return;
if (File.Exists(_document.Path))
{
var launcher = TopLevel.GetTopLevel(this)?.Launcher;
if (launcher != null)
{
var launchResult = await launcher.LaunchUriAsync(new Uri(_document.Path));
if (launchResult) return;
await DialogHelper.ShowMessage($"Couldn't launch the file: {_document.Path}");
Logger.LogToFile($"[ERROR] Couldn't launch the file: {_document.Path}");
}
else
{
await DialogHelper.ShowMessage("Launcher was null for some reason. Please report this to the developer.");
Logger.LogToFile("Launcher was null for some reason. Please report this to the developer.");
}
}
else
if (!File.Exists(_document.Path))
{
await DialogHelper.ShowMessage("Document does not exist");
Logger.LogToFile("Document does not exist");
return;
}
var topLevel = TopLevel.GetTopLevel(this);
if (topLevel?.Launcher is null)
{
await DialogHelper.ShowMessage("Launcher unavailable");
Logger.LogToFile("Launcher unavailable");
return;
}
try
{
var storageFile =
await topLevel.StorageProvider.TryGetFileFromPathAsync(new Uri(_document.Path));
if (storageFile is null)
{
await DialogHelper.ShowMessage("Unable to access document");
Logger.LogToFile("Unable to access document");
return;
}
var success = await topLevel.Launcher.LaunchFileAsync(storageFile);
if (success) return;
await DialogHelper.ShowMessage($"Couldn't open file");
Logger.LogToFile($"[ERROR] Couldn't open file: {_document.Path}");
}
catch (Exception ex)
{
await DialogHelper.ShowMessage($"[ERROR] Couldn't open {_document.Path}: Due to current limitations with Android's newer external permissions policies. This will be fixed in a later release.");
Logger.LogToFile($"[ERROR] Couldn't open file: {ex.Message}");
}
}
}

View File

@@ -41,9 +41,10 @@
<Grid ColumnDefinitions="Auto, *, Auto" Margin="0, 10">
<Label Grid.Column="0" Classes="heading1">Documents</Label>
<Button Grid.Column="2">Add New</Button>
<Button x:Name="AddDocButton" Grid.Column="2">Add New</Button>
</Grid>
<StackPanel x:Name="DocsStack" HorizontalAlignment="Stretch" Margin="20, 5"/>
</StackPanel>
</ScrollViewer>

View File

@@ -9,6 +9,7 @@ using Avalonia.Media;
using Avalonia.Media.Imaging;
using Avalonia.Platform.Storage;
using Avalonia.Threading;
using CritterFolio.Controls;
using CritterFolio.DataModels;
using CritterFolio.Services;
@@ -18,6 +19,7 @@ public partial class CritterPage : Page
{
private Critter? _critter;
private List<CritterComboBoxDisplay> _critterDisplays = [];
private List<Document> _documents = [];
public CritterPage()
{
@@ -31,9 +33,13 @@ public partial class CritterPage : Page
PfpButton.Click += OpenFileButton_Clicked;
GenderOption.ItemsSource = Enum.GetNames(typeof(Gender));
AddDocButton.Click += AddDocButtonOnClick;
await UpdateInfo();
}
public override void Refresh()
{
base.Refresh();
@@ -125,8 +131,36 @@ public partial class CritterPage : Page
Console.WriteLine($"Pfp image for {_critter.Id} was moved, deleted, or is corrupt.");
}
}
RegenDocsList();
}
private async void RegenDocsList()
{
_documents.Clear();
var docs = await DatabaseService.GetAllDocumentsForCritter(_critter.Id);
_documents.AddRange(docs);
DocsStack.Children.Clear();
foreach (var document in _documents)
{
CreateDocItem(document);
}
}
private void CreateDocItem(Document document)
{
var item = new DocumentItem();
item.Init(document);
DocsStack.Children.Add(item);
item.DocumentWasDeleted += async (sender, args) =>
{
RegenDocsList();
};
}
private void CreateHeaderButtons()
{
var saveBttn = new Button()
@@ -264,6 +298,7 @@ public partial class CritterPage : Page
private async void OpenFileButton_Clicked(object? sender, RoutedEventArgs args)
{
var topLevel = TopLevel.GetTopLevel(this);
if (topLevel is null) return;
var files = await topLevel.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
{
@@ -289,8 +324,41 @@ public partial class CritterPage : Page
{
await DialogHelper.ShowMessage(e.ToString());
}
}
private async void AddDocButtonOnClick(object? sender, RoutedEventArgs e)
{
if (_critter is null) return;
var topLevel = TopLevel.GetTopLevel(this);
if (topLevel is null) return;
var files = await topLevel.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
{
Title = "Add Document",
AllowMultiple = false,
});
if (files.Count == 0) return;
var file = files[0];
var newFileLocation = Path.Combine(Sys.UserDataPath, "DocumentCache", $"{_critter.Id}-{_critter.Name}-{file.Name}");
await using var sourceStream = await file.OpenReadAsync();
await using var destStream = File.Create(newFileLocation);
await sourceStream.CopyToAsync(destStream);
var doc = new Document()
{
Name = file.Name,
CritterId = _critter.Id,
Path = newFileLocation,
Description = ""
};
await DatabaseService.AddDocument(doc);
CreateDocItem(doc);
}
}

View File

@@ -0,0 +1,6 @@
namespace CritterFolio.Services;
public static class AndroidPermissions
{
}