From 97fc260e2756e8fe3de0408d64166d347da10e31 Mon Sep 17 00:00:00 2001 From: femalemonkeyman Date: Tue, 7 May 2024 10:19:58 -0400 Subject: [PATCH] --- lib/discord_rpc.dart | 20 -- lib/main.dart | 15 +- lib/media/providers/anime/animepahe.dart | 2 +- lib/media/providers/anime/anitaku.dart | 96 +++--- lib/media/providers/anime/aniwatch.dart | 27 +- lib/pages/media_page.dart | 69 ++-- lib/pages/novel_page.dart | 12 +- lib/pages/settings_page.dart | 9 +- lib/viewers/Anime/media_anime.dart | 397 ++++++++++++----------- lib/widgets/search_button.dart | 59 ++-- linux/my_application.cc | 2 +- pubspec.yaml | 7 +- 12 files changed, 358 insertions(+), 357 deletions(-) delete mode 100644 lib/discord_rpc.dart diff --git a/lib/discord_rpc.dart b/lib/discord_rpc.dart deleted file mode 100644 index 6495729..0000000 --- a/lib/discord_rpc.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'dart:io'; -import 'package:discord_rpc/discord_rpc.dart' show DiscordRPC; -export 'package:discord_rpc/discord_rpc.dart'; - -final bool isPhone = !Platform.isAndroid && !Platform.isIOS; -bool init = true; - -DiscordRPC? startRpc() { - if (isPhone) { - if (init) { - DiscordRPC.initialize(); - init = !init; - } - return DiscordRPC(applicationId: '1142579045075255306') - ..start( - autoRegister: true, - ); - } - return null; -} diff --git a/lib/main.dart b/lib/main.dart index 63b9b38..bc5099c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -9,6 +9,7 @@ import 'package:go_router/go_router.dart'; import 'package:isar/isar.dart'; import 'package:media_kit/media_kit.dart'; import 'package:path_provider/path_provider.dart'; +import 'package:tokuwari/pages/settings_page.dart'; import 'package:tokuwari/viewers/Novel/media_novel.dart'; import 'package:window_manager/window_manager.dart'; import 'package:tokuwari_models/info_models.dart'; @@ -107,7 +108,7 @@ class Navigation extends StatelessWidget { GoRoute( parentNavigatorKey: _rootKey, path: 'viewer', - onExit: (context) { + onExit: (context, state) { SystemChrome.setEnabledSystemUIMode( SystemUiMode.manual, overlays: SystemUiOverlay.values, @@ -189,6 +190,11 @@ class Navigation extends StatelessWidget { ), ], ), + GoRoute( + name: 'settings', + path: '/settings', + builder: (context, state) => Settings(), + ), ], ); @@ -200,12 +206,7 @@ class Navigation extends StatelessWidget { useMaterial3: true, darkIsTrueBlack: true, scheme: FlexScheme.deepPurple, - pageTransitionsTheme: const PageTransitionsTheme( - builders: { - TargetPlatform.linux: OpenUpwardsPageTransitionsBuilder(), - }, - ), - typography: Typography.material2021(platform: Theme.of(context).platform), + //typography: Typography.material2021(platform: Theme.of(context).platform), ), scrollBehavior: const Allow(), debugShowCheckedModeBanner: false, diff --git a/lib/media/providers/anime/animepahe.dart b/lib/media/providers/anime/animepahe.dart index 6975d18..4aadbcd 100644 --- a/lib/media/providers/anime/animepahe.dart +++ b/lib/media/providers/anime/animepahe.dart @@ -91,7 +91,7 @@ Anime paheInfo(final String id) async { parse(i.data).getElementsByTagName('script').where((element) => element.text.contains('eval')).first.text, ).unpack().split('source=\'')[1].split('\'')[0], ].reversed, - ), //qualities, + ), subtitles: {}, headers: {'referer': 'https://kwik.cx'}, ); diff --git a/lib/media/providers/anime/anitaku.dart b/lib/media/providers/anime/anitaku.dart index 6597f15..72136b7 100644 --- a/lib/media/providers/anime/anitaku.dart +++ b/lib/media/providers/anime/anitaku.dart @@ -6,55 +6,71 @@ import 'package:encrypt/encrypt.dart'; import 'package:html/parser.dart'; import 'package:tokuwari_models/info_models.dart'; -const String gogo = 'https://anitaku.to/'; +const String gogo = 'https://anitaku.so'; Provider gogoList(final AniData data) async { try { final msync = await Dio() .get( - '$anisync/${data.malid}', - options: Options( - responseType: ResponseType.plain, - ), + '$anisync/${data.malid}', + options: Options( + responseType: ResponseType.plain, + ), + ) + .then((value) { + return jsonDecode(value.data)['Sites']['Gogoanime'].keys.first; + }); + final gogoPage = await Dio().get('$gogo/category/$msync'); + final goid = parse(gogoPage.data).getElementsByClassName('movie_id').first.attributes['value']; + final episodeList = + await Dio().get('https://ajax.gogocdn.net/ajax/load-list-episode?ep_start=0&ep_end=9999&id=$goid'); + return [ + for (final src in parse(episodeList.data).getElementsByTagName('a').reversed) + MediaProv( + provider: 'gogo', + provId: '$gogo${src.attributes['href']?.trim()}', + title: '', + number: src.getElementsByClassName('name').first.nodes[1].text!.trim(), + call: () => gogoInfo('$gogo${src.attributes['href']?.trim()}'), ) - .then( - (value) => jsonDecode(value.data)['Sites']['Gogoanime'].keys.first, - ); - final gogoPage = await Dio().get('${gogo}category/$msync'); - print(gogoPage); - return []; + ]; } catch (e) { + print(e); return []; } } -Anime gogoInfo() async { - final iv = IV.fromUtf8("3134003223491201"); - final episodeResponse = await Dio().get('$gogo/mahoutsukai-no-yome-season-2-episode-7'); - final episode = parse(episodeResponse.data).body?.getElementsByClassName('active')[0].attributes['data-video']; - final videoResponse = await Dio().get(episode!); - final encryptedParams = Encrypted.fromBase64(videoResponse.data.split('data-value="')[1].split('"><')[0]); - final params = String.fromCharCodes( - AES(Key.fromUtf8('37911490979715163134003223491201'), mode: AESMode.cbc).decrypt(encryptedParams, iv: iv)); - final encrypt = params.split('&')[0]; - final updatedParams = params.replaceAll( - '$encrypt&', - '${AES(Key.fromUtf8('37911490979715163134003223491201'), mode: AESMode.cbc).encrypt( - Uint8List.fromList(encrypt.codeUnits), - iv: iv, - ).base64}&', - ); - final response = await Dio() - .get( - 'https://playtaku.online/encrypt-ajax.php?id=$updatedParams&alias=$encrypt', - options: Options( - headers: {'referer': episode, 'host': 'playtaku.online', 'x-requested-with': 'XMLHttpRequest'}, - ), - ) - .then((value) => jsonDecode(value.data)['data']); - final decryptedSources = AES(Key.fromUtf8('54674138327930866480207815084989'), mode: AESMode.cbc) - .decrypt(Encrypted.fromBase64(response), iv: iv); - final sources = jsonDecode(String.fromCharCodes(decryptedSources)); - - return Source(qualities: sources['source'][0]['file'], subtitles: {}); +Anime gogoInfo(final String id) async { + try { + final iv = IV.fromUtf8("3134003223491201"); + final inst = AES(Key.fromUtf8('37911490979715163134003223491201'), mode: AESMode.cbc); + final episodeResponse = await Dio().get(id); + final episode = parse(episodeResponse.data).body?.getElementsByClassName('active')[0].attributes['data-video']; + final videoResponse = await Dio().get(episode!); + final encryptedParams = Encrypted.fromBase64(videoResponse.data.split('data-value="')[1].split('"><')[0]); + final params = String.fromCharCodes(inst.decrypt(encryptedParams, iv: iv)); + final encrypt = params.split('&')[0]; + final updatedParams = params.replaceFirst( + '$encrypt&', + '${inst.encrypt( + Uint8List.fromList(encrypt.codeUnits), + iv: iv, + ).base64}&', + ); + final response = await Dio() + .get( + 'https://playtaku.online/encrypt-ajax.php?id=$updatedParams&alias=$encrypt', + options: Options( + headers: {'referer': episode, 'host': 'playtaku.online', 'x-requested-with': 'XMLHttpRequest'}, + ), + ) + .then((value) => jsonDecode(value.data)['data']); + final decryptedSources = AES(Key.fromUtf8('54674138327930866480207815084989'), mode: AESMode.cbc) + .decrypt(Encrypted.fromBase64(response), iv: iv); + final sources = jsonDecode(String.fromCharCodes(decryptedSources)); + return Source(qualities: {'default': sources['source'][0]['file']}, subtitles: {}); + } catch (_, stack) { + print(stack); + return const Source(qualities: {}, subtitles: {}); + } } diff --git a/lib/media/providers/anime/aniwatch.dart b/lib/media/providers/anime/aniwatch.dart index 2e227b4..c35eee0 100644 --- a/lib/media/providers/anime/aniwatch.dart +++ b/lib/media/providers/anime/aniwatch.dart @@ -5,7 +5,7 @@ import 'package:html/parser.dart'; import 'package:key/key.dart'; import 'package:tokuwari_models/info_models.dart'; -const String zoro = "https://aniwatch.to/"; +const String zoro = "https://hianime.to/"; const String mega = 'https://megacloud.tv/embed-2/ajax/e-1/getSources?id='; int retries = 0; @@ -42,20 +42,18 @@ Provider zoroList(final AniData data) async { Anime zoroInfo(final id) async { final Options options = Options(responseType: ResponseType.plain); - final String sId = await Dio() - .get( - '${zoro}ajax/v2/episode/servers?episodeId=$id', - options: options, - ) - .then( - (value) => parse(jsonDecode(value.data)['html']) - .getElementsByClassName("item server-item") - .firstWhere( - (element) => element.text.contains('Vid'), - ) - .attributes['data-id']!, - ); try { + final String sId = await Dio() + .get( + '${zoro}ajax/v2/episode/servers?episodeId=$id', + options: options, + ) + .then( + (value) => parse(jsonDecode(value.data)['html']) + .getElementsByClassName("item server-item") + .first + .attributes['data-id']!, + ); final String link = await Dio() .get( '${zoro}ajax/v2/episode/sources?id=$sId', @@ -94,6 +92,7 @@ Anime zoroInfo(final id) async { retries++; return await zoroInfo(id); } else { + print(_); return const Source(qualities: {}, subtitles: {}); } } diff --git a/lib/pages/media_page.dart b/lib/pages/media_page.dart index 9ec9aab..4b39212 100644 --- a/lib/pages/media_page.dart +++ b/lib/pages/media_page.dart @@ -16,7 +16,7 @@ query (\$page: Int!, \$type: MediaType, \$tag: String, \$search: String, \$genre total currentPage }, - media(sort: [TRENDING_DESC], type: \$type, search: \$search, genre_in: \$genre, tag: \$tag) { + media(sort: [TRENDING_DESC], type: \$type, search: \$search, genre_in: \$genre, tag: \$tag, isAdult: false) { id title { romaji @@ -143,8 +143,9 @@ class AniPageState extends State { }, ), ); - loading = false; - setState(() {}); + setState(() { + loading = false; + }); } catch (e) { animeData.addAll([]); } @@ -236,42 +237,40 @@ class AniPageState extends State { await queryData(); }, ), - child: CustomScrollView( - slivers: [ - SliverToBoxAdapter( - child: Center( - child: SearchButton( - text: "Anilist", - controller: textController, - search: () async { - await searchData(); - }, - ), + child: SafeArea( + child: CustomScrollView( + slivers: [ + SearchButton( + text: "Anilist", + controller: textController, + search: () async { + await searchData(); + }, ), - ), - SliverToBoxAdapter( - child: Wrap( - alignment: WrapAlignment.spaceAround, - children: [ - TextButton( - onPressed: () => updateGenre(), - child: const Text( - "Filter by genre", + SliverToBoxAdapter( + child: Wrap( + alignment: WrapAlignment.spaceAround, + children: [ + TextButton( + onPressed: () => updateGenre(), + child: const Text( + "Filter by genre", + ), ), - ), - ], + ], + ), ), - ), - (animeData.isNotEmpty) - ? Grid( - data: animeData, - ) - : const SliverToBoxAdapter( - child: Center( - child: CircularProgressIndicator(), + (animeData.isNotEmpty) + ? Grid( + data: animeData, + ) + : const SliverToBoxAdapter( + child: Center( + child: CircularProgressIndicator(), + ), ), - ), - ], + ], + ), ), ), ); diff --git a/lib/pages/novel_page.dart b/lib/pages/novel_page.dart index a5afa1a..136fdc9 100644 --- a/lib/pages/novel_page.dart +++ b/lib/pages/novel_page.dart @@ -43,14 +43,10 @@ class NovelPageState extends State { : SafeArea( child: CustomScrollView( slivers: [ - SliverToBoxAdapter( - child: Center( - child: SearchButton( - text: 'Novels', - controller: SearchController(), - search: () {}, - ), - ), + SearchButton( + text: 'Novels', + controller: SearchController(), + search: () {}, ), SliverToBoxAdapter( child: Wrap( diff --git a/lib/pages/settings_page.dart b/lib/pages/settings_page.dart index 70e2273..3f92a83 100644 --- a/lib/pages/settings_page.dart +++ b/lib/pages/settings_page.dart @@ -12,6 +12,13 @@ class Settings extends StatefulWidget { class SettingsState extends State { @override Widget build(context) { - return const Scaffold(); + return const Scaffold( + body: Column( + children: [ + BackButton(), + Placeholder(), + ], + ), + ); } } diff --git a/lib/viewers/Anime/media_anime.dart b/lib/viewers/Anime/media_anime.dart index 8f1ee3f..91c5f8d 100644 --- a/lib/viewers/Anime/media_anime.dart +++ b/lib/viewers/Anime/media_anime.dart @@ -3,7 +3,6 @@ import 'dart:io'; import 'package:async/async.dart'; import 'package:flutter/services.dart'; import 'package:go_router/go_router.dart'; -import 'package:tokuwari/discord_rpc.dart'; import 'package:tokuwari/widgets/loading.dart'; import 'package:tokuwari_models/info_models.dart'; import 'package:flutter/material.dart'; @@ -41,8 +40,6 @@ extension DurationExtension on Duration { } } -final GlobalKey vKey = GlobalKey(); - class AniViewer extends StatefulWidget { final AniData anime; final int episode; @@ -73,7 +70,6 @@ class AniViewerState extends State { ); late final CancelableOperation load = CancelableOperation.fromFuture(play()); final bool isPhone = !Platform.isAndroid && !Platform.isIOS; - final DiscordRPC? discord = startRpc(); @override void initState() { @@ -85,9 +81,6 @@ class AniViewerState extends State { DeviceOrientation.landscapeRight, ], ); - // player.stream.log.listen((event) { - // print(event); - // }); } Future play() async { @@ -100,6 +93,8 @@ class AniViewerState extends State { ), play: false, ); + await controller.waitUntilFirstFrameRendered; + await player.play(); if (media.subtitles.isNotEmpty) { await player.setSubtitleTrack( SubtitleTrack.uri( @@ -111,19 +106,6 @@ class AniViewerState extends State { ), ); } - if (discord != null) { - discord! - ..start(autoRegister: true) - ..updatePresence( - DiscordPresence( - largeImageKey: widget.anime.image, - details: "Watching: ${widget.anime.title}", - state: "Episode: ${widget.episode + 1} / ${widget.anime.mediaProv.length}", - ), - ); - } - await controller.waitUntilFirstFrameRendered; - await player.play(); } return media; } @@ -133,7 +115,6 @@ class AniViewerState extends State { super.dispose(); load.cancel(); await player.dispose(); - discord?.clearPresence(); } @override @@ -164,7 +145,6 @@ class AniViewerState extends State { child: Stack( children: [ Video( - key: vKey, controller: controller, controls: NoVideoControls, pauseUponEnteringBackgroundMode: false, @@ -206,202 +186,206 @@ class Controls extends StatefulWidget { } class ControlsState extends State { - late final RestartableTimer enter = RestartableTimer( - const Duration(seconds: 3), - () => setState( - () { - hide = true; - }, - ), - ); + final bool isPhone = !Platform.isAndroid && !Platform.isIOS; bool hide = true; @override void dispose() { - enter.cancel(); super.dispose(); } @override - Widget build(BuildContext context) => MouseRegion( - onHover: (_) => setState(() { - hide = false; - enter.reset(); - }), - child: AnimatedOpacity( - opacity: (hide) ? 0 : 1, - duration: const Duration(milliseconds: 500), - child: DecoratedBox( - decoration: const BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [ - Color(0xCC000000), - Color(0x00000000), - Color(0x00000000), - Color(0x00000000), - Color(0x00000000), - Color(0x00000000), - Color(0xCC000000), - ], - ), - ), - child: Padding( - padding: const EdgeInsets.all(15), - child: Column( - children: [ - Row( - children: [ - BackButton( - onPressed: () { - windowManager.setFullScreen(false); - context.pop(); - }, + Widget build(BuildContext context) { + final width = MediaQuery.of(context).size.width; + return AnimatedOpacity( + opacity: (hide) ? 0 : 1, + duration: const Duration(milliseconds: 500), + child: DecoratedBox( + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Color(0xCC000000), + Color(0x00000000), + Color(0x00000000), + Color(0x00000000), + Color(0x00000000), + Color(0x00000000), + Color(0xCC000000), + ], + ), + ), + child: Padding( + padding: const EdgeInsets.all(15), + child: Column( + children: [ + AbsorbPointer( + absorbing: hide, + child: Row( + children: [ + CloseButton( + onPressed: () { + windowManager.setFullScreen(false); + context.pop(); + }, + ), + const Spacer(), + Text( + "Episode ${widget.episode + 1} ${widget.anime.mediaProv[widget.episode].title}", + style: const TextStyle( + color: Color.fromARGB(255, 255, 255, 255), ), - const Spacer(), - Text( - "Episode ${widget.episode + 1}", - style: const TextStyle( - color: Color.fromARGB(255, 255, 255, 255), - ), + ), + const Spacer(), + const Spacer( + flex: 100, + ), + PopupMenuButton( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(15), ), - const Spacer(), - if (widget.anime.mediaProv[widget.episode].title.isNotEmpty) - Text( - widget.anime.mediaProv[widget.episode].title, - style: const TextStyle( - color: Color.fromARGB(255, 255, 255, 255), + itemBuilder: (c) => [ + if (widget.media.subtitles.isNotEmpty) + PopupMenuItem( + child: const Text('Subtitles'), + onTap: () => showModalBottomSheet( + context: context, + showDragHandle: true, + builder: (context) => ListView( + children: List.generate( + widget.media.subtitles.length, + (index) { + return ListTile( + title: Text( + widget.media.subtitles.keys.elementAt(index), + ), + onTap: () { + widget.player.setSubtitleTrack( + SubtitleTrack.uri( + widget.media.subtitles.values.elementAt(index), + ), + ); + context.pop(); + }, + ); + }, + ), + ), + ), ), - ), - const Spacer( - flex: 100, - ), - PopupMenuButton( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(15), - ), - itemBuilder: (c) => [ - if (widget.media.subtitles.isNotEmpty) - PopupMenuItem( - child: const Text('Subtitles'), - onTap: () => showModalBottomSheet( - context: context, - showDragHandle: true, - builder: (context) => ListView( - children: List.generate( - widget.media.subtitles.length, - (index) { - return ListTile( + PopupMenuItem( + child: const Text('Quality'), + onTap: () => showModalBottomSheet( + showDragHandle: true, + context: context, + builder: (context) => ListView( + children: (widget.media.qualities.length == 1) + ? List.generate( + widget.player.state.tracks.video.length - 2, + (i) => ListTile( title: Text( - widget.media.subtitles.keys.elementAt(index), + widget.player.state.tracks.video[i + 2].h.toString(), ), onTap: () { - widget.player.setSubtitleTrack( - SubtitleTrack.uri( - widget.media.subtitles.values.elementAt(index), - ), + widget.player.setVideoTrack( + widget.player.state.tracks.video[i + 2], ); context.pop(); }, - ); - }, - ), - ), - ), - ), - PopupMenuItem( - child: const Text('Quality'), - onTap: () => showModalBottomSheet( - showDragHandle: true, - context: context, - builder: (context) => ListView( - children: (widget.media.qualities.length == 1) - ? List.generate( - widget.player.state.tracks.video.length - 2, - (i) => ListTile( - title: Text( - widget.player.state.tracks.video[i + 2].h.toString(), - ), - onTap: () { - widget.player.setVideoTrack( - widget.player.state.tracks.video[i + 2], - ); + ), + ) + : List.generate( + widget.media.qualities.length, + (i) => ListTile( + title: Text(widget.media.qualities.keys.elementAt(i)), + onTap: () async { + final subs = widget.player.state.track.subtitle; + await widget.player.open( + Media( + widget.media.qualities.values.elementAt(i), + httpHeaders: widget.media.headers, + start: widget.player.state.position, + ), + play: false, + ); + widget.player.setSubtitleTrack(subs); + await widget.player.play(); + if (context.mounted) { context.pop(); - }, - ), - ) - : List.generate( - widget.media.qualities.length, - (i) => ListTile( - title: Text(widget.media.qualities.keys.elementAt(i)), - onTap: () async { - final subs = widget.player.state.track.subtitle; - await widget.player.open( - Media( - widget.media.qualities.values.elementAt(i), - httpHeaders: widget.media.headers, - start: widget.player.state.position, - ), - play: false, - ); - widget.player.setSubtitleTrack(subs); - await widget.player.play(); - if (context.mounted) { - context.pop(); - } - }, - ), + } + }, ), - ), + ), ), ), - ], - ), - ], - ), - const Spacer(), - ProgressBar(player: widget.player), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - IconButton( - onPressed: (widget.episode == 0) - ? null - : () => context.pushReplacement( - '/anime/info/viewer', - extra: { - 'index': widget.episode - 1, - 'data': widget.anime, - }, - ), - icon: const Icon(Icons.skip_previous), - ), - PlayButton( - player: widget.player, - ), - IconButton( - onPressed: (widget.episode == widget.anime.mediaProv.length - 1) - ? null - : () => context.pushReplacement( - '/anime/info/viewer', - extra: { - 'index': widget.episode + 1, - 'data': widget.anime, - }, - ), - icon: const Icon(Icons.skip_next), - ), - const Spacer(), - if (isPhone) const FullScreenButton(), - ], - ), - ], + ), + ], + ), + ], + ), ), - ), + // const Spacer(), + Expanded( + child: GestureDetector( + onTap: () => setState(() { + hide = !hide; + }), + onDoubleTapDown: (event) { + final pos = widget.player.state.position; + if (event.globalPosition.dx < width / 3) widget.player.seek(pos - const Duration(seconds: 3)); + if (event.globalPosition.dx > width / 1.5) widget.player.seek(pos + const Duration(seconds: 3)); + }, + ), + ), + AbsorbPointer( + absorbing: hide, + child: ProgressBar(player: widget.player), + ), + AbsorbPointer( + absorbing: hide, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + onPressed: (widget.episode == 0) + ? null + : () => context.pushReplacement( + '/anime/info/viewer', + extra: { + 'index': widget.episode - 1, + 'data': widget.anime, + }, + ), + icon: const Icon(Icons.skip_previous), + ), + PlayButton( + player: widget.player, + ), + IconButton( + onPressed: (widget.episode == widget.anime.mediaProv.length - 1) + ? null + : () => context.pushReplacement( + '/anime/info/viewer', + extra: { + 'index': widget.episode + 1, + 'data': widget.anime, + }, + ), + icon: const Icon(Icons.skip_next), + ), + VolumeSlider(player: widget.player), + const Spacer(), + if (isPhone) const FullScreenButton(), + ], + ), + ), + ], ), ), - ); + ), + ); + } } class ProgressBar extends StatefulWidget { @@ -478,6 +462,31 @@ class PlayButton extends StatelessWidget { ); } +class VolumeSlider extends StatefulWidget { + final Player player; + const VolumeSlider({super.key, required this.player}); + + @override + State createState() => VolumeSliderState(); +} + +class VolumeSliderState extends State { + @override + Widget build(context) => Slider( + min: 0, + max: 100, + divisions: 100, + label: widget.player.state.volume.round().toString(), + value: widget.player.state.volume, + onChanged: (value) => Future.microtask( + () async { + await widget.player.setVolume(value); + setState(() {}); + }, + ), + ); +} + class FullScreenButton extends StatefulWidget { const FullScreenButton({super.key}); diff --git a/lib/widgets/search_button.dart b/lib/widgets/search_button.dart index 994f858..95acd4f 100644 --- a/lib/widgets/search_button.dart +++ b/lib/widgets/search_button.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; class SearchButton extends StatelessWidget { const SearchButton({ @@ -13,40 +14,34 @@ class SearchButton extends StatelessWidget { @override Widget build(context) { - return SafeArea( - child: Padding( - padding: const EdgeInsets.only(top: 10, bottom: 8), - child: Card( - elevation: 5, - shape: const StadiumBorder(), - child: TextField( - controller: controller, - onSubmitted: (string) => search(), - decoration: InputDecoration( - border: InputBorder.none, - constraints: BoxConstraints( - maxWidth: MediaQuery.of(context).size.width / 1.2, - ), - prefixIcon: const Icon(Icons.search), - hintText: text, - suffixIcon: Row( - mainAxisSize: MainAxisSize.min, - children: [ - IconButton( - onPressed: () { - controller.clear(); - search(); - }, - icon: const Icon(Icons.clear), - ), - IconButton( - onPressed: () {}, - icon: const Icon(Icons.settings), - ), - ], + return SliverPadding( + padding: const EdgeInsets.only(bottom: 8), + sliver: SliverAppBar( + floating: true, + flexibleSpace: SearchBar( + controller: controller, + elevation: const WidgetStatePropertyAll(0), + shape: const WidgetStatePropertyAll(BeveledRectangleBorder()), + onSubmitted: (string) => search(), + onTapOutside: (event) => FocusManager.instance.primaryFocus!.unfocus(), + leading: const Icon(Icons.search), + hintText: text, + trailing: [ + Visibility( + visible: FocusManager.instance.primaryFocus?.hasFocus ?? false, + child: IconButton( + onPressed: () { + controller.clear(); + search(); + }, + icon: const Icon(Icons.clear), ), ), - ), + IconButton( + onPressed: () => context.pushNamed('settings'), + icon: const Icon(Icons.settings), + ), + ], ), ), ); diff --git a/linux/my_application.cc b/linux/my_application.cc index e657618..209fea0 100644 --- a/linux/my_application.cc +++ b/linux/my_application.cc @@ -27,7 +27,7 @@ static void my_application_activate(GApplication* application) { // in case the window manager does more exotic layout, e.g. tiling. // If running on Wayland assume the header bar will work (may need changing // if future cases occur). - gboolean use_header_bar = FALSE; + gboolean use_header_bar = TRUE; #ifdef GDK_WINDOWING_X11 GdkScreen *screen = gtk_window_get_screen(window); if (GDK_IS_X11_SCREEN(screen)) { diff --git a/pubspec.yaml b/pubspec.yaml index 1e72173..265b38c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -14,7 +14,6 @@ dependencies: flutter: sdk: flutter sliver_tools: - discord_rpc: path: dio: async: @@ -67,9 +66,9 @@ dependencies: #Modules key: - # path: ../Projects/modules/key - git: - url: https://github.com/femalemonkeyman/key + path: ../Projects/modules/key + # git: + # url: https://github.com/femalemonkeyman/key dependency_overrides: graphql: ^5.2.0-beta.6