mirror of
https://github.com/immich-app/immich
synced 2025-11-14 17:36:12 +00:00
chore: bump line length to 120 (#20191)
This commit is contained in:
parent
977c9b96ba
commit
ad65e9011a
517 changed files with 4520 additions and 9514 deletions
|
|
@ -31,8 +31,7 @@ final _features = [
|
|||
return Future.value();
|
||||
}
|
||||
|
||||
final assets =
|
||||
await ref.read(remoteAssetRepositoryProvider).getSome(user.id);
|
||||
final assets = await ref.read(remoteAssetRepositoryProvider).getSome(user.id);
|
||||
|
||||
final selectedAssets = await ctx.pushRoute<Set<BaseAsset>>(
|
||||
DriftAssetSelectionTimelineRoute(
|
||||
|
|
@ -75,9 +74,7 @@ final _features = [
|
|||
_Feature(
|
||||
name: 'WAL Checkpoint',
|
||||
icon: Icons.save_rounded,
|
||||
onTap: (_, ref) => ref
|
||||
.read(driftProvider)
|
||||
.customStatement("pragma wal_checkpoint(truncate)"),
|
||||
onTap: (_, ref) => ref.read(driftProvider).customStatement("pragma wal_checkpoint(truncate)"),
|
||||
),
|
||||
_Feature(
|
||||
name: '',
|
||||
|
|
|
|||
|
|
@ -117,9 +117,8 @@ class LocalMediaSummaryPage extends StatelessWidget {
|
|||
return SliverList.builder(
|
||||
itemBuilder: (_, index) {
|
||||
final album = albums[index];
|
||||
final countFuture = db.managers.localAlbumAssetEntity
|
||||
.filter((f) => f.albumId.id.equals(album.id))
|
||||
.count();
|
||||
final countFuture =
|
||||
db.managers.localAlbumAssetEntity.filter((f) => f.albumId.id.equals(album.id)).count();
|
||||
return _Summary(
|
||||
leading: const Icon(Icons.photo_album_rounded),
|
||||
name: album.name,
|
||||
|
|
@ -226,9 +225,8 @@ class RemoteMediaSummaryPage extends StatelessWidget {
|
|||
return SliverList.builder(
|
||||
itemBuilder: (_, index) {
|
||||
final album = albums[index];
|
||||
final countFuture = db.managers.remoteAlbumAssetEntity
|
||||
.filter((f) => f.albumId.id.equals(album.id))
|
||||
.count();
|
||||
final countFuture =
|
||||
db.managers.remoteAlbumAssetEntity.filter((f) => f.albumId.id.equals(album.id)).count();
|
||||
return _Summary(
|
||||
leading: const Icon(Icons.photo_album_rounded),
|
||||
name: album.name,
|
||||
|
|
|
|||
|
|
@ -50,9 +50,7 @@ class _DriftAlbumsPageState extends ConsumerState<DriftAlbumsPage> {
|
|||
|
||||
void onSearch(String searchTerm, QuickFilterMode sortMode) {
|
||||
final userId = ref.watch(currentUserProvider)?.id;
|
||||
ref
|
||||
.read(remoteAlbumProvider.notifier)
|
||||
.searchAlbums(searchTerm, userId, sortMode);
|
||||
ref.read(remoteAlbumProvider.notifier).searchAlbums(searchTerm, userId, sortMode);
|
||||
}
|
||||
|
||||
Future<void> onRefresh() async {
|
||||
|
|
@ -88,8 +86,7 @@ class _DriftAlbumsPageState extends ConsumerState<DriftAlbumsPage> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final albums =
|
||||
ref.watch(remoteAlbumProvider.select((s) => s.filteredAlbums));
|
||||
final albums = ref.watch(remoteAlbumProvider.select((s) => s.filteredAlbums));
|
||||
|
||||
final userId = ref.watch(currentUserProvider)?.id;
|
||||
|
||||
|
|
@ -221,9 +218,7 @@ class _SortButtonState extends ConsumerState<_SortButton> {
|
|||
const EdgeInsets.fromLTRB(16, 16, 32, 16),
|
||||
),
|
||||
backgroundColor: WidgetStateProperty.all(
|
||||
albumSortOption == sortMode
|
||||
? context.colorScheme.primary
|
||||
: Colors.transparent,
|
||||
albumSortOption == sortMode ? context.colorScheme.primary : Colors.transparent,
|
||||
),
|
||||
shape: WidgetStateProperty.all(
|
||||
const RoundedRectangleBorder(
|
||||
|
|
@ -436,9 +431,7 @@ class _QuickFilterButton extends StatelessWidget {
|
|||
child: Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
color: isSelected
|
||||
? context.colorScheme.onPrimary
|
||||
: context.colorScheme.onSurface,
|
||||
color: isSelected ? context.colorScheme.onPrimary : context.colorScheme.onSurface,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
|
|
@ -564,8 +557,7 @@ class _AlbumList extends ConsumerWidget {
|
|||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: context.colorScheme.surfaceContainer,
|
||||
borderRadius:
|
||||
const BorderRadius.all(Radius.circular(16)),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(16)),
|
||||
border: Border.all(
|
||||
color: context.colorScheme.outline.withAlpha(50),
|
||||
width: 1,
|
||||
|
|
|
|||
|
|
@ -23,8 +23,7 @@ class DriftArchivePage extends StatelessWidget {
|
|||
throw Exception('User must be logged in to access archive');
|
||||
}
|
||||
|
||||
final timelineService =
|
||||
ref.watch(timelineFactoryProvider).archive(user.id);
|
||||
final timelineService = ref.watch(timelineFactoryProvider).archive(user.id);
|
||||
ref.onDispose(timelineService.dispose);
|
||||
return timelineService;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -37,8 +37,7 @@ class DriftAssetSelectionTimelinePage extends ConsumerWidget {
|
|||
);
|
||||
}
|
||||
|
||||
final timelineService =
|
||||
ref.watch(timelineFactoryProvider).remoteAssets(user.id);
|
||||
final timelineService = ref.watch(timelineFactoryProvider).remoteAssets(user.id);
|
||||
ref.onDispose(timelineService.dispose);
|
||||
return timelineService;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -15,8 +15,7 @@ class DriftCreateAlbumPage extends ConsumerStatefulWidget {
|
|||
const DriftCreateAlbumPage({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<DriftCreateAlbumPage> createState() =>
|
||||
_DriftCreateAlbumPageState();
|
||||
ConsumerState<DriftCreateAlbumPage> createState() => _DriftCreateAlbumPageState();
|
||||
}
|
||||
|
||||
class _DriftCreateAlbumPageState extends ConsumerState<DriftCreateAlbumPage> {
|
||||
|
|
@ -285,9 +284,7 @@ class _DriftCreateAlbumPageState extends ConsumerState<DriftCreateAlbumPage> {
|
|||
'create'.t(),
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: _canCreateAlbum
|
||||
? context.primaryColor
|
||||
: context.themeData.disabledColor,
|
||||
color: _canCreateAlbum ? context.primaryColor : context.themeData.disabledColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
@ -351,8 +348,7 @@ class _AlbumTitleTextFieldState extends State<_AlbumTitleTextField> {
|
|||
),
|
||||
controller: widget.textController,
|
||||
onTap: () {
|
||||
if (widget.textController.text ==
|
||||
'create_album_page_untitled'.t(context: context)) {
|
||||
if (widget.textController.text == 'create_album_page_untitled'.t(context: context)) {
|
||||
widget.textController.clear();
|
||||
}
|
||||
},
|
||||
|
|
@ -411,12 +407,10 @@ class _AlbumViewerEditableDescription extends StatefulWidget {
|
|||
final FocusNode focusNode;
|
||||
|
||||
@override
|
||||
State<_AlbumViewerEditableDescription> createState() =>
|
||||
_AlbumViewerEditableDescriptionState();
|
||||
State<_AlbumViewerEditableDescription> createState() => _AlbumViewerEditableDescriptionState();
|
||||
}
|
||||
|
||||
class _AlbumViewerEditableDescriptionState
|
||||
extends State<_AlbumViewerEditableDescription> {
|
||||
class _AlbumViewerEditableDescriptionState extends State<_AlbumViewerEditableDescription> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
|
@ -458,19 +452,18 @@ class _AlbumViewerEditableDescriptionState
|
|||
horizontal: 12.0,
|
||||
vertical: 16.0,
|
||||
),
|
||||
suffixIcon:
|
||||
widget.focusNode.hasFocus && widget.textController.text.isNotEmpty
|
||||
? IconButton(
|
||||
onPressed: () {
|
||||
widget.textController.clear();
|
||||
},
|
||||
icon: Icon(
|
||||
Icons.cancel_rounded,
|
||||
color: context.primaryColor,
|
||||
),
|
||||
splashRadius: 10.0,
|
||||
)
|
||||
: null,
|
||||
suffixIcon: widget.focusNode.hasFocus && widget.textController.text.isNotEmpty
|
||||
? IconButton(
|
||||
onPressed: () {
|
||||
widget.textController.clear();
|
||||
},
|
||||
icon: Icon(
|
||||
Icons.cancel_rounded,
|
||||
color: context.primaryColor,
|
||||
),
|
||||
splashRadius: 10.0,
|
||||
)
|
||||
: null,
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: context.colorScheme.outline.withValues(alpha: 0.3),
|
||||
|
|
|
|||
|
|
@ -23,8 +23,7 @@ class DriftFavoritePage extends StatelessWidget {
|
|||
throw Exception('User must be logged in to access favorite');
|
||||
}
|
||||
|
||||
final timelineService =
|
||||
ref.watch(timelineFactoryProvider).favorite(user.id);
|
||||
final timelineService = ref.watch(timelineFactoryProvider).favorite(user.id);
|
||||
ref.onDispose(timelineService.dispose);
|
||||
return timelineService;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -79,9 +79,7 @@ class _ActionButtonGrid extends ConsumerWidget {
|
|||
onTap: () => context.pushRoute(const SharedLinkRoute()),
|
||||
label: 'shared_links'.t(context: context),
|
||||
),
|
||||
isTrashEnable
|
||||
? const SizedBox(width: 8)
|
||||
: const SizedBox.shrink(),
|
||||
isTrashEnable ? const SizedBox(width: 8) : const SizedBox.shrink(),
|
||||
isTrashEnable
|
||||
? _ActionButton(
|
||||
icon: Icons.delete_outline_rounded,
|
||||
|
|
@ -267,8 +265,7 @@ class _PlacesCollectionCard extends StatelessWidget {
|
|||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||
color:
|
||||
context.colorScheme.secondaryContainer.withAlpha(100),
|
||||
color: context.colorScheme.secondaryContainer.withAlpha(100),
|
||||
),
|
||||
child: IgnorePointer(
|
||||
child: MapThumbnail(
|
||||
|
|
@ -278,9 +275,7 @@ class _PlacesCollectionCard extends StatelessWidget {
|
|||
-157.91959,
|
||||
),
|
||||
showAttribution: false,
|
||||
themeMode: context.isDarkTheme
|
||||
? ThemeMode.dark
|
||||
: ThemeMode.light,
|
||||
themeMode: context.isDarkTheme ? ThemeMode.dark : ThemeMode.light,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -102,8 +102,7 @@ class _AlbumList extends ConsumerWidget {
|
|||
color: context.colorScheme.onSurfaceSecondary,
|
||||
),
|
||||
),
|
||||
onTap: () =>
|
||||
context.pushRoute(LocalTimelineRoute(album: album)),
|
||||
onTap: () => context.pushRoute(LocalTimelineRoute(album: album)),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -14,12 +14,10 @@ class DriftLockedFolderPage extends ConsumerStatefulWidget {
|
|||
const DriftLockedFolderPage({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<DriftLockedFolderPage> createState() =>
|
||||
_DriftLockedFolderPageState();
|
||||
ConsumerState<DriftLockedFolderPage> createState() => _DriftLockedFolderPageState();
|
||||
}
|
||||
|
||||
class _DriftLockedFolderPageState extends ConsumerState<DriftLockedFolderPage>
|
||||
with WidgetsBindingObserver {
|
||||
class _DriftLockedFolderPageState extends ConsumerState<DriftLockedFolderPage> with WidgetsBindingObserver {
|
||||
bool _showOverlay = false;
|
||||
|
||||
@override
|
||||
|
|
@ -54,8 +52,7 @@ class _DriftLockedFolderPageState extends ConsumerState<DriftLockedFolderPage>
|
|||
throw Exception('User must be logged in to access locked folder');
|
||||
}
|
||||
|
||||
final timelineService =
|
||||
ref.watch(timelineFactoryProvider).lockedFolder(user.id);
|
||||
final timelineService = ref.watch(timelineFactoryProvider).lockedFolder(user.id);
|
||||
ref.onDispose(timelineService.dispose);
|
||||
return timelineService;
|
||||
},
|
||||
|
|
@ -64,8 +61,7 @@ class _DriftLockedFolderPageState extends ConsumerState<DriftLockedFolderPage>
|
|||
child: _showOverlay
|
||||
? const SizedBox()
|
||||
: PopScope(
|
||||
onPopInvokedWithResult: (didPop, _) =>
|
||||
didPop ? ref.read(authProvider.notifier).lockPinCode() : null,
|
||||
onPopInvokedWithResult: (didPop, _) => didPop ? ref.read(authProvider.notifier).lockPinCode() : null,
|
||||
child: Timeline(
|
||||
appBar: MesmerizingSliverAppBar(
|
||||
title: 'locked_folder'.t(context: context),
|
||||
|
|
|
|||
|
|
@ -40,8 +40,7 @@ class DriftMemoryPage extends HookConsumerWidget {
|
|||
final currentAsset = useState<RemoteAsset?>(null);
|
||||
|
||||
/// The list of all of the asset page controllers
|
||||
final memoryAssetPageControllers =
|
||||
List.generate(memories.length, (i) => usePageController());
|
||||
final memoryAssetPageControllers = List.generate(memories.length, (i) => usePageController());
|
||||
|
||||
/// The main vertically scrolling page controller with each list of memories
|
||||
final memoryPageController = usePageController(initialPage: memoryIndex);
|
||||
|
|
@ -73,19 +72,16 @@ class DriftMemoryPage extends HookConsumerWidget {
|
|||
// Wait for the next frame to ensure the page is built
|
||||
SchedulerBinding.instance.addPostFrameCallback((_) {
|
||||
final previousIndex = currentMemoryIndex.value - 1;
|
||||
final previousMemoryController =
|
||||
memoryAssetPageControllers[previousIndex];
|
||||
final previousMemoryController = memoryAssetPageControllers[previousIndex];
|
||||
|
||||
// Ensure the controller is attached
|
||||
if (previousMemoryController.hasClients) {
|
||||
previousMemoryController
|
||||
.jumpToPage(memories[previousIndex].assets.length - 1);
|
||||
previousMemoryController.jumpToPage(memories[previousIndex].assets.length - 1);
|
||||
} else {
|
||||
// Wait for the next frame until it is attached
|
||||
SchedulerBinding.instance.addPostFrameCallback((_) {
|
||||
if (previousMemoryController.hasClients) {
|
||||
previousMemoryController
|
||||
.jumpToPage(memories[previousIndex].assets.length - 1);
|
||||
previousMemoryController.jumpToPage(memories[previousIndex].assets.length - 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -96,8 +92,7 @@ class DriftMemoryPage extends HookConsumerWidget {
|
|||
toNextAsset(int currentAssetIndex) {
|
||||
if (currentAssetIndex + 1 < currentMemory.value.assets.length) {
|
||||
// Go to the next asset
|
||||
PageController controller =
|
||||
memoryAssetPageControllers[currentMemoryIndex.value];
|
||||
PageController controller = memoryAssetPageControllers[currentMemoryIndex.value];
|
||||
|
||||
controller.nextPage(
|
||||
curve: Curves.easeInOut,
|
||||
|
|
@ -112,8 +107,7 @@ class DriftMemoryPage extends HookConsumerWidget {
|
|||
toPreviousAsset(int currentAssetIndex) {
|
||||
if (currentAssetIndex > 0) {
|
||||
// Go to the previous asset
|
||||
PageController controller =
|
||||
memoryAssetPageControllers[currentMemoryIndex.value];
|
||||
PageController controller = memoryAssetPageControllers[currentMemoryIndex.value];
|
||||
|
||||
controller.previousPage(
|
||||
curve: Curves.easeInOut,
|
||||
|
|
@ -126,8 +120,7 @@ class DriftMemoryPage extends HookConsumerWidget {
|
|||
}
|
||||
|
||||
updateProgressText() {
|
||||
assetProgress.value =
|
||||
"${currentAssetPage.value + 1}|${currentMemory.value.assets.length}";
|
||||
assetProgress.value = "${currentAssetPage.value + 1}|${currentMemory.value.assets.length}";
|
||||
}
|
||||
|
||||
/// Downloads and caches the image for the asset at this [currentMemory]'s index
|
||||
|
|
@ -179,8 +172,7 @@ class DriftMemoryPage extends HookConsumerWidget {
|
|||
|
||||
// Precache the next page right away if we are on the first page
|
||||
if (currentAssetPage.value == 0) {
|
||||
Future.delayed(const Duration(milliseconds: 200))
|
||||
.then((_) => precacheAsset(1));
|
||||
Future.delayed(const Duration(milliseconds: 200)).then((_) => precacheAsset(1));
|
||||
}
|
||||
|
||||
Future<void> onAssetChanged(int otherIndex) async {
|
||||
|
|
@ -212,12 +204,10 @@ class DriftMemoryPage extends HookConsumerWidget {
|
|||
// maxScrollExtend contains the sum of horizontal pixels of all assets for depth = 1
|
||||
// or sum of vertical pixels of all memories for depth = 0
|
||||
if (notification is ScrollUpdateNotification) {
|
||||
final isEpiloguePage =
|
||||
(memoryPageController.page?.floor() ?? 0) >= memories.length;
|
||||
final isEpiloguePage = (memoryPageController.page?.floor() ?? 0) >= memories.length;
|
||||
|
||||
final offset = notification.metrics.pixels;
|
||||
if (isEpiloguePage &&
|
||||
(offset > notification.metrics.maxScrollExtent + 150)) {
|
||||
if (isEpiloguePage && (offset > notification.metrics.maxScrollExtent + 150)) {
|
||||
context.maybePop();
|
||||
return true;
|
||||
}
|
||||
|
|
@ -366,8 +356,7 @@ class DriftMemoryPage extends HookConsumerWidget {
|
|||
),
|
||||
),
|
||||
),
|
||||
if (currentAsset.value != null &&
|
||||
currentAsset.value!.isVideo)
|
||||
if (currentAsset.value != null && currentAsset.value!.isVideo)
|
||||
Positioned(
|
||||
bottom: 24,
|
||||
right: 32,
|
||||
|
|
|
|||
|
|
@ -26,8 +26,7 @@ class DriftPartnerDetailPage extends StatelessWidget {
|
|||
overrides: [
|
||||
timelineServiceProvider.overrideWith(
|
||||
(ref) {
|
||||
final timelineService =
|
||||
ref.watch(timelineFactoryProvider).remoteAssets(partner.id);
|
||||
final timelineService = ref.watch(timelineFactoryProvider).remoteAssets(partner.id);
|
||||
ref.onDispose(timelineService.dispose);
|
||||
return timelineService;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -91,8 +91,7 @@ class _Map extends StatelessWidget {
|
|||
width: context.width,
|
||||
// TODO: migrate to DriftMapRoute after merging #19898
|
||||
child: MapThumbnail(
|
||||
onTap: (_, __) => context
|
||||
.pushRoute(MapRoute(initialLocation: currentLocation)),
|
||||
onTap: (_, __) => context.pushRoute(MapRoute(initialLocation: currentLocation)),
|
||||
zoom: 8,
|
||||
centre: currentLocation ??
|
||||
const LatLng(
|
||||
|
|
@ -100,8 +99,7 @@ class _Map extends StatelessWidget {
|
|||
-157.91959,
|
||||
),
|
||||
showAttribution: false,
|
||||
themeMode:
|
||||
context.isDarkTheme ? ThemeMode.dark : ThemeMode.light,
|
||||
themeMode: context.isDarkTheme ? ThemeMode.dark : ThemeMode.light,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -20,8 +20,7 @@ class DriftPlaceDetailPage extends StatelessWidget {
|
|||
overrides: [
|
||||
timelineServiceProvider.overrideWith(
|
||||
(ref) {
|
||||
final timelineService =
|
||||
ref.watch(timelineFactoryProvider).place(place);
|
||||
final timelineService = ref.watch(timelineFactoryProvider).place(place);
|
||||
ref.onDispose(timelineService.dispose);
|
||||
return timelineService;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -24,8 +24,7 @@ class DriftRecentlyTakenPage extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
|
||||
final timelineService =
|
||||
ref.watch(timelineFactoryProvider).remoteAssets(user.id);
|
||||
final timelineService = ref.watch(timelineFactoryProvider).remoteAssets(user.id);
|
||||
ref.onDispose(timelineService.dispose);
|
||||
return timelineService;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -37,8 +37,7 @@ class _RemoteAlbumPageState extends ConsumerState<RemoteAlbumPage> {
|
|||
}
|
||||
|
||||
Future<void> addAssets(BuildContext context) async {
|
||||
final albumAssets =
|
||||
await ref.read(remoteAlbumProvider.notifier).getAssets(widget.album.id);
|
||||
final albumAssets = await ref.read(remoteAlbumProvider.notifier).getAssets(widget.album.id);
|
||||
|
||||
final newAssets = await context.pushRoute<Set<BaseAsset>>(
|
||||
DriftAssetSelectionTimelineRoute(
|
||||
|
|
@ -82,9 +81,7 @@ class _RemoteAlbumPageState extends ConsumerState<RemoteAlbumPage> {
|
|||
}
|
||||
|
||||
try {
|
||||
await ref
|
||||
.read(remoteAlbumProvider.notifier)
|
||||
.addUsers(widget.album.id, newUsers);
|
||||
await ref.read(remoteAlbumProvider.notifier).addUsers(widget.album.id, newUsers);
|
||||
|
||||
if (newUsers.isNotEmpty) {
|
||||
ImmichToast.show(
|
||||
|
|
@ -157,9 +154,7 @@ class _RemoteAlbumPageState extends ConsumerState<RemoteAlbumPage> {
|
|||
|
||||
if (confirmed == true) {
|
||||
try {
|
||||
await ref
|
||||
.read(remoteAlbumProvider.notifier)
|
||||
.deleteAlbum(widget.album.id);
|
||||
await ref.read(remoteAlbumProvider.notifier).deleteAlbum(widget.album.id);
|
||||
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
|
|
@ -237,9 +232,7 @@ class _RemoteAlbumPageState extends ConsumerState<RemoteAlbumPage> {
|
|||
overrides: [
|
||||
timelineServiceProvider.overrideWith(
|
||||
(ref) {
|
||||
final timelineService = ref
|
||||
.watch(timelineFactoryProvider)
|
||||
.remoteAlbum(albumId: widget.album.id);
|
||||
final timelineService = ref.watch(timelineFactoryProvider).remoteAlbum(albumId: widget.album.id);
|
||||
ref.onDispose(timelineService.dispose);
|
||||
return timelineService;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -23,8 +23,7 @@ class DriftTrashPage extends StatelessWidget {
|
|||
throw Exception('User must be logged in to access trash');
|
||||
}
|
||||
|
||||
final timelineService =
|
||||
ref.watch(timelineFactoryProvider).trash(user.id);
|
||||
final timelineService = ref.watch(timelineFactoryProvider).trash(user.id);
|
||||
ref.onDispose(timelineService.dispose);
|
||||
return timelineService;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -14,8 +14,7 @@ import 'package:immich_mobile/providers/user.provider.dart';
|
|||
import 'package:immich_mobile/widgets/common/user_circle_avatar.dart';
|
||||
|
||||
// TODO: Refactor this provider when we have user provider/service/repository pattern in place
|
||||
final driftUsersProvider =
|
||||
FutureProvider.autoDispose<List<UserDto>>((ref) async {
|
||||
final driftUsersProvider = FutureProvider.autoDispose<List<UserDto>>((ref) async {
|
||||
final drift = ref.watch(driftProvider);
|
||||
final currentUser = ref.watch(currentUserProvider);
|
||||
|
||||
|
|
@ -57,8 +56,7 @@ class DriftUserSelectionPage extends HookConsumerWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final AsyncValue<List<UserDto>> suggestedShareUsers =
|
||||
ref.watch(driftUsersProvider);
|
||||
final AsyncValue<List<UserDto>> suggestedShareUsers = ref.watch(driftUsersProvider);
|
||||
final sharedUsersList = useState<Set<UserDto>>({});
|
||||
|
||||
addNewUsersHandler() {
|
||||
|
|
@ -174,8 +172,7 @@ class DriftUserSelectionPage extends HookConsumerWidget {
|
|||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed:
|
||||
sharedUsersList.value.isEmpty ? null : addNewUsersHandler,
|
||||
onPressed: sharedUsersList.value.isEmpty ? null : addNewUsersHandler,
|
||||
child: const Text(
|
||||
"add",
|
||||
style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
|
||||
|
|
@ -186,16 +183,13 @@ class DriftUserSelectionPage extends HookConsumerWidget {
|
|||
body: suggestedShareUsers.widgetWhen(
|
||||
onData: (users) {
|
||||
// Get shared users for this album from the database
|
||||
final sharedUsers =
|
||||
ref.watch(remoteAlbumSharedUsersProvider(album.id));
|
||||
final sharedUsers = ref.watch(remoteAlbumSharedUsersProvider(album.id));
|
||||
|
||||
return sharedUsers.when(
|
||||
data: (albumSharedUsers) {
|
||||
// Filter out users that are already shared with this album and the owner
|
||||
final filteredUsers = users.where((user) {
|
||||
return !albumSharedUsers
|
||||
.any((sharedUser) => sharedUser.id == user.id) &&
|
||||
user.id != album.ownerId;
|
||||
return !albumSharedUsers.any((sharedUser) => sharedUser.id == user.id) && user.id != album.ownerId;
|
||||
}).toList();
|
||||
|
||||
return buildUserList(filteredUsers);
|
||||
|
|
@ -203,8 +197,7 @@ class DriftUserSelectionPage extends HookConsumerWidget {
|
|||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
error: (error, stack) {
|
||||
// If we can't load shared users, just filter out the owner
|
||||
final filteredUsers =
|
||||
users.where((user) => user.id != album.ownerId).toList();
|
||||
final filteredUsers = users.where((user) => user.id != album.ownerId).toList();
|
||||
return buildUserList(filteredUsers);
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -22,8 +22,7 @@ class DriftVideoPage extends StatelessWidget {
|
|||
throw Exception('User must be logged in to video');
|
||||
}
|
||||
|
||||
final timelineService =
|
||||
ref.watch(timelineFactoryProvider).video(user.id);
|
||||
final timelineService = ref.watch(timelineFactoryProvider).video(user.id);
|
||||
ref.onDispose(timelineService.dispose);
|
||||
return timelineService;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -19,9 +19,7 @@ class LocalTimelinePage extends StatelessWidget {
|
|||
overrides: [
|
||||
timelineServiceProvider.overrideWith(
|
||||
(ref) {
|
||||
final timelineService = ref
|
||||
.watch(timelineFactoryProvider)
|
||||
.localAlbum(albumId: album.id);
|
||||
final timelineService = ref.watch(timelineFactoryProvider).localAlbum(albumId: album.id);
|
||||
ref.onDispose(timelineService.dispose);
|
||||
return timelineService;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -36,8 +36,7 @@ class DriftSearchPage extends HookConsumerWidget {
|
|||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final textSearchType = useState<TextSearchType>(TextSearchType.context);
|
||||
final searchHintText =
|
||||
useState<String>('sunrise_on_the_beach'.t(context: context));
|
||||
final searchHintText = useState<String>('sunrise_on_the_beach'.t(context: context));
|
||||
final textSearchController = useTextEditingController();
|
||||
final filter = useState<SearchFilter>(
|
||||
SearchFilter(
|
||||
|
|
@ -52,8 +51,7 @@ class DriftSearchPage extends HookConsumerWidget {
|
|||
isFavorite: false,
|
||||
),
|
||||
mediaType: preFilter?.mediaType ?? AssetType.other,
|
||||
language:
|
||||
"${context.locale.languageCode}-${context.locale.countryCode}",
|
||||
language: "${context.locale.languageCode}-${context.locale.countryCode}",
|
||||
),
|
||||
);
|
||||
|
||||
|
|
@ -91,9 +89,7 @@ class DriftSearchPage extends HookConsumerWidget {
|
|||
|
||||
isSearching.value = true;
|
||||
ref.watch(paginatedSearchProvider.notifier).clear();
|
||||
final hasResult = await ref
|
||||
.watch(paginatedSearchProvider.notifier)
|
||||
.search(filter.value);
|
||||
final hasResult = await ref.watch(paginatedSearchProvider.notifier).search(filter.value);
|
||||
|
||||
if (!hasResult) {
|
||||
context.showSnackBar(
|
||||
|
|
@ -107,9 +103,7 @@ class DriftSearchPage extends HookConsumerWidget {
|
|||
|
||||
loadMoreSearchResult() async {
|
||||
isSearching.value = true;
|
||||
final hasResult = await ref
|
||||
.watch(paginatedSearchProvider.notifier)
|
||||
.search(filter.value);
|
||||
final hasResult = await ref.watch(paginatedSearchProvider.notifier).search(filter.value);
|
||||
|
||||
if (!hasResult) {
|
||||
context.showSnackBar(
|
||||
|
|
@ -157,9 +151,7 @@ class DriftSearchPage extends HookConsumerWidget {
|
|||
);
|
||||
|
||||
peopleCurrentFilterWidget.value = Text(
|
||||
value
|
||||
.map((e) => e.name != '' ? e.name : 'no_name'.t(context: context))
|
||||
.join(', '),
|
||||
value.map((e) => e.name != '' ? e.name : 'no_name'.t(context: context)).join(', '),
|
||||
style: context.textTheme.labelLarge,
|
||||
);
|
||||
}
|
||||
|
|
@ -424,8 +416,7 @@ class DriftSearchPage extends HookConsumerWidget {
|
|||
);
|
||||
if (value) {
|
||||
filterText.add(
|
||||
'search_filter_display_option_not_in_album'
|
||||
.t(context: context),
|
||||
'search_filter_display_option_not_in_album'.t(context: context),
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
|
@ -572,9 +563,7 @@ class DriftSearchPage extends HookConsumerWidget {
|
|||
'search_by_context'.t(context: context),
|
||||
style: context.textTheme.bodyLarge?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
color: textSearchType.value == TextSearchType.context
|
||||
? context.colorScheme.primary
|
||||
: null,
|
||||
color: textSearchType.value == TextSearchType.context ? context.colorScheme.primary : null,
|
||||
),
|
||||
),
|
||||
selectedColor: context.colorScheme.primary,
|
||||
|
|
@ -582,8 +571,7 @@ class DriftSearchPage extends HookConsumerWidget {
|
|||
),
|
||||
onPressed: () {
|
||||
textSearchType.value = TextSearchType.context;
|
||||
searchHintText.value =
|
||||
'sunrise_on_the_beach'.t(context: context);
|
||||
searchHintText.value = 'sunrise_on_the_beach'.t(context: context);
|
||||
},
|
||||
),
|
||||
MenuItemButton(
|
||||
|
|
@ -593,9 +581,7 @@ class DriftSearchPage extends HookConsumerWidget {
|
|||
'search_filter_filename'.t(context: context),
|
||||
style: context.textTheme.bodyLarge?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
color: textSearchType.value == TextSearchType.filename
|
||||
? context.colorScheme.primary
|
||||
: null,
|
||||
color: textSearchType.value == TextSearchType.filename ? context.colorScheme.primary : null,
|
||||
),
|
||||
),
|
||||
selectedColor: context.colorScheme.primary,
|
||||
|
|
@ -603,8 +589,7 @@ class DriftSearchPage extends HookConsumerWidget {
|
|||
),
|
||||
onPressed: () {
|
||||
textSearchType.value = TextSearchType.filename;
|
||||
searchHintText.value =
|
||||
'file_name_or_extension'.t(context: context);
|
||||
searchHintText.value = 'file_name_or_extension'.t(context: context);
|
||||
},
|
||||
),
|
||||
MenuItemButton(
|
||||
|
|
@ -614,20 +599,15 @@ class DriftSearchPage extends HookConsumerWidget {
|
|||
'search_by_description'.t(context: context),
|
||||
style: context.textTheme.bodyLarge?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
color:
|
||||
textSearchType.value == TextSearchType.description
|
||||
? context.colorScheme.primary
|
||||
: null,
|
||||
color: textSearchType.value == TextSearchType.description ? context.colorScheme.primary : null,
|
||||
),
|
||||
),
|
||||
selectedColor: context.colorScheme.primary,
|
||||
selected:
|
||||
textSearchType.value == TextSearchType.description,
|
||||
selected: textSearchType.value == TextSearchType.description,
|
||||
),
|
||||
onPressed: () {
|
||||
textSearchType.value = TextSearchType.description;
|
||||
searchHintText.value =
|
||||
'search_by_description_example'.t(context: context);
|
||||
searchHintText.value = 'search_by_description_example'.t(context: context);
|
||||
},
|
||||
),
|
||||
],
|
||||
|
|
@ -657,9 +637,7 @@ class DriftSearchPage extends HookConsumerWidget {
|
|||
hintText: searchHintText.value,
|
||||
key: const Key('search_text_field'),
|
||||
controller: textSearchController,
|
||||
contentPadding: preFilter != null
|
||||
? const EdgeInsets.only(left: 24)
|
||||
: const EdgeInsets.all(8),
|
||||
contentPadding: preFilter != null ? const EdgeInsets.only(left: 24) : const EdgeInsets.all(8),
|
||||
prefixIcon: preFilter != null
|
||||
? null
|
||||
: Icon(
|
||||
|
|
@ -718,8 +696,7 @@ class DriftSearchPage extends HookConsumerWidget {
|
|||
SearchFilterChip(
|
||||
icon: Icons.display_settings_outlined,
|
||||
onTap: showDisplayOptionPicker,
|
||||
label:
|
||||
'search_filter_display_options'.t(context: context),
|
||||
label: 'search_filter_display_options'.t(context: context),
|
||||
currentFilter: displayOptionCurrentFilterWidget.value,
|
||||
),
|
||||
],
|
||||
|
|
@ -755,16 +732,13 @@ class _SearchResultGrid extends ConsumerWidget {
|
|||
|
||||
return NotificationListener<ScrollEndNotification>(
|
||||
onNotification: (notification) {
|
||||
final isBottomSheetNotification = notification.context
|
||||
?.findAncestorWidgetOfExactType<DraggableScrollableSheet>() !=
|
||||
null;
|
||||
final isBottomSheetNotification =
|
||||
notification.context?.findAncestorWidgetOfExactType<DraggableScrollableSheet>() != null;
|
||||
|
||||
final metrics = notification.metrics;
|
||||
final isVerticalScroll = metrics.axis == Axis.vertical;
|
||||
|
||||
if (metrics.pixels >= metrics.maxScrollExtent &&
|
||||
isVerticalScroll &&
|
||||
!isBottomSheetNotification) {
|
||||
if (metrics.pixels >= metrics.maxScrollExtent && isVerticalScroll && !isBottomSheetNotification) {
|
||||
onScrollEnd();
|
||||
}
|
||||
|
||||
|
|
@ -775,9 +749,7 @@ class _SearchResultGrid extends ConsumerWidget {
|
|||
overrides: [
|
||||
timelineServiceProvider.overrideWith(
|
||||
(ref) {
|
||||
final timelineService = ref
|
||||
.watch(timelineFactoryProvider)
|
||||
.fromAssets(searchResult.assets);
|
||||
final timelineService = ref.watch(timelineFactoryProvider).fromAssets(searchResult.assets);
|
||||
ref.onDispose(timelineService.dispose);
|
||||
return timelineService;
|
||||
},
|
||||
|
|
@ -806,9 +778,7 @@ class _SearchEmptyContent extends StatelessWidget {
|
|||
const SizedBox(height: 40),
|
||||
Center(
|
||||
child: Image.asset(
|
||||
context.isDarkTheme
|
||||
? 'assets/polaroid-dark.png'
|
||||
: 'assets/polaroid-light.png',
|
||||
context.isDarkTheme ? 'assets/polaroid-dark.png' : 'assets/polaroid-light.png',
|
||||
height: 125,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -4,16 +4,14 @@ import 'package:immich_mobile/domain/services/search.service.dart';
|
|||
import 'package:immich_mobile/models/search/search_filter.model.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/search.provider.dart';
|
||||
|
||||
final paginatedSearchProvider =
|
||||
StateNotifierProvider<PaginatedSearchNotifier, SearchResult>(
|
||||
final paginatedSearchProvider = StateNotifierProvider<PaginatedSearchNotifier, SearchResult>(
|
||||
(ref) => PaginatedSearchNotifier(ref.watch(searchServiceProvider)),
|
||||
);
|
||||
|
||||
class PaginatedSearchNotifier extends StateNotifier<SearchResult> {
|
||||
final SearchService _searchService;
|
||||
|
||||
PaginatedSearchNotifier(this._searchService)
|
||||
: super(const SearchResult(assets: [], nextPage: 1));
|
||||
PaginatedSearchNotifier(this._searchService) : super(const SearchResult(assets: [], nextPage: 1));
|
||||
|
||||
Future<bool> search(SearchFilter filter) async {
|
||||
if (state.nextPage == null) {
|
||||
|
|
|
|||
|
|
@ -35,9 +35,7 @@ class ArchiveActionButton extends ConsumerWidget {
|
|||
if (context.mounted) {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg: result.success
|
||||
? successMessage
|
||||
: 'scaffold_body_error_occurred'.t(context: context),
|
||||
msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context),
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
toastType: result.success ? ToastType.success : ToastType.error,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -25,12 +25,10 @@ class BaseActionButton extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final miniWidth =
|
||||
minWidth ?? (context.isMobile ? context.width / 4.5 : 75.0);
|
||||
final miniWidth = minWidth ?? (context.isMobile ? context.width / 4.5 : 75.0);
|
||||
final iconTheme = IconTheme.of(context);
|
||||
final iconSize = iconTheme.size ?? 24.0;
|
||||
final iconColor =
|
||||
this.iconColor ?? iconTheme.color ?? context.themeData.iconTheme.color;
|
||||
final iconColor = this.iconColor ?? iconTheme.color ?? context.themeData.iconTheme.color;
|
||||
final textColor = context.themeData.textTheme.labelLarge?.color;
|
||||
|
||||
if (menuItem) {
|
||||
|
|
|
|||
|
|
@ -17,8 +17,7 @@ class CastActionButton extends ConsumerWidget {
|
|||
|
||||
return BaseActionButton(
|
||||
iconData: isCasting ? Icons.cast_connected_rounded : Icons.cast_rounded,
|
||||
iconColor:
|
||||
isCasting ? context.primaryColor : null, // null = default color
|
||||
iconColor: isCasting ? context.primaryColor : null, // null = default color
|
||||
label: "cast".t(context: context),
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
|
|
|
|||
|
|
@ -35,9 +35,7 @@ class DeleteLocalActionButton extends ConsumerWidget {
|
|||
if (context.mounted) {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg: result.success
|
||||
? successMessage
|
||||
: 'scaffold_body_error_occurred'.t(context: context),
|
||||
msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context),
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
toastType: result.success ? ToastType.success : ToastType.error,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -20,8 +20,7 @@ class DeletePermanentActionButton extends ConsumerWidget {
|
|||
return;
|
||||
}
|
||||
|
||||
final result =
|
||||
await ref.read(actionProvider.notifier).deleteRemoteAndLocal(source);
|
||||
final result = await ref.read(actionProvider.notifier).deleteRemoteAndLocal(source);
|
||||
ref.read(multiSelectProvider.notifier).reset();
|
||||
|
||||
if (source == ActionSource.viewer) {
|
||||
|
|
@ -36,9 +35,7 @@ class DeletePermanentActionButton extends ConsumerWidget {
|
|||
if (context.mounted) {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg: result.success
|
||||
? successMessage
|
||||
: 'scaffold_body_error_occurred'.t(context: context),
|
||||
msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context),
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
toastType: result.success ? ToastType.success : ToastType.error,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -17,8 +17,7 @@ class DeleteTrashActionButton extends ConsumerWidget {
|
|||
return;
|
||||
}
|
||||
|
||||
final result =
|
||||
await ref.read(actionProvider.notifier).deleteRemoteAndLocal(source);
|
||||
final result = await ref.read(actionProvider.notifier).deleteRemoteAndLocal(source);
|
||||
ref.read(multiSelectProvider.notifier).reset();
|
||||
|
||||
final successMessage = 'assets_permanently_deleted_count'.t(
|
||||
|
|
@ -29,9 +28,7 @@ class DeleteTrashActionButton extends ConsumerWidget {
|
|||
if (context.mounted) {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg: result.success
|
||||
? successMessage
|
||||
: 'scaffold_body_error_occurred'.t(context: context),
|
||||
msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context),
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
toastType: result.success ? ToastType.success : ToastType.error,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -35,8 +35,7 @@ class DownloadActionButton extends ConsumerWidget {
|
|||
} else if (result.count > 0) {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg: 'download_action_prompt'
|
||||
.t(context: context, args: {'count': result.count.toString()}),
|
||||
msg: 'download_action_prompt'.t(context: context, args: {'count': result.count.toString()}),
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
toastType: ToastType.success,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -18,8 +18,7 @@ class EditLocationActionButton extends ConsumerWidget {
|
|||
return;
|
||||
}
|
||||
|
||||
final result =
|
||||
await ref.read(actionProvider.notifier).editLocation(source, context);
|
||||
final result = await ref.read(actionProvider.notifier).editLocation(source, context);
|
||||
if (result == null) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -34,9 +33,7 @@ class EditLocationActionButton extends ConsumerWidget {
|
|||
if (context.mounted) {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg: result.success
|
||||
? successMessage
|
||||
: 'scaffold_body_error_occurred'.t(context: context),
|
||||
msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context),
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
toastType: result.success ? ToastType.success : ToastType.error,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -39,9 +39,7 @@ class FavoriteActionButton extends ConsumerWidget {
|
|||
if (context.mounted) {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg: result.success
|
||||
? successMessage
|
||||
: 'scaffold_body_error_occurred'.t(context: context),
|
||||
msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context),
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
toastType: result.success ? ToastType.success : ToastType.error,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -14,9 +14,7 @@ class MotionPhotoActionButton extends ConsumerWidget {
|
|||
final isPlaying = ref.watch(isPlayingMotionVideoProvider);
|
||||
|
||||
return BaseActionButton(
|
||||
iconData: isPlaying
|
||||
? Icons.motion_photos_pause_outlined
|
||||
: Icons.play_circle_outline_rounded,
|
||||
iconData: isPlaying ? Icons.motion_photos_pause_outlined : Icons.play_circle_outline_rounded,
|
||||
label: "play_motion_photo".t(context: context),
|
||||
onPressed: ref.read(isPlayingMotionVideoProvider.notifier).toggle,
|
||||
menuItem: menuItem,
|
||||
|
|
|
|||
|
|
@ -20,8 +20,7 @@ class MoveToLockFolderActionButton extends ConsumerWidget {
|
|||
return;
|
||||
}
|
||||
|
||||
final result =
|
||||
await ref.read(actionProvider.notifier).moveToLockFolder(source);
|
||||
final result = await ref.read(actionProvider.notifier).moveToLockFolder(source);
|
||||
ref.read(multiSelectProvider.notifier).reset();
|
||||
|
||||
if (source == ActionSource.viewer) {
|
||||
|
|
@ -36,9 +35,7 @@ class MoveToLockFolderActionButton extends ConsumerWidget {
|
|||
if (context.mounted) {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg: result.success
|
||||
? successMessage
|
||||
: 'scaffold_body_error_occurred'.t(context: context),
|
||||
msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context),
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
toastType: result.success ? ToastType.success : ToastType.error,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -23,9 +23,7 @@ class RemoveFromAlbumActionButton extends ConsumerWidget {
|
|||
return;
|
||||
}
|
||||
|
||||
final result = await ref
|
||||
.read(actionProvider.notifier)
|
||||
.removeFromAlbum(source, albumId);
|
||||
final result = await ref.read(actionProvider.notifier).removeFromAlbum(source, albumId);
|
||||
ref.read(multiSelectProvider.notifier).reset();
|
||||
|
||||
final successMessage = 'remove_from_album_action_prompt'.t(
|
||||
|
|
@ -36,9 +34,7 @@ class RemoveFromAlbumActionButton extends ConsumerWidget {
|
|||
if (context.mounted) {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg: result.success
|
||||
? successMessage
|
||||
: 'scaffold_body_error_occurred'.t(context: context),
|
||||
msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context),
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
toastType: result.success ? ToastType.success : ToastType.error,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -18,8 +18,7 @@ class RemoveFromLockFolderActionButton extends ConsumerWidget {
|
|||
return;
|
||||
}
|
||||
|
||||
final result =
|
||||
await ref.read(actionProvider.notifier).removeFromLockFolder(source);
|
||||
final result = await ref.read(actionProvider.notifier).removeFromLockFolder(source);
|
||||
ref.read(multiSelectProvider.notifier).reset();
|
||||
|
||||
final successMessage = 'remove_from_lock_folder_action_prompt'.t(
|
||||
|
|
@ -30,9 +29,7 @@ class RemoveFromLockFolderActionButton extends ConsumerWidget {
|
|||
if (context.mounted) {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg: result.success
|
||||
? successMessage
|
||||
: 'scaffold_body_error_occurred'.t(context: context),
|
||||
msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context),
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
toastType: result.success ? ToastType.success : ToastType.error,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -28,9 +28,7 @@ class RestoreTrashActionButton extends ConsumerWidget {
|
|||
if (context.mounted) {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg: result.success
|
||||
? successMessage
|
||||
: 'scaffold_body_error_occurred'.t(context: context),
|
||||
msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context),
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
toastType: result.success ? ToastType.success : ToastType.error,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -37,8 +37,7 @@ class ShareActionButton extends ConsumerWidget {
|
|||
} else if (result.count > 0) {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg: 'share_action_prompt'
|
||||
.t(context: context, args: {'count': result.count.toString()}),
|
||||
msg: 'share_action_prompt'.t(context: context, args: {'count': result.count.toString()}),
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
toastType: ToastType.success,
|
||||
);
|
||||
|
|
@ -48,8 +47,7 @@ class ShareActionButton extends ConsumerWidget {
|
|||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return BaseActionButton(
|
||||
iconData:
|
||||
Platform.isAndroid ? Icons.share_rounded : Icons.ios_share_rounded,
|
||||
iconData: Platform.isAndroid ? Icons.share_rounded : Icons.ios_share_rounded,
|
||||
label: 'share'.t(context: context),
|
||||
onPressed: () => _onTap(context, ref),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -24,8 +24,7 @@ class StackActionButton extends ConsumerWidget {
|
|||
throw Exception('User must be logged in to access stack action');
|
||||
}
|
||||
|
||||
final result =
|
||||
await ref.read(actionProvider.notifier).stack(user.id, source);
|
||||
final result = await ref.read(actionProvider.notifier).stack(user.id, source);
|
||||
ref.read(multiSelectProvider.notifier).reset();
|
||||
|
||||
final successMessage = 'stack_action_prompt'.t(
|
||||
|
|
@ -36,9 +35,7 @@ class StackActionButton extends ConsumerWidget {
|
|||
if (context.mounted) {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg: result.success
|
||||
? successMessage
|
||||
: 'scaffold_body_error_occurred'.t(context: context),
|
||||
msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context),
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
toastType: result.success ? ToastType.success : ToastType.error,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -35,9 +35,7 @@ class TrashActionButton extends ConsumerWidget {
|
|||
if (context.mounted) {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg: result.success
|
||||
? successMessage
|
||||
: 'scaffold_body_error_occurred'.t(context: context),
|
||||
msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context),
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
toastType: result.success ? ToastType.success : ToastType.error,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -29,9 +29,7 @@ class UnArchiveActionButton extends ConsumerWidget {
|
|||
if (context.mounted) {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg: result.success
|
||||
? successMessage
|
||||
: 'scaffold_body_error_occurred'.t(context: context),
|
||||
msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context),
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
toastType: result.success ? ToastType.success : ToastType.error,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -39,9 +39,7 @@ class UnFavoriteActionButton extends ConsumerWidget {
|
|||
if (context.mounted) {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg: result.success
|
||||
? successMessage
|
||||
: 'scaffold_body_error_occurred'.t(context: context),
|
||||
msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context),
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
toastType: result.success ? ToastType.success : ToastType.error,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -29,9 +29,7 @@ class UnStackActionButton extends ConsumerWidget {
|
|||
if (context.mounted) {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg: result.success
|
||||
? successMessage
|
||||
: 'scaffold_body_error_occurred'.t(context: context),
|
||||
msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context),
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
toastType: result.success ? ToastType.success : ToastType.error,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -28,9 +28,7 @@ class UploadActionButton extends ConsumerWidget {
|
|||
if (context.mounted) {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg: result.success
|
||||
? successMessage
|
||||
: 'scaffold_body_error_occurred'.t(context: context),
|
||||
msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context),
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
toastType: result.success ? ToastType.success : ToastType.error,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,8 +2,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
|
||||
|
||||
class StackChildrenNotifier
|
||||
extends AutoDisposeFamilyAsyncNotifier<List<RemoteAsset>, BaseAsset?> {
|
||||
class StackChildrenNotifier extends AutoDisposeFamilyAsyncNotifier<List<RemoteAsset>, BaseAsset?> {
|
||||
@override
|
||||
Future<List<RemoteAsset>> build(BaseAsset? asset) async {
|
||||
if (asset == null || asset is! RemoteAsset || asset.stackId == null) {
|
||||
|
|
@ -14,7 +13,7 @@ class StackChildrenNotifier
|
|||
}
|
||||
}
|
||||
|
||||
final stackChildrenNotifier = AsyncNotifierProvider.autoDispose
|
||||
.family<StackChildrenNotifier, List<RemoteAsset>, BaseAsset?>(
|
||||
final stackChildrenNotifier =
|
||||
AsyncNotifierProvider.autoDispose.family<StackChildrenNotifier, List<RemoteAsset>, BaseAsset?>(
|
||||
StackChildrenNotifier.new,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -14,8 +14,7 @@ class AssetStackRow extends ConsumerWidget {
|
|||
int opacity = ref.watch(
|
||||
assetViewerProvider.select((state) => state.backgroundOpacity),
|
||||
);
|
||||
final showControls =
|
||||
ref.watch(assetViewerProvider.select((s) => s.showingControls));
|
||||
final showControls = ref.watch(assetViewerProvider.select((s) => s.showingControls));
|
||||
|
||||
if (!showControls) {
|
||||
opacity = 0;
|
||||
|
|
@ -68,8 +67,7 @@ class _StackList extends ConsumerWidget {
|
|||
child: Container(
|
||||
height: 60,
|
||||
width: 60,
|
||||
decoration: index ==
|
||||
ref.watch(assetViewerProvider.select((s) => s.stackIndex))
|
||||
decoration: index == ref.watch(assetViewerProvider.select((s) => s.stackIndex))
|
||||
? const BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.all(Radius.circular(6)),
|
||||
|
|
|
|||
|
|
@ -120,12 +120,10 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
|||
super.dispose();
|
||||
}
|
||||
|
||||
bool get showingBottomSheet =>
|
||||
ref.read(assetViewerProvider.select((s) => s.showingBottomSheet));
|
||||
bool get showingBottomSheet => ref.read(assetViewerProvider.select((s) => s.showingBottomSheet));
|
||||
|
||||
Color get backgroundColor {
|
||||
final opacity =
|
||||
ref.read(assetViewerProvider.select((s) => s.backgroundOpacity));
|
||||
final opacity = ref.read(assetViewerProvider.select((s) => s.backgroundOpacity));
|
||||
return Colors.black.withAlpha(opacity);
|
||||
}
|
||||
|
||||
|
|
@ -139,9 +137,7 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
|||
// This is used to calculate the scale of the asset when the bottom sheet is showing.
|
||||
// It is a small increment to ensure that the asset is slightly zoomed in when the
|
||||
// bottom sheet is showing, which emulates the zoom effect.
|
||||
double get _getScaleForBottomSheet =>
|
||||
(viewController?.prevValue.scale ?? viewController?.value.scale ?? 1.0) +
|
||||
0.01;
|
||||
double get _getScaleForBottomSheet => (viewController?.prevValue.scale ?? viewController?.value.scale ?? 1.0) + 0.01;
|
||||
|
||||
double _getVerticalOffsetForBottomSheet(double extent) =>
|
||||
(context.height * extent) - (context.height * _kBottomSheetMinimumExtent);
|
||||
|
|
@ -235,8 +231,8 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
|||
void _onPageBuild(PhotoViewControllerBase controller) {
|
||||
viewController ??= controller;
|
||||
if (showingBottomSheet) {
|
||||
final verticalOffset = (context.height * bottomSheetController.size) -
|
||||
(context.height * _kBottomSheetMinimumExtent);
|
||||
final verticalOffset =
|
||||
(context.height * bottomSheetController.size) - (context.height * _kBottomSheetMinimumExtent);
|
||||
controller.position = Offset(0, -verticalOffset);
|
||||
}
|
||||
}
|
||||
|
|
@ -262,9 +258,8 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
|||
viewController = controller;
|
||||
dragDownPosition = details.localPosition;
|
||||
initialPhotoViewState = controller.value;
|
||||
final isZoomed =
|
||||
scaleStateController.scaleState == PhotoViewScaleState.zoomedIn ||
|
||||
scaleStateController.scaleState == PhotoViewScaleState.covering;
|
||||
final isZoomed = scaleStateController.scaleState == PhotoViewScaleState.zoomedIn ||
|
||||
scaleStateController.scaleState == PhotoViewScaleState.covering;
|
||||
if (!showingBottomSheet && isZoomed) {
|
||||
blockGestures = true;
|
||||
}
|
||||
|
|
@ -349,8 +344,7 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
|||
updatedScale = initialPhotoViewState.scale! * (1.0 - scaleReduction);
|
||||
}
|
||||
|
||||
final backgroundOpacity =
|
||||
(255 * (1.0 - (scaleReduction / dragRatio))).round();
|
||||
final backgroundOpacity = (255 * (1.0 - (scaleReduction / dragRatio))).round();
|
||||
|
||||
viewController?.updateMultiple(
|
||||
position: initialPhotoViewState.position + delta,
|
||||
|
|
@ -494,8 +488,7 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
|||
}
|
||||
|
||||
void _snapBottomSheet() {
|
||||
if (bottomSheetController.size > _kBottomSheetSnapExtent ||
|
||||
bottomSheetController.size < 0.4) {
|
||||
if (bottomSheetController.size > _kBottomSheetSnapExtent || bottomSheetController.size < 0.4) {
|
||||
return;
|
||||
}
|
||||
isSnapping = true;
|
||||
|
|
@ -514,8 +507,7 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
|||
BaseAsset asset = ref.read(timelineServiceProvider).getAsset(index);
|
||||
final stackChildren = ref.read(stackChildrenNotifier(asset)).valueOrNull;
|
||||
if (stackChildren != null && stackChildren.isNotEmpty) {
|
||||
asset = stackChildren
|
||||
.elementAt(ref.read(assetViewerProvider.select((s) => s.stackIndex)));
|
||||
asset = stackChildren.elementAt(ref.read(assetViewerProvider.select((s) => s.stackIndex)));
|
||||
}
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
|
|
@ -547,8 +539,7 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
|||
BaseAsset asset = ref.read(timelineServiceProvider).getAsset(index);
|
||||
final stackChildren = ref.read(stackChildrenNotifier(asset)).valueOrNull;
|
||||
if (stackChildren != null && stackChildren.isNotEmpty) {
|
||||
asset = stackChildren
|
||||
.elementAt(ref.read(assetViewerProvider.select((s) => s.stackIndex)));
|
||||
asset = stackChildren.elementAt(ref.read(assetViewerProvider.select((s) => s.stackIndex)));
|
||||
}
|
||||
|
||||
final isPlayingMotionVideo = ref.read(isPlayingMotionVideoProvider);
|
||||
|
|
@ -564,8 +555,7 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
|||
return PhotoViewGalleryPageOptions(
|
||||
key: ValueKey(asset.heroTag),
|
||||
imageProvider: getFullImageProvider(asset, size: size),
|
||||
heroAttributes:
|
||||
PhotoViewHeroAttributes(tag: '${asset.heroTag}_$heroOffset'),
|
||||
heroAttributes: PhotoViewHeroAttributes(tag: '${asset.heroTag}_$heroOffset'),
|
||||
filterQuality: FilterQuality.high,
|
||||
tightMode: true,
|
||||
initialScale: PhotoViewComputedScale.contained * 0.999,
|
||||
|
|
@ -600,8 +590,7 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
|||
onDragUpdate: _onDragUpdate,
|
||||
onDragEnd: _onDragEnd,
|
||||
onTapDown: _onTapDown,
|
||||
heroAttributes:
|
||||
PhotoViewHeroAttributes(tag: '${asset.heroTag}_$heroOffset'),
|
||||
heroAttributes: PhotoViewHeroAttributes(tag: '${asset.heroTag}_$heroOffset'),
|
||||
filterQuality: FilterQuality.high,
|
||||
initialScale: PhotoViewComputedScale.contained * 0.99,
|
||||
maxScale: 1.0,
|
||||
|
|
@ -615,8 +604,7 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
|||
asset: asset,
|
||||
image: Image(
|
||||
key: ValueKey(asset),
|
||||
image:
|
||||
getFullImageProvider(asset, size: Size(ctx.width, ctx.height)),
|
||||
image: getFullImageProvider(asset, size: Size(ctx.width, ctx.height)),
|
||||
fit: BoxFit.contain,
|
||||
height: ctx.height,
|
||||
width: ctx.width,
|
||||
|
|
@ -641,8 +629,7 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
|||
ref.watch(isPlayingMotionVideoProvider);
|
||||
|
||||
// Listen for casting changes and send initial asset to the cast provider
|
||||
ref.listen(castProvider.select((value) => value.isCasting),
|
||||
(_, isCasting) async {
|
||||
ref.listen(castProvider.select((value) => value.isCasting), (_, isCasting) async {
|
||||
if (!isCasting) return;
|
||||
|
||||
final asset = ref.read(currentAssetNotifier);
|
||||
|
|
|
|||
|
|
@ -108,7 +108,6 @@ class AssetViewerStateNotifier extends AutoDisposeNotifier<AssetViewerState> {
|
|||
}
|
||||
}
|
||||
|
||||
final assetViewerProvider =
|
||||
AutoDisposeNotifierProvider<AssetViewerStateNotifier, AssetViewerState>(
|
||||
final assetViewerProvider = AutoDisposeNotifierProvider<AssetViewerStateNotifier, AssetViewerState>(
|
||||
AssetViewerStateNotifier.new,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -29,8 +29,7 @@ class ViewerBottomBar extends ConsumerWidget {
|
|||
int opacity = ref.watch(
|
||||
assetViewerProvider.select((state) => state.backgroundOpacity),
|
||||
);
|
||||
final showControls =
|
||||
ref.watch(assetViewerProvider.select((s) => s.showingControls));
|
||||
final showControls = ref.watch(assetViewerProvider.select((s) => s.showingControls));
|
||||
|
||||
if (!showControls) {
|
||||
opacity = 0;
|
||||
|
|
@ -38,10 +37,8 @@ class ViewerBottomBar extends ConsumerWidget {
|
|||
|
||||
final actions = <Widget>[
|
||||
const ShareActionButton(source: ActionSource.viewer),
|
||||
if (asset.isLocalOnly)
|
||||
const UploadActionButton(source: ActionSource.viewer),
|
||||
if (asset.hasRemote && isOwner)
|
||||
const ArchiveActionButton(source: ActionSource.viewer),
|
||||
if (asset.isLocalOnly) const UploadActionButton(source: ActionSource.viewer),
|
||||
if (asset.hasRemote && isOwner) const ArchiveActionButton(source: ActionSource.viewer),
|
||||
];
|
||||
|
||||
return IgnorePointer(
|
||||
|
|
@ -55,11 +52,9 @@ class ViewerBottomBar extends ConsumerWidget {
|
|||
? const SizedBox.shrink()
|
||||
: Theme(
|
||||
data: context.themeData.copyWith(
|
||||
iconTheme:
|
||||
const IconThemeData(size: 22, color: Colors.white),
|
||||
iconTheme: const IconThemeData(size: 22, color: Colors.white),
|
||||
textTheme: context.themeData.textTheme.copyWith(
|
||||
labelLarge:
|
||||
context.themeData.textTheme.labelLarge?.copyWith(
|
||||
labelLarge: context.themeData.textTheme.labelLarge?.copyWith(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -52,8 +52,7 @@ class AssetDetailBottomSheet extends ConsumerWidget {
|
|||
if (asset.hasRemote) ...[
|
||||
const ShareLinkActionButton(source: ActionSource.viewer),
|
||||
const ArchiveActionButton(source: ActionSource.viewer),
|
||||
if (!asset.hasLocal)
|
||||
const DownloadActionButton(source: ActionSource.viewer),
|
||||
if (!asset.hasLocal) const DownloadActionButton(source: ActionSource.viewer),
|
||||
isTrashEnable
|
||||
? const TrashActionButton(source: ActionSource.viewer)
|
||||
: const DeletePermanentActionButton(source: ActionSource.viewer),
|
||||
|
|
@ -100,18 +99,14 @@ class _AssetDetailBottomSheet extends ConsumerWidget {
|
|||
String _getFileInfo(BaseAsset asset, ExifInfo? exifInfo) {
|
||||
final height = asset.height ?? exifInfo?.height;
|
||||
final width = asset.width ?? exifInfo?.width;
|
||||
final resolution = (width != null && height != null)
|
||||
? "${width.toInt()} x ${height.toInt()}"
|
||||
: null;
|
||||
final fileSize =
|
||||
exifInfo?.fileSize != null ? formatBytes(exifInfo!.fileSize!) : null;
|
||||
final resolution = (width != null && height != null) ? "${width.toInt()} x ${height.toInt()}" : null;
|
||||
final fileSize = exifInfo?.fileSize != null ? formatBytes(exifInfo!.fileSize!) : null;
|
||||
|
||||
return switch ((fileSize, resolution)) {
|
||||
(null, null) => '',
|
||||
(String fileSize, null) => fileSize,
|
||||
(null, String resolution) => resolution,
|
||||
(String fileSize, String resolution) =>
|
||||
'$fileSize$_kSeparator$resolution',
|
||||
(String fileSize, String resolution) => '$fileSize$_kSeparator$resolution',
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -133,17 +128,12 @@ class _AssetDetailBottomSheet extends ConsumerWidget {
|
|||
return null;
|
||||
}
|
||||
|
||||
final fNumber =
|
||||
exifInfo.fNumber.isNotEmpty ? 'ƒ/${exifInfo.fNumber}' : null;
|
||||
final exposureTime =
|
||||
exifInfo.exposureTime.isNotEmpty ? exifInfo.exposureTime : null;
|
||||
final focalLength =
|
||||
exifInfo.focalLength.isNotEmpty ? '${exifInfo.focalLength} mm' : null;
|
||||
final fNumber = exifInfo.fNumber.isNotEmpty ? 'ƒ/${exifInfo.fNumber}' : null;
|
||||
final exposureTime = exifInfo.exposureTime.isNotEmpty ? exifInfo.exposureTime : null;
|
||||
final focalLength = exifInfo.focalLength.isNotEmpty ? '${exifInfo.focalLength} mm' : null;
|
||||
final iso = exifInfo.iso != null ? 'ISO ${exifInfo.iso}' : null;
|
||||
|
||||
return [fNumber, exposureTime, focalLength, iso]
|
||||
.where((spec) => spec != null && spec.isNotEmpty)
|
||||
.join(_kSeparator);
|
||||
return [fNumber, exposureTime, focalLength, iso].where((spec) => spec != null && spec.isNotEmpty).join(_kSeparator);
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
|||
|
|
@ -71,17 +71,13 @@ class _SheetLocationDetailsState extends ConsumerState<SheetLocationDetails> {
|
|||
final hasCoordinates = exifInfo?.hasCoordinates ?? false;
|
||||
|
||||
// Guard no lat/lng
|
||||
if (!hasCoordinates ||
|
||||
(asset != null && asset is LocalAsset && asset!.hasRemote)) {
|
||||
if (!hasCoordinates || (asset != null && asset is LocalAsset && asset!.hasRemote)) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
final remoteId = asset is LocalAsset
|
||||
? (asset as LocalAsset).remoteId
|
||||
: (asset as RemoteAsset).id;
|
||||
final remoteId = asset is LocalAsset ? (asset as LocalAsset).remoteId : (asset as RemoteAsset).id;
|
||||
final locationName = _getLocationName(exifInfo);
|
||||
final coordinates =
|
||||
"${exifInfo!.latitude!.toStringAsFixed(4)}, ${exifInfo!.longitude!.toStringAsFixed(4)}";
|
||||
final coordinates = "${exifInfo!.latitude!.toStringAsFixed(4)}, ${exifInfo!.longitude!.toStringAsFixed(4)}";
|
||||
|
||||
return Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
|
|
|
|||
|
|
@ -30,13 +30,11 @@ class ViewerTopAppBar extends ConsumerWidget implements PreferredSizeWidget {
|
|||
final isOwner = asset is RemoteAsset && asset.ownerId == user?.id;
|
||||
final isInLockedView = ref.watch(inLockedViewProvider);
|
||||
|
||||
final isShowingSheet = ref
|
||||
.watch(assetViewerProvider.select((state) => state.showingBottomSheet));
|
||||
final isShowingSheet = ref.watch(assetViewerProvider.select((state) => state.showingBottomSheet));
|
||||
int opacity = ref.watch(
|
||||
assetViewerProvider.select((state) => state.backgroundOpacity),
|
||||
);
|
||||
final showControls =
|
||||
ref.watch(assetViewerProvider.select((s) => s.showingControls));
|
||||
final showControls = ref.watch(assetViewerProvider.select((s) => s.showingControls));
|
||||
|
||||
if (!showControls) {
|
||||
opacity = 0;
|
||||
|
|
@ -45,8 +43,7 @@ class ViewerTopAppBar extends ConsumerWidget implements PreferredSizeWidget {
|
|||
final isCasting = ref.watch(
|
||||
castProvider.select((c) => c.isCasting),
|
||||
);
|
||||
final websocketConnected =
|
||||
ref.watch(websocketProvider.select((c) => c.isConnected));
|
||||
final websocketConnected = ref.watch(websocketProvider.select((c) => c.isConnected));
|
||||
|
||||
final actions = <Widget>[
|
||||
if (isCasting || (asset.hasRemote && websocketConnected))
|
||||
|
|
@ -78,8 +75,7 @@ class ViewerTopAppBar extends ConsumerWidget implements PreferredSizeWidget {
|
|||
opacity: opacity / 255,
|
||||
duration: Durations.short2,
|
||||
child: AppBar(
|
||||
backgroundColor:
|
||||
isShowingSheet ? Colors.transparent : Colors.black.withAlpha(125),
|
||||
backgroundColor: isShowingSheet ? Colors.transparent : Colors.black.withAlpha(125),
|
||||
leading: const _AppBarBackButton(),
|
||||
iconTheme: const IconThemeData(size: 22, color: Colors.white),
|
||||
actionsIconTheme: const IconThemeData(size: 22, color: Colors.white),
|
||||
|
|
@ -117,12 +113,9 @@ class _AppBarBackButton extends ConsumerWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final isShowingSheet = ref
|
||||
.watch(assetViewerProvider.select((state) => state.showingBottomSheet));
|
||||
final backgroundColor =
|
||||
isShowingSheet && !context.isDarkTheme ? Colors.white : Colors.black;
|
||||
final foregroundColor =
|
||||
isShowingSheet && !context.isDarkTheme ? Colors.black : Colors.white;
|
||||
final isShowingSheet = ref.watch(assetViewerProvider.select((state) => state.showingBottomSheet));
|
||||
final backgroundColor = isShowingSheet && !context.isDarkTheme ? Colors.white : Colors.black;
|
||||
final foregroundColor = isShowingSheet && !context.isDarkTheme ? Colors.black : Colors.white;
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 12.0),
|
||||
|
|
|
|||
|
|
@ -92,9 +92,7 @@ class NativeVideoViewer extends HookConsumerWidget {
|
|||
|
||||
try {
|
||||
if (asset.hasLocal && asset.livePhotoVideoId == null) {
|
||||
final id = asset is LocalAsset
|
||||
? (asset as LocalAsset).id
|
||||
: (asset as RemoteAsset).localId!;
|
||||
final id = asset is LocalAsset ? (asset as LocalAsset).id : (asset as RemoteAsset).localId!;
|
||||
final file = await const StorageRepository().getFileForAsset(id);
|
||||
if (file == null) {
|
||||
throw Exception('No file found for the video');
|
||||
|
|
@ -111,10 +109,8 @@ class NativeVideoViewer extends HookConsumerWidget {
|
|||
|
||||
// Use a network URL for the video player controller
|
||||
final serverEndpoint = Store.get(StoreKey.serverEndpoint);
|
||||
final isOriginalVideo =
|
||||
ref.read(settingsProvider).get<bool>(Setting.loadOriginalVideo);
|
||||
final String postfixUrl =
|
||||
isOriginalVideo ? 'original' : 'video/playback';
|
||||
final isOriginalVideo = ref.read(settingsProvider).get<bool>(Setting.loadOriginalVideo);
|
||||
final String postfixUrl = isOriginalVideo ? 'original' : 'video/playback';
|
||||
final String videoUrl = asset.livePhotoVideoId != null
|
||||
? '$serverEndpoint/assets/${asset.livePhotoVideoId}/$postfixUrl'
|
||||
: '$serverEndpoint/assets/$remoteId/$postfixUrl';
|
||||
|
|
@ -142,8 +138,7 @@ class NativeVideoViewer extends HookConsumerWidget {
|
|||
}
|
||||
|
||||
try {
|
||||
aspectRatio.value =
|
||||
await ref.read(assetServiceProvider).getAspectRatio(asset);
|
||||
aspectRatio.value = await ref.read(assetServiceProvider).getAspectRatio(asset);
|
||||
} catch (error) {
|
||||
log.severe(
|
||||
'Error getting aspect ratio for asset ${asset.name}: $error',
|
||||
|
|
@ -159,8 +154,7 @@ class NativeVideoViewer extends HookConsumerWidget {
|
|||
}
|
||||
|
||||
final videoPlayback = ref.read(videoPlaybackValueProvider);
|
||||
if ((isBuffering.value ||
|
||||
videoPlayback.state == VideoPlaybackState.initializing) &&
|
||||
if ((isBuffering.value || videoPlayback.state == VideoPlaybackState.initializing) &&
|
||||
videoPlayback.state != VideoPlaybackState.buffering) {
|
||||
ref.read(videoPlaybackValueProvider.notifier).value =
|
||||
videoPlayback.copyWith(state: VideoPlaybackState.buffering);
|
||||
|
|
@ -219,8 +213,7 @@ class NativeVideoViewer extends HookConsumerWidget {
|
|||
return;
|
||||
}
|
||||
|
||||
final videoPlayback =
|
||||
VideoPlaybackValue.fromNativeController(videoController);
|
||||
final videoPlayback = VideoPlaybackValue.fromNativeController(videoController);
|
||||
ref.read(videoPlaybackValueProvider.notifier).value = videoPlayback;
|
||||
|
||||
if (ref.read(assetViewerProvider.select((s) => s.showingBottomSheet))) {
|
||||
|
|
@ -241,8 +234,7 @@ class NativeVideoViewer extends HookConsumerWidget {
|
|||
return;
|
||||
}
|
||||
|
||||
final videoPlayback =
|
||||
VideoPlaybackValue.fromNativeController(videoController);
|
||||
final videoPlayback = VideoPlaybackValue.fromNativeController(videoController);
|
||||
if (videoPlayback.state == VideoPlaybackState.playing) {
|
||||
// Sync with the controls playing
|
||||
WakelockPlus.enable();
|
||||
|
|
@ -251,8 +243,7 @@ class NativeVideoViewer extends HookConsumerWidget {
|
|||
WakelockPlus.disable();
|
||||
}
|
||||
|
||||
ref.read(videoPlaybackValueProvider.notifier).status =
|
||||
videoPlayback.state;
|
||||
ref.read(videoPlaybackValueProvider.notifier).status = videoPlayback.state;
|
||||
}
|
||||
|
||||
void onPlaybackPositionChanged() {
|
||||
|
|
@ -271,8 +262,7 @@ class NativeVideoViewer extends HookConsumerWidget {
|
|||
return;
|
||||
}
|
||||
|
||||
ref.read(videoPlaybackValueProvider.notifier).position =
|
||||
Duration(seconds: playbackInfo.position);
|
||||
ref.read(videoPlaybackValueProvider.notifier).position = Duration(seconds: playbackInfo.position);
|
||||
|
||||
// Check if the video is buffering
|
||||
if (playbackInfo.status == PlaybackStatus.playing) {
|
||||
|
|
@ -296,10 +286,8 @@ class NativeVideoViewer extends HookConsumerWidget {
|
|||
}
|
||||
|
||||
void removeListeners(NativeVideoPlayerController controller) {
|
||||
controller.onPlaybackPositionChanged
|
||||
.removeListener(onPlaybackPositionChanged);
|
||||
controller.onPlaybackStatusChanged
|
||||
.removeListener(onPlaybackStatusChanged);
|
||||
controller.onPlaybackPositionChanged.removeListener(onPlaybackPositionChanged);
|
||||
controller.onPlaybackStatusChanged.removeListener(onPlaybackStatusChanged);
|
||||
controller.onPlaybackReady.removeListener(onPlaybackReady);
|
||||
controller.onPlaybackEnded.removeListener(onPlaybackEnded);
|
||||
}
|
||||
|
|
@ -324,9 +312,7 @@ class NativeVideoViewer extends HookConsumerWidget {
|
|||
nc.loadVideoSource(source).catchError((error) {
|
||||
log.severe('Error loading video source: $error');
|
||||
});
|
||||
final loopVideo = ref
|
||||
.read(appSettingsServiceProvider)
|
||||
.getSetting<bool>(AppSettingsEnum.loopVideo);
|
||||
final loopVideo = ref.read(appSettingsServiceProvider).getSetting<bool>(AppSettingsEnum.loopVideo);
|
||||
nc.setLoop(!asset.isMotionPhoto && loopVideo);
|
||||
|
||||
controller.value = nc;
|
||||
|
|
|
|||
|
|
@ -23,15 +23,12 @@ class VideoViewerControls extends HookConsumerWidget {
|
|||
final assetIsVideo = ref.watch(
|
||||
currentAssetNotifier.select((asset) => asset != null && asset.isVideo),
|
||||
);
|
||||
bool showControls =
|
||||
ref.watch(assetViewerProvider.select((s) => s.showingControls));
|
||||
final showBottomSheet =
|
||||
ref.watch(assetViewerProvider.select((s) => s.showingBottomSheet));
|
||||
bool showControls = ref.watch(assetViewerProvider.select((s) => s.showingControls));
|
||||
final showBottomSheet = ref.watch(assetViewerProvider.select((s) => s.showingBottomSheet));
|
||||
if (showBottomSheet) {
|
||||
showControls = false;
|
||||
}
|
||||
final VideoPlaybackState state =
|
||||
ref.watch(videoPlaybackValueProvider.select((value) => value.state));
|
||||
final VideoPlaybackState state = ref.watch(videoPlaybackValueProvider.select((value) => value.state));
|
||||
|
||||
final cast = ref.watch(castProvider);
|
||||
|
||||
|
|
@ -45,15 +42,12 @@ class VideoViewerControls extends HookConsumerWidget {
|
|||
final state = ref.read(videoPlaybackValueProvider).state;
|
||||
|
||||
// Do not hide on paused
|
||||
if (state != VideoPlaybackState.paused &&
|
||||
state != VideoPlaybackState.completed &&
|
||||
assetIsVideo) {
|
||||
if (state != VideoPlaybackState.paused && state != VideoPlaybackState.completed && assetIsVideo) {
|
||||
ref.read(assetViewerProvider.notifier).setControls(false);
|
||||
}
|
||||
},
|
||||
);
|
||||
final showBuffering =
|
||||
state == VideoPlaybackState.buffering && !cast.isCasting;
|
||||
final showBuffering = state == VideoPlaybackState.buffering && !cast.isCasting;
|
||||
|
||||
/// Shows the controls and starts the timer to hide them
|
||||
void showControlsAndStartHideTimer() {
|
||||
|
|
@ -62,8 +56,7 @@ class VideoViewerControls extends HookConsumerWidget {
|
|||
}
|
||||
|
||||
// When we change position, show or hide timer
|
||||
ref.listen(videoPlayerControlsProvider.select((v) => v.position),
|
||||
(previous, next) {
|
||||
ref.listen(videoPlayerControlsProvider.select((v) => v.position), (previous, next) {
|
||||
showControlsAndStartHideTimer();
|
||||
});
|
||||
|
||||
|
|
@ -111,14 +104,13 @@ class VideoViewerControls extends HookConsumerWidget {
|
|||
)
|
||||
else
|
||||
GestureDetector(
|
||||
onTap: () =>
|
||||
ref.read(assetViewerProvider.notifier).setControls(false),
|
||||
onTap: () => ref.read(assetViewerProvider.notifier).setControls(false),
|
||||
child: CenterPlayButton(
|
||||
backgroundColor: Colors.black54,
|
||||
iconColor: Colors.white,
|
||||
isFinished: state == VideoPlaybackState.completed,
|
||||
isPlaying: state == VideoPlaybackState.playing ||
|
||||
(cast.isCasting && cast.castState == CastState.playing),
|
||||
isPlaying:
|
||||
state == VideoPlaybackState.playing || (cast.isCasting && cast.castState == CastState.playing),
|
||||
show: assetIsVideo && showControls,
|
||||
onPressed: togglePlay,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -21,8 +21,7 @@ class BackupToggleButton extends ConsumerStatefulWidget {
|
|||
ConsumerState<BackupToggleButton> createState() => BackupToggleButtonState();
|
||||
}
|
||||
|
||||
class BackupToggleButtonState extends ConsumerState<BackupToggleButton>
|
||||
with SingleTickerProviderStateMixin {
|
||||
class BackupToggleButtonState extends ConsumerState<BackupToggleButton> with SingleTickerProviderStateMixin {
|
||||
late AnimationController _animationController;
|
||||
late Animation<double> _gradientAnimation;
|
||||
bool _isEnabled = false;
|
||||
|
|
@ -42,9 +41,7 @@ class BackupToggleButtonState extends ConsumerState<BackupToggleButton>
|
|||
),
|
||||
);
|
||||
|
||||
_isEnabled = ref
|
||||
.read(appSettingsServiceProvider)
|
||||
.getSetting(AppSettingsEnum.enableBackup);
|
||||
_isEnabled = ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.enableBackup);
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -54,9 +51,7 @@ class BackupToggleButtonState extends ConsumerState<BackupToggleButton>
|
|||
}
|
||||
|
||||
Future<void> _onToggle(bool value) async {
|
||||
await ref
|
||||
.read(appSettingsServiceProvider)
|
||||
.setSetting(AppSettingsEnum.enableBackup, value);
|
||||
await ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.enableBackup, value);
|
||||
|
||||
setState(() {
|
||||
_isEnabled = value;
|
||||
|
|
@ -141,8 +136,7 @@ class BackupToggleButtonState extends ConsumerState<BackupToggleButton>
|
|||
borderRadius: const BorderRadius.all(Radius.circular(20.5)),
|
||||
onTap: () => isCanceling ? null : _onToggle(!_isEnabled),
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
|
|
@ -180,8 +174,7 @@ class BackupToggleButtonState extends ConsumerState<BackupToggleButton>
|
|||
children: [
|
||||
Text(
|
||||
"enable_backup".t(context: context),
|
||||
style:
|
||||
context.textTheme.titleMedium?.copyWith(
|
||||
style: context.textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: context.primaryColor,
|
||||
),
|
||||
|
|
@ -214,9 +207,7 @@ class BackupToggleButtonState extends ConsumerState<BackupToggleButton>
|
|||
height: 18,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
backgroundColor: context
|
||||
.colorScheme.onSurface
|
||||
.withValues(alpha: 0.2),
|
||||
backgroundColor: context.colorScheme.onSurface.withValues(alpha: 0.2),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
@ -226,8 +217,7 @@ class BackupToggleButtonState extends ConsumerState<BackupToggleButton>
|
|||
),
|
||||
Switch.adaptive(
|
||||
value: _isEnabled,
|
||||
onChanged: (value) =>
|
||||
isCanceling ? null : _onToggle(value),
|
||||
onChanged: (value) => isCanceling ? null : _onToggle(value),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -31,12 +31,10 @@ class BaseBottomSheet extends ConsumerStatefulWidget {
|
|||
});
|
||||
|
||||
@override
|
||||
ConsumerState<BaseBottomSheet> createState() =>
|
||||
_BaseDraggableScrollableSheetState();
|
||||
ConsumerState<BaseBottomSheet> createState() => _BaseDraggableScrollableSheetState();
|
||||
}
|
||||
|
||||
class _BaseDraggableScrollableSheetState
|
||||
extends ConsumerState<BaseBottomSheet> {
|
||||
class _BaseDraggableScrollableSheetState extends ConsumerState<BaseBottomSheet> {
|
||||
late DraggableScrollableController _controller;
|
||||
|
||||
@override
|
||||
|
|
@ -71,8 +69,7 @@ class _BaseDraggableScrollableSheetState
|
|||
shouldCloseOnMinExtent: widget.shouldCloseOnMinExtent,
|
||||
builder: (BuildContext context, ScrollController scrollController) {
|
||||
return Card(
|
||||
color: widget.backgroundColor ??
|
||||
context.colorScheme.surfaceContainerHigh,
|
||||
color: widget.backgroundColor ?? context.colorScheme.surfaceContainerHigh,
|
||||
borderOnForeground: false,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
elevation: 6.0,
|
||||
|
|
|
|||
|
|
@ -71,5 +71,4 @@ ImageProvider getThumbnailImageProvider({
|
|||
}
|
||||
|
||||
bool _shouldUseLocalAsset(BaseAsset asset) =>
|
||||
asset.hasLocal &&
|
||||
(!asset.hasRemote || !AppSetting.get(Setting.preferRemoteImage));
|
||||
asset.hasLocal && (!asset.hasRemote || !AppSetting.get(Setting.preferRemoteImage));
|
||||
|
|
|
|||
|
|
@ -17,8 +17,7 @@ import 'package:immich_mobile/providers/image/exceptions/image_loading_exception
|
|||
import 'package:logging/logging.dart';
|
||||
|
||||
class LocalThumbProvider extends ImageProvider<LocalThumbProvider> {
|
||||
final AssetMediaRepository _assetMediaRepository =
|
||||
const AssetMediaRepository();
|
||||
final AssetMediaRepository _assetMediaRepository = const AssetMediaRepository();
|
||||
final CacheManager? cacheManager;
|
||||
|
||||
final String id;
|
||||
|
|
@ -63,20 +62,17 @@ class LocalThumbProvider extends ImageProvider<LocalThumbProvider> {
|
|||
CacheManager cache,
|
||||
ImageDecoderCallback decode,
|
||||
) async {
|
||||
final cacheKey =
|
||||
'${key.id}-${key.updatedAt}-${key.size.width}x${key.size.height}';
|
||||
final cacheKey = '${key.id}-${key.updatedAt}-${key.size.width}x${key.size.height}';
|
||||
|
||||
final fileFromCache = await cache.getFileFromCache(cacheKey);
|
||||
if (fileFromCache != null) {
|
||||
try {
|
||||
final buffer =
|
||||
await ImmutableBuffer.fromFilePath(fileFromCache.file.path);
|
||||
final buffer = await ImmutableBuffer.fromFilePath(fileFromCache.file.path);
|
||||
return decode(buffer);
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
final thumbnailBytes =
|
||||
await _assetMediaRepository.getThumbnail(key.id, size: key.size);
|
||||
final thumbnailBytes = await _assetMediaRepository.getThumbnail(key.id, size: key.size);
|
||||
if (thumbnailBytes == null) {
|
||||
PaintingBinding.instance.imageCache.evict(key);
|
||||
throw StateError(
|
||||
|
|
@ -103,8 +99,7 @@ class LocalThumbProvider extends ImageProvider<LocalThumbProvider> {
|
|||
}
|
||||
|
||||
class LocalFullImageProvider extends ImageProvider<LocalFullImageProvider> {
|
||||
final AssetMediaRepository _assetMediaRepository =
|
||||
const AssetMediaRepository();
|
||||
final AssetMediaRepository _assetMediaRepository = const AssetMediaRepository();
|
||||
final StorageRepository _storageRepository = const StorageRepository();
|
||||
|
||||
final String id;
|
||||
|
|
@ -160,8 +155,7 @@ class LocalFullImageProvider extends ImageProvider<LocalFullImageProvider> {
|
|||
throw StateError('Unsupported asset type ${key.type}');
|
||||
}
|
||||
} catch (error, stack) {
|
||||
Logger('ImmichLocalImageProvider')
|
||||
.severe('Error loading local image ${key.name}', error, stack);
|
||||
Logger('ImmichLocalImageProvider').severe('Error loading local image ${key.name}', error, stack);
|
||||
throw const ImageLoadingException(
|
||||
'Could not load image from local storage',
|
||||
);
|
||||
|
|
@ -172,8 +166,7 @@ class LocalFullImageProvider extends ImageProvider<LocalFullImageProvider> {
|
|||
LocalFullImageProvider key,
|
||||
ImageDecoderCallback decode,
|
||||
) async {
|
||||
final thumbBytes =
|
||||
await _assetMediaRepository.getThumbnail(key.id, size: key.size);
|
||||
final thumbBytes = await _assetMediaRepository.getThumbnail(key.id, size: key.size);
|
||||
if (thumbBytes == null) {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -191,8 +184,7 @@ class LocalFullImageProvider extends ImageProvider<LocalFullImageProvider> {
|
|||
}
|
||||
|
||||
final fileSize = await file.length();
|
||||
final devicePixelRatio =
|
||||
PlatformDispatcher.instance.views.first.devicePixelRatio;
|
||||
final devicePixelRatio = PlatformDispatcher.instance.views.first.devicePixelRatio;
|
||||
final isLargeFile = fileSize > 20 * 1024 * 1024; // 20MB
|
||||
final isHEIC = file.path.toLowerCase().contains(RegExp(r'\.(heic|heif)$'));
|
||||
final isProgressive = isLargeFile || (isHEIC && !Platform.isIOS);
|
||||
|
|
@ -204,8 +196,7 @@ class LocalFullImageProvider extends ImageProvider<LocalFullImageProvider> {
|
|||
(key.size.width * progressiveMultiplier).clamp(256, 1024),
|
||||
(key.size.height * progressiveMultiplier).clamp(256, 1024),
|
||||
);
|
||||
final mediumThumb =
|
||||
await _assetMediaRepository.getThumbnail(key.id, size: size);
|
||||
final mediumThumb = await _assetMediaRepository.getThumbnail(key.id, size: size);
|
||||
if (mediumThumb != null) {
|
||||
final mediumBuffer = await ImmutableBuffer.fromUint8List(mediumThumb);
|
||||
yield await decode(mediumBuffer);
|
||||
|
|
@ -221,8 +212,7 @@ class LocalFullImageProvider extends ImageProvider<LocalFullImageProvider> {
|
|||
(key.size.width * progressiveMultiplier).clamp(512, 2048),
|
||||
(key.size.height * progressiveMultiplier).clamp(512, 2048),
|
||||
);
|
||||
final highThumb =
|
||||
await _assetMediaRepository.getThumbnail(key.id, size: size);
|
||||
final highThumb = await _assetMediaRepository.getThumbnail(key.id, size: size);
|
||||
if (highThumb != null) {
|
||||
final highBuffer = await ImmutableBuffer.fromUint8List(highThumb);
|
||||
yield await decode(highBuffer);
|
||||
|
|
@ -238,15 +228,11 @@ class LocalFullImageProvider extends ImageProvider<LocalFullImageProvider> {
|
|||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
if (other is LocalFullImageProvider) {
|
||||
return id == other.id &&
|
||||
size == other.size &&
|
||||
type == other.type &&
|
||||
name == other.name;
|
||||
return id == other.id && size == other.size && type == other.type && name == other.name;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
id.hashCode ^ size.hashCode ^ type.hashCode ^ name.hashCode;
|
||||
int get hashCode => id.hashCode ^ size.hashCode ^ type.hashCode ^ name.hashCode;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,10 +26,8 @@ class Thumbnail extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final thumbHash =
|
||||
asset is RemoteAsset ? (asset as RemoteAsset).thumbHash : null;
|
||||
final provider =
|
||||
getThumbnailImageProvider(asset: asset, remoteId: remoteId, size: size);
|
||||
final thumbHash = asset is RemoteAsset ? (asset as RemoteAsset).thumbHash : null;
|
||||
final provider = getThumbnailImageProvider(asset: asset, remoteId: remoteId, size: size);
|
||||
|
||||
return OctoImage.fromSet(
|
||||
image: provider,
|
||||
|
|
@ -72,8 +70,7 @@ OctoErrorBuilder _blurHashErrorBuilder(
|
|||
BoxFit? fit,
|
||||
}) =>
|
||||
(context, e, s) {
|
||||
Logger("ImThumbnail")
|
||||
.warning("Error loading thumbnail for ${asset?.name}", e, s);
|
||||
Logger("ImThumbnail").warning("Error loading thumbnail for ${asset?.name}", e, s);
|
||||
provider?.evict();
|
||||
return Stack(
|
||||
alignment: Alignment.center,
|
||||
|
|
|
|||
|
|
@ -28,9 +28,8 @@ class ThumbnailTile extends ConsumerWidget {
|
|||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final heroOffset = TabsRouterScope.of(context)?.controller.activeIndex ?? 0;
|
||||
|
||||
final assetContainerColor = context.isDarkTheme
|
||||
? context.primaryColor.darken(amount: 0.4)
|
||||
: context.primaryColor.lighten(amount: 0.75);
|
||||
final assetContainerColor =
|
||||
context.isDarkTheme ? context.primaryColor.darken(amount: 0.4) : context.primaryColor.lighten(amount: 0.75);
|
||||
|
||||
final isSelected = ref.watch(
|
||||
multiSelectProvider.select(
|
||||
|
|
@ -53,8 +52,7 @@ class ThumbnailTile extends ConsumerWidget {
|
|||
)
|
||||
: const BoxDecoration();
|
||||
|
||||
final hasStack =
|
||||
asset is RemoteAsset && (asset as RemoteAsset).stackId != null;
|
||||
final hasStack = asset is RemoteAsset && (asset as RemoteAsset).stackId != null;
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
|
|
@ -63,9 +61,8 @@ class ThumbnailTile extends ConsumerWidget {
|
|||
curve: Curves.decelerate,
|
||||
decoration: borderStyle,
|
||||
child: ClipRRect(
|
||||
borderRadius: isSelected || lockSelection
|
||||
? const BorderRadius.all(Radius.circular(15.0))
|
||||
: BorderRadius.zero,
|
||||
borderRadius:
|
||||
isSelected || lockSelection ? const BorderRadius.all(Radius.circular(15.0)) : BorderRadius.zero,
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
|
|
@ -141,9 +138,7 @@ class ThumbnailTile extends ConsumerWidget {
|
|||
child: _SelectionIndicator(
|
||||
isSelected: isSelected,
|
||||
isLocked: lockSelection,
|
||||
color: lockSelection
|
||||
? context.colorScheme.surfaceContainerHighest
|
||||
: assetContainerColor,
|
||||
color: lockSelection ? context.colorScheme.surfaceContainerHighest : assetContainerColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -46,11 +46,9 @@ class DriftMemoryCard extends StatelessWidget {
|
|||
BoxFit fit = BoxFit.contain;
|
||||
if (asset.width != null && asset.height != null) {
|
||||
final aspectRatio = asset.width! / asset.height!;
|
||||
final phoneAspectRatio =
|
||||
constraints.maxWidth / constraints.maxHeight;
|
||||
final phoneAspectRatio = constraints.maxWidth / constraints.maxHeight;
|
||||
// Look for a 25% difference in either direction
|
||||
if (phoneAspectRatio * .75 < aspectRatio &&
|
||||
phoneAspectRatio * 1.25 > aspectRatio) {
|
||||
if (phoneAspectRatio * .75 < aspectRatio && phoneAspectRatio * 1.25 > aspectRatio) {
|
||||
// Cover to look nice if we have nearly the same aspect ratio
|
||||
fit = BoxFit.cover;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,8 +47,7 @@ class DriftMemoryLane extends ConsumerWidget {
|
|||
),
|
||||
);
|
||||
},
|
||||
children:
|
||||
memories.map((memory) => DriftMemoryCard(memory: memory)).toList(),
|
||||
children: memories.map((memory) => DriftMemoryCard(memory: memory)).toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,8 +13,7 @@ class PartnerUserAvatar extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final url =
|
||||
"${Store.get(StoreKey.serverEndpoint)}/users/${partner.id}/profile-image";
|
||||
final url = "${Store.get(StoreKey.serverEndpoint)}/users/${partner.id}/profile-image";
|
||||
final nameFirstLetter = partner.name.isNotEmpty ? partner.name[0] : "";
|
||||
return CircleAvatar(
|
||||
radius: 16,
|
||||
|
|
|
|||
|
|
@ -97,14 +97,12 @@ class DriftRemoteAlbumOption extends ConsumerWidget {
|
|||
ListTile(
|
||||
leading: Icon(
|
||||
Icons.delete,
|
||||
color:
|
||||
context.isDarkTheme ? Colors.red[400] : Colors.red[800],
|
||||
color: context.isDarkTheme ? Colors.red[400] : Colors.red[800],
|
||||
),
|
||||
title: Text(
|
||||
'delete_album'.t(context: context),
|
||||
style: textStyle.copyWith(
|
||||
color:
|
||||
context.isDarkTheme ? Colors.red[400] : Colors.red[800],
|
||||
color: context.isDarkTheme ? Colors.red[400] : Colors.red[800],
|
||||
),
|
||||
),
|
||||
onTap: onDeleteAlbum,
|
||||
|
|
|
|||
|
|
@ -90,8 +90,7 @@ class RenderFixedRow extends RenderBox
|
|||
}
|
||||
}
|
||||
|
||||
double get intrinsicWidth =>
|
||||
dimension * childCount + spacing * (childCount - 1);
|
||||
double get intrinsicWidth => dimension * childCount + spacing * (childCount - 1);
|
||||
|
||||
@override
|
||||
double computeMinIntrinsicWidth(double height) => intrinsicWidth;
|
||||
|
|
|
|||
|
|
@ -39,9 +39,7 @@ class FixedSegment extends Segment {
|
|||
@override
|
||||
double indexToLayoutOffset(int index) {
|
||||
final relativeIndex = index - gridIndex;
|
||||
return relativeIndex < 0
|
||||
? startOffset
|
||||
: gridOffset + (mainAxisExtend * relativeIndex);
|
||||
return relativeIndex < 0 ? startOffset : gridOffset + (mainAxisExtend * relativeIndex);
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -98,8 +96,7 @@ class _FixedSegmentRow extends ConsumerWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final isScrubbing =
|
||||
ref.watch(timelineStateProvider.select((s) => s.isScrubbing));
|
||||
final isScrubbing = ref.watch(timelineStateProvider.select((s) => s.isScrubbing));
|
||||
final timelineService = ref.read(timelineServiceProvider);
|
||||
|
||||
if (isScrubbing) {
|
||||
|
|
@ -215,11 +212,8 @@ class _AssetTileWidget extends ConsumerWidget {
|
|||
|
||||
return RepaintBoundary(
|
||||
child: GestureDetector(
|
||||
onTap: () => lockSelection
|
||||
? null
|
||||
: _handleOnTap(context, ref, assetIndex, asset),
|
||||
onLongPress: () =>
|
||||
lockSelection ? null : _handleOnLongPress(ref, asset),
|
||||
onTap: () => lockSelection ? null : _handleOnTap(context, ref, assetIndex, asset),
|
||||
onLongPress: () => lockSelection ? null : _handleOnLongPress(ref, asset),
|
||||
child: ThumbnailTile(
|
||||
asset,
|
||||
lockSelection: lockSelection,
|
||||
|
|
|
|||
|
|
@ -37,17 +37,13 @@ class FixedSegmentBuilder extends SegmentBuilder {
|
|||
GroupAssetsBy.month => HeaderType.month,
|
||||
GroupAssetsBy.day ||
|
||||
GroupAssetsBy.auto =>
|
||||
bucket is TimeBucket && bucket.date.month != previousDate?.month
|
||||
? HeaderType.monthAndDay
|
||||
: HeaderType.day,
|
||||
bucket is TimeBucket && bucket.date.month != previousDate?.month ? HeaderType.monthAndDay : HeaderType.day,
|
||||
GroupAssetsBy.none => HeaderType.none,
|
||||
};
|
||||
final headerExtent = SegmentBuilder.headerExtent(timelineHeader);
|
||||
|
||||
final segmentStartOffset = startOffset;
|
||||
startOffset += headerExtent +
|
||||
(tileHeight * numberOfRows) +
|
||||
spacing * (numberOfRows - 1);
|
||||
startOffset += headerExtent + (tileHeight * numberOfRows) + spacing * (numberOfRows - 1);
|
||||
final segmentEndOffset = startOffset;
|
||||
|
||||
segments.add(
|
||||
|
|
|
|||
|
|
@ -43,10 +43,8 @@ class TimelineHeader extends StatelessWidget {
|
|||
|
||||
final date = (bucket as TimeBucket).date;
|
||||
|
||||
final isMonthHeader =
|
||||
header == HeaderType.month || header == HeaderType.monthAndDay;
|
||||
final isDayHeader =
|
||||
header == HeaderType.day || header == HeaderType.monthAndDay;
|
||||
final isMonthHeader = header == HeaderType.month || header == HeaderType.monthAndDay;
|
||||
final isDayHeader = header == HeaderType.day || header == HeaderType.monthAndDay;
|
||||
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(
|
||||
|
|
@ -111,9 +109,7 @@ class _BulkSelectIconButton extends ConsumerWidget {
|
|||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
List<BaseAsset> bucketAssets;
|
||||
try {
|
||||
bucketAssets = ref
|
||||
.watch(timelineServiceProvider)
|
||||
.getAssets(assetOffset, bucket.assetCount);
|
||||
bucketAssets = ref.watch(timelineServiceProvider).getAssets(assetOffset, bucket.assetCount);
|
||||
} catch (e) {
|
||||
bucketAssets = <BaseAsset>[];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,15 +58,13 @@ List<_Segment> _buildSegments({
|
|||
DateTime? lastDate;
|
||||
double lastOffset = -offsetThreshold;
|
||||
for (final layoutSegment in layoutSegments) {
|
||||
final scrollPercentage =
|
||||
layoutSegment.startOffset / layoutSegments.last.endOffset;
|
||||
final scrollPercentage = layoutSegment.startOffset / layoutSegments.last.endOffset;
|
||||
final startOffset = scrollPercentage * timelineHeight;
|
||||
|
||||
final date = (layoutSegment.bucket as TimeBucket).date;
|
||||
final label = formatter.format(date);
|
||||
|
||||
final showSegment = lastOffset + offsetThreshold <= startOffset &&
|
||||
(lastDate == null || date.year != lastDate.year);
|
||||
final showSegment = lastOffset + offsetThreshold <= startOffset && (lastDate == null || date.year != lastDate.year);
|
||||
|
||||
segments.add(
|
||||
_Segment(
|
||||
|
|
@ -85,8 +83,7 @@ List<_Segment> _buildSegments({
|
|||
return segments;
|
||||
}
|
||||
|
||||
class ScrubberState extends ConsumerState<Scrubber>
|
||||
with TickerProviderStateMixin {
|
||||
class ScrubberState extends ConsumerState<Scrubber> with TickerProviderStateMixin {
|
||||
double _thumbTopOffset = 0.0;
|
||||
bool _isDragging = false;
|
||||
List<_Segment> _segments = [];
|
||||
|
|
@ -98,17 +95,14 @@ class ScrubberState extends ConsumerState<Scrubber>
|
|||
late AnimationController _labelAnimationController;
|
||||
late Animation<double> _labelAnimation;
|
||||
|
||||
double get _scrubberHeight =>
|
||||
widget.timelineHeight - widget.topPadding - widget.bottomPadding;
|
||||
double get _scrubberHeight => widget.timelineHeight - widget.topPadding - widget.bottomPadding;
|
||||
|
||||
late ScrollController _scrollController;
|
||||
|
||||
double get _currentOffset {
|
||||
if (_scrollController.hasClients != true) return 0.0;
|
||||
|
||||
return _scrollController.offset *
|
||||
_scrubberHeight /
|
||||
_scrollController.position.maxScrollExtent;
|
||||
return _scrollController.offset * _scrubberHeight / _scrollController.position.maxScrollExtent;
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -148,8 +142,7 @@ class ScrubberState extends ConsumerState<Scrubber>
|
|||
void didUpdateWidget(covariant Scrubber oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
|
||||
if (oldWidget.layoutSegments.lastOrNull?.endOffset !=
|
||||
widget.layoutSegments.lastOrNull?.endOffset) {
|
||||
if (oldWidget.layoutSegments.lastOrNull?.endOffset != widget.layoutSegments.lastOrNull?.endOffset) {
|
||||
_segments = _buildSegments(
|
||||
layoutSegments: widget.layoutSegments,
|
||||
timelineHeight: _scrubberHeight,
|
||||
|
|
@ -179,8 +172,7 @@ class ScrubberState extends ConsumerState<Scrubber>
|
|||
return false;
|
||||
}
|
||||
|
||||
if (notification is ScrollStartNotification ||
|
||||
notification is ScrollUpdateNotification) {
|
||||
if (notification is ScrollStartNotification || notification is ScrollUpdateNotification) {
|
||||
ref.read(timelineStateProvider.notifier).setScrolling(true);
|
||||
} else if (notification is ScrollEndNotification) {
|
||||
ref.read(timelineStateProvider.notifier).setScrolling(false);
|
||||
|
|
@ -287,8 +279,7 @@ class ScrubberState extends ConsumerState<Scrubber>
|
|||
return widget.layoutSegments.indexWhere(
|
||||
(layoutSegment) {
|
||||
final bucket = layoutSegment.bucket as TimeBucket;
|
||||
return bucket.date.year == segment.date.year &&
|
||||
bucket.date.month == segment.date.month;
|
||||
return bucket.date.year == segment.date.year && bucket.date.month == segment.date.month;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
@ -299,10 +290,7 @@ class ScrubberState extends ConsumerState<Scrubber>
|
|||
final viewportHeight = _scrollController.position.viewportDimension;
|
||||
|
||||
final targetScrollOffset = layoutSegment.startOffset;
|
||||
final centeredOffset = targetScrollOffset -
|
||||
(viewportHeight / 4) +
|
||||
100 +
|
||||
(widget.monthSegmentSnappingOffset ?? 0.0);
|
||||
final centeredOffset = targetScrollOffset - (viewportHeight / 4) + 100 + (widget.monthSegmentSnappingOffset ?? 0.0);
|
||||
|
||||
_scrollController.jumpTo(centeredOffset.clamp(0.0, maxScrollExtent));
|
||||
}
|
||||
|
|
@ -354,8 +342,7 @@ class ScrubberState extends ConsumerState<Scrubber>
|
|||
isDragging: _isDragging,
|
||||
),
|
||||
),
|
||||
if (_scrollController.hasClients &&
|
||||
_scrollController.position.maxScrollExtent > 0)
|
||||
if (_scrollController.hasClients && _scrollController.position.maxScrollExtent > 0)
|
||||
PositionedDirectional(
|
||||
top: _thumbTopOffset + widget.topPadding,
|
||||
end: 0,
|
||||
|
|
@ -492,9 +479,8 @@ class _Scrubber extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final backgroundColor = context.isDarkTheme
|
||||
? context.colorScheme.primary.darken(amount: .5)
|
||||
: context.colorScheme.primary;
|
||||
final backgroundColor =
|
||||
context.isDarkTheme ? context.colorScheme.primary.darken(amount: .5) : context.colorScheme.primary;
|
||||
|
||||
return _SlideFadeTransition(
|
||||
animation: thumbAnimation,
|
||||
|
|
@ -590,8 +576,7 @@ class _SlideFadeTransition extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
return AnimatedBuilder(
|
||||
animation: _animation,
|
||||
builder: (context, child) =>
|
||||
_animation.value == 0.0 ? const SizedBox() : child!,
|
||||
builder: (context, child) => _animation.value == 0.0 ? const SizedBox() : child!,
|
||||
child: SlideTransition(
|
||||
position: Tween(
|
||||
begin: const Offset(0.3, 0.0),
|
||||
|
|
|
|||
|
|
@ -42,8 +42,7 @@ abstract class Segment {
|
|||
|
||||
bool containsIndex(int index) => firstIndex <= index && index <= lastIndex;
|
||||
|
||||
bool isWithinOffset(double offset) =>
|
||||
startOffset <= offset && offset <= endOffset;
|
||||
bool isWithinOffset(double offset) => startOffset <= offset && offset <= endOffset;
|
||||
|
||||
int getMinChildIndexForScrollOffset(double scrollOffset);
|
||||
int getMaxChildIndexForScrollOffset(double scrollOffset);
|
||||
|
|
@ -88,13 +87,9 @@ abstract class Segment {
|
|||
}
|
||||
|
||||
extension SegmentListExtension on List<Segment> {
|
||||
bool equals(List<Segment> other) =>
|
||||
length == other.length &&
|
||||
lastOrNull?.endOffset == other.lastOrNull?.endOffset;
|
||||
bool equals(List<Segment> other) => length == other.length && lastOrNull?.endOffset == other.lastOrNull?.endOffset;
|
||||
|
||||
Segment? findByIndex(int index) =>
|
||||
firstWhereOrNull((s) => s.containsIndex(index));
|
||||
Segment? findByIndex(int index) => firstWhereOrNull((s) => s.containsIndex(index));
|
||||
|
||||
Segment? findByOffset(double offset) =>
|
||||
firstWhereOrNull((s) => s.isWithinOffset(offset)) ?? lastOrNull;
|
||||
Segment? findByOffset(double offset) => firstWhereOrNull((s) => s.isWithinOffset(offset)) ?? lastOrNull;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -105,9 +105,7 @@ final timelineSegmentProvider = StreamProvider.autoDispose<List<Segment>>(
|
|||
final availableTileWidth = args.maxWidth - (spacing * (columnCount - 1));
|
||||
final tileExtent = math.max(0, availableTileWidth) / columnCount;
|
||||
|
||||
final groupBy = args.groupBy ??
|
||||
GroupAssetsBy
|
||||
.values[ref.watch(settingsProvider).get(Setting.groupAssetsBy)];
|
||||
final groupBy = args.groupBy ?? GroupAssetsBy.values[ref.watch(settingsProvider).get(Setting.groupAssetsBy)];
|
||||
|
||||
final timelineService = ref.watch(timelineServiceProvider);
|
||||
yield* timelineService.watchBuckets().map((buckets) {
|
||||
|
|
@ -123,7 +121,6 @@ final timelineSegmentProvider = StreamProvider.autoDispose<List<Segment>>(
|
|||
dependencies: [timelineServiceProvider, timelineArgsProvider],
|
||||
);
|
||||
|
||||
final timelineStateProvider =
|
||||
NotifierProvider<TimelineStateNotifier, TimelineState>(
|
||||
final timelineStateProvider = NotifierProvider<TimelineStateNotifier, TimelineState>(
|
||||
TimelineStateNotifier.new,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -101,8 +101,7 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
|
|||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_reloadSubscription =
|
||||
EventStream.shared.listen<TimelineReloadEvent>((_) => setState(() {}));
|
||||
_reloadSubscription = EventStream.shared.listen<TimelineReloadEvent>((_) => setState(() {}));
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -115,8 +114,7 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
|
|||
@override
|
||||
Widget build(BuildContext _) {
|
||||
final asyncSegments = ref.watch(timelineSegmentProvider);
|
||||
final maxHeight =
|
||||
ref.watch(timelineArgsProvider.select((args) => args.maxHeight));
|
||||
final maxHeight = ref.watch(timelineArgsProvider.select((args) => args.maxHeight));
|
||||
final isSelectionMode = ref.watch(
|
||||
multiSelectProvider.select((s) => s.forceEnable),
|
||||
);
|
||||
|
|
@ -124,17 +122,11 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
|
|||
return asyncSegments.widgetWhen(
|
||||
onData: (segments) {
|
||||
final childCount = (segments.lastOrNull?.lastIndex ?? -1) + 1;
|
||||
final double appBarExpandedHeight =
|
||||
widget.appBar != null && widget.appBar is MesmerizingSliverAppBar
|
||||
? 200
|
||||
: 0;
|
||||
final topPadding = context.padding.top +
|
||||
(widget.appBar == null ? 0 : kToolbarHeight) +
|
||||
10;
|
||||
final double appBarExpandedHeight = widget.appBar != null && widget.appBar is MesmerizingSliverAppBar ? 200 : 0;
|
||||
final topPadding = context.padding.top + (widget.appBar == null ? 0 : kToolbarHeight) + 10;
|
||||
|
||||
const scrubberBottomPadding = 100.0;
|
||||
final bottomPadding = context.padding.bottom +
|
||||
(widget.appBar == null ? 0 : scrubberBottomPadding);
|
||||
final bottomPadding = context.padding.bottom + (widget.appBar == null ? 0 : scrubberBottomPadding);
|
||||
|
||||
return PrimaryScrollController(
|
||||
controller: _scrollController,
|
||||
|
|
@ -145,16 +137,12 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
|
|||
timelineHeight: maxHeight,
|
||||
topPadding: topPadding,
|
||||
bottomPadding: bottomPadding,
|
||||
monthSegmentSnappingOffset:
|
||||
widget.topSliverWidgetHeight ?? 0 + appBarExpandedHeight,
|
||||
monthSegmentSnappingOffset: widget.topSliverWidgetHeight ?? 0 + appBarExpandedHeight,
|
||||
child: CustomScrollView(
|
||||
primary: true,
|
||||
cacheExtent: maxHeight * 2,
|
||||
slivers: [
|
||||
if (isSelectionMode)
|
||||
const SelectionSliverAppBar()
|
||||
else if (widget.appBar != null)
|
||||
widget.appBar!,
|
||||
if (isSelectionMode) const SelectionSliverAppBar() else if (widget.appBar != null) widget.appBar!,
|
||||
if (widget.topSliverWidget != null) widget.topSliverWidget!,
|
||||
_SliverSegmentedList(
|
||||
segments: segments,
|
||||
|
|
@ -162,8 +150,7 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
|
|||
(ctx, index) {
|
||||
if (index >= childCount) return null;
|
||||
final segment = segments.findByIndex(index);
|
||||
return segment?.builder(ctx, index) ??
|
||||
const SizedBox.shrink();
|
||||
return segment?.builder(ctx, index) ?? const SizedBox.shrink();
|
||||
},
|
||||
childCount: childCount,
|
||||
addAutomaticKeepAlives: false,
|
||||
|
|
@ -233,8 +220,7 @@ class _SliverSegmentedList extends SliverMultiBoxAdaptorWidget {
|
|||
}) : _segments = segments;
|
||||
|
||||
@override
|
||||
_RenderSliverTimelineBoxAdaptor createRenderObject(BuildContext context) =>
|
||||
_RenderSliverTimelineBoxAdaptor(
|
||||
_RenderSliverTimelineBoxAdaptor createRenderObject(BuildContext context) => _RenderSliverTimelineBoxAdaptor(
|
||||
childManager: context as SliverMultiBoxAdaptorElement,
|
||||
segments: _segments,
|
||||
);
|
||||
|
|
@ -266,17 +252,13 @@ class _RenderSliverTimelineBoxAdaptor extends RenderSliverMultiBoxAdaptor {
|
|||
}) : _segments = segments;
|
||||
|
||||
int getMinChildIndexForScrollOffset(double offset) =>
|
||||
_segments.findByOffset(offset)?.getMinChildIndexForScrollOffset(offset) ??
|
||||
0;
|
||||
_segments.findByOffset(offset)?.getMinChildIndexForScrollOffset(offset) ?? 0;
|
||||
|
||||
int getMaxChildIndexForScrollOffset(double offset) =>
|
||||
_segments.findByOffset(offset)?.getMaxChildIndexForScrollOffset(offset) ??
|
||||
0;
|
||||
_segments.findByOffset(offset)?.getMaxChildIndexForScrollOffset(offset) ?? 0;
|
||||
|
||||
double indexToLayoutOffset(int index) =>
|
||||
(_segments.findByIndex(index) ?? _segments.lastOrNull)
|
||||
?.indexToLayoutOffset(index) ??
|
||||
0;
|
||||
(_segments.findByIndex(index) ?? _segments.lastOrNull)?.indexToLayoutOffset(index) ?? 0;
|
||||
|
||||
double estimateMaxScrollOffset() => _segments.lastOrNull?.endOffset ?? 0;
|
||||
|
||||
|
|
@ -288,8 +270,7 @@ class _RenderSliverTimelineBoxAdaptor extends RenderSliverMultiBoxAdaptor {
|
|||
// Assume initially that we have enough children to fill the viewport/cache area.
|
||||
childManager.setDidUnderflow(false);
|
||||
|
||||
final double scrollOffset =
|
||||
constraints.scrollOffset + constraints.cacheOrigin;
|
||||
final double scrollOffset = constraints.scrollOffset + constraints.cacheOrigin;
|
||||
assert(scrollOffset >= 0.0);
|
||||
|
||||
final double remainingExtent = constraints.remainingCacheExtent;
|
||||
|
|
@ -298,31 +279,26 @@ class _RenderSliverTimelineBoxAdaptor extends RenderSliverMultiBoxAdaptor {
|
|||
final double targetScrollOffset = scrollOffset + remainingExtent;
|
||||
|
||||
// Find the index of the first child that should be visible or in the leading cache area.
|
||||
final int firstRequiredChildIndex =
|
||||
getMinChildIndexForScrollOffset(scrollOffset);
|
||||
final int firstRequiredChildIndex = getMinChildIndexForScrollOffset(scrollOffset);
|
||||
|
||||
// Find the index of the last child that should be visible or in the trailing cache area.
|
||||
final int? lastRequiredChildIndex = targetScrollOffset.isFinite
|
||||
? getMaxChildIndexForScrollOffset(targetScrollOffset)
|
||||
: null;
|
||||
final int? lastRequiredChildIndex =
|
||||
targetScrollOffset.isFinite ? getMaxChildIndexForScrollOffset(targetScrollOffset) : null;
|
||||
|
||||
// Remove children that are no longer visible or within the cache area.
|
||||
if (firstChild == null) {
|
||||
collectGarbage(0, 0);
|
||||
} else {
|
||||
final int leadingChildrenToRemove =
|
||||
calculateLeadingGarbage(firstIndex: firstRequiredChildIndex);
|
||||
final int trailingChildrenToRemove = lastRequiredChildIndex == null
|
||||
? 0
|
||||
: calculateTrailingGarbage(lastIndex: lastRequiredChildIndex);
|
||||
final int leadingChildrenToRemove = calculateLeadingGarbage(firstIndex: firstRequiredChildIndex);
|
||||
final int trailingChildrenToRemove =
|
||||
lastRequiredChildIndex == null ? 0 : calculateTrailingGarbage(lastIndex: lastRequiredChildIndex);
|
||||
collectGarbage(leadingChildrenToRemove, trailingChildrenToRemove);
|
||||
}
|
||||
|
||||
// If there are currently no children laid out (e.g., initial load),
|
||||
// try to add the first child needed for the current scroll offset.
|
||||
if (firstChild == null) {
|
||||
final double firstChildLayoutOffset =
|
||||
indexToLayoutOffset(firstRequiredChildIndex);
|
||||
final double firstChildLayoutOffset = indexToLayoutOffset(firstRequiredChildIndex);
|
||||
final bool childAdded = addInitialChild(
|
||||
index: firstRequiredChildIndex,
|
||||
layoutOffset: firstChildLayoutOffset,
|
||||
|
|
@ -330,8 +306,7 @@ class _RenderSliverTimelineBoxAdaptor extends RenderSliverMultiBoxAdaptor {
|
|||
|
||||
if (!childAdded) {
|
||||
// There are either no children, or we are past the end of all our children.
|
||||
final double max =
|
||||
firstRequiredChildIndex <= 0 ? 0.0 : computeMaxScrollOffset();
|
||||
final double max = firstRequiredChildIndex <= 0 ? 0.0 : computeMaxScrollOffset();
|
||||
geometry = SliverGeometry(scrollExtent: max, maxPaintExtent: max);
|
||||
childManager.didFinishLayout();
|
||||
return;
|
||||
|
|
@ -342,26 +317,20 @@ class _RenderSliverTimelineBoxAdaptor extends RenderSliverMultiBoxAdaptor {
|
|||
RenderBox? highestLaidOutChild;
|
||||
final childConstraints = constraints.asBoxConstraints();
|
||||
|
||||
for (int currentIndex = indexOf(firstChild!) - 1;
|
||||
currentIndex >= firstRequiredChildIndex;
|
||||
--currentIndex) {
|
||||
final RenderBox? newLeadingChild =
|
||||
insertAndLayoutLeadingChild(childConstraints);
|
||||
for (int currentIndex = indexOf(firstChild!) - 1; currentIndex >= firstRequiredChildIndex; --currentIndex) {
|
||||
final RenderBox? newLeadingChild = insertAndLayoutLeadingChild(childConstraints);
|
||||
if (newLeadingChild == null) {
|
||||
// If a child is missing where we expect one, it indicates
|
||||
// an inconsistency in offset that needs correction.
|
||||
final Segment? segment =
|
||||
_segments.findByIndex(currentIndex) ?? _segments.firstOrNull;
|
||||
final Segment? segment = _segments.findByIndex(currentIndex) ?? _segments.firstOrNull;
|
||||
geometry = SliverGeometry(
|
||||
// Request a scroll correction based on where the missing child should have been.
|
||||
scrollOffsetCorrection:
|
||||
segment?.indexToLayoutOffset(currentIndex) ?? 0.0,
|
||||
scrollOffsetCorrection: segment?.indexToLayoutOffset(currentIndex) ?? 0.0,
|
||||
);
|
||||
// Parent will re-layout everything.
|
||||
return;
|
||||
}
|
||||
final childParentData =
|
||||
newLeadingChild.parentData! as SliverMultiBoxAdaptorParentData;
|
||||
final childParentData = newLeadingChild.parentData! as SliverMultiBoxAdaptorParentData;
|
||||
childParentData.layoutOffset = indexToLayoutOffset(currentIndex);
|
||||
assert(childParentData.index == currentIndex);
|
||||
highestLaidOutChild ??= newLeadingChild;
|
||||
|
|
@ -375,10 +344,8 @@ class _RenderSliverTimelineBoxAdaptor extends RenderSliverMultiBoxAdaptor {
|
|||
// The [firstChild] that existed at the start of performLayout is still the first one we need.
|
||||
if (highestLaidOutChild == null) {
|
||||
firstChild!.layout(childConstraints);
|
||||
final childParentData =
|
||||
firstChild!.parentData! as SliverMultiBoxAdaptorParentData;
|
||||
childParentData.layoutOffset =
|
||||
indexToLayoutOffset(firstRequiredChildIndex);
|
||||
final childParentData = firstChild!.parentData! as SliverMultiBoxAdaptorParentData;
|
||||
childParentData.layoutOffset = indexToLayoutOffset(firstRequiredChildIndex);
|
||||
highestLaidOutChild = firstChild;
|
||||
}
|
||||
|
||||
|
|
@ -389,8 +356,7 @@ class _RenderSliverTimelineBoxAdaptor extends RenderSliverMultiBoxAdaptor {
|
|||
double calculatedMaxScrollOffset = double.infinity;
|
||||
|
||||
for (int currentIndex = indexOf(mostRecentlyLaidOutChild!) + 1;
|
||||
lastRequiredChildIndex == null ||
|
||||
currentIndex <= lastRequiredChildIndex;
|
||||
lastRequiredChildIndex == null || currentIndex <= lastRequiredChildIndex;
|
||||
++currentIndex) {
|
||||
RenderBox? child = childAfter(mostRecentlyLaidOutChild!);
|
||||
|
||||
|
|
@ -400,11 +366,8 @@ class _RenderSliverTimelineBoxAdaptor extends RenderSliverMultiBoxAdaptor {
|
|||
after: mostRecentlyLaidOutChild,
|
||||
);
|
||||
if (child == null) {
|
||||
final Segment? segment =
|
||||
_segments.findByIndex(currentIndex) ?? _segments.lastOrNull;
|
||||
calculatedMaxScrollOffset =
|
||||
segment?.indexToLayoutOffset(currentIndex) ??
|
||||
computeMaxScrollOffset();
|
||||
final Segment? segment = _segments.findByIndex(currentIndex) ?? _segments.lastOrNull;
|
||||
calculatedMaxScrollOffset = segment?.indexToLayoutOffset(currentIndex) ?? computeMaxScrollOffset();
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
|
|
@ -412,28 +375,23 @@ class _RenderSliverTimelineBoxAdaptor extends RenderSliverMultiBoxAdaptor {
|
|||
}
|
||||
|
||||
mostRecentlyLaidOutChild = child;
|
||||
final childParentData = mostRecentlyLaidOutChild.parentData!
|
||||
as SliverMultiBoxAdaptorParentData;
|
||||
final childParentData = mostRecentlyLaidOutChild.parentData! as SliverMultiBoxAdaptorParentData;
|
||||
assert(childParentData.index == currentIndex);
|
||||
childParentData.layoutOffset = indexToLayoutOffset(currentIndex);
|
||||
}
|
||||
|
||||
final int lastLaidOutChildIndex = indexOf(lastChild!);
|
||||
final double leadingScrollOffset =
|
||||
indexToLayoutOffset(firstRequiredChildIndex);
|
||||
final double trailingScrollOffset =
|
||||
indexToLayoutOffset(lastLaidOutChildIndex + 1);
|
||||
final double leadingScrollOffset = indexToLayoutOffset(firstRequiredChildIndex);
|
||||
final double trailingScrollOffset = indexToLayoutOffset(lastLaidOutChildIndex + 1);
|
||||
|
||||
assert(
|
||||
firstRequiredChildIndex == 0 ||
|
||||
(childScrollOffset(firstChild!) ?? -1.0) - scrollOffset <=
|
||||
precisionErrorTolerance,
|
||||
(childScrollOffset(firstChild!) ?? -1.0) - scrollOffset <= precisionErrorTolerance,
|
||||
);
|
||||
assert(debugAssertChildListIsNonEmptyAndContiguous());
|
||||
assert(indexOf(firstChild!) == firstRequiredChildIndex);
|
||||
assert(
|
||||
lastRequiredChildIndex == null ||
|
||||
lastLaidOutChildIndex <= lastRequiredChildIndex,
|
||||
lastRequiredChildIndex == null || lastLaidOutChildIndex <= lastRequiredChildIndex,
|
||||
);
|
||||
|
||||
calculatedMaxScrollOffset = math.min(
|
||||
|
|
@ -453,11 +411,9 @@ class _RenderSliverTimelineBoxAdaptor extends RenderSliverMultiBoxAdaptor {
|
|||
to: trailingScrollOffset,
|
||||
);
|
||||
|
||||
final double targetEndScrollOffsetForPaint =
|
||||
constraints.scrollOffset + constraints.remainingPaintExtent;
|
||||
final int? targetLastIndexForPaint = targetEndScrollOffsetForPaint.isFinite
|
||||
? getMaxChildIndexForScrollOffset(targetEndScrollOffsetForPaint)
|
||||
: null;
|
||||
final double targetEndScrollOffsetForPaint = constraints.scrollOffset + constraints.remainingPaintExtent;
|
||||
final int? targetLastIndexForPaint =
|
||||
targetEndScrollOffsetForPaint.isFinite ? getMaxChildIndexForScrollOffset(targetEndScrollOffsetForPaint) : null;
|
||||
|
||||
final maxPaintExtent = math.max(paintExtent, calculatedMaxScrollOffset);
|
||||
|
||||
|
|
@ -468,8 +424,7 @@ class _RenderSliverTimelineBoxAdaptor extends RenderSliverMultiBoxAdaptor {
|
|||
// Indicates if there's content scrolled off-screen.
|
||||
// This is true if the last child needed for painting is actually laid out,
|
||||
// or if the first child is partially visible.
|
||||
hasVisualOverflow: (targetLastIndexForPaint != null &&
|
||||
lastLaidOutChildIndex >= targetLastIndexForPaint) ||
|
||||
hasVisualOverflow: (targetLastIndexForPaint != null && lastLaidOutChildIndex >= targetLastIndexForPaint) ||
|
||||
constraints.scrollOffset > 0.0,
|
||||
cacheExtent: cacheExtent,
|
||||
);
|
||||
|
|
@ -489,8 +444,7 @@ class _MultiSelectStatusButton extends ConsumerWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final selectCount =
|
||||
ref.watch(multiSelectProvider.select((s) => s.selectedAssets.length));
|
||||
final selectCount = ref.watch(multiSelectProvider.select((s) => s.selectedAssets.length));
|
||||
return ElevatedButton.icon(
|
||||
onPressed: () => ref.read(multiSelectProvider.notifier).reset(),
|
||||
icon: Icon(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue