diff --git a/assets/giphy.png b/assets/giphy.png new file mode 100644 index 0000000..7361bbd Binary files /dev/null and b/assets/giphy.png differ diff --git a/assets/tenor.png b/assets/tenor.png new file mode 100644 index 0000000..71bb874 Binary files /dev/null and b/assets/tenor.png differ diff --git a/lib/components/root/gif.dart b/lib/components/root/gif.dart index 01381b9..ed9ad0e 100644 --- a/lib/components/root/gif.dart +++ b/lib/components/root/gif.dart @@ -12,6 +12,7 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:giphy_api_client/giphy_api_client.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:path/path.dart'; +import 'package:shimmer/shimmer.dart'; import 'package:super_clipboard/super_clipboard.dart'; class Gif extends HookConsumerWidget { @@ -21,6 +22,7 @@ class Gif extends HookConsumerWidget { Widget build(BuildContext context, ref) { final controller = useScrollController(); final focusNode = useFocusNode(); + final searchFocusNode = useFocusNode(); final tenorTrending = Queries.useTenorGet(ref); final giphyTrending = Queries.useGiphyGet(ref); @@ -83,6 +85,26 @@ class Gif extends HookConsumerWidget { return null; }, [text.value]); + final tenorImg = useMemoized( + () => Image.asset( + "assets/tenor.png", + height: 20, + width: 20, + ), + []); + + final giphyImg = useMemoized( + () => Image.asset( + "assets/giphy.png", + height: 40, + width: 40, + ), + []); + + final isEverythingLoading = text.value.isEmpty || searchGifs.isEmpty + ? (!tenorTrending.hasPageData && !giphyTrending.hasPageData) + : (!tenorSearch.hasPageData && !giphySearch.hasPageData); + return Column( children: [ CallbackShortcuts( @@ -92,114 +114,171 @@ class Gif extends HookConsumerWidget { }, }, child: TextField( + focusNode: searchFocusNode, onChanged: (value) => text.value = value, decoration: const InputDecoration( hintText: 'Search GIFs and Stickers', ), ), ), - Expanded( - child: GridView.builder( - controller: controller, - gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( - maxCrossAxisExtent: 200, - childAspectRatio: 1, - crossAxisSpacing: 10, - mainAxisSpacing: 10, - ), - itemCount: gifs.length + 1, - itemBuilder: (context, index) { - if (index == gifs.length) { - return Waypoint.item( - controller: controller, - onTouchEdge: () async { - if (text.value.isEmpty || searchGifs.isEmpty) { - if (giphyTrending.hasNextPage) { - await giphyTrending.fetchNext(); - } - if (tenorTrending.hasNextPage) { - await tenorTrending.fetchNext(); - } - } else { - if (giphySearch.hasNextPage) { - await giphySearch.fetchNext(); - } - if (tenorSearch.hasNextPage) { - await tenorSearch.fetchNext(); - } - } - }, - child: Stack( - children: const [ - Center( - child: SizedBox( - height: 50, - width: 50, - child: CircularProgressIndicator(), - ), + if (isEverythingLoading) + Expanded( + child: GridView.builder( + gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: 200, + childAspectRatio: 1, + crossAxisSpacing: 10, + mainAxisSpacing: 10, + ), + itemCount: 4, + itemBuilder: (context, index) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: Shimmer.fromColors( + baseColor: Colors.grey[850]!, + highlightColor: Colors.grey[800]!, + child: Container( + decoration: BoxDecoration( + color: Colors.grey[900], + borderRadius: BorderRadius.circular(10), ), - ], + ), ), ); - } - final gif = gifs[index]; - - final image = CachedNetworkImage( - imageUrl: gif, - fit: BoxFit.contain, - ); - - return InkWell( - borderRadius: BorderRadius.circular(10), - onTap: () async { - final imageFile = await DefaultCacheManager() - .getFileFromCache(gif) - .then((s) async => await s?.file.readAsBytes()); - if (imageFile == null) { - return; - } - await ClipboardWriter.instance.write([ - DataWriterItem(suggestedName: basename(gif)) - ..add(Formats.png(imageFile)) - ..add(Formats.bmp(imageFile)) - ..add(Formats.webp(imageFile)) - ..add(Formats.gif(imageFile)) - ..add(Formats.tiff(imageFile)) - ..add( - Formats.htmlText( - '', + }, + ), + ) + else + Expanded( + child: GridView.builder( + controller: controller, + gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: 200, + childAspectRatio: 1, + crossAxisSpacing: 10, + mainAxisSpacing: 10, + ), + itemCount: gifs.length + 1, + itemBuilder: (context, index) { + if (index == gifs.length) { + return Waypoint.item( + controller: controller, + onTouchEdge: () async { + if (text.value.isEmpty || searchGifs.isEmpty) { + if (giphyTrending.hasNextPage) { + await giphyTrending.fetchNext(); + } + if (tenorTrending.hasNextPage) { + await tenorTrending.fetchNext(); + } + } else { + if (giphySearch.hasNextPage) { + await giphySearch.fetchNext(); + } + if (tenorSearch.hasNextPage) { + await tenorSearch.fetchNext(); + } + } + }, + child: Stack( + children: const [ + Center( + child: SizedBox( + height: 50, + width: 50, + child: CircularProgressIndicator(), + ), ), - ) - ]); - SnackBar snackBar = SnackBar( - content: Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SizedBox(width: 35, child: image), - const SizedBox(width: 10), - const Text("Copied to clipboard!"), ], ), - behavior: SnackBarBehavior.floating, - duration: const Duration(seconds: 2), ); - if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar(snackBar); - Actions.invoke(context, const CloseWindowIntent()); - } - }, - focusNode: index == 0 ? focusNode : null, - canRequestFocus: true, - focusColor: Theme.of(context).colorScheme.secondary, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: image, - ), - ); - }, + } + final gif = gifs[index]; + + final image = CachedNetworkImage( + imageUrl: gif, + fit: BoxFit.contain, + ); + + return CallbackShortcuts( + bindings: { + LogicalKeySet(LogicalKeyboardKey.escape): () { + FocusScope.of(context).requestFocus(searchFocusNode); + }, + }, + child: InkWell( + borderRadius: BorderRadius.circular(10), + onTap: () async { + final imageFile = await DefaultCacheManager() + .getFileFromCache(gif) + .then((s) async => await s?.file.readAsBytes()); + if (imageFile == null) { + return; + } + await ClipboardWriter.instance.write([ + DataWriterItem(suggestedName: basename(gif)) + ..add(Formats.png(imageFile)) + ..add(Formats.bmp(imageFile)) + ..add(Formats.webp(imageFile)) + ..add(Formats.gif(imageFile)) + ..add(Formats.tiff(imageFile)) + ..add( + Formats.htmlText( + '', + ), + ) + ]); + SnackBar snackBar = SnackBar( + content: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox(width: 35, child: image), + const SizedBox(width: 10), + const Text("Copied to clipboard!"), + ], + ), + behavior: SnackBarBehavior.floating, + duration: const Duration(seconds: 2), + ); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar(snackBar); + Actions.invoke(context, const CloseWindowIntent()); + } + }, + focusNode: index == 0 ? focusNode : null, + canRequestFocus: true, + focusColor: Theme.of(context).colorScheme.secondary, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: image, + ), + ), + ); + }, + ), ), - ), + SizedBox( + height: 20, + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Text( + "Powered by", + style: Theme.of(context).textTheme.bodySmall, + ), + const SizedBox(width: 5), + giphyImg, + const SizedBox(width: 5), + Text( + "and", + style: Theme.of(context).textTheme.bodySmall, + ), + const SizedBox(width: 5), + tenorImg, + ], + ), + ) ], ); } diff --git a/pubspec.lock b/pubspec.lock index 7bd6482..f7c08cd 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -913,6 +913,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.3" + shimmer: + dependency: "direct main" + description: + name: shimmer + sha256: "1f1009b5845a1f88f1c5630212279540486f97409e9fc3f63883e71070d107bf" + url: "https://pub.dev" + source: hosted + version: "2.0.0" sky_engine: dependency: transitive description: flutter diff --git a/pubspec.yaml b/pubspec.yaml index 2af1781..cba1131 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -45,6 +45,7 @@ dependencies: path: ^1.8.2 easy_debounce: ^2.0.3 uuid: ^3.0.7 + shimmer: ^2.0.0 dev_dependencies: flutter_test: