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.App;
using Android.Content.PM; using Android.Content.PM;
using Android.OS;
using AndroidX.Core.App;
using Avalonia; using Avalonia;
using Avalonia.Android; using Avalonia.Android;
using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.ApplicationLifetimes;
using Java.Security;
using Environment = System.Environment;
using Permission = Android.Content.PM.Permission;
namespace CritterFolio.Android; namespace CritterFolio.Android;
@@ -21,6 +28,12 @@ public class MainActivity : AvaloniaMainActivity<App>
.WithInterFont(); .WithInterFont();
} }
protected override void OnCreate(Bundle? savedInstanceState)
{
base.OnCreate(savedInstanceState);
RequestStorageAccess();
}
public override void OnBackPressed() public override void OnBackPressed()
{ {
if (Sys.Navigation != null && Sys.Navigation.IsLastPage()) if (Sys.Navigation != null && Sys.Navigation.IsLastPage())
@@ -38,4 +51,39 @@ public class MainActivity : AvaloniaMainActivity<App>
Sys.Navigation?.PopPage(); 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"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:installLocation="auto"> <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.INTERNET" />-->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application android:label="CritterFolio" android:icon="@drawable/Icon" /> <application android:label="CritterFolio" android:icon="@drawable/Icon" />
</manifest> </manifest>

View File

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

View File

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

View File

@@ -14,6 +14,8 @@ public partial class DocumentItem : UserControl
{ {
private Document? _document; private Document? _document;
public event EventHandler? DocumentWasDeleted;
public DocumentItem() public DocumentItem()
{ {
InitializeComponent(); InitializeComponent();
@@ -35,7 +37,10 @@ public partial class DocumentItem : UserControl
if (File.Exists(_document.Path)) if (File.Exists(_document.Path))
{ {
var result = await DialogHelper.ShowConfirmationDialog("Are you sure you want to delete this document?"); 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 else
{ {
@@ -46,28 +51,48 @@ public partial class DocumentItem : UserControl
private async void ViewButtonOnClick(object? sender, RoutedEventArgs e) private async void ViewButtonOnClick(object? sender, RoutedEventArgs e)
{ {
if (_document is null) return; if (_document is null) return;
if (File.Exists(_document.Path))
{
var launcher = TopLevel.GetTopLevel(this)?.Launcher;
if (launcher != null) if (!File.Exists(_document.Path))
{
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
{ {
await DialogHelper.ShowMessage("Document does not exist"); await DialogHelper.ShowMessage("Document does not exist");
Logger.LogToFile("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"> <Grid ColumnDefinitions="Auto, *, Auto" Margin="0, 10">
<Label Grid.Column="0" Classes="heading1">Documents</Label> <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> </Grid>
<StackPanel x:Name="DocsStack" HorizontalAlignment="Stretch" Margin="20, 5"/>
</StackPanel> </StackPanel>
</ScrollViewer> </ScrollViewer>

View File

@@ -9,6 +9,7 @@ using Avalonia.Media;
using Avalonia.Media.Imaging; using Avalonia.Media.Imaging;
using Avalonia.Platform.Storage; using Avalonia.Platform.Storage;
using Avalonia.Threading; using Avalonia.Threading;
using CritterFolio.Controls;
using CritterFolio.DataModels; using CritterFolio.DataModels;
using CritterFolio.Services; using CritterFolio.Services;
@@ -18,6 +19,7 @@ public partial class CritterPage : Page
{ {
private Critter? _critter; private Critter? _critter;
private List<CritterComboBoxDisplay> _critterDisplays = []; private List<CritterComboBoxDisplay> _critterDisplays = [];
private List<Document> _documents = [];
public CritterPage() public CritterPage()
{ {
@@ -31,9 +33,13 @@ public partial class CritterPage : Page
PfpButton.Click += OpenFileButton_Clicked; PfpButton.Click += OpenFileButton_Clicked;
GenderOption.ItemsSource = Enum.GetNames(typeof(Gender)); GenderOption.ItemsSource = Enum.GetNames(typeof(Gender));
AddDocButton.Click += AddDocButtonOnClick;
await UpdateInfo(); await UpdateInfo();
} }
public override void Refresh() public override void Refresh()
{ {
base.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."); 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() private void CreateHeaderButtons()
{ {
var saveBttn = new Button() var saveBttn = new Button()
@@ -264,6 +298,7 @@ public partial class CritterPage : Page
private async void OpenFileButton_Clicked(object? sender, RoutedEventArgs args) private async void OpenFileButton_Clicked(object? sender, RoutedEventArgs args)
{ {
var topLevel = TopLevel.GetTopLevel(this); var topLevel = TopLevel.GetTopLevel(this);
if (topLevel is null) return;
var files = await topLevel.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions var files = await topLevel.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
{ {
@@ -289,8 +324,41 @@ public partial class CritterPage : Page
{ {
await DialogHelper.ShowMessage(e.ToString()); 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
{
}