mirror of
https://github.com/immich-app/immich.git
synced 2025-12-12 07:41:02 -08:00
refactor: use custom rating bar & provider
This commit is contained in:
@@ -4,7 +4,6 @@ import 'package:auto_route/auto_route.dart';
|
|||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_rating_bar/flutter_rating_bar.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/constants/enums.dart';
|
import 'package:immich_mobile/constants/enums.dart';
|
||||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.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/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_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/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/asset_viewer/sheet_tile.widget.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/action.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/action.provider.dart';
|
||||||
@@ -56,12 +56,6 @@ class AssetDetailBottomSheet extends ConsumerWidget {
|
|||||||
final currentAlbum = ref.watch(currentRemoteAlbumProvider);
|
final currentAlbum = ref.watch(currentRemoteAlbumProvider);
|
||||||
final isArchived = asset is RemoteAsset && asset.visibility == AssetVisibility.archive;
|
final isArchived = asset is RemoteAsset && asset.visibility == AssetVisibility.archive;
|
||||||
final advancedTroubleshooting = ref.watch(settingsProvider.notifier).get(Setting.advancedTroubleshooting);
|
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(
|
final buttonContext = ActionButtonContext(
|
||||||
asset: asset,
|
asset: asset,
|
||||||
@@ -79,7 +73,7 @@ class AssetDetailBottomSheet extends ConsumerWidget {
|
|||||||
|
|
||||||
return BaseBottomSheet(
|
return BaseBottomSheet(
|
||||||
actions: actions,
|
actions: actions,
|
||||||
slivers: [_AssetDetailBottomSheet(isRatingEnabled: isRatingEnabled)],
|
slivers: [const _AssetDetailBottomSheet()],
|
||||||
controller: controller,
|
controller: controller,
|
||||||
initialChildSize: initialChildSize,
|
initialChildSize: initialChildSize,
|
||||||
minChildSize: 0.1,
|
minChildSize: 0.1,
|
||||||
@@ -93,9 +87,7 @@ class AssetDetailBottomSheet extends ConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _AssetDetailBottomSheet extends ConsumerWidget {
|
class _AssetDetailBottomSheet extends ConsumerWidget {
|
||||||
final bool isRatingEnabled;
|
const _AssetDetailBottomSheet();
|
||||||
|
|
||||||
const _AssetDetailBottomSheet({required this.isRatingEnabled});
|
|
||||||
|
|
||||||
String _getDateTime(BuildContext ctx, BaseAsset asset, ExifInfo? exifInfo) {
|
String _getDateTime(BuildContext ctx, BaseAsset asset, ExifInfo? exifInfo) {
|
||||||
DateTime dateTime = asset.createdAt.toLocal();
|
DateTime dateTime = asset.createdAt.toLocal();
|
||||||
@@ -243,6 +235,9 @@ class _AssetDetailBottomSheet extends ConsumerWidget {
|
|||||||
final cameraTitle = _getCameraInfoTitle(exifInfo);
|
final cameraTitle = _getCameraInfoTitle(exifInfo);
|
||||||
final lensTitle = exifInfo?.lens != null && exifInfo!.lens!.isNotEmpty ? exifInfo.lens : null;
|
final lensTitle = exifInfo?.lens != null && exifInfo!.lens!.isNotEmpty ? exifInfo.lens : null;
|
||||||
final isOwner = ref.watch(currentUserProvider)?.id == (asset is RemoteAsset ? asset.ownerId : 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
|
// Build file info tile based on asset type
|
||||||
Widget buildFileInfoTile() {
|
Widget buildFileInfoTile() {
|
||||||
@@ -350,11 +345,11 @@ class _AssetDetailBottomSheet extends ConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
RatingBar.builder(
|
RatingBar(
|
||||||
initialRating: exifInfo?.rating?.toDouble() ?? 0,
|
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,
|
itemSize: 32,
|
||||||
glow: false,
|
|
||||||
onRatingUpdate: (rating) async {
|
onRatingUpdate: (rating) async {
|
||||||
await ref.read(actionProvider.notifier).updateRating(ActionSource.viewer, rating.round());
|
await ref.read(actionProvider.notifier).updateRating(ActionSource.viewer, rating.round());
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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<int>? 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<RatingBar> createState() => _RatingBarState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RatingBarState extends State<RatingBar> {
|
||||||
|
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);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,3 +11,9 @@ final userMetadataProvider = FutureProvider.family<List<UserMetadata>, String>((
|
|||||||
final repository = ref.watch(userMetadataRepository);
|
final repository = ref.watch(userMetadataRepository);
|
||||||
return repository.getUserMetadata(userId);
|
return repository.getUserMetadata(userId);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
final userMetadataPreferencesProvider = FutureProvider.family<Preferences?, String>((ref, String userId) async {
|
||||||
|
final metadataList = await ref.watch(userMetadataProvider(userId).future);
|
||||||
|
final metadataWithPrefs = metadataList.firstWhere((meta) => meta.preferences != null);
|
||||||
|
return metadataWithPrefs.preferences;
|
||||||
|
});
|
||||||
|
|||||||
@@ -665,14 +665,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.27"
|
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:
|
flutter_riverpod:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ dependencies:
|
|||||||
flutter_displaymode: ^0.7.0
|
flutter_displaymode: ^0.7.0
|
||||||
flutter_hooks: ^0.21.3+1
|
flutter_hooks: ^0.21.3+1
|
||||||
flutter_local_notifications: ^17.2.1+2
|
flutter_local_notifications: ^17.2.1+2
|
||||||
flutter_rating_bar: ^4.0.1
|
|
||||||
flutter_secure_storage: ^9.2.4
|
flutter_secure_storage: ^9.2.4
|
||||||
flutter_svg: ^2.2.1
|
flutter_svg: ^2.2.1
|
||||||
flutter_udid: ^4.0.0
|
flutter_udid: ^4.0.0
|
||||||
|
|||||||
Reference in New Issue
Block a user