mirror of
https://codeberg.org/shroff/phylum.git
synced 2026-02-07 03:59:14 -06:00
222 lines
10 KiB
Dart
222 lines
10 KiB
Dart
import 'package:cached_network_image/cached_network_image.dart';
|
|
import 'package:cached_network_image_platform_interface/cached_network_image_platform_interface.dart';
|
|
import 'package:drift/drift.dart' show TableOrViewStatements;
|
|
import 'package:flutter/material.dart';
|
|
import 'package:phylum/libphylum/actions/action_resource.dart';
|
|
import 'package:phylum/libphylum/db/db.dart';
|
|
import 'package:phylum/libphylum/db/imgres.dart';
|
|
import 'package:phylum/libphylum/phylum_account.dart';
|
|
import 'package:phylum/libphylum/phylum_api_types.dart';
|
|
import 'package:phylum/ui/explorer/resource_icon_extension.dart';
|
|
import 'package:phylum/ui/explorer/resource_sync_state.dart';
|
|
import 'package:phylum/util/file_size.dart';
|
|
import 'package:phylum/util/time.dart';
|
|
import 'package:provider/provider.dart';
|
|
|
|
import 'explorer_controller.dart';
|
|
|
|
const _subtitleIconPadding = 2.0;
|
|
const _subtitleIconSize = 16.0;
|
|
|
|
class FolderGridItem extends StatelessWidget {
|
|
final Resource resource;
|
|
final bool dropTargetActive;
|
|
|
|
FolderGridItem({required this.resource, required this.dropTargetActive}) : super(key: ValueKey(resource.id));
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final account = context.read<PhylumAccount>();
|
|
final showBorder = context.select<ExplorerState, bool>((state) => state.focusId == resource.id && state.showFocus);
|
|
final highlight = context.select<ExplorerState, bool>((state) => state.isSelected(resource.id));
|
|
final dim = context.select<ExplorerState, bool>((state) => state.isSelected(resource.id) && state.dragging);
|
|
|
|
final sync = context.select<PhylumActionQueueState, ResourceSyncState>((state) {
|
|
return state.actions.fold(ResourceSyncState.none, (acc, action) {
|
|
if (action is ResourceAction && action.resourceId == resource.id) {
|
|
final actionState = ResourceSyncState.fromStatus(action.status);
|
|
if (actionState.index > acc.index) return actionState;
|
|
}
|
|
return acc;
|
|
});
|
|
});
|
|
|
|
final theme = Theme.of(context);
|
|
final colorScheme = theme.colorScheme;
|
|
|
|
final border = dropTargetActive
|
|
? BorderSide(color: colorScheme.secondary, width: 2.0)
|
|
: showBorder
|
|
? BorderSide(color: colorScheme.primary, width: 2.0)
|
|
: const BorderSide(color: Colors.transparent, width: 2.0);
|
|
|
|
final color = highlight
|
|
? dim
|
|
? colorScheme.primaryContainer.withAlpha(192)
|
|
: colorScheme.primaryContainer
|
|
: dropTargetActive
|
|
? colorScheme.secondaryContainer
|
|
: null;
|
|
|
|
return IconTheme(
|
|
data: IconThemeData(
|
|
color: colorScheme.onSurfaceVariant,
|
|
),
|
|
child: Card(
|
|
color: color,
|
|
borderOnForeground: true,
|
|
clipBehavior: Clip.antiAlias,
|
|
shape: dropTargetActive || showBorder
|
|
? RoundedRectangleBorder(side: border, borderRadius: BorderRadius.circular(12.0))
|
|
: null,
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
Expanded(
|
|
child: Stack(
|
|
fit: StackFit.expand,
|
|
children: [
|
|
StreamBuilder(
|
|
stream: account.db.latestVersion(resource.id).watchSingleOrNull(),
|
|
builder: (context, snapshot) {
|
|
String imageUrl = '';
|
|
if (snapshot.data != null && (snapshot.data!.imgres & imgResThumbnail) != 0) {
|
|
imageUrl = (account.apiClient.createUriBuilder('/api/v1/fs/img/${resource.id}:')
|
|
..queryParameters['version'] = snapshot.data!.id
|
|
..queryParameters['res'] = imgResThumbnail.toString())
|
|
.build()
|
|
.toString();
|
|
}
|
|
return CachedNetworkImage(
|
|
imageUrl: imageUrl,
|
|
alignment: const Alignment(0.0, -0.75),
|
|
fit: BoxFit.cover,
|
|
httpHeaders: account.apiClient.requestHeaders,
|
|
placeholder: (context, url) => FittedBox(
|
|
fit: BoxFit.contain,
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(8.0),
|
|
child: resource.getIcon(account),
|
|
)),
|
|
imageRenderMethodForWeb: ImageRenderMethodForWeb.HttpGet,
|
|
errorWidget: (context, url, err) => FittedBox(
|
|
fit: BoxFit.contain,
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(8.0),
|
|
child: resource.getIcon(account),
|
|
)),
|
|
);
|
|
}),
|
|
],
|
|
)),
|
|
Container(
|
|
decoration: BoxDecoration(color: color ?? Theme.of(context).colorScheme.surfaceContainerLow),
|
|
padding: const EdgeInsets.symmetric(vertical: 6.0, horizontal: 12.0),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
Text(
|
|
resource.name,
|
|
maxLines: 1,
|
|
softWrap: false,
|
|
overflow: TextOverflow.fade,
|
|
style: theme.textTheme.bodyLarge!.copyWith(
|
|
color: highlight
|
|
? dim
|
|
? colorScheme.primary.withAlpha(192)
|
|
: colorScheme.primary
|
|
: colorScheme.onSurface,
|
|
),
|
|
),
|
|
Row(
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
if (sync != ResourceSyncState.none)
|
|
Padding(
|
|
padding: const EdgeInsets.only(right: _subtitleIconPadding),
|
|
child: Icon(sync.icon, size: _subtitleIconSize),
|
|
),
|
|
if (resource.grants != null)
|
|
Padding(
|
|
padding: const EdgeInsets.only(right: _subtitleIconPadding),
|
|
child: Icon(Icons.people_alt, size: _subtitleIconSize),
|
|
),
|
|
StreamBuilder<int>(
|
|
stream: context.read<PhylumAccount>().db.availableVersions(resource.id).watchSingle(),
|
|
initialData: 1,
|
|
builder: (context, snapshot) {
|
|
if ((snapshot.data ?? 0) > 1) {
|
|
return Padding(
|
|
padding: const EdgeInsets.only(right: _subtitleIconPadding),
|
|
child: Icon(Icons.history, size: _subtitleIconSize),
|
|
);
|
|
}
|
|
return const SizedBox();
|
|
}),
|
|
StreamBuilder<int>(
|
|
stream: account.db.countPublinks(resource.id).watchSingle(),
|
|
initialData: 0,
|
|
builder: (context, snapshot) {
|
|
if ((snapshot.data ?? 0) > 0) {
|
|
return const Padding(
|
|
padding: EdgeInsets.only(right: _subtitleIconPadding),
|
|
child: Icon(Icons.public, size: _subtitleIconSize),
|
|
);
|
|
}
|
|
return const SizedBox();
|
|
}),
|
|
StreamBuilder<bool?>(
|
|
stream: (account.db.bookmarks.select()..where((b) => b.resourceId.equals(resource.id)))
|
|
.map((b) => !b.deleted)
|
|
.watchSingleOrNull(),
|
|
initialData: false,
|
|
builder: (context, snapshot) {
|
|
if (snapshot.data == true) {
|
|
return const Padding(
|
|
padding: EdgeInsets.only(right: _subtitleIconPadding),
|
|
child: Icon(Icons.bookmark, size: _subtitleIconSize),
|
|
);
|
|
}
|
|
return const SizedBox();
|
|
}),
|
|
StreamBuilder(
|
|
stream: account.db.latestVersion(resource.id).watchSingleOrNull(),
|
|
builder: (context, snapshot) {
|
|
String subtitle = "";
|
|
if (resource.modified != null) {
|
|
subtitle = resource.modified!.formatShort();
|
|
}
|
|
final data = snapshot.data;
|
|
if (data != null) {
|
|
if (subtitle.isNotEmpty) {
|
|
subtitle += " \u2022 ";
|
|
}
|
|
subtitle += data.size.formatForDisplay();
|
|
}
|
|
return Text(
|
|
subtitle,
|
|
maxLines: 1,
|
|
softWrap: false,
|
|
overflow: TextOverflow.fade,
|
|
style: theme.textTheme.bodyMedium!.copyWith(
|
|
color: highlight
|
|
? dim
|
|
? colorScheme.primary.withAlpha(192)
|
|
: colorScheme.primary
|
|
: colorScheme.onSurfaceVariant,
|
|
),
|
|
);
|
|
}),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|