diff --git a/CHANGELOG.md b/CHANGELOG.md index 479bf76..88a13b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,63 +1,4 @@ -## 1.0.0 -- chore: bump minimum Dart SDK version to 3.7 -- chore: bump `extended_image` to `^10.0.1` -- chore: bump `provider` to `^6.1.5+1` - -## 0.1.2 - -- fix: the endless loop can still happen if you switch between tabs quickly and time it perfectly while still loading gifs. - -[All Code Changes](https://github.com/Flyclops/tenor_flutter/compare/0.1.1...0.1.2) - -## 0.1.1 - -- fix: if you switch tabs very quickly before the initial gif fetch then you will get stuck in an endless loop and crash the app. - -[All Code Changes](https://github.com/Flyclops/tenor_flutter/compare/0.1.0...0.1.1) - -## 0.1.0 - -- feat: Integrate [tenor_dart's](https://pub.dev/packages/tenor_dart) new `TenorResult.source` parameter to track which tab the GIF was selected from for analytics -- fix: not being able to scroll down to load more on tablets or when using display zoom on iPad -- refactor: `TenorViewEmojis`, `TenorViewGifs`, `TenorViewStickers` and `TenorTabView` now have a `gifsPerRow` parameter instead of `mediaWidth` to be more explicit and support a wider range of devices - -[All Code Changes](https://github.com/Flyclops/tenor_flutter/compare/0.0.5...0.1.0) - -## 0.0.5 - -- fix: error when only one tab is passed in - -[All Code Changes](https://github.com/Flyclops/tenor_flutter/compare/0.0.4...0.0.5) - -## 0.0.4 - -- feature: add `coverAppBar` _(false)_ to disable calculations preventing bottom sheet from covering AppBar -- refactor: expose `animationStyle` as a parameter of `TenorStyle` -- refactor: expose `initialExtent` as a parameter of `showAsBottomSheet()`, defaults to `maxExtent` if not used -- refactor: expose `snapSizes` as a parameter of `showAsBottomSheet()` -- refactor: expose `useSafeArea` as a parameter of `showAsBottomSheet()` - -[All Code Changes](https://github.com/Flyclops/tenor_flutter/compare/0.0.3...0.0.4) - -## 0.0.3 - -- chore: write tests -- chore: updated/added examples and broke them up for a better experience -- fix: category was displaying `searchTerm` and now it's displaying `name` as that is localized from Tenor - - added `stripHashtag` _(true)_ to `TenorCategoryStyle` so you can display a hashtag before each category name if you'd like _(comes like that from Tenor)_ -- fix: issue where featured category could not be localized -- refactor: expose `keyboardDismissBehavior` as a parameter of `showAsBottomSheet()` -- refactor: expose `searchFieldHintText` as a parameter of `showAsBottomSheet()` to make localization easier - -[All Code Changes](https://github.com/Flyclops/tenor_flutter/compare/0.0.2...0.0.3) - -## 0.0.2 - -- chore: write tests -- refactor: support `tenor_dart` changes - -[All Code Changes](https://github.com/Flyclops/tenor_flutter/compare/0.0.1...0.0.2) - ## 0.0.1 -- Initial version +- refactor: [klipy_flutter](https://pub.dev/packages/klipy_flutter) so that it supports the KLIPY API via their [migration docs](https://docs.klipy.com/migrate-from-tenor). +- BREAKING: `contentFilter` has been removed and can be set via the [Partner Panel](https://docs.klipy.com/migrate-from-tenor/content-filtering) \ No newline at end of file diff --git a/README.md b/README.md index 5506d23..697ad9a 100644 --- a/README.md +++ b/README.md @@ -1,85 +1,84 @@ -# Tenor Flutter +# KLIPY Flutter

- Tenor Flutter Pub Package - Build Status - Coverage Status - Tenor Dart Stars - License BSD 3-Clause + KLIPY Flutter Pub Package + Build Status + Coverage Status + KLIPY Dart Stars + License BSD 3-Clause

