diff --git a/mobile/lib/presentation/widgets/asset_viewer/bottom_sheet.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/bottom_sheet.widget.dart index 9ce8317767..8baadb4498 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/bottom_sheet.widget.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/bottom_sheet.widget.dart @@ -4,7 +4,6 @@ import 'package:auto_route/auto_route.dart'; import 'package:collection/collection.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_rating_bar/flutter_rating_bar.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; @@ -17,6 +16,7 @@ import 'package:immich_mobile/presentation/widgets/album/album_tile.dart'; import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart'; import 'package:immich_mobile/presentation/widgets/asset_viewer/bottom_sheet/sheet_location_details.widget.dart'; import 'package:immich_mobile/presentation/widgets/asset_viewer/bottom_sheet/sheet_people_details.widget.dart'; +import 'package:immich_mobile/presentation/widgets/asset_viewer/rating_bar.widget.dart'; import 'package:immich_mobile/presentation/widgets/asset_viewer/sheet_tile.widget.dart'; import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart'; import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; @@ -56,12 +56,6 @@ class AssetDetailBottomSheet extends ConsumerWidget { final currentAlbum = ref.watch(currentRemoteAlbumProvider); final isArchived = asset is RemoteAsset && asset.visibility == AssetVisibility.archive; final advancedTroubleshooting = ref.watch(settingsProvider.notifier).get(Setting.advancedTroubleshooting); - final isRatingEnabled = ref - .watch(userMetadataProvider(ref.watch(currentUserProvider)?.id ?? '')) - .maybeWhen( - data: (metadataList) => metadataList.any((meta) => meta.preferences?.ratingsEnabled ?? false), - orElse: () => false, - ); final buttonContext = ActionButtonContext( asset: asset, @@ -79,7 +73,7 @@ class AssetDetailBottomSheet extends ConsumerWidget { return BaseBottomSheet( actions: actions, - slivers: [_AssetDetailBottomSheet(isRatingEnabled: isRatingEnabled)], + slivers: [const _AssetDetailBottomSheet()], controller: controller, initialChildSize: initialChildSize, minChildSize: 0.1, @@ -93,9 +87,7 @@ class AssetDetailBottomSheet extends ConsumerWidget { } class _AssetDetailBottomSheet extends ConsumerWidget { - final bool isRatingEnabled; - - const _AssetDetailBottomSheet({required this.isRatingEnabled}); + const _AssetDetailBottomSheet(); String _getDateTime(BuildContext ctx, BaseAsset asset, ExifInfo? exifInfo) { DateTime dateTime = asset.createdAt.toLocal(); @@ -243,6 +235,9 @@ class _AssetDetailBottomSheet extends ConsumerWidget { final cameraTitle = _getCameraInfoTitle(exifInfo); final lensTitle = exifInfo?.lens != null && exifInfo!.lens!.isNotEmpty ? exifInfo.lens : null; final isOwner = ref.watch(currentUserProvider)?.id == (asset is RemoteAsset ? asset.ownerId : null); + final isRatingEnabled = ref + .watch(userMetadataPreferencesProvider(ref.watch(currentUserProvider)?.id ?? '')) + .maybeWhen(data: (prefs) => prefs?.ratingsEnabled ?? false, orElse: () => false); // Build file info tile based on asset type Widget buildFileInfoTile() { @@ -350,11 +345,11 @@ class _AssetDetailBottomSheet extends ConsumerWidget { ), ), const SizedBox(height: 8), - RatingBar.builder( + RatingBar( initialRating: exifInfo?.rating?.toDouble() ?? 0, - itemBuilder: (context, _) => Icon(Icons.star, color: context.themeData.colorScheme.primary), + filledColor: context.themeData.colorScheme.primary, + unfilledColor: context.themeData.colorScheme.onSurface.withAlpha(100), itemSize: 32, - glow: false, onRatingUpdate: (rating) async { await ref.read(actionProvider.notifier).updateRating(ActionSource.viewer, rating.round()); }, diff --git a/mobile/lib/presentation/widgets/asset_viewer/rating_bar.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/rating_bar.widget.dart new file mode 100644 index 0000000000..cc278725b2 --- /dev/null +++ b/mobile/lib/presentation/widgets/asset_viewer/rating_bar.widget.dart @@ -0,0 +1,84 @@ +import 'package:flutter/material.dart'; + +class RatingBar extends StatefulWidget { + final double initialRating; + final int itemCount; + final double itemSize; + final Color filledColor; + final Color unfilledColor; + final ValueChanged? onRatingUpdate; + final Widget? itemBuilder; + + const RatingBar({ + super.key, + this.initialRating = 0.0, + this.itemCount = 5, + this.itemSize = 40.0, + this.filledColor = Colors.amber, + this.unfilledColor = Colors.grey, + this.onRatingUpdate, + this.itemBuilder, + }); + + @override + State createState() => _RatingBarState(); +} + +class _RatingBarState extends State { + late double _currentRating; + + @override + void initState() { + super.initState(); + _currentRating = widget.initialRating; + } + + void _updateRating(Offset localPosition, bool isRTL, {bool isTap = false}) { + final totalWidth = widget.itemCount * widget.itemSize; + double dx = localPosition.dx; + + if (isRTL) dx = totalWidth - dx; + + double newRating; + + if (dx <= 0) { + newRating = 0; + } else if (dx >= totalWidth) { + newRating = widget.itemCount.toDouble(); + } else { + int tappedIndex = (dx ~/ widget.itemSize).clamp(0, widget.itemCount - 1); + newRating = tappedIndex + 1.0; + + if (isTap && newRating == _currentRating && _currentRating != 0) { + newRating = 0; + } + } + + if (_currentRating != newRating) { + setState(() { + _currentRating = newRating; + }); + widget.onRatingUpdate?.call(newRating.round()); + } + } + + @override + Widget build(BuildContext context) { + final isRTL = Directionality.of(context) == TextDirection.rtl; + + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTapDown: (details) => _updateRating(details.localPosition, isRTL, isTap: true), + onPanUpdate: (details) => _updateRating(details.localPosition, isRTL, isTap: false), + child: Row( + mainAxisSize: MainAxisSize.min, + textDirection: isRTL ? TextDirection.rtl : TextDirection.ltr, + children: List.generate(widget.itemCount, (index) { + bool filled = _currentRating > index; + return widget.itemBuilder ?? + Icon(Icons.star, size: widget.itemSize, color: filled ? widget.filledColor : widget.unfilledColor); + }), + ), + ); + } +} diff --git a/mobile/lib/providers/infrastructure/user_metadata.provider.dart b/mobile/lib/providers/infrastructure/user_metadata.provider.dart index 2ecf70d572..87f82baac5 100644 --- a/mobile/lib/providers/infrastructure/user_metadata.provider.dart +++ b/mobile/lib/providers/infrastructure/user_metadata.provider.dart @@ -11,3 +11,9 @@ final userMetadataProvider = FutureProvider.family, String>(( final repository = ref.watch(userMetadataRepository); return repository.getUserMetadata(userId); }); + +final userMetadataPreferencesProvider = FutureProvider.family((ref, String userId) async { + final metadataList = await ref.watch(userMetadataProvider(userId).future); + final metadataWithPrefs = metadataList.firstWhere((meta) => meta.preferences != null); + return metadataWithPrefs.preferences; +}); diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index 30ab654447..6a067f509f 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -665,14 +665,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.27" - flutter_rating_bar: - dependency: "direct main" - description: - name: flutter_rating_bar - sha256: d2af03469eac832c591a1eba47c91ecc871fe5708e69967073c043b2d775ed93 - url: "https://pub.dev" - source: hosted - version: "4.0.1" flutter_riverpod: dependency: transitive description: diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index 2aff6720aa..a49a012031 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -33,7 +33,6 @@ dependencies: flutter_displaymode: ^0.7.0 flutter_hooks: ^0.21.3+1 flutter_local_notifications: ^17.2.1+2 - flutter_rating_bar: ^4.0.1 flutter_secure_storage: ^9.2.4 flutter_svg: ^2.2.1 flutter_udid: ^4.0.0