chore: bump dart sdk to 3.8 (#20355)
* chore: bump dart sdk to 3.8 * chore: make build * make pigeon * chore: format files --------- Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
This commit is contained in:
@@ -16,9 +16,7 @@ import 'package:immich_mobile/widgets/activities/dismissible_activity.dart';
|
||||
|
||||
@RoutePage()
|
||||
class ActivitiesPage extends HookConsumerWidget {
|
||||
const ActivitiesPage({
|
||||
super.key,
|
||||
});
|
||||
const ActivitiesPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
@@ -60,9 +58,7 @@ class ActivitiesPage extends HookConsumerWidget {
|
||||
itemBuilder: (context, index) {
|
||||
// Additional vertical gap after the last element
|
||||
if (index == data.length) {
|
||||
return const SizedBox(
|
||||
height: 80,
|
||||
);
|
||||
return const SizedBox(height: 80);
|
||||
}
|
||||
|
||||
final activity = data[index];
|
||||
@@ -73,8 +69,9 @@ class ActivitiesPage extends HookConsumerWidget {
|
||||
child: DismissibleActivity(
|
||||
activity.id,
|
||||
ActivityTile(activity),
|
||||
onDismiss:
|
||||
canDelete ? (activityId) async => await activityNotifier.removeActivity(activity.id) : null,
|
||||
onDismiss: canDelete
|
||||
? (activityId) async => await activityNotifier.removeActivity(activity.id)
|
||||
: null,
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -12,17 +12,13 @@ import 'package:intl/intl.dart';
|
||||
|
||||
@RoutePage()
|
||||
class AppLogPage extends HookConsumerWidget {
|
||||
const AppLogPage({
|
||||
super.key,
|
||||
});
|
||||
const AppLogPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final immichLogger = LogService.I;
|
||||
final shouldReload = useState(false);
|
||||
final logMessages = useFuture(
|
||||
useMemoized(() => immichLogger.getMessages(), [shouldReload.value]),
|
||||
);
|
||||
final logMessages = useFuture(useMemoized(() => immichLogger.getMessages(), [shouldReload.value]));
|
||||
|
||||
Widget colorStatusIndicator(Color color) {
|
||||
return Column(
|
||||
@@ -31,38 +27,29 @@ class AppLogPage extends HookConsumerWidget {
|
||||
Container(
|
||||
width: 10,
|
||||
height: 10,
|
||||
decoration: BoxDecoration(
|
||||
color: color,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
decoration: BoxDecoration(color: color, shape: BoxShape.circle),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildLeadingIcon(LogLevel level) => switch (level) {
|
||||
LogLevel.info => colorStatusIndicator(context.primaryColor),
|
||||
LogLevel.severe => colorStatusIndicator(Colors.redAccent),
|
||||
LogLevel.warning => colorStatusIndicator(Colors.orangeAccent),
|
||||
_ => colorStatusIndicator(Colors.grey),
|
||||
};
|
||||
LogLevel.info => colorStatusIndicator(context.primaryColor),
|
||||
LogLevel.severe => colorStatusIndicator(Colors.redAccent),
|
||||
LogLevel.warning => colorStatusIndicator(Colors.orangeAccent),
|
||||
_ => colorStatusIndicator(Colors.grey),
|
||||
};
|
||||
|
||||
Color getTileColor(LogLevel level) => switch (level) {
|
||||
LogLevel.info => Colors.transparent,
|
||||
LogLevel.severe => Colors.redAccent.withValues(alpha: 0.25),
|
||||
LogLevel.warning => Colors.orangeAccent.withValues(alpha: 0.25),
|
||||
_ => context.primaryColor.withValues(alpha: 0.1),
|
||||
};
|
||||
LogLevel.info => Colors.transparent,
|
||||
LogLevel.severe => Colors.redAccent.withValues(alpha: 0.25),
|
||||
LogLevel.warning => Colors.orangeAccent.withValues(alpha: 0.25),
|
||||
_ => context.primaryColor.withValues(alpha: 0.1),
|
||||
};
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text(
|
||||
"Logs",
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16.0,
|
||||
),
|
||||
),
|
||||
title: const Text("Logs", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16.0)),
|
||||
scrolledUnderElevation: 1,
|
||||
elevation: 2,
|
||||
actions: [
|
||||
@@ -81,12 +68,7 @@ class AppLogPage extends HookConsumerWidget {
|
||||
Builder(
|
||||
builder: (BuildContext iconContext) {
|
||||
return IconButton(
|
||||
icon: Icon(
|
||||
Icons.share_rounded,
|
||||
color: context.primaryColor,
|
||||
semanticLabel: "Share logs",
|
||||
size: 20.0,
|
||||
),
|
||||
icon: Icon(Icons.share_rounded, color: context.primaryColor, semanticLabel: "Share logs", size: 20.0),
|
||||
onPressed: () {
|
||||
ImmichLogger.shareLogs(iconContext);
|
||||
},
|
||||
@@ -98,10 +80,7 @@ class AppLogPage extends HookConsumerWidget {
|
||||
onPressed: () {
|
||||
context.maybePop();
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.arrow_back_ios_new_rounded,
|
||||
size: 20.0,
|
||||
),
|
||||
icon: const Icon(Icons.arrow_back_ios_new_rounded, size: 20.0),
|
||||
),
|
||||
centerTitle: true,
|
||||
),
|
||||
@@ -113,11 +92,7 @@ class AppLogPage extends HookConsumerWidget {
|
||||
itemBuilder: (context, index) {
|
||||
var logMessage = logMessages.data![index];
|
||||
return ListTile(
|
||||
onTap: () => context.pushRoute(
|
||||
AppLogDetailRoute(
|
||||
logMessage: logMessage,
|
||||
),
|
||||
),
|
||||
onTap: () => context.pushRoute(AppLogDetailRoute(logMessage: logMessage)),
|
||||
trailing: const Icon(Icons.arrow_forward_ios_rounded),
|
||||
visualDensity: VisualDensity.compact,
|
||||
dense: true,
|
||||
@@ -125,18 +100,11 @@ class AppLogPage extends HookConsumerWidget {
|
||||
minLeadingWidth: 10,
|
||||
title: Text(
|
||||
truncateLogMessage(logMessage.message, 4),
|
||||
style: TextStyle(
|
||||
fontSize: 14.0,
|
||||
color: context.colorScheme.onSurface,
|
||||
fontFamily: "Inconsolata",
|
||||
),
|
||||
style: TextStyle(fontSize: 14.0, color: context.colorScheme.onSurface, fontFamily: "Inconsolata"),
|
||||
),
|
||||
subtitle: Text(
|
||||
"at ${DateFormat("HH:mm:ss.SSS").format(logMessage.createdAt)} in ${logMessage.logger}",
|
||||
style: TextStyle(
|
||||
fontSize: 12.0,
|
||||
color: context.colorScheme.onSurfaceSecondary,
|
||||
),
|
||||
style: TextStyle(fontSize: 12.0, color: context.colorScheme.onSurfaceSecondary),
|
||||
),
|
||||
leading: buildLeadingIcon(logMessage.level),
|
||||
);
|
||||
|
||||
@@ -27,11 +27,7 @@ class AppLogDetailPage extends HookConsumerWidget {
|
||||
padding: const EdgeInsets.only(bottom: 8.0),
|
||||
child: Text(
|
||||
header,
|
||||
style: TextStyle(
|
||||
fontSize: 12.0,
|
||||
color: context.primaryColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
style: TextStyle(fontSize: 12.0, color: context.primaryColor, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
@@ -41,38 +37,26 @@ class AppLogDetailPage extends HookConsumerWidget {
|
||||
SnackBar(
|
||||
content: Text(
|
||||
"Copied to clipboard",
|
||||
style: context.textTheme.bodyLarge?.copyWith(
|
||||
color: context.primaryColor,
|
||||
),
|
||||
style: context.textTheme.bodyLarge?.copyWith(color: context.primaryColor),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
},
|
||||
icon: Icon(
|
||||
Icons.copy,
|
||||
size: 16.0,
|
||||
color: context.primaryColor,
|
||||
),
|
||||
icon: Icon(Icons.copy, size: 16.0, color: context.primaryColor),
|
||||
),
|
||||
],
|
||||
),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: context.colorScheme.surfaceContainerHigh,
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(15.0),
|
||||
),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(15.0)),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: SelectableText(
|
||||
text,
|
||||
style: const TextStyle(
|
||||
fontSize: 12.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontFamily: "Inconsolata",
|
||||
),
|
||||
style: const TextStyle(fontSize: 12.0, fontWeight: FontWeight.bold, fontFamily: "Inconsolata"),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -91,29 +75,19 @@ class AppLogDetailPage extends HookConsumerWidget {
|
||||
padding: const EdgeInsets.only(bottom: 8.0),
|
||||
child: Text(
|
||||
"FROM",
|
||||
style: TextStyle(
|
||||
fontSize: 12.0,
|
||||
color: context.primaryColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
style: TextStyle(fontSize: 12.0, color: context.primaryColor, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: context.colorScheme.surfaceContainerHigh,
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(15.0),
|
||||
),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(15.0)),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: SelectableText(
|
||||
context1.toString(),
|
||||
style: const TextStyle(
|
||||
fontSize: 12.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontFamily: "Inconsolata",
|
||||
),
|
||||
style: const TextStyle(fontSize: 12.0, fontWeight: FontWeight.bold, fontFamily: "Inconsolata"),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -123,20 +97,14 @@ class AppLogDetailPage extends HookConsumerWidget {
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text("Log Detail"),
|
||||
),
|
||||
appBar: AppBar(title: const Text("Log Detail")),
|
||||
body: SafeArea(
|
||||
child: ListView(
|
||||
children: [
|
||||
buildTextWithCopyButton("MESSAGE", logMessage.message),
|
||||
if (logMessage.error != null) buildTextWithCopyButton("DETAILS", logMessage.error.toString()),
|
||||
if (logMessage.logger != null) buildLogContext1(logMessage.logger.toString()),
|
||||
if (logMessage.stack != null)
|
||||
buildTextWithCopyButton(
|
||||
"STACK TRACE",
|
||||
logMessage.stack.toString(),
|
||||
),
|
||||
if (logMessage.stack != null) buildTextWithCopyButton("STACK TRACE", logMessage.stack.toString()),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -49,11 +49,9 @@ class _ChangeExperiencePageState extends ConsumerState<ChangeExperiencePage> {
|
||||
|
||||
// Cancel uploads
|
||||
await Store.put(StoreKey.backgroundBackup, false);
|
||||
ref.read(backupProvider.notifier).configureBackgroundBackup(
|
||||
enabled: false,
|
||||
onBatteryInfo: () {},
|
||||
onError: (_) {},
|
||||
);
|
||||
ref
|
||||
.read(backupProvider.notifier)
|
||||
.configureBackgroundBackup(enabled: false, onBatteryInfo: () {}, onError: (_) {});
|
||||
ref.read(backupProvider.notifier).setAutoBackup(false);
|
||||
ref.read(backupProvider.notifier).cancelBackup();
|
||||
ref.read(manualUploadProvider.notifier).cancelBackup();
|
||||
@@ -65,14 +63,8 @@ class _ChangeExperiencePageState extends ConsumerState<ChangeExperiencePage> {
|
||||
|
||||
if (permission.isGranted) {
|
||||
await ref.read(backgroundSyncProvider).syncLocal(full: true);
|
||||
await migrateDeviceAssetToSqlite(
|
||||
ref.read(isarProvider),
|
||||
ref.read(driftProvider),
|
||||
);
|
||||
await migrateBackupAlbumsToSqlite(
|
||||
ref.read(isarProvider),
|
||||
ref.read(driftProvider),
|
||||
);
|
||||
await migrateDeviceAssetToSqlite(ref.read(isarProvider), ref.read(driftProvider));
|
||||
await migrateBackupAlbumsToSqlite(ref.read(isarProvider), ref.read(driftProvider));
|
||||
}
|
||||
} else {
|
||||
await ref.read(backgroundSyncProvider).cancel();
|
||||
@@ -98,16 +90,8 @@ class _ChangeExperiencePageState extends ConsumerState<ChangeExperiencePage> {
|
||||
AnimatedSwitcher(
|
||||
duration: Durations.long4,
|
||||
child: hasMigrated
|
||||
? const Icon(
|
||||
Icons.check_circle_rounded,
|
||||
color: Colors.green,
|
||||
size: 48.0,
|
||||
)
|
||||
: const SizedBox(
|
||||
width: 50.0,
|
||||
height: 50.0,
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
? const Icon(Icons.check_circle_rounded, color: Colors.green, size: 48.0)
|
||||
: const SizedBox(width: 50.0, height: 50.0, child: CircularProgressIndicator()),
|
||||
),
|
||||
const SizedBox(height: 16.0),
|
||||
Center(
|
||||
|
||||
@@ -20,10 +20,7 @@ import 'package:immich_mobile/widgets/album/shared_album_thumbnail_image.dart';
|
||||
class CreateAlbumPage extends HookConsumerWidget {
|
||||
final List<Asset>? assets;
|
||||
|
||||
const CreateAlbumPage({
|
||||
super.key,
|
||||
this.assets,
|
||||
});
|
||||
const CreateAlbumPage({super.key, this.assets});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
@@ -32,9 +29,7 @@ class CreateAlbumPage extends HookConsumerWidget {
|
||||
final albumDescriptionTextFieldFocusNode = useFocusNode();
|
||||
final isAlbumTitleTextFieldFocus = useState(false);
|
||||
final isAlbumTitleEmpty = useState(true);
|
||||
final selectedAssets = useState<Set<Asset>>(
|
||||
assets != null ? Set.from(assets!) : const {},
|
||||
);
|
||||
final selectedAssets = useState<Set<Asset>>(assets != null ? Set.from(assets!) : const {});
|
||||
|
||||
void onBackgroundTapped() {
|
||||
albumTitleTextFieldFocusNode.unfocus();
|
||||
@@ -50,10 +45,7 @@ class CreateAlbumPage extends HookConsumerWidget {
|
||||
|
||||
onSelectPhotosButtonPressed() async {
|
||||
AssetSelectionPageResult? selectedAsset = await context.pushRoute<AssetSelectionPageResult?>(
|
||||
AlbumAssetSelectionRoute(
|
||||
existingAssets: selectedAssets.value,
|
||||
canDeselect: true,
|
||||
),
|
||||
AlbumAssetSelectionRoute(existingAssets: selectedAssets.value, canDeselect: true),
|
||||
);
|
||||
if (selectedAsset == null) {
|
||||
selectedAssets.value = const {};
|
||||
@@ -64,10 +56,7 @@ class CreateAlbumPage extends HookConsumerWidget {
|
||||
|
||||
buildTitleInputField() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
right: 10,
|
||||
left: 10,
|
||||
),
|
||||
padding: const EdgeInsets.only(right: 10, left: 10),
|
||||
child: AlbumTitleTextField(
|
||||
isAlbumTitleEmpty: isAlbumTitleEmpty,
|
||||
albumTitleTextFieldFocusNode: albumTitleTextFieldFocusNode,
|
||||
@@ -79,10 +68,7 @@ class CreateAlbumPage extends HookConsumerWidget {
|
||||
|
||||
buildDescriptionInputField() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
right: 10,
|
||||
left: 10,
|
||||
),
|
||||
padding: const EdgeInsets.only(right: 10, left: 10),
|
||||
child: AlbumViewerEditableDescription(
|
||||
albumDescription: '',
|
||||
descriptionFocusNode: albumDescriptionTextFieldFocusNode,
|
||||
@@ -95,10 +81,7 @@ class CreateAlbumPage extends HookConsumerWidget {
|
||||
return SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 200, left: 18),
|
||||
child: Text(
|
||||
'create_shared_album_page_share_add_assets',
|
||||
style: context.textTheme.labelLarge,
|
||||
).tr(),
|
||||
child: Text('create_shared_album_page_share_add_assets', style: context.textTheme.labelLarge).tr(),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -115,18 +98,11 @@ class CreateAlbumPage extends HookConsumerWidget {
|
||||
style: FilledButton.styleFrom(
|
||||
alignment: Alignment.centerLeft,
|
||||
padding: const EdgeInsets.symmetric(vertical: 24, horizontal: 16),
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(10),
|
||||
),
|
||||
),
|
||||
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(10))),
|
||||
backgroundColor: context.colorScheme.surfaceContainerHigh,
|
||||
),
|
||||
onPressed: onSelectPhotosButtonPressed,
|
||||
icon: Icon(
|
||||
Icons.add_rounded,
|
||||
color: context.primaryColor,
|
||||
),
|
||||
icon: Icon(Icons.add_rounded, color: context.primaryColor),
|
||||
label: Padding(
|
||||
padding: const EdgeInsets.only(left: 8.0),
|
||||
child: Text(
|
||||
@@ -174,17 +150,12 @@ class CreateAlbumPage extends HookConsumerWidget {
|
||||
crossAxisSpacing: 5.0,
|
||||
mainAxisSpacing: 5,
|
||||
),
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, int index) {
|
||||
return GestureDetector(
|
||||
onTap: onBackgroundTapped,
|
||||
child: SharedAlbumThumbnailImage(
|
||||
asset: selectedAssets.value.elementAt(index),
|
||||
),
|
||||
);
|
||||
},
|
||||
childCount: selectedAssets.value.length,
|
||||
),
|
||||
delegate: SliverChildBuilderDelegate((BuildContext context, int index) {
|
||||
return GestureDetector(
|
||||
onTap: onBackgroundTapped,
|
||||
child: SharedAlbumThumbnailImage(asset: selectedAssets.value.elementAt(index)),
|
||||
);
|
||||
}, childCount: selectedAssets.value.length),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -194,10 +165,9 @@ class CreateAlbumPage extends HookConsumerWidget {
|
||||
|
||||
Future<void> createAlbum() async {
|
||||
onBackgroundTapped();
|
||||
var newAlbum = await ref.watch(albumProvider.notifier).createAlbum(
|
||||
ref.read(albumTitleProvider),
|
||||
selectedAssets.value,
|
||||
);
|
||||
var newAlbum = await ref
|
||||
.watch(albumProvider.notifier)
|
||||
.createAlbum(ref.read(albumTitleProvider), selectedAssets.value);
|
||||
|
||||
if (newAlbum != null) {
|
||||
ref.read(albumProvider.notifier).refreshRemoteAlbums();
|
||||
@@ -220,9 +190,7 @@ class CreateAlbumPage extends HookConsumerWidget {
|
||||
},
|
||||
icon: const Icon(Icons.close_rounded),
|
||||
),
|
||||
title: const Text(
|
||||
'create_album',
|
||||
).tr(),
|
||||
title: const Text('create_album').tr(),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: albumTitleController.text.isNotEmpty ? createAlbum : null,
|
||||
|
||||
@@ -6,22 +6,13 @@ import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/providers/asset_viewer/download.provider.dart';
|
||||
|
||||
class DownloadPanel extends ConsumerWidget {
|
||||
const DownloadPanel({
|
||||
super.key,
|
||||
});
|
||||
const DownloadPanel({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final showProgress = ref.watch(
|
||||
downloadStateProvider.select((state) => state.showProgress),
|
||||
);
|
||||
final showProgress = ref.watch(downloadStateProvider.select((state) => state.showProgress));
|
||||
|
||||
final tasks = ref
|
||||
.watch(
|
||||
downloadStateProvider.select((state) => state.taskProgress),
|
||||
)
|
||||
.entries
|
||||
.toList();
|
||||
final tasks = ref.watch(downloadStateProvider.select((state) => state.taskProgress)).entries.toList();
|
||||
|
||||
onCancelDownload(String id) {
|
||||
ref.watch(downloadStateProvider.notifier).cancelDownload(id);
|
||||
@@ -74,47 +65,35 @@ class DownloadTaskTile extends StatelessWidget {
|
||||
final progressPercent = (progress * 100).round();
|
||||
|
||||
String getStatusText() => switch (status) {
|
||||
TaskStatus.running => 'downloading'.tr(),
|
||||
TaskStatus.complete => 'download_complete'.tr(),
|
||||
TaskStatus.failed => 'download_failed'.tr(),
|
||||
TaskStatus.canceled => 'download_canceled'.tr(),
|
||||
TaskStatus.paused => 'download_paused'.tr(),
|
||||
TaskStatus.enqueued => 'download_enqueue'.tr(),
|
||||
TaskStatus.notFound => 'download_notfound'.tr(),
|
||||
TaskStatus.waitingToRetry => 'download_waiting_to_retry'.tr(),
|
||||
};
|
||||
TaskStatus.running => 'downloading'.tr(),
|
||||
TaskStatus.complete => 'download_complete'.tr(),
|
||||
TaskStatus.failed => 'download_failed'.tr(),
|
||||
TaskStatus.canceled => 'download_canceled'.tr(),
|
||||
TaskStatus.paused => 'download_paused'.tr(),
|
||||
TaskStatus.enqueued => 'download_enqueue'.tr(),
|
||||
TaskStatus.notFound => 'download_notfound'.tr(),
|
||||
TaskStatus.waitingToRetry => 'download_waiting_to_retry'.tr(),
|
||||
};
|
||||
|
||||
return SizedBox(
|
||||
key: const ValueKey('download_progress'),
|
||||
width: context.width - 32,
|
||||
child: Card(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(16),
|
||||
),
|
||||
),
|
||||
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16))),
|
||||
child: ListTile(
|
||||
minVerticalPadding: 18,
|
||||
leading: const Icon(Icons.video_file_outlined),
|
||||
title: Text(
|
||||
getStatusText(),
|
||||
style: context.textTheme.labelLarge,
|
||||
),
|
||||
title: Text(getStatusText(), style: context.textTheme.labelLarge),
|
||||
trailing: IconButton(
|
||||
icon: Icon(Icons.close, color: context.colorScheme.onError),
|
||||
onPressed: onCancelDownload,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: context.colorScheme.error.withAlpha(200),
|
||||
),
|
||||
style: ElevatedButton.styleFrom(backgroundColor: context.colorScheme.error.withAlpha(200)),
|
||||
),
|
||||
subtitle: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
fileName,
|
||||
style: context.textTheme.labelMedium,
|
||||
),
|
||||
Text(fileName, style: context.textTheme.labelMedium),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
@@ -125,10 +104,7 @@ class DownloadTaskTile extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'$progressPercent%',
|
||||
style: context.textTheme.labelSmall,
|
||||
),
|
||||
Text('$progressPercent%', style: context.textTheme.labelSmall),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
||||
@@ -36,11 +36,7 @@ class GalleryStackedChildren extends HookConsumerWidget {
|
||||
shrinkWrap: true,
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: stackElements.length,
|
||||
padding: const EdgeInsets.only(
|
||||
left: 5,
|
||||
right: 5,
|
||||
bottom: 30,
|
||||
),
|
||||
padding: const EdgeInsets.only(left: 5, right: 5, bottom: 30),
|
||||
itemBuilder: (context, index) {
|
||||
final currentAsset = stackElements.elementAt(index);
|
||||
final assetId = currentAsset.remoteId;
|
||||
@@ -63,9 +59,7 @@ class GalleryStackedChildren extends HookConsumerWidget {
|
||||
? const BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.all(Radius.circular(6)),
|
||||
border: Border.fromBorderSide(
|
||||
BorderSide(color: Colors.white, width: 2),
|
||||
),
|
||||
border: Border.fromBorderSide(BorderSide(color: Colors.white, width: 2)),
|
||||
)
|
||||
: const BoxDecoration(
|
||||
color: Colors.white,
|
||||
|
||||
@@ -87,11 +87,7 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||
if (index < totalAssets.value && index >= 0) {
|
||||
final asset = loadAsset(index);
|
||||
await precacheImage(
|
||||
ImmichImage.imageProvider(
|
||||
asset: asset,
|
||||
width: context.width,
|
||||
height: context.height,
|
||||
),
|
||||
ImmichImage.imageProvider(asset: asset, width: context.width, height: context.height),
|
||||
context,
|
||||
onError: onError,
|
||||
);
|
||||
@@ -103,23 +99,20 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(
|
||||
() {
|
||||
if (ref.read(showControlsProvider)) {
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
|
||||
} else {
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive);
|
||||
}
|
||||
useEffect(() {
|
||||
if (ref.read(showControlsProvider)) {
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
|
||||
} else {
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive);
|
||||
}
|
||||
|
||||
// Delay this a bit so we can finish loading the page
|
||||
Timer(const Duration(milliseconds: 400), () {
|
||||
precacheNextImage(currentIndex.value + 1);
|
||||
});
|
||||
// Delay this a bit so we can finish loading the page
|
||||
Timer(const Duration(milliseconds: 400), () {
|
||||
precacheNextImage(currentIndex.value + 1);
|
||||
});
|
||||
|
||||
return null;
|
||||
},
|
||||
const [],
|
||||
);
|
||||
return null;
|
||||
}, const []);
|
||||
|
||||
useEffect(() {
|
||||
final asset = loadAsset(currentIndex.value);
|
||||
@@ -136,9 +129,7 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||
duration: const Duration(seconds: 1),
|
||||
content: Text(
|
||||
"local_asset_cast_failed".tr(),
|
||||
style: context.textTheme.bodyLarge?.copyWith(
|
||||
color: context.primaryColor,
|
||||
),
|
||||
style: context.textTheme.bodyLarge?.copyWith(color: context.primaryColor),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -147,9 +138,7 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}, [
|
||||
ref.watch(castProvider).isCasting,
|
||||
]);
|
||||
}, [ref.watch(castProvider).isCasting]);
|
||||
|
||||
void showInfo() {
|
||||
final asset = ref.read(currentAssetProvider);
|
||||
@@ -157,9 +146,7 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||
return;
|
||||
}
|
||||
showModalBottomSheet(
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(15.0)),
|
||||
),
|
||||
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(15.0))),
|
||||
barrierColor: Colors.transparent,
|
||||
isScrollControlled: true,
|
||||
showDragHandle: true,
|
||||
@@ -174,20 +161,10 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||
expand: false,
|
||||
builder: (context, scrollController) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: context.viewInsets.bottom,
|
||||
),
|
||||
child: ref.watch(appSettingsServiceProvider).getSetting<bool>(
|
||||
AppSettingsEnum.advancedTroubleshooting,
|
||||
)
|
||||
? AdvancedBottomSheet(
|
||||
assetDetail: asset,
|
||||
scrollController: scrollController,
|
||||
)
|
||||
: DetailPanel(
|
||||
asset: asset,
|
||||
scrollController: scrollController,
|
||||
),
|
||||
padding: EdgeInsets.only(bottom: context.viewInsets.bottom),
|
||||
child: ref.watch(appSettingsServiceProvider).getSetting<bool>(AppSettingsEnum.advancedTroubleshooting)
|
||||
? AdvancedBottomSheet(assetDetail: asset, scrollController: scrollController)
|
||||
: DetailPanel(asset: asset, scrollController: scrollController),
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -258,10 +235,7 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||
tightMode: true,
|
||||
initialScale: PhotoViewComputedScale.contained * 0.99,
|
||||
minScale: PhotoViewComputedScale.contained * 0.99,
|
||||
errorBuilder: (context, error, stackTrace) => ImmichImage(
|
||||
asset,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
errorBuilder: (context, error, stackTrace) => ImmichImage(asset, fit: BoxFit.contain),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -283,11 +257,7 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||
asset: asset,
|
||||
image: Image(
|
||||
key: ValueKey(asset),
|
||||
image: ImmichImage.imageProvider(
|
||||
asset: asset,
|
||||
width: context.width,
|
||||
height: context.height,
|
||||
),
|
||||
image: ImmichImage.imageProvider(asset: asset, width: context.width, height: context.height),
|
||||
fit: BoxFit.contain,
|
||||
height: context.height,
|
||||
width: context.width,
|
||||
@@ -342,17 +312,8 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
BackdropFilter(
|
||||
filter: ui.ImageFilter.blur(
|
||||
sigmaX: 10,
|
||||
sigmaY: 10,
|
||||
),
|
||||
),
|
||||
ImmichThumbnail(
|
||||
key: ValueKey(asset),
|
||||
asset: asset,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
BackdropFilter(filter: ui.ImageFilter.blur(sigmaX: 10, sigmaY: 10)),
|
||||
ImmichThumbnail(key: ValueKey(asset), asset: asset, fit: BoxFit.contain),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -361,9 +322,9 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||
scrollPhysics: isZoomed.value
|
||||
? const NeverScrollableScrollPhysics() // Don't allow paging while scrolled in
|
||||
: (Platform.isIOS
|
||||
? const FastScrollPhysics() // Use bouncing physics for iOS
|
||||
: const FastClampingScrollPhysics() // Use heavy physics for Android
|
||||
),
|
||||
? const FastScrollPhysics() // Use bouncing physics for iOS
|
||||
: const FastClampingScrollPhysics() // Use heavy physics for Android
|
||||
),
|
||||
itemCount: totalAssets.value,
|
||||
scrollDirection: Axis.horizontal,
|
||||
onPageChanged: (value, _) {
|
||||
@@ -401,9 +362,7 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||
duration: const Duration(seconds: 2),
|
||||
content: Text(
|
||||
"local_asset_cast_failed".tr(),
|
||||
style: context.textTheme.bodyLarge?.copyWith(
|
||||
color: context.primaryColor,
|
||||
),
|
||||
style: context.textTheme.bodyLarge?.copyWith(color: context.primaryColor),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -416,10 +375,7 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: GalleryAppBar(
|
||||
key: const ValueKey('app-bar'),
|
||||
showInfo: showInfo,
|
||||
),
|
||||
child: GalleryAppBar(key: const ValueKey('app-bar'), showInfo: showInfo),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
|
||||
@@ -79,10 +79,8 @@ class HeaderSettingsPage extends HookConsumerWidget {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 16.0),
|
||||
itemCount: list.length,
|
||||
itemBuilder: (ctx, index) => list[index],
|
||||
separatorBuilder: (context, index) => const Padding(
|
||||
padding: EdgeInsets.only(bottom: 16.0, left: 8, right: 8),
|
||||
child: Divider(),
|
||||
),
|
||||
separatorBuilder: (context, index) =>
|
||||
const Padding(padding: EdgeInsets.only(bottom: 16.0, left: 8, right: 8), child: Divider()),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -109,12 +107,9 @@ class HeaderKeyValueSettings extends StatelessWidget {
|
||||
final SettingsHeader header;
|
||||
final Function() onRemove;
|
||||
|
||||
HeaderKeyValueSettings({
|
||||
super.key,
|
||||
required this.header,
|
||||
required this.onRemove,
|
||||
}) : keyController = TextEditingController(text: header.key),
|
||||
valueController = TextEditingController(text: header.value);
|
||||
HeaderKeyValueSettings({super.key, required this.header, required this.onRemove})
|
||||
: keyController = TextEditingController(text: header.key),
|
||||
valueController = TextEditingController(text: header.value);
|
||||
|
||||
String? emptyFieldValidator(String? value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
@@ -150,9 +145,7 @@ class HeaderKeyValueSettings extends StatelessWidget {
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 8),
|
||||
child: IconButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
),
|
||||
style: ElevatedButton.styleFrom(padding: const EdgeInsets.symmetric(vertical: 12)),
|
||||
color: Colors.red[400],
|
||||
onPressed: onRemove,
|
||||
icon: const Icon(Icons.delete_outline),
|
||||
|
||||
@@ -8,10 +8,7 @@ class LargeLeadingTile extends StatelessWidget {
|
||||
required this.onTap,
|
||||
required this.title,
|
||||
this.subtitle,
|
||||
this.leadingPadding = const EdgeInsets.symmetric(
|
||||
vertical: 8,
|
||||
horizontal: 16.0,
|
||||
),
|
||||
this.leadingPadding = const EdgeInsets.symmetric(vertical: 8, horizontal: 16.0),
|
||||
this.borderRadius = 20.0,
|
||||
this.trailing,
|
||||
this.selected = false,
|
||||
@@ -47,18 +44,12 @@ class LargeLeadingTile extends StatelessWidget {
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Padding(
|
||||
padding: leadingPadding,
|
||||
child: leading,
|
||||
),
|
||||
Padding(padding: leadingPadding, child: leading),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: context.width * 0.6,
|
||||
child: title,
|
||||
),
|
||||
SizedBox(width: context.width * 0.6, child: title),
|
||||
subtitle ?? const SizedBox.shrink(),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -78,17 +78,15 @@ class NativeVideoViewerPage extends HookConsumerWidget {
|
||||
throw Exception('No file found for the video');
|
||||
}
|
||||
|
||||
final source = await VideoSource.init(
|
||||
path: file.path,
|
||||
type: VideoSourceType.file,
|
||||
);
|
||||
final source = await VideoSource.init(path: file.path, type: VideoSourceType.file);
|
||||
return source;
|
||||
}
|
||||
|
||||
// Use a network URL for the video player controller
|
||||
final serverEndpoint = Store.get(StoreKey.serverEndpoint);
|
||||
final isOriginalVideo =
|
||||
ref.read(appSettingsServiceProvider).getSetting<bool>(AppSettingsEnum.loadOriginalVideo);
|
||||
final isOriginalVideo = ref
|
||||
.read(appSettingsServiceProvider)
|
||||
.getSetting<bool>(AppSettingsEnum.loadOriginalVideo);
|
||||
final String postfixUrl = isOriginalVideo ? 'original' : 'video/playback';
|
||||
final String videoUrl = asset.livePhotoVideoId != null
|
||||
? '$serverEndpoint/assets/${asset.livePhotoVideoId}/$postfixUrl'
|
||||
@@ -101,30 +99,24 @@ class NativeVideoViewerPage extends HookConsumerWidget {
|
||||
);
|
||||
return source;
|
||||
} catch (error) {
|
||||
log.severe(
|
||||
'Error creating video source for asset ${asset.fileName}: $error',
|
||||
);
|
||||
log.severe('Error creating video source for asset ${asset.fileName}: $error');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
final videoSource = useMemoized<Future<VideoSource?>>(() => createSource());
|
||||
final aspectRatio = useState<double?>(asset.aspectRatio);
|
||||
useMemoized(
|
||||
() async {
|
||||
if (!context.mounted || aspectRatio.value != null) {
|
||||
return null;
|
||||
}
|
||||
useMemoized(() async {
|
||||
if (!context.mounted || aspectRatio.value != null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
aspectRatio.value = await ref.read(assetServiceProvider).getAspectRatio(asset);
|
||||
} catch (error) {
|
||||
log.severe(
|
||||
'Error getting aspect ratio for asset ${asset.fileName}: $error',
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
try {
|
||||
aspectRatio.value = await ref.read(assetServiceProvider).getAspectRatio(asset);
|
||||
} catch (error) {
|
||||
log.severe('Error getting aspect ratio for asset ${asset.fileName}: $error');
|
||||
}
|
||||
});
|
||||
|
||||
void checkIfBuffering() {
|
||||
if (!context.mounted) {
|
||||
@@ -134,8 +126,9 @@ class NativeVideoViewerPage extends HookConsumerWidget {
|
||||
final videoPlayback = ref.read(videoPlaybackValueProvider);
|
||||
if ((isBuffering.value || videoPlayback.state == VideoPlaybackState.initializing) &&
|
||||
videoPlayback.state != VideoPlaybackState.buffering) {
|
||||
ref.read(videoPlaybackValueProvider.notifier).value =
|
||||
videoPlayback.copyWith(state: VideoPlaybackState.buffering);
|
||||
ref.read(videoPlaybackValueProvider.notifier).value = videoPlayback.copyWith(
|
||||
state: VideoPlaybackState.buffering,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -322,48 +315,42 @@ class NativeVideoViewerPage extends HookConsumerWidget {
|
||||
// This delay seems like a hacky way to resolve underlying bugs in video
|
||||
// playback, but other resolutions failed thus far
|
||||
Timer(
|
||||
Platform.isIOS
|
||||
? Duration(milliseconds: 300 * playbackDelayFactor)
|
||||
: imageToVideo
|
||||
? Duration(milliseconds: 200 * playbackDelayFactor)
|
||||
: Duration(milliseconds: 400 * playbackDelayFactor), () {
|
||||
if (!context.mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
currentAsset.value = value;
|
||||
if (currentAsset.value == asset) {
|
||||
onPlaybackReady();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
useEffect(
|
||||
() {
|
||||
// If opening a remote video from a hero animation, delay visibility to avoid a stutter
|
||||
final timer = isVisible.value
|
||||
? null
|
||||
: Timer(
|
||||
const Duration(milliseconds: 300),
|
||||
() => isVisible.value = true,
|
||||
);
|
||||
|
||||
return () {
|
||||
timer?.cancel();
|
||||
final playerController = controller.value;
|
||||
if (playerController == null) {
|
||||
Platform.isIOS
|
||||
? Duration(milliseconds: 300 * playbackDelayFactor)
|
||||
: imageToVideo
|
||||
? Duration(milliseconds: 200 * playbackDelayFactor)
|
||||
: Duration(milliseconds: 400 * playbackDelayFactor),
|
||||
() {
|
||||
if (!context.mounted) {
|
||||
return;
|
||||
}
|
||||
removeListeners(playerController);
|
||||
playerController.stop().catchError((error) {
|
||||
log.fine('Error stopping video: $error');
|
||||
});
|
||||
|
||||
WakelockPlus.disable();
|
||||
};
|
||||
},
|
||||
const [],
|
||||
);
|
||||
currentAsset.value = value;
|
||||
if (currentAsset.value == asset) {
|
||||
onPlaybackReady();
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
useEffect(() {
|
||||
// If opening a remote video from a hero animation, delay visibility to avoid a stutter
|
||||
final timer = isVisible.value ? null : Timer(const Duration(milliseconds: 300), () => isVisible.value = true);
|
||||
|
||||
return () {
|
||||
timer?.cancel();
|
||||
final playerController = controller.value;
|
||||
if (playerController == null) {
|
||||
return;
|
||||
}
|
||||
removeListeners(playerController);
|
||||
playerController.stop().catchError((error) {
|
||||
log.fine('Error stopping video: $error');
|
||||
});
|
||||
|
||||
WakelockPlus.disable();
|
||||
};
|
||||
}, const []);
|
||||
|
||||
useOnAppLifecycleStateChange((_, state) async {
|
||||
if (state == AppLifecycleState.resumed && shouldPlayOnForeground.value) {
|
||||
@@ -393,12 +380,7 @@ class NativeVideoViewerPage extends HookConsumerWidget {
|
||||
child: AspectRatio(
|
||||
key: ValueKey(asset),
|
||||
aspectRatio: aspectRatio.value!,
|
||||
child: isCurrent
|
||||
? NativeVideoPlayerView(
|
||||
key: ValueKey(asset),
|
||||
onViewReady: initController,
|
||||
)
|
||||
: null,
|
||||
child: isCurrent ? NativeVideoPlayerView(key: ValueKey(asset), onViewReady: initController) : null,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -18,67 +18,31 @@ import 'package:immich_mobile/widgets/settings/preference_settings/preference_se
|
||||
import 'package:immich_mobile/widgets/settings/settings_card.dart';
|
||||
|
||||
enum SettingSection {
|
||||
beta(
|
||||
'beta_sync',
|
||||
Icons.sync_outlined,
|
||||
"beta_sync_subtitle",
|
||||
),
|
||||
advanced(
|
||||
'advanced',
|
||||
Icons.build_outlined,
|
||||
"advanced_settings_tile_subtitle",
|
||||
),
|
||||
assetViewer(
|
||||
'asset_viewer_settings_title',
|
||||
Icons.image_outlined,
|
||||
"asset_viewer_settings_subtitle",
|
||||
),
|
||||
backup(
|
||||
'backup',
|
||||
Icons.cloud_upload_outlined,
|
||||
"backup_setting_subtitle",
|
||||
),
|
||||
languages(
|
||||
'language',
|
||||
Icons.language,
|
||||
"setting_languages_subtitle",
|
||||
),
|
||||
networking(
|
||||
'networking_settings',
|
||||
Icons.wifi,
|
||||
"networking_subtitle",
|
||||
),
|
||||
notifications(
|
||||
'notifications',
|
||||
Icons.notifications_none_rounded,
|
||||
"setting_notifications_subtitle",
|
||||
),
|
||||
preferences(
|
||||
'preferences_settings_title',
|
||||
Icons.interests_outlined,
|
||||
"preferences_settings_subtitle",
|
||||
),
|
||||
timeline(
|
||||
'asset_list_settings_title',
|
||||
Icons.auto_awesome_mosaic_outlined,
|
||||
"asset_list_settings_subtitle",
|
||||
);
|
||||
beta('beta_sync', Icons.sync_outlined, "beta_sync_subtitle"),
|
||||
advanced('advanced', Icons.build_outlined, "advanced_settings_tile_subtitle"),
|
||||
assetViewer('asset_viewer_settings_title', Icons.image_outlined, "asset_viewer_settings_subtitle"),
|
||||
backup('backup', Icons.cloud_upload_outlined, "backup_setting_subtitle"),
|
||||
languages('language', Icons.language, "setting_languages_subtitle"),
|
||||
networking('networking_settings', Icons.wifi, "networking_subtitle"),
|
||||
notifications('notifications', Icons.notifications_none_rounded, "setting_notifications_subtitle"),
|
||||
preferences('preferences_settings_title', Icons.interests_outlined, "preferences_settings_subtitle"),
|
||||
timeline('asset_list_settings_title', Icons.auto_awesome_mosaic_outlined, "asset_list_settings_subtitle");
|
||||
|
||||
final String title;
|
||||
final String subtitle;
|
||||
final IconData icon;
|
||||
|
||||
Widget get widget => switch (this) {
|
||||
SettingSection.beta => const _BetaLandscapeToggle(),
|
||||
SettingSection.advanced => const AdvancedSettings(),
|
||||
SettingSection.assetViewer => const AssetViewerSettings(),
|
||||
SettingSection.backup => const BackupSettings(),
|
||||
SettingSection.languages => const LanguageSettings(),
|
||||
SettingSection.networking => const NetworkingSettings(),
|
||||
SettingSection.notifications => const NotificationSetting(),
|
||||
SettingSection.preferences => const PreferenceSetting(),
|
||||
SettingSection.timeline => const AssetListSettings(),
|
||||
};
|
||||
SettingSection.beta => const _BetaLandscapeToggle(),
|
||||
SettingSection.advanced => const AdvancedSettings(),
|
||||
SettingSection.assetViewer => const AssetViewerSettings(),
|
||||
SettingSection.backup => const BackupSettings(),
|
||||
SettingSection.languages => const LanguageSettings(),
|
||||
SettingSection.networking => const NetworkingSettings(),
|
||||
SettingSection.notifications => const NotificationSetting(),
|
||||
SettingSection.preferences => const PreferenceSetting(),
|
||||
SettingSection.timeline => const AssetListSettings(),
|
||||
};
|
||||
|
||||
const SettingSection(this.title, this.icon, this.subtitle);
|
||||
}
|
||||
@@ -91,10 +55,7 @@ class SettingsPage extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
context.locale;
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
centerTitle: false,
|
||||
title: const Text('settings').tr(),
|
||||
),
|
||||
appBar: AppBar(centerTitle: false, title: const Text('settings').tr()),
|
||||
body: context.isMobile ? const _MobileLayout() : const _TabletLayout(),
|
||||
);
|
||||
}
|
||||
@@ -164,10 +125,7 @@ class _TabletLayout extends HookWidget {
|
||||
),
|
||||
),
|
||||
const VerticalDivider(width: 1),
|
||||
Expanded(
|
||||
flex: 4,
|
||||
child: selectedSection.value.widget,
|
||||
),
|
||||
Expanded(flex: 4, child: selectedSection.value.widget),
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -198,10 +156,7 @@ class SettingsSubPage extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
context.locale;
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
centerTitle: false,
|
||||
title: Text(section.title).tr(),
|
||||
),
|
||||
appBar: AppBar(centerTitle: false, title: Text(section.title).tr()),
|
||||
body: section.widget,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -43,31 +43,26 @@ class SplashScreenPageState extends ConsumerState<SplashScreenPage> {
|
||||
final accessToken = Store.tryGet(StoreKey.accessToken);
|
||||
|
||||
if (accessToken != null && serverUrl != null && endpoint != null) {
|
||||
ref.read(authProvider.notifier).saveAuthInfo(accessToken: accessToken).then(
|
||||
(a) => {
|
||||
log.info('Successfully updated auth info with access token: $accessToken'),
|
||||
},
|
||||
ref
|
||||
.read(authProvider.notifier)
|
||||
.saveAuthInfo(accessToken: accessToken)
|
||||
.then(
|
||||
(a) => {log.info('Successfully updated auth info with access token: $accessToken')},
|
||||
onError: (exception) => {
|
||||
log.severe(
|
||||
'Failed to update auth info with access token: $accessToken',
|
||||
),
|
||||
log.severe('Failed to update auth info with access token: $accessToken'),
|
||||
ref.read(authProvider.notifier).logout(),
|
||||
context.replaceRoute(const LoginRoute()),
|
||||
},
|
||||
);
|
||||
} else {
|
||||
log.severe(
|
||||
'Missing crucial offline login info - Logging out completely',
|
||||
);
|
||||
log.severe('Missing crucial offline login info - Logging out completely');
|
||||
ref.read(authProvider.notifier).logout();
|
||||
context.replaceRoute(const LoginRoute());
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.router.current.name == SplashScreenRoute.name) {
|
||||
context.replaceRoute(
|
||||
Store.isBetaTimelineEnabled ? const TabShellRoute() : const TabControllerRoute(),
|
||||
);
|
||||
context.replaceRoute(Store.isBetaTimelineEnabled ? const TabShellRoute() : const TabControllerRoute());
|
||||
}
|
||||
|
||||
if (Store.isBetaTimelineEnabled) {
|
||||
@@ -85,11 +80,7 @@ class SplashScreenPageState extends ConsumerState<SplashScreenPage> {
|
||||
Widget build(BuildContext context) {
|
||||
return const Scaffold(
|
||||
body: Center(
|
||||
child: Image(
|
||||
image: AssetImage('assets/immich-logo.png'),
|
||||
width: 80,
|
||||
filterQuality: FilterQuality.high,
|
||||
),
|
||||
child: Image(image: AssetImage('assets/immich-logo.png'), width: 80, filterQuality: FilterQuality.high),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -36,9 +36,7 @@ class TabControllerPage extends HookConsumerWidget {
|
||||
width: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
context.primaryColor,
|
||||
),
|
||||
valueColor: AlwaysStoppedAnimation<Color>(context.primaryColor),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -65,51 +63,31 @@ class TabControllerPage extends HookConsumerWidget {
|
||||
final navigationDestinations = [
|
||||
NavigationDestination(
|
||||
label: 'photos'.tr(),
|
||||
icon: const Icon(
|
||||
Icons.photo_library_outlined,
|
||||
),
|
||||
icon: const Icon(Icons.photo_library_outlined),
|
||||
selectedIcon: buildIcon(
|
||||
isProcessing: isRefreshingAssets,
|
||||
icon: Icon(
|
||||
Icons.photo_library,
|
||||
color: context.primaryColor,
|
||||
),
|
||||
icon: Icon(Icons.photo_library, color: context.primaryColor),
|
||||
),
|
||||
),
|
||||
NavigationDestination(
|
||||
label: 'search'.tr(),
|
||||
icon: const Icon(
|
||||
Icons.search_rounded,
|
||||
),
|
||||
selectedIcon: Icon(
|
||||
Icons.search,
|
||||
color: context.primaryColor,
|
||||
),
|
||||
icon: const Icon(Icons.search_rounded),
|
||||
selectedIcon: Icon(Icons.search, color: context.primaryColor),
|
||||
),
|
||||
NavigationDestination(
|
||||
label: 'albums'.tr(),
|
||||
icon: const Icon(
|
||||
Icons.photo_album_outlined,
|
||||
),
|
||||
icon: const Icon(Icons.photo_album_outlined),
|
||||
selectedIcon: buildIcon(
|
||||
isProcessing: isRefreshingRemoteAlbums,
|
||||
icon: Icon(
|
||||
Icons.photo_album_rounded,
|
||||
color: context.primaryColor,
|
||||
),
|
||||
icon: Icon(Icons.photo_album_rounded, color: context.primaryColor),
|
||||
),
|
||||
),
|
||||
NavigationDestination(
|
||||
label: 'library'.tr(),
|
||||
icon: const Icon(
|
||||
Icons.space_dashboard_outlined,
|
||||
),
|
||||
icon: const Icon(Icons.space_dashboard_outlined),
|
||||
selectedIcon: buildIcon(
|
||||
isProcessing: isRefreshingAssets,
|
||||
icon: Icon(
|
||||
Icons.space_dashboard_rounded,
|
||||
color: context.primaryColor,
|
||||
),
|
||||
icon: Icon(Icons.space_dashboard_rounded, color: context.primaryColor),
|
||||
),
|
||||
),
|
||||
];
|
||||
@@ -125,13 +103,7 @@ class TabControllerPage extends HookConsumerWidget {
|
||||
Widget navigationRail(TabsRouter tabsRouter) {
|
||||
return NavigationRail(
|
||||
destinations: navigationDestinations
|
||||
.map(
|
||||
(e) => NavigationRailDestination(
|
||||
icon: e.icon,
|
||||
label: Text(e.label),
|
||||
selectedIcon: e.selectedIcon,
|
||||
),
|
||||
)
|
||||
.map((e) => NavigationRailDestination(icon: e.icon, label: Text(e.label), selectedIcon: e.selectedIcon))
|
||||
.toList(),
|
||||
onDestinationSelected: (index) => onNavigationSelected(tabsRouter, index),
|
||||
selectedIndex: tabsRouter.activeIndex,
|
||||
@@ -142,17 +114,9 @@ class TabControllerPage extends HookConsumerWidget {
|
||||
|
||||
final multiselectEnabled = ref.watch(multiselectProvider);
|
||||
return AutoTabsRouter(
|
||||
routes: [
|
||||
const PhotosRoute(),
|
||||
SearchRoute(),
|
||||
const AlbumsRoute(),
|
||||
const LibraryRoute(),
|
||||
],
|
||||
routes: [const PhotosRoute(), SearchRoute(), const AlbumsRoute(), const LibraryRoute()],
|
||||
duration: const Duration(milliseconds: 600),
|
||||
transitionBuilder: (context, child, animation) => FadeTransition(
|
||||
opacity: animation,
|
||||
child: child,
|
||||
),
|
||||
transitionBuilder: (context, child, animation) => FadeTransition(opacity: animation, child: child),
|
||||
builder: (context, child) {
|
||||
final tabsRouter = AutoTabsRouter.of(context);
|
||||
return PopScope(
|
||||
|
||||
@@ -56,56 +56,30 @@ class _TabShellPageState extends ConsumerState<TabShellPage> {
|
||||
final navigationDestinations = [
|
||||
NavigationDestination(
|
||||
label: 'photos'.tr(),
|
||||
icon: const Icon(
|
||||
Icons.photo_library_outlined,
|
||||
),
|
||||
selectedIcon: Icon(
|
||||
Icons.photo_library,
|
||||
color: context.primaryColor,
|
||||
),
|
||||
icon: const Icon(Icons.photo_library_outlined),
|
||||
selectedIcon: Icon(Icons.photo_library, color: context.primaryColor),
|
||||
),
|
||||
NavigationDestination(
|
||||
label: 'search'.tr(),
|
||||
icon: const Icon(
|
||||
Icons.search_rounded,
|
||||
),
|
||||
selectedIcon: Icon(
|
||||
Icons.search,
|
||||
color: context.primaryColor,
|
||||
),
|
||||
icon: const Icon(Icons.search_rounded),
|
||||
selectedIcon: Icon(Icons.search, color: context.primaryColor),
|
||||
),
|
||||
NavigationDestination(
|
||||
label: 'albums'.tr(),
|
||||
icon: const Icon(
|
||||
Icons.photo_album_outlined,
|
||||
),
|
||||
selectedIcon: Icon(
|
||||
Icons.photo_album_rounded,
|
||||
color: context.primaryColor,
|
||||
),
|
||||
icon: const Icon(Icons.photo_album_outlined),
|
||||
selectedIcon: Icon(Icons.photo_album_rounded, color: context.primaryColor),
|
||||
),
|
||||
NavigationDestination(
|
||||
label: 'library'.tr(),
|
||||
icon: const Icon(
|
||||
Icons.space_dashboard_outlined,
|
||||
),
|
||||
selectedIcon: Icon(
|
||||
Icons.space_dashboard_rounded,
|
||||
color: context.primaryColor,
|
||||
),
|
||||
icon: const Icon(Icons.space_dashboard_outlined),
|
||||
selectedIcon: Icon(Icons.space_dashboard_rounded, color: context.primaryColor),
|
||||
),
|
||||
];
|
||||
|
||||
Widget navigationRail(TabsRouter tabsRouter) {
|
||||
return NavigationRail(
|
||||
destinations: navigationDestinations
|
||||
.map(
|
||||
(e) => NavigationRailDestination(
|
||||
icon: e.icon,
|
||||
label: Text(e.label),
|
||||
selectedIcon: e.selectedIcon,
|
||||
),
|
||||
)
|
||||
.map((e) => NavigationRailDestination(icon: e.icon, label: Text(e.label), selectedIcon: e.selectedIcon))
|
||||
.toList(),
|
||||
onDestinationSelected: (index) => _onNavigationSelected(tabsRouter, index, ref),
|
||||
selectedIndex: tabsRouter.activeIndex,
|
||||
@@ -115,17 +89,9 @@ class _TabShellPageState extends ConsumerState<TabShellPage> {
|
||||
}
|
||||
|
||||
return AutoTabsRouter(
|
||||
routes: [
|
||||
const MainTimelineRoute(),
|
||||
DriftSearchRoute(),
|
||||
const DriftAlbumsRoute(),
|
||||
const DriftLibraryRoute(),
|
||||
],
|
||||
routes: [const MainTimelineRoute(), DriftSearchRoute(), const DriftAlbumsRoute(), const DriftLibraryRoute()],
|
||||
duration: const Duration(milliseconds: 600),
|
||||
transitionBuilder: (context, child, animation) => FadeTransition(
|
||||
opacity: animation,
|
||||
child: child,
|
||||
),
|
||||
transitionBuilder: (context, child, animation) => FadeTransition(opacity: animation, child: child),
|
||||
builder: (context, child) {
|
||||
final tabsRouter = AutoTabsRouter.of(context);
|
||||
return PopScope(
|
||||
@@ -142,10 +108,7 @@ class _TabShellPageState extends ConsumerState<TabShellPage> {
|
||||
],
|
||||
)
|
||||
: child,
|
||||
bottomNavigationBar: _BottomNavigationBar(
|
||||
tabsRouter: tabsRouter,
|
||||
destinations: navigationDestinations,
|
||||
),
|
||||
bottomNavigationBar: _BottomNavigationBar(tabsRouter: tabsRouter, destinations: navigationDestinations),
|
||||
),
|
||||
);
|
||||
},
|
||||
@@ -175,10 +138,7 @@ void _onNavigationSelected(TabsRouter router, int index, WidgetRef ref) {
|
||||
}
|
||||
|
||||
class _BottomNavigationBar extends ConsumerWidget {
|
||||
const _BottomNavigationBar({
|
||||
required this.tabsRouter,
|
||||
required this.destinations,
|
||||
});
|
||||
const _BottomNavigationBar({required this.tabsRouter, required this.destinations});
|
||||
|
||||
final List<Widget> destinations;
|
||||
final TabsRouter tabsRouter;
|
||||
|
||||
Reference in New Issue
Block a user