mirror of
https://github.com/immich-app/immich.git
synced 2025-06-01 12:44:17 -04:00
add local media summary page
This commit is contained in:
parent
35c3f7211f
commit
1977458c79
@ -5,25 +5,31 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
||||||
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
|
|
||||||
final _features = [
|
final _features = [
|
||||||
_Feature(
|
_Feature(
|
||||||
name: 'Sync Local',
|
name: 'Sync Local',
|
||||||
icon: Icons.photo_album_rounded,
|
icon: Icons.photo_album_rounded,
|
||||||
onTap: (ref) => ref.read(backgroundSyncProvider).syncLocal(),
|
onTap: (_, ref) => ref.read(backgroundSyncProvider).syncLocal(),
|
||||||
),
|
),
|
||||||
_Feature(
|
_Feature(
|
||||||
name: 'Sync Remote',
|
name: 'Sync Remote',
|
||||||
icon: Icons.refresh_rounded,
|
icon: Icons.refresh_rounded,
|
||||||
onTap: (ref) => ref.read(backgroundSyncProvider).syncRemote(),
|
onTap: (_, ref) => ref.read(backgroundSyncProvider).syncRemote(),
|
||||||
),
|
),
|
||||||
_Feature(
|
_Feature(
|
||||||
name: 'WAL Checkpoint',
|
name: 'WAL Checkpoint',
|
||||||
icon: Icons.save_rounded,
|
icon: Icons.save_rounded,
|
||||||
onTap: (ref) => ref
|
onTap: (_, ref) => ref
|
||||||
.read(driftProvider)
|
.read(driftProvider)
|
||||||
.customStatement("pragma wal_checkpoint(truncate)"),
|
.customStatement("pragma wal_checkpoint(truncate)"),
|
||||||
),
|
),
|
||||||
|
_Feature(
|
||||||
|
name: 'Local Media Summary',
|
||||||
|
icon: Icons.table_chart_rounded,
|
||||||
|
onTap: (ctx, _) => ctx.pushRoute(const LocalMediaSummaryRoute()),
|
||||||
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
@RoutePage()
|
@RoutePage()
|
||||||
@ -44,7 +50,7 @@ class FeatInDevPage extends StatelessWidget {
|
|||||||
builder: (ctx, ref, _) => ListTile(
|
builder: (ctx, ref, _) => ListTile(
|
||||||
title: Text(feat.name),
|
title: Text(feat.name),
|
||||||
trailing: Icon(feat.icon),
|
trailing: Icon(feat.icon),
|
||||||
onTap: () => unawaited(feat.onTap(ref)),
|
onTap: () => unawaited(feat.onTap(ctx, ref)),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -63,5 +69,5 @@ class _Feature {
|
|||||||
|
|
||||||
final String name;
|
final String name;
|
||||||
final IconData icon;
|
final IconData icon;
|
||||||
final Future<void> Function(WidgetRef _) onTap;
|
final Future<void> Function(BuildContext, WidgetRef _) onTap;
|
||||||
}
|
}
|
||||||
|
125
mobile/lib/presentation/pages/local_media_stat.page.dart
Normal file
125
mobile/lib/presentation/pages/local_media_stat.page.dart
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
import 'package:auto_route/auto_route.dart';
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/local_album.model.dart';
|
||||||
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
||||||
|
|
||||||
|
final _stats = [
|
||||||
|
_Stat(
|
||||||
|
name: 'Local Assets',
|
||||||
|
load: (db) => db.managers.localAssetEntity.count(),
|
||||||
|
),
|
||||||
|
_Stat(
|
||||||
|
name: 'Local Albums',
|
||||||
|
load: (db) => db.managers.localAlbumEntity.count(),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
@RoutePage()
|
||||||
|
class LocalMediaSummaryPage extends StatelessWidget {
|
||||||
|
const LocalMediaSummaryPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(title: const Text('Local Media Summary')),
|
||||||
|
body: Consumer(
|
||||||
|
builder: (ctx, ref, __) {
|
||||||
|
final db = ref.watch(driftProvider);
|
||||||
|
final albumsFuture = ref.watch(localAlbumRepository).getAll();
|
||||||
|
|
||||||
|
return CustomScrollView(
|
||||||
|
slivers: [
|
||||||
|
SliverList.builder(
|
||||||
|
itemBuilder: (_, index) {
|
||||||
|
final stat = _stats[index];
|
||||||
|
final countFuture = stat.load(db);
|
||||||
|
return _Summary(name: stat.name, countFuture: countFuture);
|
||||||
|
},
|
||||||
|
itemCount: _stats.length,
|
||||||
|
),
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
const Divider(),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 15),
|
||||||
|
child: Text(
|
||||||
|
"Album summary",
|
||||||
|
style: ctx.textTheme.titleMedium,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
FutureBuilder(
|
||||||
|
future: albumsFuture,
|
||||||
|
initialData: <LocalAlbum>[],
|
||||||
|
builder: (_, snap) {
|
||||||
|
final albums = snap.data!;
|
||||||
|
if (albums.isEmpty) {
|
||||||
|
return const SliverToBoxAdapter(child: SizedBox.shrink());
|
||||||
|
}
|
||||||
|
|
||||||
|
albums.sortBy((a) => a.name);
|
||||||
|
return SliverList.builder(
|
||||||
|
itemBuilder: (_, index) {
|
||||||
|
final album = albums[index];
|
||||||
|
final countFuture = db.managers.localAlbumAssetEntity
|
||||||
|
.filter((f) => f.albumId.id.equals(album.id))
|
||||||
|
.count();
|
||||||
|
return _Summary(
|
||||||
|
name: album.name,
|
||||||
|
countFuture: countFuture,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
itemCount: albums.length,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore: prefer-single-widget-per-file
|
||||||
|
class _Summary extends StatelessWidget {
|
||||||
|
final String name;
|
||||||
|
final Future<int> countFuture;
|
||||||
|
|
||||||
|
const _Summary({required this.name, required this.countFuture});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return FutureBuilder<int>(
|
||||||
|
future: countFuture,
|
||||||
|
builder: (ctx, snapshot) {
|
||||||
|
final Widget subtitle;
|
||||||
|
|
||||||
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||||
|
subtitle = const CircularProgressIndicator();
|
||||||
|
} else if (snapshot.hasError) {
|
||||||
|
subtitle = const Icon(Icons.error_rounded);
|
||||||
|
} else {
|
||||||
|
subtitle = Text('${snapshot.data ?? 0}');
|
||||||
|
}
|
||||||
|
return ListTile(title: Text(name), trailing: subtitle);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _Stat {
|
||||||
|
const _Stat({required this.name, required this.load});
|
||||||
|
|
||||||
|
final String name;
|
||||||
|
final Future<int> Function(Drift _) load;
|
||||||
|
}
|
@ -62,6 +62,7 @@ import 'package:immich_mobile/pages/search/recently_taken.page.dart';
|
|||||||
import 'package:immich_mobile/pages/search/search.page.dart';
|
import 'package:immich_mobile/pages/search/search.page.dart';
|
||||||
import 'package:immich_mobile/pages/share_intent/share_intent.page.dart';
|
import 'package:immich_mobile/pages/share_intent/share_intent.page.dart';
|
||||||
import 'package:immich_mobile/presentation/pages/feat_in_development.page.dart';
|
import 'package:immich_mobile/presentation/pages/feat_in_development.page.dart';
|
||||||
|
import 'package:immich_mobile/presentation/pages/local_media_stat.page.dart';
|
||||||
import 'package:immich_mobile/providers/api.provider.dart';
|
import 'package:immich_mobile/providers/api.provider.dart';
|
||||||
import 'package:immich_mobile/providers/gallery_permission.provider.dart';
|
import 'package:immich_mobile/providers/gallery_permission.provider.dart';
|
||||||
import 'package:immich_mobile/routing/auth_guard.dart';
|
import 'package:immich_mobile/routing/auth_guard.dart';
|
||||||
@ -294,6 +295,10 @@ class AppRouter extends RootStackRouter {
|
|||||||
page: FeatInDevRoute.page,
|
page: FeatInDevRoute.page,
|
||||||
guards: [_authGuard, _duplicateGuard],
|
guards: [_authGuard, _duplicateGuard],
|
||||||
),
|
),
|
||||||
|
AutoRoute(
|
||||||
|
page: LocalMediaSummaryRoute.page,
|
||||||
|
guards: [_authGuard, _duplicateGuard],
|
||||||
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -855,6 +855,22 @@ class LocalAlbumsRoute extends PageRouteInfo<void> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// generated route for
|
||||||
|
/// [LocalMediaSummaryPage]
|
||||||
|
class LocalMediaSummaryRoute extends PageRouteInfo<void> {
|
||||||
|
const LocalMediaSummaryRoute({List<PageRouteInfo>? children})
|
||||||
|
: super(LocalMediaSummaryRoute.name, initialChildren: children);
|
||||||
|
|
||||||
|
static const String name = 'LocalMediaSummaryRoute';
|
||||||
|
|
||||||
|
static PageInfo page = PageInfo(
|
||||||
|
name,
|
||||||
|
builder: (data) {
|
||||||
|
return const LocalMediaSummaryPage();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// generated route for
|
/// generated route for
|
||||||
/// [LoginPage]
|
/// [LoginPage]
|
||||||
class LoginRoute extends PageRouteInfo<void> {
|
class LoginRoute extends PageRouteInfo<void> {
|
||||||
|
@ -179,7 +179,6 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget {
|
|||||||
child: action,
|
child: action,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (kDebugMode)
|
|
||||||
if (kDebugMode)
|
if (kDebugMode)
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.science_rounded),
|
icon: const Icon(Icons.science_rounded),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user