-This package integrates [Tenor GIF search](https://tenor.com/) into [Flutter](https://flutter.dev/) by utilizing the [tenor_dart](https://pub.dev/packages/tenor_dart) package to communicate directly with the [Tenor API V2](https://developers.google.com/tenor/guides/quickstart) via [http](https://pub.dev/packages/http). +This package integrates [KLIPY GIF search](https://klipy.com/) into [Flutter](https://flutter.dev/) by utilizing the [klipy_dart](https://pub.dev/packages/klipy_dart) package to communicate directly with the [KLIPY API](https://docs.klipy.com/getting-started) via [http](https://pub.dev/packages/http). We are currently using the [migration from Tenor](https://docs.klipy.com/migrate-from-tenor) option but plan to build this package out to be feature complete with the KLIPY API. -The package currently provides an opinionated yet customizable UI experience for searching and selecting from a list of GIFs/Stickers from the Tenor GIF search API. +The package currently provides an opinionated yet customizable UI experience for searching and selecting from a list of GIFs/Stickers from the KLIPY GIF search API. -

Tenor Flutter Demo

+

KLIPY Flutter Demo

-

Show some ❤️ and star the repo to support this package.

+

Show some ❤️ and star the repo to support this package.

## What to know -- In order to start using Tenor Flutter you must obtain an API key by registering your project with [Tenor](https://developers.google.com/tenor/guides/quickstart). -- Tenor requires proper [attribution](https://developers.google.com/tenor/guides/attribution) for projects using their API. This package enables "Powered By Tenor" and "Search Tenor" by default. You are only required to have one. +- In order to start using KLIPY Dart you must obtain an API key by registering your project with [KLIPY](https://docs.klipy.com/getting-started). +- KLIPY requires proper [attribution](https://docs.klipy.com/attribution) for projects using their API. This package does not handle the attribution process, so you will need to take care of it yourself. -## Obtaining Tenor API v2 key +## Obtaining KLIPY API key -1. Log in to the [Google Cloud Console](https://console.cloud.google.com) -2. Create a [new project](https://console.cloud.google.com/projectcreate) -3. Go to the Google Cloud Marketplace and find the [Tenor API](https://console.cloud.google.com/marketplace/product/google/tenor.googleapis.com) -4. Click `Enable` to activate it -5. In the navigation menu, go to the `APIs & Services` tab and select [Credentials](https://console.cloud.google.com/apis/credentials) -6. Click `+ Create Credentials` and choose `API key` -7. Copy the generated API key -8. Provide this API key as a parameter to `Tenor(apiKey: 'YOUR_API_KEY')` +1. Log in to the [partner panel](https://partner.klipy.com) +2. Add a [new platform](https://partner.klipy.com/api-keys) +3. Click `Create Key` +4. Copy the generated API key +5. Provide this API key as a parameter to `KlipyClient(apiKey: 'YOUR_API_KEY')` ## Usage ### Installation ``` -flutter pub add tenor_flutter +flutter pub add klipy_flutter ``` -Having trouble? Read the pub.dev installation page. +Having trouble? Read the pub.dev installation page. ### Import Import the package into the dart file where it will be used: ``` -import 'package:tenor_flutter/tenor_flutter.dart'; +import 'package:klipy_flutter/klipy_flutter.dart'; ``` ### Initialize -You must pass in a valid `apiKey` provided by [Tenor](https://developers.google.com/tenor/guides/quickstart). It's **strongly recommended** to also pass in a `clientKey` as this will help you distinguish which project is making the requests. +You must pass in a valid `apiKey` provided by [KLIPY](https://docs.klipy.com/getting-started). + +If you would like to distinguish between projects/devices then consider creating seperate [API keys](https://partner.klipy.com/api-keys) under the same platform. ``` -final tenorClient = Tenor(apiKey: 'YOUR_API_KEY', clientKey: 'YOUR_PROJECT_NAME'); +final klipyClient = KlipyClient(apiKey: 'YOUR_API_KEY'); ``` ## Example -For more elaborate examples feel free to check out [example/lib/main.dart](https://github.com/Flyclops/tenor_flutter/blob/main/example/lib/main.dart). +For more elaborate examples feel free to check out [example/lib/main.dart](https://github.com/Flyclops/klipy_flutter/blob/main/example/lib/main.dart). Here's how to display the UI as a bottom sheet and then print the user's selection. If `null` is returned, it means the user closed the sheet without choosing a GIF. ``` -final tenorClient = Tenor(apiKey: 'YOUR_API_KEY', clientKey: 'YOUR_PROJECT_NAME'); -final TenorResult? result = await tenorClient.showAsBottomSheet(context: context); +final klipyClient = KlipyClient(apiKey: 'YOUR_API_KEY'); +final KlipyResultObject? result = await klipyClient.showAsBottomSheet(context: context); print(result?.media.tinyGif?.url); ``` ## Don't need the UI? -If you're seeking a solution that allows for full customization without the need of dependencies then consider [Tenor Dart](https://github.com/Flyclops/tenor_dart). +If you're seeking a solution that allows for full customization without the need of dependencies then consider [KLIPY Dart](https://github.com/Flyclops/klipy_dart). ## Sponsors - - + + @@ -90,14 +89,14 @@ If you're seeking a solution that allows for full customization without the need ## What's next? - Documentation -- Tests _([Contributions](https://github.com/Flyclops/tenor_flutter/blob/main/CONTRIBUTING.md) welcome)_ ^\_^ +- Tests _([Contributions](https://github.com/Flyclops/klipy_flutter/blob/main/CONTRIBUTING.md) welcome)_ ^\_^ - Further improvements ## Contributing -If you read this far then you are awesome! There are a multiple ways in which you can [contribute](https://github.com/Flyclops/tenor_flutter/blob/main/CONTRIBUTING.md): +If you read this far then you are awesome! There are a multiple ways in which you can [contribute](https://github.com/Flyclops/klipy_flutter/blob/main/CONTRIBUTING.md): -- Pick up any issue marked with "[good first issue](https://github.com/flyclops/tenor_flutter/issues?q=is:open+is:issue+label:%22good+first+issue%22)" +- Pick up any issue marked with "[good first issue](https://github.com/flyclops/klipy_flutter/issues?q=is:open+is:issue+label:%22good+first+issue%22)" - Propose any feature, enhancement - Report a bug - Fix a bug diff --git a/assets/media_watermark.png b/assets/media_watermark.png new file mode 100644 index 0000000..9bafc25 Binary files /dev/null and b/assets/media_watermark.png differ diff --git a/assets/powered_by_dark.png b/assets/powered_by_dark.png index 9f084f0..294e47c 100644 Binary files a/assets/powered_by_dark.png and b/assets/powered_by_dark.png differ diff --git a/assets/powered_by_light.png b/assets/powered_by_light.png index 86b6a4f..3198bb6 100644 Binary files a/assets/powered_by_light.png and b/assets/powered_by_light.png differ diff --git a/example/README.md b/example/README.md index 56af23f..bebc0e3 100644 --- a/example/README.md +++ b/example/README.md @@ -1,3 +1,3 @@ -# Tenor Flutter Example +# KLIPY Flutter Example -If you want to test this example create a `.env` file located at `example/.env` with your [Tenor API](https://developers.google.com/tenor/guides/quickstart) key `TENOR_API_KEY=PUT_YOUR_KEY_HERE` and then run `example/lib/main.dart`. +If you want to test this example create a `.env` file located at `example/.env` with your [KLIPY API](https://docs.klipy.com/getting-started) key `KLIPY_API_KEY=PUT_YOUR_KEY_HERE` and then run `example/lib/main.dart`. diff --git a/example/assets/demo.gif b/example/assets/demo.gif index 5e0a07b..00db346 100644 Binary files a/example/assets/demo.gif and b/example/assets/demo.gif differ diff --git a/example/assets/domino_logo_github.png b/example/assets/domino_logo_github.png index 8b27928..acd7ea3 100644 Binary files a/example/assets/domino_logo_github.png and b/example/assets/domino_logo_github.png differ diff --git a/example/assets/flyclops_logo_github.png b/example/assets/flyclops_logo_github.png index a24c69e..7a56c57 100644 Binary files a/example/assets/flyclops_logo_github.png and b/example/assets/flyclops_logo_github.png differ diff --git a/example/lib/examples/dark_theme.dart b/example/lib/examples/dark_theme.dart index 91a7eaa..b70b93c 100644 --- a/example/lib/examples/dark_theme.dart +++ b/example/lib/examples/dark_theme.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_config/flutter_config.dart'; -import 'package:tenor_flutter/tenor_flutter.dart'; +import 'package:klipy_flutter/klipy_flutter.dart'; class DarkTheme extends StatefulWidget { const DarkTheme({super.key}); @@ -10,10 +10,10 @@ class DarkTheme extends StatefulWidget { } class DarkThemeState extends State { - // replace apiKey with an api key provided by Tenor > https://developers.google.com/tenor/guides/quickstart - var tenor = Tenor(apiKey: FlutterConfig.get('TENOR_API_KEY')); + // replace apiKey with an api key provided by KLIPY > https://docs.klipy.com/getting-started + var klipyClient = KlipyClient(apiKey: FlutterConfig.get('KLIPY_API_KEY')); // define a result that we can display later - TenorResult? selectedResult; + KlipyResultObject? selectedResult; @override Widget build(BuildContext context) { @@ -44,11 +44,11 @@ class DarkThemeState extends State { FloatingActionButton( backgroundColor: Colors.black, onPressed: () async { - final result = await tenor.showAsBottomSheet( + final result = await klipyClient.showAsBottomSheet( context: context, - style: TenorStyle( + style: KlipyStyle( color: const Color(0xFF2b2d31), - searchFieldStyle: const TenorSearchFieldStyle( + searchFieldStyle: const KlipySearchFieldStyle( fillColor: Color(0xFF1e1f22), hintStyle: TextStyle( color: Color(0xFFb5bac1), @@ -61,10 +61,10 @@ class DarkThemeState extends State { fontWeight: FontWeight.normal, ), ), - attributionStyle: const TenorAttributionStyle( + attributionStyle: const KlipyAttributionStyle( brightnes: Brightness.dark, ), - tabBarStyle: TenorTabBarStyle( + tabBarStyle: KlipyTabBarStyle( decoration: BoxDecoration( color: const Color(0xFF1e1f22), border: Border.all( @@ -85,7 +85,7 @@ class DarkThemeState extends State { unselectedLabelColor: const Color(0xFFb5bac1), labelColor: Colors.white, ), - selectedCategoryStyle: const TenorSelectedCategoryStyle( + selectedCategoryStyle: const KlipySelectedCategoryStyle( icon: Icon( Icons.arrow_back_ios_new, size: 15, @@ -97,35 +97,35 @@ class DarkThemeState extends State { fontWeight: FontWeight.normal, ), ), - tabViewStyle: const TenorTabViewStyle( + tabViewStyle: const KlipyTabViewStyle( mediaBackgroundColor: Color(0xFF404249), ), ), tabs: [ - TenorTab( + KlipyTab( name: 'Emojis', - view: TenorViewEmojis( - client: tenor, - style: const TenorTabViewStyle( + view: KlipyViewEmojis( + client: klipyClient, + style: const KlipyTabViewStyle( mediaBackgroundColor: Color(0xFF404249), ), ), ), - TenorTab( + KlipyTab( name: 'GIFs', - view: TenorViewGifs( - client: tenor, - style: const TenorTabViewStyle( + view: KlipyViewGifs( + client: klipyClient, + style: const KlipyTabViewStyle( mediaBackgroundColor: Color(0xFF404249), ), featuredCategory: '📈 Featured22', ), ), - TenorTab( + KlipyTab( name: 'Stickers', - view: TenorViewStickers( - client: tenor, - style: const TenorTabViewStyle( + view: KlipyViewStickers( + client: klipyClient, + style: const KlipyTabViewStyle( mediaBackgroundColor: Color(0xFF404249), ), ), diff --git a/example/lib/examples/localization.dart b/example/lib/examples/localization.dart index d521fcc..0c8d590 100644 --- a/example/lib/examples/localization.dart +++ b/example/lib/examples/localization.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_config/flutter_config.dart'; -import 'package:tenor_flutter/tenor_flutter.dart'; +import 'package:klipy_flutter/klipy_flutter.dart'; class Localization extends StatefulWidget { const Localization({super.key}); @@ -10,14 +10,14 @@ class Localization extends StatefulWidget { } class LocalizationState extends State { - // replace apiKey with an api key provided by Tenor > https://developers.google.com/tenor/guides/quickstart - var tenor = Tenor( - apiKey: FlutterConfig.get('TENOR_API_KEY'), + // replace apiKey with an api key provided by KLIPY > https://docs.klipy.com/getting-started + var klipyClient = KlipyClient( + apiKey: FlutterConfig.get('KLIPY_API_KEY'), country: 'es', locale: 'es_ES', ); // define a result that we can display later - TenorResult? selectedResult; + KlipyResultObject? selectedResult; @override Widget build(BuildContext context) { @@ -47,27 +47,27 @@ class LocalizationState extends State { children: [ FloatingActionButton( onPressed: () async { - final result = await tenor.showAsBottomSheet( + final result = await klipyClient.showAsBottomSheet( context: context, - searchFieldHintText: 'Buscar Tenor', + searchFieldHintText: 'Buscar KLIPY', tabs: [ - TenorTab( + KlipyTab( name: 'Caritas', - view: TenorViewEmojis( - client: tenor, + view: KlipyViewEmojis( + client: klipyClient, ), ), - TenorTab( + KlipyTab( name: 'Fotos', - view: TenorViewGifs( - client: tenor, + view: KlipyViewGifs( + client: klipyClient, featuredCategory: '📈 Destacada', ), ), - TenorTab( + KlipyTab( name: 'Pegatinas', - view: TenorViewStickers( - client: tenor, + view: KlipyViewStickers( + client: klipyClient, ), ), ], diff --git a/example/lib/main.dart b/example/lib/main.dart index 38b0f1a..03374c3 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_config/flutter_config.dart'; -import 'package:tenor_flutter/tenor_flutter.dart'; -import 'package:tenor_flutter_example/examples/dark_theme.dart'; -import 'package:tenor_flutter_example/examples/localization.dart'; +import 'package:klipy_flutter/klipy_flutter.dart'; +import 'package:klipy_flutter_example/examples/dark_theme.dart'; +import 'package:klipy_flutter_example/examples/localization.dart'; void main() async { // only used to load api key from .env file, not required @@ -18,7 +18,7 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( - title: 'Tenor Flutter Demo', + title: 'KLIPY Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), @@ -35,27 +35,28 @@ class MyHomePage extends StatefulWidget { } class _MyHomePageState extends State { - // replace apiKey with an api key provided by Tenor > https://developers.google.com/tenor/guides/quickstart - var tenor = Tenor(apiKey: FlutterConfig.get('TENOR_API_KEY')); + // replace apiKey with an api key provided by KLIPY > https://docs.klipy.com/getting-started + var klipyClient = KlipyClient(apiKey: FlutterConfig.get('KLIPY_API_KEY')); // define a result that we can display later - TenorResult? selectedResult; + KlipyResultObject? selectedResult; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( backgroundColor: Theme.of(context).colorScheme.inversePrimary, - title: const Text('Tenor Flutter Demo'), + title: const Text('KLIPY Flutter Demo'), ), body: _exampleBody(), floatingActionButton: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ - // A default implementation of tenor flutter. Displays the gif picker + // A default implementation of klipy flutter. Displays the gif picker // as a bottom sheet and then updates the selectedResult in state. FloatingActionButton( onPressed: () async { - final result = await tenor.showAsBottomSheet(context: context); + final result = + await klipyClient.showAsBottomSheet(context: context); setState(() { selectedResult = result; }); @@ -68,7 +69,7 @@ class _MyHomePageState extends State { ); } - // Additional examples, see: https://github.com/Flyclops/tenor_flutter/tree/main/example/lib/examples + // Additional examples, see: https://github.com/Flyclops/klipy_flutter/tree/main/example/lib/examples Widget _exampleBody() { final selectedGif = selectedResult?.media.tinyGif ?? selectedResult?.media.tinyGifTransparent; @@ -87,12 +88,12 @@ class _MyHomePageState extends State { alignment: WrapAlignment.center, spacing: 8, children: [ - // https://github.com/Flyclops/tenor_flutter/tree/main/example/lib/examples/dark_theme.dart + // https://github.com/Flyclops/klipy_flutter/tree/main/example/lib/examples/dark_theme.dart ElevatedButton( onPressed: () => push(const DarkTheme()), child: const Text('Dark Theme'), ), - // https://github.com/Flyclops/tenor_flutter/tree/main/example/lib/examples/localization.dart + // https://github.com/Flyclops/klipy_flutter/tree/main/example/lib/examples/localization.dart ElevatedButton( onPressed: () => push(const Localization()), child: const Text('Localization'), @@ -106,10 +107,12 @@ class _MyHomePageState extends State { mainAxisAlignment: MainAxisAlignment.center, children: [ selectedResult != null && selectedGif != null - ? Image.network( - selectedGif.url, - width: selectedGif.dimensions.width, - height: selectedGif.dimensions.height, + ? KlipyMediaWidget( + media: Image.network( + selectedGif.url, + width: selectedGif.dimensions.width, + height: selectedGif.dimensions.height, + ), ) : const Text('No GIF selected'), selectedGif != null diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 0b50a34..7317123 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,19 +1,18 @@ -name: tenor_flutter_example -description: "An example of how to use Tenor Flutter" +name: klipy_flutter_example +description: "An example of how to use KLIPY Flutter" publish_to: "none" - -version: 1.0.0+1 +version: 1.0.0 environment: sdk: ">=3.2.6 <4.0.0" dependencies: + cupertino_icons: ^1.0.2 flutter: sdk: flutter flutter_config: ^2.0.2 - tenor_flutter: + klipy_flutter: path: ../ - cupertino_icons: ^1.0.2 dev_dependencies: flutter_test: diff --git a/lib/klipy_flutter.dart b/lib/klipy_flutter.dart new file mode 100644 index 0000000..207be10 --- /dev/null +++ b/lib/klipy_flutter.dart @@ -0,0 +1,15 @@ +// hide KlipyClient from klipy_dart so we can extend it later +export 'package:klipy_dart/klipy_dart.dart' hide KlipyClient; + +export 'src/components/attribution.dart' show KlipyAttributionStyle; +export 'src/components/drag_handle.dart' show KlipyDragHandleStyle; +export 'src/components/media_widget.dart' show KlipyMediaWidget; +export 'src/components/search_field.dart' + show KlipySelectedCategoryStyle, KlipySearchFieldStyle; +export 'src/components/tab_bar.dart' show KlipyTabBarStyle; +export 'src/components/tab_view_emojis.dart'; +export 'src/components/tab_view_gifs.dart'; +export 'src/components/tab_view_stickers.dart'; +export 'src/components/tab_view.dart' show KlipyTabView, KlipyTabViewStyle; +export 'src/klipy_client.dart'; +export 'src/models/models.dart'; diff --git a/lib/src/components/attribution.dart b/lib/src/components/attribution.dart index 8787431..20157f4 100644 --- a/lib/src/components/attribution.dart +++ b/lib/src/components/attribution.dart @@ -1,59 +1,51 @@ import 'package:flutter/material.dart'; -class TenorAttributionStyle { +class KlipyAttributionStyle { final Brightness brightnes; final double height; final EdgeInsets padding; - const TenorAttributionStyle({ + const KlipyAttributionStyle({ this.brightnes = Brightness.light, - this.height = 15, - this.padding = const EdgeInsets.symmetric( - vertical: 8, - ), + this.height = 20, + this.padding = const EdgeInsets.symmetric(vertical: 8), }); } -class TenorAttribution extends StatelessWidget { - final TenorAttributionStyle style; +class KlipyAttribution extends StatelessWidget { + final KlipyAttributionStyle style; - const TenorAttribution({ - this.style = const TenorAttributionStyle(), + const KlipyAttribution({ + this.style = const KlipyAttributionStyle(), super.key, }); @override Widget build(BuildContext context) { + String logoPath = + style.brightnes == Brightness.light + ? 'powered_by_dark.png' + : 'powered_by_light.png'; return Padding( // If safe area is required, add it. - padding: MediaQuery.of(context).padding.bottom > 0 - ? style.padding.copyWith( - bottom: MediaQuery.of(context).padding.bottom, - ) - : style.padding, + padding: + MediaQuery.of(context).padding.bottom > 0 + ? style.padding.copyWith( + bottom: MediaQuery.of(context).padding.bottom, + ) + : style.padding, child: Center( - child: _logo(context), - ), - ); - } - - Widget _logo(BuildContext context) { - String logoPath = style.brightnes == Brightness.light - ? 'powered_by_dark.png' - : 'powered_by_light.png'; - - return Container( - height: style.height, - decoration: BoxDecoration( - image: DecorationImage( - fit: BoxFit.fitHeight, - image: AssetImage( + child: Container( + alignment: Alignment.center, + width: double.infinity, + child: Image.asset( 'assets/$logoPath', - package: 'tenor_flutter', + package: 'klipy_flutter', + height: style.height, + fit: BoxFit.contain, ), ), ), - width: double.infinity, ); } } diff --git a/lib/src/components/category.dart b/lib/src/components/category.dart index 766d43f..77b5289 100644 --- a/lib/src/components/category.dart +++ b/lib/src/components/category.dart @@ -1,8 +1,8 @@ import 'package:extended_image/extended_image.dart'; import 'package:flutter/material.dart'; -import 'package:tenor_dart/tenor_dart.dart'; +import 'package:klipy_flutter/klipy_flutter.dart'; -class TenorCategoryStyle { +class KlipyCategoryStyle { /// Allows you to set a fallback decoration for people to see if the image loads slow or fails. final Decoration decoration; @@ -12,7 +12,7 @@ class TenorCategoryStyle { /// Color that shows between the category text and image. final Color imageOverlayColor; - /// Tenor returns "#" back with the category name, this will strip it. + /// KLIPY returns "#" back with the category name, this will strip it. final bool stripHashtag; /// Control how the category text looks. @@ -21,10 +21,8 @@ class TenorCategoryStyle { /// Used to stop text from touching the edges when `FittedBox` kicks in. final EdgeInsets padding; - const TenorCategoryStyle({ - this.decoration = const BoxDecoration( - color: Color(0xFFBEB9AC), - ), + const KlipyCategoryStyle({ + this.decoration = const BoxDecoration(color: Color(0xFFBEB9AC)), this.height = 100, this.imageOverlayColor = const Color.fromRGBO(0, 0, 0, 0.5), this.padding = const EdgeInsets.all(8), @@ -37,29 +35,29 @@ class TenorCategoryStyle { }); } -class TenorCategoryWidget extends StatelessWidget { - final TenorCategory? category; - final Function(TenorCategory)? onTap; - final TenorCategoryStyle style; +class KlipyCategoryWidget extends StatelessWidget { + final KlipyCategoryObject? category; + final Function(KlipyCategoryObject)? onTap; + final KlipyCategoryStyle style; - const TenorCategoryWidget({ + const KlipyCategoryWidget({ this.category, this.onTap, - this.style = const TenorCategoryStyle(), + this.style = const KlipyCategoryStyle(), super.key, }); @override Widget build(BuildContext context) { // for promotion - final tenorCategory = category; - final tenorCategoryImage = tenorCategory?.image; + final klipyCategory = category; + final klipyCategoryImage = klipyCategory?.image; // early out - if (tenorCategory == null) return const SizedBox.shrink(); + if (klipyCategory == null) return const SizedBox.shrink(); return GestureDetector( - onTap: () => onTap?.call(tenorCategory), + onTap: () => onTap?.call(klipyCategory), child: Container( height: style.height, decoration: style.decoration, @@ -67,9 +65,9 @@ class TenorCategoryWidget extends StatelessWidget { child: Stack( fit: StackFit.expand, children: [ - if (tenorCategoryImage != null && tenorCategoryImage.isNotEmpty) + if (klipyCategoryImage != null && klipyCategoryImage.isNotEmpty) ExtendedImage.network( - tenorCategoryImage, + klipyCategoryImage, cache: true, gaplessPlayback: true, fit: BoxFit.cover, @@ -81,9 +79,9 @@ class TenorCategoryWidget extends StatelessWidget { child: FittedBox( fit: BoxFit.fitWidth, child: Text( - style.stripHashtag && tenorCategory.name.startsWith('#') - ? tenorCategory.name.substring(1) - : tenorCategory.name, + style.stripHashtag && klipyCategory.name.startsWith('#') + ? klipyCategory.name.substring(1) + : klipyCategory.name, style: style.textStyle, ), ), diff --git a/lib/src/components/components.dart b/lib/src/components/components.dart index cc9a5aa..ea0eb16 100644 --- a/lib/src/components/components.dart +++ b/lib/src/components/components.dart @@ -1,11 +1,12 @@ export 'attribution.dart'; export 'category.dart'; export 'drag_handle.dart'; +export 'media_widget.dart'; export 'search_field.dart'; export 'selectable_gif.dart'; export 'sheet.dart'; export 'tab_bar.dart'; -export 'tab_view.dart'; export 'tab_view_emojis.dart'; export 'tab_view_gifs.dart'; export 'tab_view_stickers.dart'; +export 'tab_view.dart'; diff --git a/lib/src/components/drag_handle.dart b/lib/src/components/drag_handle.dart index 64fb529..5b79e14 100644 --- a/lib/src/components/drag_handle.dart +++ b/lib/src/components/drag_handle.dart @@ -1,31 +1,27 @@ import 'package:flutter/material.dart'; -class TenorDragHandleStyle { +class KlipyDragHandleStyle { final Decoration decoration; final double height; final EdgeInsets margin; final double width; - const TenorDragHandleStyle({ + const KlipyDragHandleStyle({ this.decoration = const BoxDecoration( color: Color(0xFF8A8A86), - borderRadius: BorderRadius.all( - Radius.circular(100), - ), + borderRadius: BorderRadius.all(Radius.circular(100)), ), this.height = 4, - this.margin = const EdgeInsets.symmetric( - vertical: 8, - ), + this.margin = const EdgeInsets.symmetric(vertical: 8), this.width = 134, }); } -class TenorDragHandle extends StatelessWidget { - final TenorDragHandleStyle _style; +class KlipyDragHandle extends StatelessWidget { + final KlipyDragHandleStyle _style; - const TenorDragHandle({ - TenorDragHandleStyle style = const TenorDragHandleStyle(), + const KlipyDragHandle({ + KlipyDragHandleStyle style = const KlipyDragHandleStyle(), super.key, }) : _style = style; diff --git a/lib/src/components/media_widget.dart b/lib/src/components/media_widget.dart new file mode 100644 index 0000000..60f113f --- /dev/null +++ b/lib/src/components/media_widget.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; + +class KlipyMediaWidget extends StatelessWidget { + final Widget media; + + const KlipyMediaWidget({required this.media, super.key}); + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + media, + Positioned( + bottom: 2, + left: 2, + child: Image.asset( + 'assets/media_watermark.png', + package: 'klipy_flutter', + height: 10, + ), + ), + ], + ); + } +} diff --git a/lib/src/components/search_field.dart b/lib/src/components/search_field.dart index 1da9c00..e6ff8c9 100644 --- a/lib/src/components/search_field.dart +++ b/lib/src/components/search_field.dart @@ -2,12 +2,12 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -import 'package:tenor_flutter/src/providers/app_bar_provider.dart'; -import 'package:tenor_flutter/src/providers/sheet_provider.dart'; -import 'package:tenor_flutter/src/utilities/debouncer.dart'; -import 'package:tenor_flutter/tenor_flutter.dart'; +import 'package:klipy_flutter/klipy_flutter.dart'; +import 'package:klipy_flutter/src/providers/app_bar_provider.dart'; +import 'package:klipy_flutter/src/providers/sheet_provider.dart'; +import 'package:klipy_flutter/src/utilities/debouncer.dart'; -class TenorSelectedCategoryStyle { +class KlipySelectedCategoryStyle { final double height; final EdgeInsets padding; final Icon? icon; @@ -16,12 +16,9 @@ class TenorSelectedCategoryStyle { /// The space between icon and text. final double spaceBetween; - const TenorSelectedCategoryStyle({ + const KlipySelectedCategoryStyle({ this.height = 52, - this.padding = const EdgeInsets.only( - left: 14, - top: 1, - ), + this.padding = const EdgeInsets.only(left: 14, top: 1), this.icon = const Icon( Icons.arrow_back_ios_new, size: 15, @@ -36,12 +33,12 @@ class TenorSelectedCategoryStyle { }); } -class TenorSearchFieldStyle { +class KlipySearchFieldStyle { final Color fillColor; final TextStyle textStyle; final TextStyle hintStyle; - const TenorSearchFieldStyle({ + const KlipySearchFieldStyle({ this.fillColor = Colors.white, this.hintStyle = const TextStyle( color: Color(0xFF8A8A86), @@ -57,34 +54,34 @@ class TenorSearchFieldStyle { } /// If you want to style this just pass in your own via the `searchFieldWidget` parameter. -class TenorSearchField extends StatefulWidget { +class KlipySearchField extends StatefulWidget { // Scroll Controller final ScrollController scrollController; final TextEditingController? searchFieldController; final Widget? searchFieldWidget; - final TenorSelectedCategoryStyle selectedCategoryStyle; - final TenorSearchFieldStyle style; + final KlipySelectedCategoryStyle selectedCategoryStyle; + final KlipySearchFieldStyle style; final String hintText; final AnimationStyle? animationStyle; - const TenorSearchField({ + const KlipySearchField({ super.key, required this.hintText, required this.scrollController, this.animationStyle, this.searchFieldController, this.searchFieldWidget, - this.selectedCategoryStyle = const TenorSelectedCategoryStyle(), - this.style = const TenorSearchFieldStyle(), + this.selectedCategoryStyle = const KlipySelectedCategoryStyle(), + this.style = const KlipySearchFieldStyle(), }); @override - State createState() => _TenorSearchFieldState(); + State createState() => _KlipySearchFieldState(); } -class _TenorSearchFieldState extends State { - late TenorAppBarProvider _appBarProvider; - late TenorSheetProvider _sheetProvider; +class _KlipySearchFieldState extends State { + late KlipyAppBarProvider _appBarProvider; + late KlipySheetProvider _sheetProvider; late TextEditingController _textEditingController; final FocusNode _focus = FocusNode(); @@ -94,22 +91,19 @@ class _TenorSearchFieldState extends State { _focus.addListener(() => _onFocusChange(widget.animationStyle)); // AppBar Provider - _appBarProvider = Provider.of(context, listen: false); + _appBarProvider = Provider.of(context, listen: false); // Listen query _appBarProvider.addListener(_listenerQuery); // Set Texfield Controller - _textEditingController = widget.searchFieldController ?? - TextEditingController( - text: _appBarProvider.queryText, - ); + _textEditingController = + widget.searchFieldController ?? + TextEditingController(text: _appBarProvider.queryText); WidgetsBinding.instance.addPostFrameCallback((_) { // Establish the debouncer - final debouncer = TenorDebouncer( - delay: _appBarProvider.debounce, - ); + final debouncer = KlipyDebouncer(delay: _appBarProvider.debounce); // Listener TextField _textEditingController.addListener(() { @@ -125,8 +119,8 @@ class _TenorSearchFieldState extends State { @override void didChangeDependencies() { - _sheetProvider = Provider.of(context); - _appBarProvider = Provider.of(context); + _sheetProvider = Provider.of(context); + _appBarProvider = Provider.of(context); super.didChangeDependencies(); } @@ -218,7 +212,8 @@ class _TenorSearchFieldState extends State { padding: const EdgeInsets.all(8), child: Icon( Icons.clear, - color: widget.style.hintStyle.color ?? + color: + widget.style.hintStyle.color ?? const Color(0xFF8A8A86), size: 20, ), @@ -241,7 +236,7 @@ class _TenorSearchFieldState extends State { _sheetProvider.scrollController.animateTo( _sheetProvider.maxExtent, duration: - animationStyle?.duration ?? tenorDefaultAnimationStyle.duration!, + animationStyle?.duration ?? klipyDefaultAnimationStyle.duration!, curve: animationStyle?.curve ?? Curves.linear, ); } diff --git a/lib/src/components/selectable_gif.dart b/lib/src/components/selectable_gif.dart index 44bce1d..e533bba 100644 --- a/lib/src/components/selectable_gif.dart +++ b/lib/src/components/selectable_gif.dart @@ -1,13 +1,13 @@ import 'package:extended_image/extended_image.dart'; import 'package:flutter/material.dart'; -import 'package:tenor_flutter/tenor_flutter.dart'; +import 'package:klipy_flutter/klipy_flutter.dart'; -class TenorSelectableGif extends StatelessWidget { +class KlipySelectableGif extends StatelessWidget { final Color backgroundColor; - final Function(TenorResult)? onTap; - final TenorResult result; + final Function(KlipyResultObject)? onTap; + final KlipyResultObject result; - const TenorSelectableGif({ + const KlipySelectableGif({ required this.result, this.backgroundColor = Colors.transparent, this.onTap, diff --git a/lib/src/components/sheet.dart b/lib/src/components/sheet.dart index bb6b47a..7af7206 100644 --- a/lib/src/components/sheet.dart +++ b/lib/src/components/sheet.dart @@ -1,28 +1,27 @@ // ignore_for_file: implementation_imports import 'package:flutter/material.dart'; +import 'package:klipy_flutter/src/components/attribution.dart'; +import 'package:klipy_flutter/src/components/drag_handle.dart'; +import 'package:klipy_flutter/src/components/search_field.dart'; +import 'package:klipy_flutter/src/components/tab_bar.dart'; +import 'package:klipy_flutter/src/models/attribution.dart'; +import 'package:klipy_flutter/src/models/tab.dart'; +import 'package:klipy_flutter/src/providers/providers.dart'; +import 'package:klipy_flutter/src/klipy_client.dart'; import 'package:provider/provider.dart'; -import 'package:tenor_flutter/src/components/attribution.dart'; - -import 'package:tenor_flutter/src/components/drag_handle.dart'; -import 'package:tenor_flutter/src/components/search_field.dart'; -import 'package:tenor_flutter/src/components/tab_bar.dart'; -import 'package:tenor_flutter/src/models/attribution.dart'; -import 'package:tenor_flutter/src/models/tab.dart'; -import 'package:tenor_flutter/src/providers/providers.dart'; -import 'package:tenor_flutter/src/tenor.dart'; - -class TenorSheet extends StatefulWidget { - final TenorAttributionType attributionType; + +class KlipySheet extends StatefulWidget { + final KlipyAttributionType attributionType; final bool coverAppBar; final int initialTabIndex; final TextEditingController? searchFieldController; final String searchFieldHintText; final Widget? searchFieldWidget; - final TenorStyle style; + final KlipyStyle style; final List? snapSizes; - final List tabs; + final List tabs; - const TenorSheet({ + const KlipySheet({ required this.attributionType, required this.coverAppBar, required this.searchFieldHintText, @@ -36,21 +35,21 @@ class TenorSheet extends StatefulWidget { }); @override - State createState() => _TenorSheetState(); + State createState() => _KlipySheetState(); } -class _TenorSheetState extends State +class _KlipySheetState extends State with SingleTickerProviderStateMixin { late TabController tabController; - late TenorSheetProvider sheetProvider; - late TenorTabProvider tabProvider; + late KlipySheetProvider sheetProvider; + late KlipyTabProvider tabProvider; late final bool canShowTabs; @override void initState() { super.initState(); - tabProvider = Provider.of(context, listen: false); + tabProvider = Provider.of(context, listen: false); canShowTabs = widget.tabs.length > 1; if (canShowTabs) { @@ -70,7 +69,7 @@ class _TenorSheetState extends State @override void didChangeDependencies() { - sheetProvider = Provider.of(context, listen: false); + sheetProvider = Provider.of(context, listen: false); super.didChangeDependencies(); } @@ -132,8 +131,9 @@ class _TenorSheetState extends State onNotification: (notification) { // Fix a weird bug where the sheet doesn't snap to the minExtent // Ends in something like 0.5000000000000001 instead of 0.5 - final extent = - double.parse(notification.extent.toStringAsPrecision(15)); + final extent = double.parse( + notification.extent.toStringAsPrecision(15), + ); if (extent == sheetProvider.minExtent) { sheetProvider.scrollController.jumpTo(sheetProvider.minExtent); } @@ -143,13 +143,15 @@ class _TenorSheetState extends State controller: sheetProvider.scrollController, expand: false, // just in case we calculate a smaller maxChildSize than initialChildSize - initialChildSize: sheetProvider.initialExtent > maxChildSize - ? maxChildSize - : sheetProvider.initialExtent, + initialChildSize: + sheetProvider.initialExtent > maxChildSize + ? maxChildSize + : sheetProvider.initialExtent, maxChildSize: maxChildSize, - minChildSize: sheetProvider.minExtent > maxChildSize - ? maxChildSize - : sheetProvider.minExtent, + minChildSize: + sheetProvider.minExtent > maxChildSize + ? maxChildSize + : sheetProvider.minExtent, snap: true, snapSizes: widget.snapSizes, builder: (context, scrollController) { @@ -164,16 +166,14 @@ class _TenorSheetState extends State child: Column( mainAxisSize: MainAxisSize.min, children: [ - const TenorDragHandle( - style: TenorDragHandleStyle(), - ), + const KlipyDragHandle(style: KlipyDragHandleStyle()), if (canShowTabs) - TenorTabBar( + KlipyTabBar( style: widget.style.tabBarStyle, tabController: tabController, tabs: widget.tabs.map((tab) => tab.name).toList(), ), - TenorSearchField( + KlipySearchField( animationStyle: widget.style.animationStyle, hintText: widget.searchFieldHintText, scrollController: scrollController, @@ -184,32 +184,33 @@ class _TenorSheetState extends State style: widget.style.searchFieldStyle, ), Expanded( - child: (canShowTabs) - ? TabBarView( - controller: tabController, - children: widget.tabs - .map( - (tab) => MultiProvider( - providers: [ - Provider( - create: (context) => constraints, - ), - Provider( - create: (context) => tab, - ), - ], - child: tab.view, - ), - ) - .toList(), - ) - : widget.tabs.first.view, + child: + (canShowTabs) + ? TabBarView( + controller: tabController, + children: + widget.tabs + .map( + (tab) => MultiProvider( + providers: [ + Provider( + create: + (context) => constraints, + ), + Provider( + create: (context) => tab, + ), + ], + child: tab.view, + ), + ) + .toList(), + ) + : widget.tabs.first.view, ), if (widget.attributionType == - TenorAttributionType.poweredBy) - TenorAttribution( - style: widget.style.attributionStyle, - ), + KlipyAttributionType.poweredBy) + KlipyAttribution(style: widget.style.attributionStyle), ], ), ), diff --git a/lib/src/components/tab_bar.dart b/lib/src/components/tab_bar.dart index c3cdb0a..4c78fd3 100644 --- a/lib/src/components/tab_bar.dart +++ b/lib/src/components/tab_bar.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -class TenorTabBarStyle { +class KlipyTabBarStyle { final Decoration decoration; final Decoration indicator; final Color labelColor; @@ -14,18 +14,12 @@ class TenorTabBarStyle { final Color unselectedLabelColor; final TextStyle unselectedLabelStyle; - const TenorTabBarStyle({ + const KlipyTabBarStyle({ this.decoration = const BoxDecoration( color: Color(0xFFEDE7D7), border: Border.symmetric( - horizontal: BorderSide( - color: Color(0xFFEDE7D7), - width: 2, - ), - vertical: BorderSide( - color: Color(0xFFEDE7D7), - width: 2, - ), + horizontal: BorderSide(color: Color(0xFFEDE7D7), width: 2), + vertical: BorderSide(color: Color(0xFFEDE7D7), width: 2), ), borderRadius: BorderRadius.all(Radius.circular(8)), ), @@ -63,10 +57,7 @@ class TenorTabBarStyle { fontWeight: FontWeight.bold, ), this.height = 30, - this.margin = const EdgeInsets.only( - left: 8, - right: 8, - ), + this.margin = const EdgeInsets.only(left: 8, right: 8), this.unselectedLabelColor = const Color(0xFF3B3B3B), this.unselectedLabelStyle = const TextStyle( decoration: TextDecoration.none, @@ -76,15 +67,15 @@ class TenorTabBarStyle { }); } -class TenorTabBar extends StatelessWidget { +class KlipyTabBar extends StatelessWidget { final TabController tabController; final List tabs; - final TenorTabBarStyle style; + final KlipyTabBarStyle style; - const TenorTabBar({ + const KlipyTabBar({ required this.tabController, required this.tabs, - this.style = const TenorTabBarStyle(), + this.style = const KlipyTabBarStyle(), super.key, }); diff --git a/lib/src/components/tab_view.dart b/lib/src/components/tab_view.dart index 004324f..7bb2dbf 100644 --- a/lib/src/components/tab_view.dart +++ b/lib/src/components/tab_view.dart @@ -4,76 +4,75 @@ import 'package:extended_image/extended_image.dart'; import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -import 'package:tenor_flutter/src/components/components.dart'; -import 'package:tenor_flutter/src/providers/app_bar_provider.dart'; -import 'package:tenor_flutter/src/providers/tab_provider.dart'; -import 'package:tenor_flutter/tenor_flutter.dart'; +import 'package:klipy_flutter/src/components/components.dart'; +import 'package:klipy_flutter/src/providers/app_bar_provider.dart'; +import 'package:klipy_flutter/src/providers/tab_provider.dart'; +import 'package:klipy_flutter/klipy_flutter.dart'; const featuredCategoryPath = '##trending-gifs'; -class TenorTabViewStyle { +class KlipyTabViewStyle { final Color mediaBackgroundColor; - const TenorTabViewStyle({ - this.mediaBackgroundColor = Colors.white, - }); + const KlipyTabViewStyle({this.mediaBackgroundColor = Colors.white}); } -class TenorTabView extends StatefulWidget { +class KlipyTabView extends StatefulWidget { final Widget Function(BuildContext, Widget?)? builder; - final TenorCategoryStyle categoryStyle; - final Tenor client; + final KlipyCategoryStyle categoryStyle; + final KlipyClient client; final String featuredCategory; final int gifsPerRow; final bool? keepAliveTabView; - final Future Function( + final Future Function( String queryText, String? pos, int limit, - TenorCategory? category, - )? onLoad; - final Function(TenorResult? gif)? onSelected; + KlipyCategoryObject? category, + )? + onLoad; + final Function(KlipyResultObject? gif)? onSelected; final bool showCategories; - final TenorTabViewStyle style; + final KlipyTabViewStyle style; - const TenorTabView({ + const KlipyTabView({ required this.client, this.builder, - this.categoryStyle = const TenorCategoryStyle(), + this.categoryStyle = const KlipyCategoryStyle(), String? featuredCategory, int? gifsPerRow, this.keepAliveTabView, this.onLoad, this.onSelected, this.showCategories = false, - this.style = const TenorTabViewStyle(), + this.style = const KlipyTabViewStyle(), super.key, - }) : featuredCategory = featuredCategory ?? '📈 Featured', - gifsPerRow = gifsPerRow ?? 3; + }) : featuredCategory = featuredCategory ?? '📈 Featured', + gifsPerRow = gifsPerRow ?? 3; @override - State createState() => _TenorTabViewState(); + State createState() => _KlipyTabViewState(); } -class _TenorTabViewState extends State +class _KlipyTabViewState extends State with AutomaticKeepAliveClientMixin { @override bool get wantKeepAlive => widget.keepAliveTabView ?? true; // Tab Provider - late TenorTabProvider _tabProvider; + late KlipyTabProvider _tabProvider; // Scroll Controller late final ScrollController _scrollController; // AppBar Provider - late TenorAppBarProvider _appBarProvider; + late KlipyAppBarProvider _appBarProvider; // Collection - TenorResponse? _collection; + KlipyResponse? _collection; // List of gifs - List _list = []; + List _list = []; // Direction final Axis _scrollDirection = Axis.vertical; @@ -87,13 +86,13 @@ class _TenorTabViewState extends State // Offset String? offset; - List _categories = []; + List _categories = []; - /// The Tenor client so we can use the API. - late final Tenor client; + /// The KLIPY client so we can use the API. + late final KlipyClient client; /// The current tabs data. - late final TenorTab tab; + late final KlipyTab tab; /// The limit of gifs to request per load. late int requestLimit; @@ -105,13 +104,13 @@ class _TenorTabViewState extends State client = widget.client; // Which tab are we? - tab = context.read(); + tab = context.read(); // We should update this whenever the size changes eventually requestLimit = _calculateLimit(); // AppBar Provider - _appBarProvider = Provider.of(context, listen: false); + _appBarProvider = Provider.of(context, listen: false); _appBarProvider.addListener(_appBarProviderListener); // Scroll Controller @@ -119,7 +118,7 @@ class _TenorTabViewState extends State _scrollController.addListener(_scrollControllerListener); // Tab Provider - _tabProvider = Provider.of(context, listen: false); + _tabProvider = Provider.of(context, listen: false); _tabProvider.addListener(_tabProviderListener); WidgetsBinding.instance.addPostFrameCallback((_) { @@ -146,9 +145,7 @@ class _TenorTabViewState extends State Widget build(BuildContext context) { super.build(context); if (_list.isEmpty && _categories.isEmpty) { - return const Center( - child: CircularProgressIndicator(), - ); + return const Center(child: CircularProgressIndicator()); } if (_appBarProvider.queryText.isEmpty && @@ -168,7 +165,7 @@ class _TenorTabViewState extends State final category = _categories[idx]; return ClipRRect( borderRadius: BorderRadius.circular(8), - child: TenorCategoryWidget( + child: KlipyCategoryWidget( style: widget.categoryStyle, category: category, onTap: (selectedCategory) { @@ -185,13 +182,11 @@ class _TenorTabViewState extends State }, itemCount: _categories.length, mainAxisSpacing: 8, - // Add safe area padding if `TenorAttributionType.poweredBy` is disabled + // Add safe area padding if `KlipyAttributionType.poweredBy` is disabled padding: - _tabProvider.attributionType == TenorAttributionType.poweredBy - ? null - : EdgeInsets.only( - bottom: MediaQuery.of(context).padding.bottom, - ), + _tabProvider.attributionType == KlipyAttributionType.poweredBy + ? EdgeInsets.zero + : const EdgeInsets.only(bottom: 0), scrollDirection: _scrollDirection, ), ), @@ -206,24 +201,24 @@ class _TenorTabViewState extends State crossAxisCount: widget.gifsPerRow, crossAxisSpacing: 8, keyboardDismissBehavior: _appBarProvider.keyboardDismissBehavior, - itemBuilder: (ctx, idx) => ClipRRect( - borderRadius: BorderRadius.circular(8), - child: TenorSelectableGif( - backgroundColor: widget.style.mediaBackgroundColor, - onTap: (selectedResult) => _selectedGif( - selectedResult, + itemBuilder: + (ctx, idx) => ClipRRect( + borderRadius: BorderRadius.circular(8), + child: KlipySelectableGif( + backgroundColor: widget.style.mediaBackgroundColor, + onTap: (selectedResult) => _selectedGif(selectedResult), + result: _list[idx], + ), ), - result: _list[idx], - ), - ), itemCount: _list.length, mainAxisSpacing: 8, - // Add safe area padding if `TenorAttributionType.poweredBy` is disabled - padding: _tabProvider.attributionType == TenorAttributionType.poweredBy - ? null - : EdgeInsets.only( - bottom: MediaQuery.of(context).padding.bottom, - ), + // Add safe area padding if `KlipyAttributionType.poweredBy` is disabled + padding: + _tabProvider.attributionType == KlipyAttributionType.poweredBy + ? null + : EdgeInsets.only( + bottom: MediaQuery.of(context).padding.bottom, + ), scrollDirection: _scrollDirection, ), ); @@ -232,8 +227,8 @@ class _TenorTabViewState extends State // Estimate the request limit based on the visible area. Doesn't need to be precise. // This function assumes all gifs are 1:1 aspect ratio for simplicity. int _calculateLimit() { - // Tenor has a hard limit of 50 per request - const int tenorRequestLimit = 50; + // KLIPY has a hard limit of 50 per request + const int klipyRequestLimit = 50; // Call this here so we get updated constraints in case of size change final constraints = context.read(); // The width of each gif (estimated) @@ -242,9 +237,9 @@ class _TenorTabViewState extends State final gifRowCount = (constraints.maxHeight / gifWidth).round(); // Based on estimates, how many gifs can we fit into the TabView final calculatedRequestLimit = widget.gifsPerRow * gifRowCount; - // If the limit is greater than Tenor's hard limit, cap it - return calculatedRequestLimit > tenorRequestLimit - ? tenorRequestLimit + // If the limit is greater than KLIPY's hard limit, cap it + return calculatedRequestLimit > klipyRequestLimit + ? klipyRequestLimit : calculatedRequestLimit; } @@ -278,13 +273,13 @@ class _TenorTabViewState extends State Future _loadCatagories() async { try { - final fromTenor = await client.categories(); + final fromKlipy = await client.categories(); final featuredGifResponse = await client.featured(limit: 1); final featuredGif = featuredGifResponse?.results.first; if (featuredGif != null) { - fromTenor.insert( + fromKlipy.insert( 0, - TenorCategory( + KlipyCategoryObject( image: featuredGif.media.tinyGif?.url ?? '', name: widget.featuredCategory, path: featuredCategoryPath, @@ -294,7 +289,7 @@ class _TenorTabViewState extends State } setState(() { - _categories = fromTenor; + _categories = fromKlipy; }); } catch (e) { // @@ -354,9 +349,9 @@ class _TenorTabViewState extends State _isLoading = false; }); } - } on TenorNetworkException { + } on KlipyNetworkException { _isLoading = false; - } on TenorApiException { + } on KlipyApiException { _isLoading = false; } catch (e) { _isLoading = false; @@ -369,27 +364,23 @@ class _TenorTabViewState extends State } // Return selected gif - void _selectedGif(TenorResult gif) { + void _selectedGif(KlipyResultObject gif) { try { - // https://developers.google.com/tenor/guides/endpoints#register-share + // https://docs.klipy.com/migrate-from-tenor/register-share client.registerShare(gif.id, search: _appBarProvider.queryText); } catch (e) { // do nothing if it fails } // return result to the consumer - Navigator.pop( - context, - gif.copyWith( - source: _tabProvider.selectedTab.name, - ), - ); + Navigator.pop(context, gif.copyWith(source: _tabProvider.selectedTab.name)); } // if you scroll within a threshhold of the bottom of the screen, load more gifs void _scrollControllerListener() { // trending-gifs, etc - final customCategorySelected = _appBarProvider.selectedCategory != null && + final customCategorySelected = + _appBarProvider.selectedCategory != null && _appBarProvider.queryText == ''; if (customCategorySelected || diff --git a/lib/src/components/tab_view_emojis.dart b/lib/src/components/tab_view_emojis.dart index e2d94e1..3ab9dfe 100644 --- a/lib/src/components/tab_view_emojis.dart +++ b/lib/src/components/tab_view_emojis.dart @@ -1,22 +1,22 @@ import 'package:flutter/material.dart'; -import 'package:tenor_flutter/src/utilities/is_tablet.dart'; -import 'package:tenor_flutter/tenor_flutter.dart'; +import 'package:klipy_flutter/src/utilities/is_tablet.dart'; +import 'package:klipy_flutter/klipy_flutter.dart'; -class TenorViewEmojis extends StatelessWidget { - final Tenor client; +class KlipyViewEmojis extends StatelessWidget { + final KlipyClient client; final int? gifsPerRow; - final TenorTabViewStyle style; + final KlipyTabViewStyle style; - const TenorViewEmojis({ + const KlipyViewEmojis({ required this.client, this.gifsPerRow, - this.style = const TenorTabViewStyle(), + this.style = const KlipyTabViewStyle(), super.key, }); @override Widget build(BuildContext context) { - return TenorTabView( + return KlipyTabView( client: client, gifsPerRow: gifsPerRow ?? (isTablet(context) ? 8 : 9), keepAliveTabView: true, @@ -24,7 +24,7 @@ class TenorViewEmojis extends StatelessWidget { if (queryText.isNotEmpty) { return await client.search( '$queryText emoji', - mediaFilter: const [TenorMediaFormat.tinyGifTransparent], + mediaFilter: const [KlipyMediaFormat.tinyGifTransparent], pos: pos, limit: limit, sticker: true, @@ -32,7 +32,7 @@ class TenorViewEmojis extends StatelessWidget { } else { return await client.search( 'emoji', - mediaFilter: const [TenorMediaFormat.tinyGifTransparent], + mediaFilter: const [KlipyMediaFormat.tinyGifTransparent], pos: pos, limit: limit, sticker: true, diff --git a/lib/src/components/tab_view_gifs.dart b/lib/src/components/tab_view_gifs.dart index 7985eb4..403eb3e 100644 --- a/lib/src/components/tab_view_gifs.dart +++ b/lib/src/components/tab_view_gifs.dart @@ -1,27 +1,27 @@ import 'package:flutter/material.dart'; -import 'package:tenor_flutter/src/components/tab_view.dart'; -import 'package:tenor_flutter/src/utilities/is_tablet.dart'; -import 'package:tenor_flutter/tenor_flutter.dart'; +import 'package:klipy_flutter/src/components/tab_view.dart'; +import 'package:klipy_flutter/src/utilities/is_tablet.dart'; +import 'package:klipy_flutter/klipy_flutter.dart'; -class TenorViewGifs extends StatelessWidget { - final Tenor client; +class KlipyViewGifs extends StatelessWidget { + final KlipyClient client; final String? featuredCategory; final int? gifsPerRow; final bool showCategories; - final TenorTabViewStyle style; + final KlipyTabViewStyle style; - const TenorViewGifs({ + const KlipyViewGifs({ required this.client, this.featuredCategory, this.gifsPerRow, this.showCategories = true, - this.style = const TenorTabViewStyle(), + this.style = const KlipyTabViewStyle(), super.key, }); @override Widget build(BuildContext context) { - return TenorTabView( + return KlipyTabView( client: client, featuredCategory: featuredCategory, gifsPerRow: gifsPerRow ?? (isTablet(context) ? 3 : 2), @@ -29,29 +29,19 @@ class TenorViewGifs extends StatelessWidget { showCategories: showCategories, onLoad: (queryText, pos, limit, category) async { if (queryText.isNotEmpty) { - return await client.search( - queryText, - pos: pos, - limit: limit, - ); + return await client.search(queryText, pos: pos, limit: limit); } else { if (showCategories) { // if a trending is selected, seatch them up if (category?.path == featuredCategoryPath) { - return await client.featured( - pos: pos, - limit: limit, - ); + return await client.featured(pos: pos, limit: limit); } // don't hit the api since we already have the categories return null; } // ask for new featured gifs since categories are disabled - return await client.featured( - pos: pos, - limit: limit, - ); + return await client.featured(pos: pos, limit: limit); } }, style: style, diff --git a/lib/src/components/tab_view_stickers.dart b/lib/src/components/tab_view_stickers.dart index bf857b5..1586356 100644 --- a/lib/src/components/tab_view_stickers.dart +++ b/lib/src/components/tab_view_stickers.dart @@ -1,22 +1,22 @@ import 'package:flutter/material.dart'; -import 'package:tenor_flutter/src/utilities/is_tablet.dart'; -import 'package:tenor_flutter/tenor_flutter.dart'; +import 'package:klipy_flutter/src/utilities/is_tablet.dart'; +import 'package:klipy_flutter/klipy_flutter.dart'; -class TenorViewStickers extends StatelessWidget { - final Tenor client; +class KlipyViewStickers extends StatelessWidget { + final KlipyClient client; final int? gifsPerRow; - final TenorTabViewStyle style; + final KlipyTabViewStyle style; - const TenorViewStickers({ + const KlipyViewStickers({ required this.client, this.gifsPerRow, - this.style = const TenorTabViewStyle(), + this.style = const KlipyTabViewStyle(), super.key, }); @override Widget build(BuildContext context) { - return TenorTabView( + return KlipyTabView( client: client, gifsPerRow: gifsPerRow ?? (isTablet(context) ? 6 : 5), keepAliveTabView: true, @@ -24,14 +24,14 @@ class TenorViewStickers extends StatelessWidget { if (queryText.isNotEmpty) { return await client.search( queryText, - mediaFilter: const [TenorMediaFormat.tinyGifTransparent], + mediaFilter: const [KlipyMediaFormat.tinyGifTransparent], pos: pos, limit: limit, sticker: true, ); } else { return await client.featured( - mediaFilter: const [TenorMediaFormat.tinyGifTransparent], + mediaFilter: const [KlipyMediaFormat.tinyGifTransparent], pos: pos, limit: limit, sticker: true, diff --git a/lib/src/klipy_client.dart b/lib/src/klipy_client.dart new file mode 100644 index 0000000..b36a1c4 --- /dev/null +++ b/lib/src/klipy_client.dart @@ -0,0 +1,159 @@ +import 'package:flutter/material.dart'; +import 'package:klipy_dart/klipy_dart.dart' as klipy_dart; +import 'package:klipy_flutter/klipy_flutter.dart'; +import 'package:klipy_flutter/src/components/components.dart'; +import 'package:klipy_flutter/src/providers/providers.dart'; +import 'package:provider/provider.dart'; + +const klipyDefaultAnimationStyle = AnimationStyle( + duration: Duration(milliseconds: 250), + reverseDuration: Duration(milliseconds: 200), +); + +class KlipyStyle { + final AnimationStyle? animationStyle; + final KlipyAttributionStyle attributionStyle; + + /// Background color of the sheet. + final Color color; + final KlipyDragHandleStyle dragHandleStyle; + final String? fontFamily; + final KlipySearchFieldStyle searchFieldStyle; + final KlipySelectedCategoryStyle selectedCategoryStyle; + + /// Shape for the sheet. + final ShapeBorder shape; + final KlipyTabBarStyle tabBarStyle; + final KlipyTabViewStyle tabViewStyle; + + const KlipyStyle({ + this.animationStyle, + this.attributionStyle = const KlipyAttributionStyle(), + this.color = const Color(0xFFF9F8F2), + this.dragHandleStyle = const KlipyDragHandleStyle(), + this.fontFamily, + this.searchFieldStyle = const KlipySearchFieldStyle(), + this.selectedCategoryStyle = const KlipySelectedCategoryStyle(), + this.shape = const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(8)), + ), + this.tabBarStyle = const KlipyTabBarStyle(), + this.tabViewStyle = const KlipyTabViewStyle(), + }); +} + +class KlipyClient extends klipy_dart.KlipyClient { + const KlipyClient({ + required super.apiKey, + super.client = const klipy_dart.KlipyHttpClient(), + super.country = 'US', + super.locale = 'en_US', + super.networkTimeout = const Duration(seconds: 5), + }); + + /// Shows a bottom sheet modal that allows you to select a KLIPY media object for use. + /// + /// If you pass in a `searchFieldWidget` you must also pass in a `searchFieldController`. The controller will automatically be disposed of. + /// + /// You must have valid [KLIPY attribution](https://docs.klipy.com/attribution) in order to use this within your app. + Future showAsBottomSheet({ + required BuildContext context, + KlipyAttributionType attributionType = KlipyAttributionType.poweredBy, + // Whether to cover the app bar with the bottom sheet. + bool coverAppBar = false, + Duration debounce = const Duration(milliseconds: 300), + double? initialExtent, + int initialTabIndex = 1, + ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = + ScrollViewKeyboardDismissBehavior.manual, + double maxExtent = 1, + double minExtent = 0.7, + String queryText = '', + TextEditingController? searchFieldController, + String searchFieldHintText = 'Search KLIPY', + Widget? searchFieldWidget, + // A list of target sizes that the showModalBottomSheet should snap to. + // The [minChildSize] and [maxChildSize] are implicitly included in snap sizes and do not need to be specified here. + List? snapSizes, + KlipyStyle style = const KlipyStyle(), + List? tabs, + bool useSafeArea = true, + }) { + final tabsToDisplay = + tabs ?? + [ + KlipyTab( + name: 'Emojis', + view: KlipyViewEmojis(client: this, style: style.tabViewStyle), + ), + KlipyTab( + name: 'GIFs', + view: KlipyViewGifs(client: this, style: style.tabViewStyle), + ), + KlipyTab( + name: 'Stickers', + view: KlipyViewStickers(client: this, style: style.tabViewStyle), + ), + ]; + + return showModalBottomSheet( + clipBehavior: Clip.antiAlias, + context: context, + isScrollControlled: true, + shape: style.shape, + sheetAnimationStyle: style.animationStyle ?? klipyDefaultAnimationStyle, + useSafeArea: useSafeArea, + builder: (context) { + return DefaultTextStyle.merge( + style: TextStyle(fontFamily: style.fontFamily), + child: Padding( + padding: EdgeInsets.only( + // move the sheet up when the keyboard is shown + bottom: MediaQuery.of(context).viewInsets.bottom, + ), + child: MultiProvider( + providers: [ + ChangeNotifierProvider( + create: + (context) => KlipyAppBarProvider( + queryText, + debounce, + keyboardDismissBehavior: keyboardDismissBehavior, + ), + ), + ChangeNotifierProvider( + create: + (context) => KlipySheetProvider( + initialExtent: initialExtent, + maxExtent: maxExtent, + minExtent: minExtent, + scrollController: DraggableScrollableController(), + ), + ), + ChangeNotifierProvider( + create: + (context) => KlipyTabProvider( + attributionType: attributionType, + client: this, + selectedTab: tabsToDisplay[initialTabIndex], + ), + ), + ], + child: KlipySheet( + attributionType: attributionType, + coverAppBar: coverAppBar, + initialTabIndex: initialTabIndex, + searchFieldController: searchFieldController, + searchFieldHintText: searchFieldHintText, + searchFieldWidget: searchFieldWidget, + snapSizes: snapSizes, + style: style, + tabs: tabsToDisplay, + ), + ), + ), + ); + }, + ); + } +} diff --git a/lib/src/models/attribution.dart b/lib/src/models/attribution.dart index 92411e2..7cbd2fa 100644 --- a/lib/src/models/attribution.dart +++ b/lib/src/models/attribution.dart @@ -1,13 +1,12 @@ -/// It is your responsibility to display attribution via atleast one of the following methods. +/// To ensure a consistent and high-quality experience across all platforms, applications using the KLIPY API must include clear attribution. +/// - Display a visible “Powered by KLIPY” mark wherever KLIPY content is shown. +/// - Set “Search KLIPY” as the default placeholder text in the search input field. /// -/// https://developers.google.com/tenor/guides/attribution -enum TenorAttributionType { +/// More information https://docs.klipy.com/attribution +enum KlipyAttributionType { /// Use this attribution during the GIF browsing experience. poweredBy, - /// Use this attribution as the placeholder text in your search box. If you have your own SearchFieldWidget and intend to use this attribution you must implement it yourself. - searchTenor, - - /// Use this attribution in the footer of a shared GIF. Must be implemented yourself. - viaTenor, + /// Use this attribution to hide poweredBy. It's your responsibility to provide the correct attribution. + none, } diff --git a/lib/src/models/tab.dart b/lib/src/models/tab.dart index 6065c93..c90dfb1 100644 --- a/lib/src/models/tab.dart +++ b/lib/src/models/tab.dart @@ -1,11 +1,8 @@ import 'package:flutter/material.dart'; -class TenorTab { +class KlipyTab { final String name; final Widget view; - const TenorTab({ - required this.name, - required this.view, - }); + const KlipyTab({required this.name, required this.view}); } diff --git a/lib/src/providers/app_bar_provider.dart b/lib/src/providers/app_bar_provider.dart index 327ad19..2bc85b6 100644 --- a/lib/src/providers/app_bar_provider.dart +++ b/lib/src/providers/app_bar_provider.dart @@ -1,12 +1,12 @@ import 'package:flutter/widgets.dart'; -import 'package:tenor_flutter/tenor_flutter.dart'; +import 'package:klipy_flutter/klipy_flutter.dart'; -class TenorAppBarProvider with ChangeNotifier { +class KlipyAppBarProvider with ChangeNotifier { final ScrollViewKeyboardDismissBehavior keyboardDismissBehavior; String _queryText = ''; String get queryText => _queryText; - TenorCategory? _selectedCategory; + KlipyCategoryObject? _selectedCategory; Duration _debounce = Duration.zero; Duration get debounce => _debounce; @@ -20,19 +20,19 @@ class TenorAppBarProvider with ChangeNotifier { notifyListeners(); } - TenorAppBarProvider( + KlipyAppBarProvider( String queryText, Duration debounce, { required this.keyboardDismissBehavior, - TenorCategory? selectedCategory, - }) : _selectedCategory = selectedCategory, - super() { + KlipyCategoryObject? selectedCategory, + }) : _selectedCategory = selectedCategory, + super() { _queryText = queryText; _debounce = debounce; } - TenorCategory? get selectedCategory => _selectedCategory; - set selectedCategory(TenorCategory? newCategory) { + KlipyCategoryObject? get selectedCategory => _selectedCategory; + set selectedCategory(KlipyCategoryObject? newCategory) { _selectedCategory = newCategory; notifyListeners(); } diff --git a/lib/src/providers/sheet_provider.dart b/lib/src/providers/sheet_provider.dart index 5aaf2d9..2e7bc65 100644 --- a/lib/src/providers/sheet_provider.dart +++ b/lib/src/providers/sheet_provider.dart @@ -1,17 +1,17 @@ import 'package:flutter/material.dart'; -class TenorSheetProvider extends ChangeNotifier { +class KlipySheetProvider extends ChangeNotifier { final double minExtent; final double maxExtent; final double initialExtent; - TenorSheetProvider({ + KlipySheetProvider({ required DraggableScrollableController scrollController, required this.minExtent, required this.maxExtent, double? initialExtent, - }) : initialExtent = initialExtent ?? maxExtent, - _scrollController = scrollController; + }) : initialExtent = initialExtent ?? maxExtent, + _scrollController = scrollController; DraggableScrollableController _scrollController; diff --git a/lib/src/providers/tab_provider.dart b/lib/src/providers/tab_provider.dart index e85bb01..46459ed 100644 --- a/lib/src/providers/tab_provider.dart +++ b/lib/src/providers/tab_provider.dart @@ -1,19 +1,19 @@ import 'package:flutter/widgets.dart'; -import 'package:tenor_flutter/tenor_flutter.dart'; +import 'package:klipy_flutter/klipy_flutter.dart'; -class TenorTabProvider with ChangeNotifier { - TenorAttributionType attributionType; - Tenor client; - TenorTab _selectedTab; +class KlipyTabProvider with ChangeNotifier { + KlipyAttributionType attributionType; + KlipyClient client; + KlipyTab _selectedTab; - TenorTabProvider({ + KlipyTabProvider({ required this.attributionType, required this.client, - required TenorTab selectedTab, + required KlipyTab selectedTab, }) : _selectedTab = selectedTab; - TenorTab get selectedTab => _selectedTab; - set selectedTab(TenorTab selectedTab) { + KlipyTab get selectedTab => _selectedTab; + set selectedTab(KlipyTab selectedTab) { _selectedTab = selectedTab; notifyListeners(); } diff --git a/lib/src/tenor.dart b/lib/src/tenor.dart deleted file mode 100644 index 5593942..0000000 --- a/lib/src/tenor.dart +++ /dev/null @@ -1,171 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import 'package:tenor_dart/tenor_dart.dart' as tenor_dart; - -import 'package:tenor_flutter/src/components/components.dart'; -import 'package:tenor_flutter/src/providers/providers.dart'; -import 'package:tenor_flutter/tenor_flutter.dart'; - -const tenorDefaultAnimationStyle = AnimationStyle( - duration: Duration(milliseconds: 250), - reverseDuration: Duration(milliseconds: 200), -); - -class TenorStyle { - final AnimationStyle? animationStyle; - final TenorAttributionStyle attributionStyle; - - /// Background color of the sheet. - final Color color; - final TenorDragHandleStyle dragHandleStyle; - final String? fontFamily; - final TenorSearchFieldStyle searchFieldStyle; - final TenorSelectedCategoryStyle selectedCategoryStyle; - - /// Shape for the sheet. - final ShapeBorder shape; - final TenorTabBarStyle tabBarStyle; - final TenorTabViewStyle tabViewStyle; - - const TenorStyle({ - this.animationStyle, - this.attributionStyle = const TenorAttributionStyle(), - this.color = const Color(0xFFF9F8F2), - this.dragHandleStyle = const TenorDragHandleStyle(), - this.fontFamily, - this.searchFieldStyle = const TenorSearchFieldStyle(), - this.selectedCategoryStyle = const TenorSelectedCategoryStyle(), - this.shape = const RoundedRectangleBorder( - borderRadius: BorderRadius.vertical( - top: Radius.circular(8), - ), - ), - this.tabBarStyle = const TenorTabBarStyle(), - this.tabViewStyle = const TenorTabViewStyle(), - }); -} - -class Tenor extends tenor_dart.Tenor { - const Tenor({ - required super.apiKey, - super.client = const tenor_dart.TenorHttpClient(), - super.clientKey, - super.contentFilter = TenorContentFilter.off, - super.country = 'US', - super.locale = 'en_US', - super.networkTimeout = const Duration(seconds: 5), - }); - - /// Shows a bottom sheet modal that allows you to select a Tenor media object for use. - /// - /// If you pass in a `searchFieldWidget` you must also pass in a `searchFieldController`. The controller will automatically be disposed of. - /// - /// You must have one valid form of [Tenor attribution](https://developers.google.com/tenor/guides/attribution) in order to use this within your app. - Future showAsBottomSheet({ - required BuildContext context, - TenorAttributionType attributionType = TenorAttributionType.poweredBy, - // Whether to cover the app bar with the bottom sheet. - bool coverAppBar = false, - Duration debounce = const Duration(milliseconds: 300), - double? initialExtent, - int initialTabIndex = 1, - ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = - ScrollViewKeyboardDismissBehavior.manual, - double maxExtent = 1, - double minExtent = 0.7, - String queryText = '', - TextEditingController? searchFieldController, - String searchFieldHintText = 'Search Tenor', - Widget? searchFieldWidget, - // A list of target sizes that the showModalBottomSheet should snap to. - // The [minChildSize] and [maxChildSize] are implicitly included in snap sizes and do not need to be specified here. - List? snapSizes, - TenorStyle style = const TenorStyle(), - List? tabs, - bool useSafeArea = true, - }) { - final tabsToDisplay = tabs ?? - [ - TenorTab( - name: 'Emojis', - view: TenorViewEmojis( - client: this, - style: style.tabViewStyle, - ), - ), - TenorTab( - name: 'GIFs', - view: TenorViewGifs( - client: this, - style: style.tabViewStyle, - ), - ), - TenorTab( - name: 'Stickers', - view: TenorViewStickers( - client: this, - style: style.tabViewStyle, - ), - ), - ]; - - return showModalBottomSheet( - clipBehavior: Clip.antiAlias, - context: context, - isScrollControlled: true, - shape: style.shape, - sheetAnimationStyle: style.animationStyle ?? tenorDefaultAnimationStyle, - useSafeArea: useSafeArea, - builder: (context) { - return DefaultTextStyle.merge( - style: TextStyle( - fontFamily: style.fontFamily, - ), - child: Padding( - padding: EdgeInsets.only( - // move the sheet up when the keyboard is shown - bottom: MediaQuery.of(context).viewInsets.bottom, - ), - child: MultiProvider( - providers: [ - ChangeNotifierProvider( - create: (context) => TenorAppBarProvider( - queryText, - debounce, - keyboardDismissBehavior: keyboardDismissBehavior, - ), - ), - ChangeNotifierProvider( - create: (context) => TenorSheetProvider( - initialExtent: initialExtent, - maxExtent: maxExtent, - minExtent: minExtent, - scrollController: DraggableScrollableController(), - ), - ), - ChangeNotifierProvider( - create: (context) => TenorTabProvider( - attributionType: attributionType, - client: this, - selectedTab: tabsToDisplay[initialTabIndex], - ), - ), - ], - child: TenorSheet( - attributionType: attributionType, - coverAppBar: coverAppBar, - initialTabIndex: initialTabIndex, - searchFieldController: searchFieldController, - searchFieldHintText: searchFieldHintText, - searchFieldWidget: searchFieldWidget, - snapSizes: snapSizes, - style: style, - tabs: tabsToDisplay, - ), - ), - ), - ); - }, - ); - } -} diff --git a/lib/src/utilities/debouncer.dart b/lib/src/utilities/debouncer.dart index 50fcfc2..c5a2943 100644 --- a/lib/src/utilities/debouncer.dart +++ b/lib/src/utilities/debouncer.dart @@ -1,12 +1,11 @@ import 'dart:async'; -class TenorDebouncer { +class KlipyDebouncer { final Duration _delay; Timer? _timer; - TenorDebouncer({ - Duration delay = const Duration(milliseconds: 300), - }) : _delay = delay; + KlipyDebouncer({Duration delay = const Duration(milliseconds: 300)}) + : _delay = delay; void call(void Function() callback) { _timer?.cancel(); diff --git a/lib/tenor_flutter.dart b/lib/tenor_flutter.dart deleted file mode 100644 index 47355c5..0000000 --- a/lib/tenor_flutter.dart +++ /dev/null @@ -1,15 +0,0 @@ -// hide Tenor so we can extend it -export 'package:tenor_dart/tenor_dart.dart' hide Tenor; - -export 'src/tenor.dart'; - -export 'src/components/attribution.dart' show TenorAttributionStyle; -export 'src/components/drag_handle.dart' show TenorDragHandleStyle; -export 'src/components/search_field.dart' - show TenorSelectedCategoryStyle, TenorSearchFieldStyle; -export 'src/components/tab_bar.dart' show TenorTabBarStyle; -export 'src/components/tab_view.dart' show TenorTabView, TenorTabViewStyle; -export 'src/components/tab_view_emojis.dart'; -export 'src/components/tab_view_gifs.dart'; -export 'src/components/tab_view_stickers.dart'; -export 'src/models/models.dart'; diff --git a/pubspec.yaml b/pubspec.yaml index 07335b3..315ada8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,11 +1,13 @@ -name: tenor_flutter -version: 1.0.0 -description: An opinionated yet customizable Flutter package for searching and selecting from a list of GIFs/Stickers from the Tenor GIF search API. +name: klipy_flutter +version: 0.0.1 +description: An opinionated yet customizable Flutter package for searching and selecting from a list of GIFs/Stickers from the KLIPY GIF search API. homepage: https://github.com/flyclops -repository: https://github.com/flyclops/tenor_flutter +repository: https://github.com/flyclops/klipy_flutter topics: - gif + - klipy - tenor + - giphy environment: sdk: ">=3.7.0 <4.0.0" @@ -16,7 +18,7 @@ dependencies: provider: ^6.1.5+1 flutter_staggered_grid_view: ^0.7.0 extended_image: ^10.0.1 - tenor_dart: ^0.0.4 + klipy_dart: ^0.0.1 dev_dependencies: flutter_test: diff --git a/test/components/attribution_test.dart b/test/components/attribution_test.dart index 110455d..d52903a 100644 --- a/test/components/attribution_test.dart +++ b/test/components/attribution_test.dart @@ -1,20 +1,19 @@ import 'package:flutter/material.dart'; -import 'package:tenor_flutter/src/components/components.dart'; +import 'package:klipy_flutter/src/components/components.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('Attribution Widget >', () { - testWidgets('Make sure safe area overwrites any previous padding', - (tester) async { + testWidgets('Make sure safe area overwrites any previous padding', ( + tester, + ) async { await tester.pumpWidget( const MediaQuery( - data: MediaQueryData( - padding: EdgeInsets.only(bottom: 10), - ), - child: TenorAttribution( - style: TenorAttributionStyle(padding: EdgeInsets.all(8)), + data: MediaQueryData(padding: EdgeInsets.only(bottom: 10)), + child: KlipyAttribution( + style: KlipyAttributionStyle(padding: EdgeInsets.all(8)), ), ), ); diff --git a/test/components/category_test.dart b/test/components/category_test.dart index da0cf43..6c35e46 100644 --- a/test/components/category_test.dart +++ b/test/components/category_test.dart @@ -1,22 +1,20 @@ -import 'package:flutter/material.dart'; -import 'package:tenor_flutter/src/components/components.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:tenor_flutter/tenor_flutter.dart'; +import 'package:flutter/material.dart'; +import 'package:klipy_flutter/src/components/components.dart'; +import 'package:klipy_flutter/klipy_flutter.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('Category Widget >', () { testWidgets('If category is null, find nothing', (tester) async { - await tester.pumpWidget( - const TenorCategoryWidget(), - ); + await tester.pumpWidget(const KlipyCategoryWidget()); expect(find.byType(GestureDetector), findsNothing); }); testWidgets('Keep hashtag', (tester) async { - final tenorCategoryTest = TenorCategory( + final klipyCategoryTest = KlipyCategoryObject( name: '#test', searchTerm: 'test search term', path: 'path/to/category', @@ -26,11 +24,9 @@ void main() { await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, - child: TenorCategoryWidget( - category: tenorCategoryTest, - style: const TenorCategoryStyle( - stripHashtag: false, - ), + child: KlipyCategoryWidget( + category: klipyCategoryTest, + style: const KlipyCategoryStyle(stripHashtag: false), ), ), ); @@ -40,7 +36,7 @@ void main() { }); testWidgets('Strip hashtag', (tester) async { - final tenorCategoryTest = TenorCategory( + final klipyCategoryTest = KlipyCategoryObject( name: '#test', searchTerm: 'test search term', path: 'path/to/category', @@ -50,11 +46,9 @@ void main() { await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, - child: TenorCategoryWidget( - category: tenorCategoryTest, - style: const TenorCategoryStyle( - stripHashtag: true, - ), + child: KlipyCategoryWidget( + category: klipyCategoryTest, + style: const KlipyCategoryStyle(stripHashtag: true), ), ), ); @@ -68,7 +62,7 @@ void main() { bool hasTapped = false; // category to populate with - final tenorCategoryTest = TenorCategory( + final klipyCategoryTest = KlipyCategoryObject( name: 'test', searchTerm: 'test search term', path: 'path/to/category', @@ -79,10 +73,10 @@ void main() { await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, - child: TenorCategoryWidget( - category: tenorCategoryTest, - onTap: (tenorCategory) { - expect(tenorCategory, tenorCategoryTest); + child: KlipyCategoryWidget( + category: klipyCategoryTest, + onTap: (klipyCategory) { + expect(klipyCategory, klipyCategoryTest); hasTapped = true; }, ), diff --git a/test/tenor_test.dart b/test/klipy_client_test.dart similarity index 65% rename from test/tenor_test.dart rename to test/klipy_client_test.dart index fb02679..a0cc1a7 100644 --- a/test/tenor_test.dart +++ b/test/klipy_client_test.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:tenor_flutter/src/components/components.dart'; -import 'package:tenor_flutter/tenor_flutter.dart'; +import 'package:klipy_flutter/src/components/components.dart'; +import 'package:klipy_flutter/klipy_flutter.dart'; import 'package:flutter_test/flutter_test.dart'; import 'mocks/mocks.dart'; @@ -8,18 +8,18 @@ import 'mocks/mocks.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); - group('Tenor >', () { - const tenorClient = Tenor(apiKey: '12345'); + group('KlipyClient >', () { + const klipyClient = KlipyClient(apiKey: '12345'); test('Make sure it is the right type', () { - expect(tenorClient, isA()); + expect(klipyClient, isA()); }); }); testWidgets('Make sure bottom sheet opens and closes', (tester) async { - final tenorClient = Tenor( + final klipyClient = KlipyClient( apiKey: '12345', - client: MockTenorHttpClient(), + client: MockKlipyHttpClient(), ); late BuildContext savedContext; @@ -36,17 +36,17 @@ void main() { ); // show bottom sheet - tenorClient.showAsBottomSheet(context: savedContext); + klipyClient.showAsBottomSheet(context: savedContext); await tester.pump(); // make sure it opens by finding a widget - expect(find.byType(TenorSheet), findsOneWidget); + expect(find.byType(KlipySheet), findsOneWidget); // close bottom sheet await tester.tapAt(const Offset(20.0, 20.0)); await tester.pumpAndSettle(); // make sure it closes by finding no widgets - expect(find.byType(TenorSheet), findsNothing); + expect(find.byType(KlipySheet), findsNothing); }); } diff --git a/test/mocks/mocks.dart b/test/mocks/mocks.dart index da657e1..2c02f99 100644 --- a/test/mocks/mocks.dart +++ b/test/mocks/mocks.dart @@ -1,4 +1,4 @@ import 'package:mocktail/mocktail.dart'; -import 'package:tenor_flutter/tenor_flutter.dart'; +import 'package:klipy_flutter/klipy_flutter.dart'; -class MockTenorHttpClient extends Mock implements TenorHttpClient {} +class MockKlipyHttpClient extends Mock implements KlipyHttpClient {} diff --git a/test/providers/app_bar_provider_test.dart b/test/providers/app_bar_provider_test.dart index b78b429..9f78f6e 100644 --- a/test/providers/app_bar_provider_test.dart +++ b/test/providers/app_bar_provider_test.dart @@ -1,22 +1,22 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:tenor_flutter/src/providers/providers.dart'; -import 'package:tenor_flutter/tenor_flutter.dart'; +import 'package:klipy_flutter/src/providers/providers.dart'; +import 'package:klipy_flutter/klipy_flutter.dart'; void main() { const testQuery = 'some search query'; const testDuration = Duration(milliseconds: 500); const testKeyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.onDrag; - final testCategory = TenorCategory( + final testCategory = KlipyCategoryObject( image: 'test image', name: 'test name', path: 'test path', searchTerm: 'test seach term', ); - group('TenorAppBarProvider >', () { + group('KlipyAppBarProvider >', () { test('Initializes as expected', () async { - final provider = TenorAppBarProvider( + final provider = KlipyAppBarProvider( testQuery, testDuration, keyboardDismissBehavior: testKeyboardDismissBehavior, @@ -30,7 +30,7 @@ void main() { test('Can set queryText', () async { const updatedQuery = 'updated query'; - final provider = TenorAppBarProvider( + final provider = KlipyAppBarProvider( testQuery, testDuration, keyboardDismissBehavior: testKeyboardDismissBehavior, @@ -44,7 +44,7 @@ void main() { }); test('Can set selectedCategory', () async { - final provider = TenorAppBarProvider( + final provider = KlipyAppBarProvider( testQuery, testDuration, keyboardDismissBehavior: testKeyboardDismissBehavior, @@ -58,7 +58,7 @@ void main() { }); test('Resets selectedCategory when queryText cleared', () async { - final provider = TenorAppBarProvider( + final provider = KlipyAppBarProvider( testQuery, testDuration, keyboardDismissBehavior: testKeyboardDismissBehavior, diff --git a/test/providers/sheet_provider_test.dart b/test/providers/sheet_provider_test.dart index 2a5469d..e717f42 100644 --- a/test/providers/sheet_provider_test.dart +++ b/test/providers/sheet_provider_test.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:tenor_flutter/src/providers/providers.dart'; +import 'package:klipy_flutter/src/providers/providers.dart'; void main() { const testMinExtent = 0.7; @@ -8,9 +8,9 @@ void main() { const testMaxExtent = 0.9; final testScrollController = DraggableScrollableController(); - group('TenorSheetProvider >', () { + group('KlipySheetProvider >', () { test('Initializes as expected', () async { - final provider = TenorSheetProvider( + final provider = KlipySheetProvider( maxExtent: testMaxExtent, minExtent: testMinExtent, initialExtent: tesInitialExtent, @@ -25,7 +25,7 @@ void main() { test('Can set scrollController', () async { final updatedScrollController = DraggableScrollableController(); - final provider = TenorSheetProvider( + final provider = KlipySheetProvider( maxExtent: testMaxExtent, minExtent: testMinExtent, scrollController: testScrollController, diff --git a/test/utilities/debouncer_test.dart b/test/utilities/debouncer_test.dart index 778460f..af0c6bb 100644 --- a/test/utilities/debouncer_test.dart +++ b/test/utilities/debouncer_test.dart @@ -1,52 +1,48 @@ -import 'package:tenor_flutter/src/utilities/debouncer.dart'; +import 'package:klipy_flutter/src/utilities/debouncer.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('Debouncer >', () { - test('Make sure the callback is called after the right amount of time', - () async { - final debouncer = TenorDebouncer( - delay: const Duration( - seconds: 1, - ), - ); + test( + 'Make sure the callback is called after the right amount of time', + () async { + final debouncer = KlipyDebouncer(delay: const Duration(seconds: 1)); - bool callbackCalled = false; + bool callbackCalled = false; - debouncer.call(() { - callbackCalled = true; - }); + debouncer.call(() { + callbackCalled = true; + }); - expect(callbackCalled, false); + expect(callbackCalled, false); - await Future.delayed(const Duration(seconds: 1), () {}); + await Future.delayed(const Duration(seconds: 1), () {}); - expect(callbackCalled, true); - }); + expect(callbackCalled, true); + }, + ); }); - test('Make sure that dispose ends the timer and does not run the callback', - () async { - final debouncer = TenorDebouncer( - delay: const Duration( - seconds: 1, - ), - ); + test( + 'Make sure that dispose ends the timer and does not run the callback', + () async { + final debouncer = KlipyDebouncer(delay: const Duration(seconds: 1)); - bool callbackCalled = false; + bool callbackCalled = false; - debouncer.call(() { - callbackCalled = true; - }); + debouncer.call(() { + callbackCalled = true; + }); - expect(callbackCalled, false); + expect(callbackCalled, false); - debouncer.dispose(); + debouncer.dispose(); - await Future.delayed(const Duration(seconds: 1), () {}); + await Future.delayed(const Duration(seconds: 1), () {}); - expect(callbackCalled, false); - }); + expect(callbackCalled, false); + }, + ); } diff --git a/test/utilities/is_tablet_test.dart b/test/utilities/is_tablet_test.dart index bc9e8be..0780742 100644 --- a/test/utilities/is_tablet_test.dart +++ b/test/utilities/is_tablet_test.dart @@ -1,18 +1,17 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:tenor_flutter/src/utilities/is_tablet.dart'; +import 'package:klipy_flutter/src/utilities/is_tablet.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('isTablet >', () { - testWidgets('true when iPad Pro (12.9-inch) (5th generation)', - (tester) async { + testWidgets('true when iPad Pro (12.9-inch) (5th generation)', ( + tester, + ) async { await tester.pumpWidget( MediaQuery( - data: const MediaQueryData( - size: Size(1192.0, 1558.0), - ), + data: const MediaQueryData(size: Size(1192.0, 1558.0)), child: MaterialApp( home: Builder( builder: (context) { @@ -28,9 +27,7 @@ void main() { testWidgets('false when iPhone 17 Pro', (tester) async { await tester.pumpWidget( MediaQuery( - data: const MediaQueryData( - size: Size(402.0, 874.0), - ), + data: const MediaQueryData(size: Size(402.0, 874.0)), child: MaterialApp( home: Builder( builder: (context) {

Flyclops

Domino!

Flyclops

Domino!

Flyclops is a independent mobile games studio specializing in casual multi-player games, both asynchronous turn-based, and real-time. Flyclops’s games have been played by millions across the globe.