tstor/ui/lib/screens/file_view.dart
royalcat 0d7aac068c
All checks were successful
docker / build-docker (linux/amd64) (push) Successful in 1m39s
docker / build-docker (linux/386) (push) Successful in 1m46s
docker / build-docker (linux/arm64/v8) (push) Successful in 8m18s
docker / build-docker (linux/arm64) (push) Successful in 8m29s
docker / build-docker (linux/arm/v7) (push) Successful in 8m49s
torrent list
2024-04-27 14:00:34 +03:00

260 lines
7.4 KiB
Dart

import 'package:flutter/material.dart';
import 'package:graphql/client.dart';
import 'package:graphql_flutter/graphql_flutter.dart';
import 'package:tstor_ui/api/client.dart';
import 'package:tstor_ui/api/fs_entry.graphql.dart';
import 'package:tstor_ui/api/torrent.graphql.dart';
import 'package:tstor_ui/components/download.dart';
import 'package:tstor_ui/components/sliver_header.dart';
import 'package:tstor_ui/font/t_icons_icons.dart';
import 'package:path/path.dart' as p;
import 'package:tstor_ui/utils/bytes.dart';
class FileViewScreen extends StatefulWidget {
final String initialPath;
const FileViewScreen({super.key, this.initialPath = "/"});
@override
State<FileViewScreen> createState() => _FileViewScreenState();
}
class _FileViewScreenState extends State<FileViewScreen> {
late String path;
late final TextEditingController pathController;
@override
void initState() {
path = widget.initialPath;
pathController = TextEditingController(text: path);
listDirFuture = client.query$ListDir(
Options$Query$ListDir(
variables: Variables$Query$ListDir(path: path),
),
);
super.initState();
}
void cd(String part) {
setState(() {
listDirFuture = null;
});
setState(() {
path = p.normalize(p.join(path, part));
pathController.text = path;
listDirFuture = client.query$ListDir(
Options$Query$ListDir(
variables: Variables$Query$ListDir(path: path),
),
);
});
}
void refresh() {
setState(() {
listDirFuture = null;
});
setState(() {
listDirFuture = client.query$ListDir(
Options$Query$ListDir(
variables: Variables$Query$ListDir(path: path),
fetchPolicy: FetchPolicy.noCache,
),
);
});
}
Future<QueryResult<Query$ListDir>>? listDirFuture;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: TextField(
controller: pathController,
onEditingComplete: () => cd(pathController.text),
),
leading: IconButton(
onPressed: () => cd(".."),
icon: const Icon(Icons.arrow_upward),
),
actions: [
IconButton(
icon: const Icon(Icons.refresh),
onPressed: refresh,
)
],
),
body: FutureBuilder(
key: GlobalKey(),
future: listDirFuture,
initialData: null,
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const Center(child: CircularProgressIndicator());
}
final data = snapshot.data!;
if (data.exception != null) {
return Text("Error\n${data.exception.toString()}");
}
final entry = snapshot.data?.parsedData?.fsEntry;
if (entry == null) {
return const Center(child: Text("Entry not exists"));
}
final entries = _getEntries(entry);
if (entries == null || entries.isEmpty) {
return const Center(child: Text("Empty dir"));
}
return CustomScrollView(
slivers: [
EntryHeaderSliver(entry: entry),
SliverList.builder(
itemCount: entries.length,
itemBuilder: (context, index) {
return DirEntry(
entry: entries[index],
onTap: (name, isFile) {
if (!isFile) {
cd(name);
}
},
);
},
),
],
);
},
),
);
}
}
List<Fragment$DirEntry>? _getEntries(Query$ListDir$fsEntry entry) {
switch (entry) {
case Query$ListDir$fsEntry$$ArchiveFS entry:
return entry.entries;
case Query$ListDir$fsEntry$$ResolverFS entry:
return entry.entries;
case Query$ListDir$fsEntry$$SimpleDir entry:
return entry.entries;
case Query$ListDir$fsEntry$$TorrentFS entry:
return entry.entries;
default:
return null;
}
}
class DirEntry extends StatelessWidget {
final Fragment$DirEntry entry;
final void Function(String name, bool isFile) onTap;
const DirEntry({super.key, required this.entry, required this.onTap});
@override
Widget build(BuildContext context) {
switch (entry) {
case Fragment$TorrentDir entry:
final completness = entry.torrent.bytesCompleted /
(entry.torrent.bytesCompleted + entry.torrent.bytesMissing);
return ListTile(
leading: const Icon(TIcons.bittorrent_bttold_logo),
title: Text(entry.name),
subtitle: Row(
mainAxisSize: MainAxisSize.max,
children: [
Text("${completness * 100}%"),
Expanded(child: LinearProgressIndicator(value: completness)),
IconButton(
onPressed: () => client.mutate$MarkTorrentDownload(
Options$Mutation$MarkTorrentDownload(
variables:
Variables$Mutation$MarkTorrentDownload(infohash: entry.torrent.infohash),
),
),
icon: const Icon(Icons.download),
)
],
),
onTap: () => onTap(entry.name, false),
);
case Fragment$DirEntry$$SimpleDir entry:
return ListTile(
leading: const Icon(Icons.folder),
title: Text(entry.name),
onTap: () => onTap(entry.name, false),
);
case Fragment$DirEntry$$ArchiveFS entry:
return ListTile(
leading: const Icon(Icons.folder_zip),
title: Text(entry.name),
onTap: () => onTap(entry.name, false),
);
case Fragment$File entry:
return ListTile(
leading: const Icon(Icons.insert_drive_file),
title: Text(entry.name),
onTap: () => onTap(entry.name, true),
);
default:
return ListTile(
leading: const Icon(Icons.question_mark),
title: Text(entry.name),
);
}
}
}
class EntryHeaderSliver extends StatelessWidget {
final Query$ListDir$fsEntry entry;
const EntryHeaderSliver({super.key, required this.entry});
@override
Widget build(BuildContext context) {
switch (entry) {
case Query$ListDir$fsEntry$$TorrentFS entry:
final total = entry.torrent.bytesCompleted + entry.torrent.bytesMissing;
return HideableHeaderSliver(
leading: const Icon(TIcons.bittorrent_bttold_logo),
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(entry.torrent.name),
Text("Size: ${total.bytesFormat()}"),
Text("InfoHash: ${entry.torrent.infohash}"),
DownloadProgress(entry.torrent.bytesCompleted, total)
],
),
actions: [
IconButton(
icon: const Icon(Icons.download),
onPressed: () => client.mutate$MarkTorrentDownload(
Options$Mutation$MarkTorrentDownload(
variables: Variables$Mutation$MarkTorrentDownload(
infohash: entry.torrent.infohash,
),
),
),
)
],
);
default:
return HideableHeaderSliver(
leading: const Icon(Icons.folder),
body: Text(entry.name),
);
}
}
}