[client] Show resource preview for all but text [#32]

This commit is contained in:
Abhishek Shroff
2025-08-01 01:07:17 +05:30
parent 5d517326aa
commit 95fb9ff36f
12 changed files with 78 additions and 76 deletions
+4 -6
View File
@@ -24,7 +24,7 @@ class AppDatabase extends _$AppDatabase {
AppDatabase.fromExecutor({required this.accountId, required QueryExecutor executor}) : super(executor);
@override
int get schemaVersion => 13;
int get schemaVersion => 14;
@override
MigrationStrategy get migration => MigrationStrategy(
@@ -63,12 +63,10 @@ class AppDatabase extends _$AppDatabase {
await m.create(resources);
await m.create(resourceVersions);
}
if (from < 12) {
await m.drop(resourceVersions);
await m.create(resourceVersions);
}
if (from < 13) {
if (from < 14) {
await m.drop(resources);
await m.drop(resourceVersions);
await m.create(resources);
await m.create(resourceVersions);
}
},
+24 -22
View File
@@ -1180,10 +1180,11 @@ class ResourceVersions extends Table
static const VerificationMeta _previewMeta =
const VerificationMeta('preview');
late final GeneratedColumn<bool> preview = GeneratedColumn<bool>(
'preview', aliasedName, false,
'preview', aliasedName, true,
type: DriftSqlType.bool,
requiredDuringInsert: true,
$customConstraints: 'NOT NULL CHECK (preview IN (0, 1))');
requiredDuringInsert: false,
$customConstraints: 'DEFAULT 0 CHECK (preview IN (0, 1))',
defaultValue: const CustomExpression('0'));
static const VerificationMeta _sizeMeta = const VerificationMeta('size');
late final GeneratedColumn<int> size = GeneratedColumn<int>(
'size', aliasedName, false,
@@ -1244,8 +1245,6 @@ class ResourceVersions extends Table
if (data.containsKey('preview')) {
context.handle(_previewMeta,
preview.isAcceptableOrUnknown(data['preview']!, _previewMeta));
} else if (isInserting) {
context.missing(_previewMeta);
}
if (data.containsKey('size')) {
context.handle(
@@ -1283,7 +1282,7 @@ class ResourceVersions extends Table
deleted: attachedDatabase.typeMapping
.read(DriftSqlType.bool, data['${effectivePrefix}deleted'])!,
preview: attachedDatabase.typeMapping
.read(DriftSqlType.bool, data['${effectivePrefix}preview'])!,
.read(DriftSqlType.bool, data['${effectivePrefix}preview']),
size: attachedDatabase.typeMapping
.read(DriftSqlType.int, data['${effectivePrefix}size'])!,
mimeType: attachedDatabase.typeMapping
@@ -1307,7 +1306,7 @@ class ResourceVersion extends DataClass implements Insertable<ResourceVersion> {
final String resourceId;
final DateTime created;
final bool deleted;
final bool preview;
final bool? preview;
final int size;
final String mimeType;
final String sha256;
@@ -1316,7 +1315,7 @@ class ResourceVersion extends DataClass implements Insertable<ResourceVersion> {
required this.resourceId,
required this.created,
required this.deleted,
required this.preview,
this.preview,
required this.size,
required this.mimeType,
required this.sha256});
@@ -1327,7 +1326,9 @@ class ResourceVersion extends DataClass implements Insertable<ResourceVersion> {
map['resource_id'] = Variable<String>(resourceId);
map['created'] = Variable<DateTime>(created);
map['deleted'] = Variable<bool>(deleted);
map['preview'] = Variable<bool>(preview);
if (!nullToAbsent || preview != null) {
map['preview'] = Variable<bool>(preview);
}
map['size'] = Variable<int>(size);
map['mime_type'] = Variable<String>(mimeType);
map['sha256'] = Variable<String>(sha256);
@@ -1340,7 +1341,9 @@ class ResourceVersion extends DataClass implements Insertable<ResourceVersion> {
resourceId: Value(resourceId),
created: Value(created),
deleted: Value(deleted),
preview: Value(preview),
preview: preview == null && nullToAbsent
? const Value.absent()
: Value(preview),
size: Value(size),
mimeType: Value(mimeType),
sha256: Value(sha256),
@@ -1355,7 +1358,7 @@ class ResourceVersion extends DataClass implements Insertable<ResourceVersion> {
resourceId: serializer.fromJson<String>(json['resource_id']),
created: serializer.fromJson<DateTime>(json['created']),
deleted: serializer.fromJson<bool>(json['deleted']),
preview: serializer.fromJson<bool>(json['preview']),
preview: serializer.fromJson<bool?>(json['preview']),
size: serializer.fromJson<int>(json['size']),
mimeType: serializer.fromJson<String>(json['mime_type']),
sha256: serializer.fromJson<String>(json['sha256']),
@@ -1369,7 +1372,7 @@ class ResourceVersion extends DataClass implements Insertable<ResourceVersion> {
'resource_id': serializer.toJson<String>(resourceId),
'created': serializer.toJson<DateTime>(created),
'deleted': serializer.toJson<bool>(deleted),
'preview': serializer.toJson<bool>(preview),
'preview': serializer.toJson<bool?>(preview),
'size': serializer.toJson<int>(size),
'mime_type': serializer.toJson<String>(mimeType),
'sha256': serializer.toJson<String>(sha256),
@@ -1381,7 +1384,7 @@ class ResourceVersion extends DataClass implements Insertable<ResourceVersion> {
String? resourceId,
DateTime? created,
bool? deleted,
bool? preview,
Value<bool?> preview = const Value.absent(),
int? size,
String? mimeType,
String? sha256}) =>
@@ -1390,7 +1393,7 @@ class ResourceVersion extends DataClass implements Insertable<ResourceVersion> {
resourceId: resourceId ?? this.resourceId,
created: created ?? this.created,
deleted: deleted ?? this.deleted,
preview: preview ?? this.preview,
preview: preview.present ? preview.value : this.preview,
size: size ?? this.size,
mimeType: mimeType ?? this.mimeType,
sha256: sha256 ?? this.sha256,
@@ -1446,7 +1449,7 @@ class ResourceVersionsCompanion extends UpdateCompanion<ResourceVersion> {
final Value<String> resourceId;
final Value<DateTime> created;
final Value<bool> deleted;
final Value<bool> preview;
final Value<bool?> preview;
final Value<int> size;
final Value<String> mimeType;
final Value<String> sha256;
@@ -1467,7 +1470,7 @@ class ResourceVersionsCompanion extends UpdateCompanion<ResourceVersion> {
required String resourceId,
required DateTime created,
required bool deleted,
required bool preview,
this.preview = const Value.absent(),
required int size,
required String mimeType,
required String sha256,
@@ -1476,7 +1479,6 @@ class ResourceVersionsCompanion extends UpdateCompanion<ResourceVersion> {
resourceId = Value(resourceId),
created = Value(created),
deleted = Value(deleted),
preview = Value(preview),
size = Value(size),
mimeType = Value(mimeType),
sha256 = Value(sha256);
@@ -1509,7 +1511,7 @@ class ResourceVersionsCompanion extends UpdateCompanion<ResourceVersion> {
Value<String>? resourceId,
Value<DateTime>? created,
Value<bool>? deleted,
Value<bool>? preview,
Value<bool?>? preview,
Value<int>? size,
Value<String>? mimeType,
Value<String>? sha256,
@@ -3763,7 +3765,7 @@ typedef $ResourceVersionsCreateCompanionBuilder = ResourceVersionsCompanion
required String resourceId,
required DateTime created,
required bool deleted,
required bool preview,
Value<bool?> preview,
required int size,
required String mimeType,
required String sha256,
@@ -3775,7 +3777,7 @@ typedef $ResourceVersionsUpdateCompanionBuilder = ResourceVersionsCompanion
Value<String> resourceId,
Value<DateTime> created,
Value<bool> deleted,
Value<bool> preview,
Value<bool?> preview,
Value<int> size,
Value<String> mimeType,
Value<String> sha256,
@@ -3982,7 +3984,7 @@ class $ResourceVersionsTableManager extends RootTableManager<
Value<String> resourceId = const Value.absent(),
Value<DateTime> created = const Value.absent(),
Value<bool> deleted = const Value.absent(),
Value<bool> preview = const Value.absent(),
Value<bool?> preview = const Value.absent(),
Value<int> size = const Value.absent(),
Value<String> mimeType = const Value.absent(),
Value<String> sha256 = const Value.absent(),
@@ -4004,7 +4006,7 @@ class $ResourceVersionsTableManager extends RootTableManager<
required String resourceId,
required DateTime created,
required bool deleted,
required bool preview,
Value<bool?> preview = const Value.absent(),
required int size,
required String mimeType,
required String sha256,
+2 -2
View File
@@ -17,10 +17,10 @@ CREATE TABLE IF NOT EXISTS resource_versions(
resource_id TEXT NOT NULL REFERENCES resources (id),
created DATETIME NOT NULL,
deleted BOOLEAN NOT NULL CHECK ("deleted" IN (0, 1)),
preview BOOLEAN NOT NULL CHECK ("preview" IN (0, 1)),
preview BOOLEAN DEFAULT 0 CHECK ("preview" IN (0, 1)),
size INTEGER NOT NULL,
mime_type TEXT NOT NULL,
sha256 TEXT NOT NULL,
sha256 TEXT NOT NULL
);
-- Use `UNION` instead of `UNION ALL` to avoid infinite loops in case of
@@ -6,12 +6,13 @@ import 'package:offtheline/offtheline.dart';
class ResourceContentsRequest extends ApiRequest {
final String resourceId;
final String? versionId;
final bool loadPreview;
const ResourceContentsRequest(this.resourceId, {this.versionId});
const ResourceContentsRequest(this.resourceId, {this.versionId, this.loadPreview = false});
@override
BaseRequest createRequest(ApiClient api, {Uint8List? data}) {
final uri = api.createUriBuilder('/api/v1/fs/contents/$resourceId:');
final uri = api.createUriBuilder('/api/v1/fs/${loadPreview ? 'preview' : 'contents'}/$resourceId:');
if (versionId != null) {
uri.queryParameters['version'] = versionId!;
}
+1 -1
View File
@@ -17,7 +17,7 @@ import 'package:phylum/ui/login/reset_password_page.dart';
import 'package:phylum/ui/menu/menu_option.dart';
import 'package:phylum/ui/menu/option_groups.dart';
import 'package:phylum/ui/open/open_resource_page.dart';
import 'package:phylum/ui/file_preview/file_preview_page.dart';
import 'package:phylum/ui/file_viewer/file_viewer_page.dart';
import 'package:provider/provider.dart';
sealed class PhylumRoute {
+1 -1
View File
@@ -8,7 +8,7 @@ import 'package:phylum/libphylum/phylum_account.dart';
import 'package:phylum/ui/app/routes.dart';
import 'package:phylum/ui/explorer/paste_helpers.dart';
import 'package:phylum/ui/explorer/selection_mode.dart';
import 'package:phylum/ui/file_preview/file_preview_dialog.dart';
import 'package:phylum/ui/file_viewer/file_viewer_dialog.dart';
import 'package:phylum/util/dialogs.dart';
import 'package:phylum/util/upload_utils.dart';
import 'package:provider/provider.dart';
@@ -5,7 +5,7 @@ import 'package:phylum/integrations/download_manager.dart';
import 'package:phylum/libphylum/db/db.dart';
import 'package:phylum/libphylum/db/resource_helpers.dart';
import 'package:phylum/libphylum/phylum_account.dart';
import 'package:phylum/ui/file_preview/file_preview_dialog.dart';
import 'package:phylum/ui/file_viewer/file_viewer_dialog.dart';
import 'package:phylum/util/file_size.dart';
import 'package:phylum/util/time.dart';
import 'package:provider/provider.dart';
@@ -13,25 +13,26 @@ import 'package:phylum/libphylum/requests/resource_contents_request.dart';
import 'package:phylum/libphylum/responses/responses.dart';
import 'package:phylum/ui/common/responsive_dialog.dart';
import 'package:phylum/ui/explorer/resource_info_view.dart';
import 'package:phylum/ui/file_preview/versioned_resource.dart';
import 'package:phylum/ui/file_viewer/versioned_resource.dart';
import 'package:printing/printing.dart';
import 'package:provider/provider.dart';
const maxPreviewSize = 1 * 1024 * 1024;
class FilePreview extends StatefulWidget {
class FileViewer extends StatefulWidget {
final bool transparent;
final VersionedResource resource;
const FilePreview({super.key, required this.transparent, required this.resource});
const FileViewer({super.key, required this.transparent, required this.resource});
@override
State<FilePreview> createState() => _FilePreviewState();
State<FileViewer> createState() => _FileViewerState();
}
class _FilePreviewState extends State<FilePreview> {
class _FileViewerState extends State<FileViewer> {
late VersionedResource _resource;
String? _error;
Widget Function(Uint8List)? _buildPreview;
Widget Function(Uint8List)? _buildViewer;
bool _loadPreview = false;
@override
void initState() {
@@ -40,7 +41,7 @@ class _FilePreviewState extends State<FilePreview> {
}
@override
void didUpdateWidget(covariant FilePreview oldWidget) {
void didUpdateWidget(covariant FileViewer oldWidget) {
super.didUpdateWidget(oldWidget);
setState(() {
_updateResource(widget.resource);
@@ -50,26 +51,19 @@ class _FilePreviewState extends State<FilePreview> {
void _updateResource(VersionedResource resource) {
_resource = resource;
if (_resource.r.dir) {
_buildPreview = null;
_error = 'Cannot preview directory';
_buildViewer = null;
_error = 'Cannot view directory';
} else {
_buildPreview = null;
if (_resource.v.mimeType.startsWith('image/')) {
_buildPreview = buildImagePreview;
}
_buildViewer = null;
context.read<PhylumAccount>().db.markResourceAccess(_resource.r.id);
if (_resource.v.mimeType.startsWith('text/')) {
_buildPreview = buildTextPreview;
}
if (_resource.v.mimeType == 'application/pdf') {
_buildPreview = buildPdfPreview;
}
if (_buildPreview == null) {
_error = 'Cannot generate preview';
} else if (_resource.v.size > maxPreviewSize) {
_buildPreview = null;
_error = 'File too large to preview';
_buildViewer = buildTextPreview;
_loadPreview = false;
} else if (_resource.v.preview == true) {
_buildViewer = buildImagePreview;
_loadPreview = true;
} else {
context.read<PhylumAccount>().db.markResourceAccess(_resource.r.id);
_error = 'Preview not available';
}
}
}
@@ -100,34 +94,40 @@ class _FilePreviewState extends State<FilePreview> {
backgroundColor: Colors.transparent,
),
body: Center(
child: _buildPreview == null
child: _buildViewer == null
? Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(_error ?? 'Unknown error', style: TextStyle(fontSize: 18)),
))
: ResourcePreviewDownloader(
: ResourceLoader(
resourceId: _resource.r.id,
versionId: _resource.v.id,
buildPreview: _buildPreview!,
buildViewer: _buildViewer!,
loadPreview: _loadPreview,
)),
);
}
}
class ResourcePreviewDownloader extends StatefulWidget {
class ResourceLoader extends StatefulWidget {
final String resourceId;
final String versionId;
final Widget Function(Uint8List data) buildPreview;
final Widget Function(Uint8List data) buildViewer;
final bool loadPreview;
ResourcePreviewDownloader({required this.resourceId, required this.buildPreview, required this.versionId})
: super(key: ValueKey(versionId));
ResourceLoader({
required this.resourceId,
required this.buildViewer,
required this.versionId,
required this.loadPreview,
}) : super(key: ValueKey(versionId));
@override
State<ResourcePreviewDownloader> createState() => _ResourcePreviewDownloaderState();
State<ResourceLoader> createState() => _ResourceLoaderState();
}
class _ResourcePreviewDownloaderState extends State<ResourcePreviewDownloader> {
class _ResourceLoaderState extends State<ResourceLoader> {
bool _downloading = false;
String? _error;
double? _progress;
@@ -149,7 +149,7 @@ class _ResourcePreviewDownloaderState extends State<ResourcePreviewDownloader> {
if (_data != null) {
return SizedBox(
width: 800,
child: widget.buildPreview(_data!),
child: widget.buildViewer(_data!),
);
}
if (!_downloading) {
@@ -176,6 +176,7 @@ class _ResourcePreviewDownloaderState extends State<ResourcePreviewDownloader> {
final response = await account.apiClient.dispatchRequestRaw(ResourceContentsRequest(
widget.resourceId,
versionId: widget.versionId,
loadPreview: widget.loadPreview,
));
if (response.statusCode < 200 || response.statusCode > 300) {
final error = PhylumApiErrorResponse.fromResponseBody(await response.bodyString()).message;
@@ -2,8 +2,8 @@ import 'package:flutter/material.dart';
import 'package:phylum/libphylum/db/db.dart';
import 'package:phylum/libphylum/phylum_account.dart';
import 'package:phylum/ui/app/shortcuts.dart';
import 'package:phylum/ui/file_preview/file_preview.dart';
import 'package:phylum/ui/file_preview/versioned_resource.dart';
import 'package:phylum/ui/file_viewer/file_viewer.dart';
import 'package:phylum/ui/file_viewer/versioned_resource.dart';
import 'package:provider/provider.dart';
class FilePreviewDialog extends StatefulWidget {
@@ -89,7 +89,7 @@ class _FilePreviewDialogState extends State<FilePreviewDialog> {
child: Focus(
focusNode: _focusNode,
autofocus: true,
child: FilePreview(transparent: true, resource: widget.resources[index]),
child: FileViewer(transparent: true, resource: widget.resources[index]),
),
),
);
@@ -5,8 +5,8 @@ import 'package:phylum/libphylum/db/resource_helpers.dart';
import 'package:phylum/libphylum/phylum_account.dart';
import 'package:phylum/ui/app/router.dart';
import 'package:phylum/ui/app/routes.dart';
import 'package:phylum/ui/file_preview/file_preview.dart';
import 'package:phylum/ui/file_preview/versioned_resource.dart';
import 'package:phylum/ui/file_viewer/file_viewer.dart';
import 'package:phylum/ui/file_viewer/versioned_resource.dart';
import 'package:provider/provider.dart';
class FilePreviewPage extends StatefulWidget {
@@ -62,7 +62,7 @@ class _ResourcePreviewDialogState extends State<FilePreviewPage> {
@override
Widget build(BuildContext context) {
if (resource != null && version != null) {
return FilePreview(
return FileViewer(
transparent: true,
resource: VersionedResource(r: resource!, v: version!, showTimestamp: false),
);
+1 -1
View File
@@ -5,7 +5,7 @@ import 'package:phylum/ui/app/shortcuts.dart';
import 'package:phylum/ui/app/router.dart';
import 'package:phylum/ui/layout/search.dart';
import 'package:phylum/ui/app/routes.dart';
import 'package:phylum/ui/file_preview/file_preview_dialog.dart';
import 'package:phylum/ui/file_viewer/file_viewer_dialog.dart';
import 'package:phylum/util/upload_utils.dart';
import 'package:provider/provider.dart';