ytdlp
This commit is contained in:
parent
0d7aac068c
commit
974814c281
20 changed files with 1532 additions and 716 deletions
ui/lib
|
@ -46,6 +46,7 @@ type Mutation {
|
|||
validateTorrents(filter: TorrentFilter!): Boolean!
|
||||
cleanupTorrents(files: Boolean, dryRun: Boolean!): CleanupResponse!
|
||||
downloadTorrent(infohash: String!, file: String): DownloadTorrentResponse
|
||||
uploadFile(file: Upload!): Boolean!
|
||||
dedupeStorage: Int!
|
||||
}
|
||||
input Pagination {
|
||||
|
@ -138,3 +139,4 @@ input TorrentsFilter {
|
|||
peersCount: IntFilter
|
||||
downloading: BooleanFilter
|
||||
}
|
||||
scalar Upload
|
||||
|
|
138
ui/lib/main.dart
138
ui/lib/main.dart
|
@ -21,23 +21,28 @@ class MyApp extends StatelessWidget {
|
|||
theme: lightDynamic != null ? ThemeData.from(colorScheme: lightDynamic) : ThemeData.light(),
|
||||
darkTheme:
|
||||
darkDynamic != null ? ThemeData.from(colorScheme: darkDynamic) : ThemeData.dark(),
|
||||
routes: {
|
||||
"/home": (context) => const HomePage(),
|
||||
// "/files": (context) => const FileViewScreen(),
|
||||
},
|
||||
initialRoute: "/home",
|
||||
home: GraphQLProvider(
|
||||
client: ValueNotifier(client),
|
||||
child: const MyHomePage(),
|
||||
child: const HomePage(),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class MyHomePage extends StatefulWidget {
|
||||
const MyHomePage({super.key});
|
||||
class HomePage extends StatefulWidget {
|
||||
const HomePage({super.key});
|
||||
|
||||
@override
|
||||
State<MyHomePage> createState() => _MyHomePageState();
|
||||
State<HomePage> createState() => _HomePageState();
|
||||
}
|
||||
|
||||
class _MyHomePageState extends State<MyHomePage> {
|
||||
class _HomePageState extends State<HomePage> {
|
||||
int currentPageIndex = 0;
|
||||
|
||||
@override
|
||||
|
@ -68,126 +73,3 @@ class _MyHomePageState extends State<MyHomePage> {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
class NavigationExample extends StatefulWidget {
|
||||
const NavigationExample({super.key});
|
||||
|
||||
@override
|
||||
State<NavigationExample> createState() => _NavigationExampleState();
|
||||
}
|
||||
|
||||
class _NavigationExampleState extends State<NavigationExample> {
|
||||
int currentPageIndex = 0;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final ThemeData theme = Theme.of(context);
|
||||
return Scaffold(
|
||||
bottomNavigationBar: NavigationBar(
|
||||
onDestinationSelected: (int index) {
|
||||
setState(() {
|
||||
currentPageIndex = index;
|
||||
});
|
||||
},
|
||||
indicatorColor: Colors.amber,
|
||||
selectedIndex: currentPageIndex,
|
||||
destinations: const <Widget>[
|
||||
NavigationDestination(
|
||||
selectedIcon: Icon(Icons.home),
|
||||
icon: Icon(Icons.home_outlined),
|
||||
label: 'Home',
|
||||
),
|
||||
NavigationDestination(
|
||||
icon: Badge(child: Icon(Icons.notifications_sharp)),
|
||||
label: 'Notifications',
|
||||
),
|
||||
NavigationDestination(
|
||||
icon: Badge(
|
||||
label: Text('2'),
|
||||
child: Icon(Icons.messenger_sharp),
|
||||
),
|
||||
label: 'Messages',
|
||||
),
|
||||
],
|
||||
),
|
||||
body: <Widget>[
|
||||
/// Home page
|
||||
Card(
|
||||
shadowColor: Colors.transparent,
|
||||
margin: const EdgeInsets.all(8.0),
|
||||
child: SizedBox.expand(
|
||||
child: Center(
|
||||
child: Text(
|
||||
'Home page',
|
||||
style: theme.textTheme.titleLarge,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
/// Notifications page
|
||||
const Padding(
|
||||
padding: EdgeInsets.all(8.0),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Card(
|
||||
child: ListTile(
|
||||
leading: Icon(Icons.notifications_sharp),
|
||||
title: Text('Notification 1'),
|
||||
subtitle: Text('This is a notification'),
|
||||
),
|
||||
),
|
||||
Card(
|
||||
child: ListTile(
|
||||
leading: Icon(Icons.notifications_sharp),
|
||||
title: Text('Notification 2'),
|
||||
subtitle: Text('This is a notification'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
/// Messages page
|
||||
ListView.builder(
|
||||
reverse: true,
|
||||
itemCount: 2,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
if (index == 0) {
|
||||
return Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: Container(
|
||||
margin: const EdgeInsets.all(8.0),
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.primary,
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
),
|
||||
child: Text(
|
||||
'Hello',
|
||||
style: theme.textTheme.bodyLarge!.copyWith(color: theme.colorScheme.onPrimary),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Container(
|
||||
margin: const EdgeInsets.all(8.0),
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.primary,
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
),
|
||||
child: Text(
|
||||
'Hi!',
|
||||
style: theme.textTheme.bodyLarge!.copyWith(color: theme.colorScheme.onPrimary),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
][currentPageIndex],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,51 +16,53 @@ class _DownloadsScreenState extends State<DownloadsScreen> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FutureBuilder(
|
||||
key: GlobalKey(),
|
||||
future: client.query$ListTorrents(Options$Query$ListTorrents(
|
||||
variables: Variables$Query$ListTorrents(downloading: filterDownloading),
|
||||
)),
|
||||
builder: (context, snapshot) {
|
||||
final torrents = snapshot.data?.parsedData?.torrents;
|
||||
return SafeArea(
|
||||
child: FutureBuilder(
|
||||
key: GlobalKey(),
|
||||
future: client.query$ListTorrents(Options$Query$ListTorrents(
|
||||
variables: Variables$Query$ListTorrents(downloading: filterDownloading),
|
||||
)),
|
||||
builder: (context, snapshot) {
|
||||
final torrents = snapshot.data?.parsedData?.torrents;
|
||||
|
||||
return NestedScrollView(
|
||||
floatHeaderSlivers: true,
|
||||
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
||||
HideableHeaderSliver(
|
||||
height: 80,
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
FilterChip(
|
||||
label: const Text("Downloading"),
|
||||
selected: filterDownloading,
|
||||
onSelected: (value) => setState(() {
|
||||
filterDownloading = value;
|
||||
}),
|
||||
),
|
||||
],
|
||||
return NestedScrollView(
|
||||
floatHeaderSlivers: true,
|
||||
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
||||
HideableHeaderSliver(
|
||||
height: 80,
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
FilterChip(
|
||||
label: const Text("Downloading"),
|
||||
selected: filterDownloading,
|
||||
onSelected: (value) => setState(() {
|
||||
filterDownloading = value;
|
||||
}),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.refresh),
|
||||
onPressed: () => setState(() {}),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.refresh),
|
||||
onPressed: () => setState(() {}),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
body: snapshot.hasData && torrents != null
|
||||
? ListView.builder(
|
||||
itemCount: torrents.length,
|
||||
itemBuilder: (context, index) => TorrentTile(torrent: torrents[index]),
|
||||
)
|
||||
: const Center(child: CircularProgressIndicator()),
|
||||
);
|
||||
},
|
||||
],
|
||||
body: snapshot.hasData && torrents != null
|
||||
? ListView.builder(
|
||||
itemCount: torrents.length,
|
||||
itemBuilder: (context, index) => TorrentTile(torrent: torrents[index]),
|
||||
)
|
||||
: const Center(child: CircularProgressIndicator()),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -72,68 +72,74 @@ class _FileViewScreenState extends State<FileViewScreen> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: TextField(
|
||||
controller: pathController,
|
||||
onEditingComplete: () => cd(pathController.text),
|
||||
return PopScope(
|
||||
canPop: false,
|
||||
onPopInvoked: (didPop) {
|
||||
cd("..");
|
||||
},
|
||||
child: 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,
|
||||
)
|
||||
],
|
||||
),
|
||||
leading: IconButton(
|
||||
onPressed: () => cd(".."),
|
||||
icon: const Icon(Icons.arrow_upward),
|
||||
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);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
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);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue