diff --git a/lib/infrastructure/device_info_service.dart b/lib/infrastructure/device_info_service.dart index ce2d93733..dcca10219 100644 --- a/lib/infrastructure/device_info_service.dart +++ b/lib/infrastructure/device_info_service.dart @@ -1,7 +1,7 @@ import 'dart:io'; import 'package:device_info/device_info.dart'; -import 'package:flutter_user_agent/flutter_user_agent.dart'; +import 'package:flutter_user_agentx/flutter_user_agent.dart'; import 'package:genshindb/domain/services/device_info_service.dart'; import 'package:package_info/package_info.dart'; diff --git a/lib/presentation/shared/rarity_rating.dart b/lib/presentation/shared/rarity_rating.dart index 5c0956d52..eb89d412c 100644 --- a/lib/presentation/shared/rarity_rating.dart +++ b/lib/presentation/shared/rarity_rating.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:smooth_star_rating/smooth_star_rating.dart'; +import 'package:genshindb/presentation/shared/smooth_star_rating.dart'; class RarityRating extends StatelessWidget { final int rarity; diff --git a/lib/presentation/shared/smooth_star_rating.dart b/lib/presentation/shared/smooth_star_rating.dart new file mode 100644 index 000000000..a5cdc9d11 --- /dev/null +++ b/lib/presentation/shared/smooth_star_rating.dart @@ -0,0 +1,232 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +typedef RatingChangeCallback = void Function(double rating); + +class SmoothStarRating extends StatefulWidget { + final int starCount; + final double rating; + final RatingChangeCallback onRated; + final Color color; + final Color borderColor; + final double size; + final bool allowHalfRating; + final IconData filledIconData; + final IconData halfFilledIconData; + final IconData defaultIconData; //this is needed only when having fullRatedIconData && halfRatedIconData + final double spacing; + final bool isReadOnly; + + SmoothStarRating({ + this.starCount = 5, + this.isReadOnly = false, + this.spacing = 0.0, + this.rating = 0.0, + this.defaultIconData = Icons.star_border, + this.onRated, + this.color, + this.borderColor, + this.size = 25, + this.filledIconData = Icons.star, + this.halfFilledIconData = Icons.star_half, + this.allowHalfRating = true, + }) { + assert(this.rating != null); + } + + @override + _SmoothStarRatingState createState() => _SmoothStarRatingState(); +} + +class _SmoothStarRatingState extends State { + final double halfStarThreshold = 0.53; //half star value starts from this number + + //tracks for user tapping on this widget + bool isWidgetTapped = false; + double currentRating; + Timer debounceTimer; + + @override + void initState() { + currentRating = widget.rating; + super.initState(); + } + + @override + void dispose() { + debounceTimer?.cancel(); + debounceTimer = null; + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Material( + color: Colors.transparent, + child: Wrap( + alignment: WrapAlignment.start, spacing: widget.spacing, children: List.generate(widget.starCount, (index) => buildStar(context, index))), + ); + } + + Widget buildStar(BuildContext context, int index) { + Icon icon; + if (index >= currentRating) { + icon = Icon( + widget.defaultIconData, + color: widget.borderColor ?? Theme.of(context).primaryColor, + size: widget.size, + ); + } else if (index > currentRating - (widget.allowHalfRating ? halfStarThreshold : 1.0) && index < currentRating) { + icon = Icon( + widget.halfFilledIconData, + color: widget.color ?? Theme.of(context).primaryColor, + size: widget.size, + ); + } else { + icon = Icon( + widget.filledIconData, + color: widget.color ?? Theme.of(context).primaryColor, + size: widget.size, + ); + } + final Widget star = widget.isReadOnly + ? icon + : kIsWeb + ? MouseRegion( + onExit: (event) { + if (widget.onRated != null && !isWidgetTapped) { + //reset to zero only if rating is not set by user + setState(() { + currentRating = 0; + }); + } + }, + onEnter: (event) { + isWidgetTapped = false; //reset + }, + onHover: (event) { + RenderBox box = context.findRenderObject() as RenderBox; + var _pos = box.globalToLocal(event.position); + var i = _pos.dx / widget.size; + var newRating = widget.allowHalfRating ? i : i.round().toDouble(); + if (newRating > widget.starCount) { + newRating = widget.starCount.toDouble(); + } + if (newRating < 0) { + newRating = 0.0; + } + setState(() { + currentRating = newRating; + }); + }, + child: GestureDetector( + onTapDown: (detail) { + isWidgetTapped = true; + + RenderBox box = context.findRenderObject() as RenderBox; + var _pos = box.globalToLocal(detail.globalPosition); + var i = ((_pos.dx - widget.spacing) / widget.size); + var newRating = widget.allowHalfRating ? i : i.round().toDouble(); + if (newRating > widget.starCount) { + newRating = widget.starCount.toDouble(); + } + if (newRating < 0) { + newRating = 0.0; + } + setState(() { + currentRating = newRating; + }); + if (widget.onRated != null) { + widget.onRated(normalizeRating(currentRating)); + } + }, + onHorizontalDragUpdate: (dragDetails) { + isWidgetTapped = true; + + RenderBox box = context.findRenderObject() as RenderBox; + var _pos = box.globalToLocal(dragDetails.globalPosition); + var i = _pos.dx / widget.size; + var newRating = widget.allowHalfRating ? i : i.round().toDouble(); + if (newRating > widget.starCount) { + newRating = widget.starCount.toDouble(); + } + if (newRating < 0) { + newRating = 0.0; + } + setState(() { + currentRating = newRating; + }); + debounceTimer?.cancel(); + debounceTimer = Timer(Duration(milliseconds: 100), () { + if (widget.onRated != null) { + currentRating = normalizeRating(newRating); + widget.onRated(currentRating); + } + }); + }, + child: icon, + ), + ) + : GestureDetector( + onTapDown: (detail) { + RenderBox box = context.findRenderObject() as RenderBox; + var _pos = box.globalToLocal(detail.globalPosition); + var i = ((_pos.dx - widget.spacing) / widget.size); + var newRating = widget.allowHalfRating ? i : i.round().toDouble(); + if (newRating > widget.starCount) { + newRating = widget.starCount.toDouble(); + } + if (newRating < 0) { + newRating = 0.0; + } + newRating = normalizeRating(newRating); + setState(() { + currentRating = newRating; + }); + }, + onTapUp: (e) { + if (widget.onRated != null) widget.onRated(currentRating); + }, + onHorizontalDragUpdate: (dragDetails) { + RenderBox box = context.findRenderObject() as RenderBox; + var _pos = box.globalToLocal(dragDetails.globalPosition); + var i = _pos.dx / widget.size; + var newRating = widget.allowHalfRating ? i : i.round().toDouble(); + if (newRating > widget.starCount) { + newRating = widget.starCount.toDouble(); + } + if (newRating < 0) { + newRating = 0.0; + } + setState(() { + currentRating = newRating; + }); + debounceTimer?.cancel(); + debounceTimer = Timer(Duration(milliseconds: 100), () { + if (widget.onRated != null) { + currentRating = normalizeRating(newRating); + widget.onRated(currentRating); + } + }); + }, + child: icon, + ); + + return star; + } + + double normalizeRating(double newRating) { + var k = newRating - newRating.floor(); + if (k != 0) { + //half stars + if (k >= halfStarThreshold) { + newRating = newRating.floor() + 1.0; + } else { + newRating = newRating.floor() + 0.5; + } + } + return newRating; + } +} diff --git a/pubspec.lock b/pubspec.lock index ac3b9a757..9af6a00ad 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -296,13 +296,13 @@ packages: description: flutter source: sdk version: "0.0.0" - flutter_user_agent: + flutter_user_agentx: dependency: "direct main" description: - name: flutter_user_agent + name: flutter_user_agentx url: "https://pub.dartlang.org" source: hosted - version: "1.2.2" + version: "1.2.4" flutter_web_plugins: dependency: transitive description: flutter @@ -733,13 +733,6 @@ packages: description: flutter source: sdk version: "0.0.99" - smooth_star_rating: - dependency: "direct main" - description: - name: smooth_star_rating - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.1" source_gen: dependency: transitive description: @@ -931,4 +924,4 @@ packages: version: "3.1.0" sdks: dart: ">=2.12.0 <3.0.0" - flutter: ">=1.24.0-6.0.pre" + flutter: ">=2.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index 7bfefdedf..546af250e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -33,7 +33,7 @@ dependencies: sdk: flutter flutter_slidable: ^0.6.0 flutter_staggered_grid_view: ^0.4.0 - flutter_user_agent: ^1.2.2 + flutter_user_agentx: ^1.2.4 flutter_webview_plugin: ^0.3.11 fluttertoast: 8.0.3 freezed_annotation: ^0.12.0 @@ -52,7 +52,6 @@ dependencies: rate_my_app: ^1.1.0+1 screenshot: ^0.3.0 shared_preferences: ^2.0.5 - smooth_star_rating: 1.1.1 sprintf: ^6.0.0 transparent_image: 2.0.0 tuple: ^2.0.0