obsolete files: give error hint on viewer, allow deleting from media store

This commit is contained in:
Thibault Deckers 2021-01-30 19:12:11 +09:00
parent 34b6ef0428
commit f6434f0b5f
3 changed files with 48 additions and 14 deletions

View file

@ -186,7 +186,7 @@ class MediaStoreImageProvider : ImageProvider() {
override suspend fun delete(context: Context, uri: Uri, path: String?) { override suspend fun delete(context: Context, uri: Uri, path: String?) {
path ?: throw Exception("failed to delete file because path is null") path ?: throw Exception("failed to delete file because path is null")
if (requireAccessPermission(context, path)) { if (File(path).exists() && requireAccessPermission(context, path)) {
// if the file is on SD card, calling the content resolver `delete()` removes the entry from the Media Store // if the file is on SD card, calling the content resolver `delete()` removes the entry from the Media Store
// but it doesn't delete the file, even if the app has the permission // but it doesn't delete the file, even if the app has the permission
val df = getDocumentFile(context, path, uri) val df = getDocumentFile(context, path, uri)

View file

@ -129,7 +129,10 @@ class _EntryPageViewState extends State<EntryPageView> {
} else if (entry.canDecode) { } else if (entry.canDecode) {
child = _buildRasterView(); child = _buildRasterView();
} }
child ??= ErrorView(onTap: () => onTap?.call(null)); child ??= ErrorView(
entry: entry,
onTap: () => onTap?.call(null),
);
return widget.heroTag != null return widget.heroTag != null
? Hero( ? Hero(
@ -146,7 +149,10 @@ class _EntryPageViewState extends State<EntryPageView> {
child: RasterImageView( child: RasterImageView(
entry: entry, entry: entry,
viewStateNotifier: _viewStateNotifier, viewStateNotifier: _viewStateNotifier,
errorBuilder: (context, error, stackTrace) => ErrorView(onTap: () => onTap?.call(null)), errorBuilder: (context, error, stackTrace) => ErrorView(
entry: entry,
onTap: () => onTap?.call(null),
),
), ),
); );
} }

View file

@ -1,26 +1,54 @@
import 'dart:io';
import 'package:aves/model/entry.dart';
import 'package:aves/theme/icons.dart'; import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/collection/empty.dart'; import 'package:aves/widgets/collection/empty.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class ErrorView extends StatelessWidget { class ErrorView extends StatefulWidget {
final AvesEntry entry;
final VoidCallback onTap; final VoidCallback onTap;
const ErrorView({@required this.onTap}); const ErrorView({
@required this.entry,
@required this.onTap,
});
@override
_ErrorViewState createState() => _ErrorViewState();
}
class _ErrorViewState extends State<ErrorView> {
Future<bool> _exists;
AvesEntry get entry => widget.entry;
@override
void initState() {
super.initState();
_exists = entry.path != null ? File(entry.path).exists() : SynchronousFuture(true);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GestureDetector( return GestureDetector(
onTap: () => onTap?.call(), onTap: () => widget.onTap?.call(),
// use a `Container` with a dummy color to make it expand // use container to expand constraints, so that the user can tap anywhere
// so that we can also detect taps around the title `Text`
child: Container( child: Container(
color: Colors.transparent, // opaque to cover potential lower quality layer below
child: EmptyContent( color: Colors.black,
child: FutureBuilder<bool>(
future: _exists,
builder: (context, snapshot) {
if (snapshot.connectionState != ConnectionState.done) return SizedBox();
final exists = snapshot.data;
return EmptyContent(
icon: AIcons.error, icon: AIcons.error,
text: 'Oops!', text: exists ? 'Oops!' : 'The file no longer exists.',
alignment: Alignment.center, alignment: Alignment.center,
), );
}),
), ),
); );
} }