Cant get android URI permissions to work...
This commit is contained in:
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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="" />
|
<Button x:Name="ViewButton" Classes="icon" Content="" />
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ namespace CritterFolio.Controls;
|
|||||||
public partial class DocumentItem : UserControl
|
public partial class DocumentItem : UserControl
|
||||||
{
|
{
|
||||||
private Document? _document;
|
private Document? _document;
|
||||||
|
|
||||||
|
public event EventHandler? DocumentWasDeleted;
|
||||||
|
|
||||||
public DocumentItem()
|
public DocumentItem()
|
||||||
{
|
{
|
||||||
@@ -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}");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace CritterFolio.Services;
|
||||||
|
|
||||||
|
public static class AndroidPermissions
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user