mirror of
https://github.com/immich-app/immich.git
synced 2024-12-29 15:11:58 +00:00
feat(mobile): memories (#2988)
* Add page view * Nice page view * refactor file structure * Added card * invalidating data * transition * styling * correct styleing * refactor * click to navigate * styling * TODO * clean up * clean up * pr feedback * pr feedback * better loading indicator
This commit is contained in:
parent
0e8d235148
commit
39a885a37c
17 changed files with 532 additions and 2 deletions
BIN
mobile/fonts/WorkSans-Black.ttf
Normal file
BIN
mobile/fonts/WorkSans-Black.ttf
Normal file
Binary file not shown.
BIN
mobile/fonts/WorkSans-Bold.ttf
Normal file
BIN
mobile/fonts/WorkSans-Bold.ttf
Normal file
Binary file not shown.
BIN
mobile/fonts/WorkSans-ExtraBold.ttf
Normal file
BIN
mobile/fonts/WorkSans-ExtraBold.ttf
Normal file
Binary file not shown.
BIN
mobile/fonts/WorkSans-Medium.ttf
Normal file
BIN
mobile/fonts/WorkSans-Medium.ttf
Normal file
Binary file not shown.
BIN
mobile/fonts/WorkSans-SemiBold.ttf
Normal file
BIN
mobile/fonts/WorkSans-SemiBold.ttf
Normal file
Binary file not shown.
|
@ -14,6 +14,7 @@ import 'package:immich_mobile/modules/home/providers/multiselect.provider.dart';
|
||||||
import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart';
|
import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart';
|
||||||
import 'package:immich_mobile/modules/home/ui/control_bottom_app_bar.dart';
|
import 'package:immich_mobile/modules/home/ui/control_bottom_app_bar.dart';
|
||||||
import 'package:immich_mobile/modules/home/ui/home_page_app_bar.dart';
|
import 'package:immich_mobile/modules/home/ui/home_page_app_bar.dart';
|
||||||
|
import 'package:immich_mobile/modules/memories/ui/memory_lane.dart';
|
||||||
import 'package:immich_mobile/modules/home/ui/profile_drawer/profile_drawer.dart';
|
import 'package:immich_mobile/modules/home/ui/profile_drawer/profile_drawer.dart';
|
||||||
import 'package:immich_mobile/routing/router.dart';
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
import 'package:immich_mobile/shared/models/album.dart';
|
import 'package:immich_mobile/shared/models/album.dart';
|
||||||
|
@ -310,6 +311,7 @@ class HomePage extends HookConsumerWidget {
|
||||||
listener: selectionListener,
|
listener: selectionListener,
|
||||||
selectionActive: selectionEnabledHook.value,
|
selectionActive: selectionEnabledHook.value,
|
||||||
onRefresh: refreshAssets,
|
onRefresh: refreshAssets,
|
||||||
|
topWidget: const MemoryLane(),
|
||||||
),
|
),
|
||||||
error: (error, _) => Center(child: Text(error.toString())),
|
error: (error, _) => Center(child: Text(error.toString())),
|
||||||
loading: buildLoadingIndicator,
|
loading: buildLoadingIndicator,
|
||||||
|
|
40
mobile/lib/modules/memories/models/memory.dart
Normal file
40
mobile/lib/modules/memories/models/memory.dart
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
||||||
|
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
|
|
||||||
|
import 'package:immich_mobile/shared/models/asset.dart';
|
||||||
|
|
||||||
|
class Memory {
|
||||||
|
final String title;
|
||||||
|
final List<Asset> assets;
|
||||||
|
Memory({
|
||||||
|
required this.title,
|
||||||
|
required this.assets,
|
||||||
|
});
|
||||||
|
|
||||||
|
Memory copyWith({
|
||||||
|
String? title,
|
||||||
|
List<Asset>? assets,
|
||||||
|
}) {
|
||||||
|
return Memory(
|
||||||
|
title: title ?? this.title,
|
||||||
|
assets: assets ?? this.assets,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'Memory(title: $title, assets: $assets)';
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
if (identical(this, other)) return true;
|
||||||
|
final listEquals = const DeepCollectionEquality().equals;
|
||||||
|
|
||||||
|
return other is Memory &&
|
||||||
|
other.title == title &&
|
||||||
|
listEquals(other.assets, assets);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => title.hashCode ^ assets.hashCode;
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/modules/memories/models/memory.dart';
|
||||||
|
import 'package:immich_mobile/modules/memories/services/memory.service.dart';
|
||||||
|
|
||||||
|
final memoryFutureProvider = FutureProvider<List<Memory>?>((ref) async {
|
||||||
|
final service = ref.watch(memoryServiceProvider);
|
||||||
|
|
||||||
|
return await service.getMemoryLane();
|
||||||
|
});
|
50
mobile/lib/modules/memories/services/memory.service.dart
Normal file
50
mobile/lib/modules/memories/services/memory.service.dart
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/shared/models/asset.dart';
|
||||||
|
import 'package:immich_mobile/modules/memories/models/memory.dart';
|
||||||
|
import 'package:immich_mobile/shared/providers/api.provider.dart';
|
||||||
|
import 'package:immich_mobile/shared/services/api.service.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
|
||||||
|
final memoryServiceProvider = StateProvider<MemoryService>((ref) {
|
||||||
|
return MemoryService(
|
||||||
|
ref.watch(apiServiceProvider),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
class MemoryService {
|
||||||
|
final log = Logger("MemoryService");
|
||||||
|
|
||||||
|
final ApiService _apiService;
|
||||||
|
|
||||||
|
MemoryService(this._apiService);
|
||||||
|
|
||||||
|
Future<List<Memory>?> getMemoryLane() async {
|
||||||
|
try {
|
||||||
|
final now = DateTime.now();
|
||||||
|
final beginningOfDate = DateTime(now.year, now.month, now.day);
|
||||||
|
final data = await _apiService.assetApi.getMemoryLane(
|
||||||
|
beginningOfDate,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (data == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Memory> memories = [];
|
||||||
|
for (final MemoryLaneResponseDto(:title, :assets) in data) {
|
||||||
|
memories.add(
|
||||||
|
Memory(
|
||||||
|
title: title,
|
||||||
|
assets: assets.map((a) => Asset.remote(a)).toList(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return memories.isNotEmpty ? memories : null;
|
||||||
|
} catch (error, stack) {
|
||||||
|
log.severe("Cannot get memories ${error.toString()}", error, stack);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
121
mobile/lib/modules/memories/ui/memory_card.dart
Normal file
121
mobile/lib/modules/memories/ui/memory_card.dart
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/shared/models/asset.dart';
|
||||||
|
import 'package:immich_mobile/shared/models/store.dart';
|
||||||
|
import 'package:immich_mobile/shared/ui/immich_image.dart';
|
||||||
|
import 'package:immich_mobile/utils/image_url_builder.dart';
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
|
||||||
|
class MemoryCard extends HookConsumerWidget {
|
||||||
|
final Asset asset;
|
||||||
|
final void Function() onTap;
|
||||||
|
final void Function() onClose;
|
||||||
|
final String title;
|
||||||
|
final String? rightCornerText;
|
||||||
|
final bool showTitle;
|
||||||
|
|
||||||
|
const MemoryCard({
|
||||||
|
required this.asset,
|
||||||
|
required this.onTap,
|
||||||
|
required this.onClose,
|
||||||
|
required this.title,
|
||||||
|
required this.showTitle,
|
||||||
|
this.rightCornerText,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final authToken = 'Bearer ${Store.get(StoreKey.accessToken)}';
|
||||||
|
|
||||||
|
buildTitle() {
|
||||||
|
return Text(
|
||||||
|
title,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 24.0,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Card(
|
||||||
|
color: Colors.black,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(25.0),
|
||||||
|
side: const BorderSide(
|
||||||
|
color: Colors.black,
|
||||||
|
width: 1.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
clipBehavior: Clip.hardEdge,
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
image: DecorationImage(
|
||||||
|
image: CachedNetworkImageProvider(
|
||||||
|
getThumbnailUrl(
|
||||||
|
asset,
|
||||||
|
),
|
||||||
|
cacheKey: getThumbnailCacheKey(
|
||||||
|
asset,
|
||||||
|
),
|
||||||
|
headers: {"Authorization": authToken},
|
||||||
|
),
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: BackdropFilter(
|
||||||
|
filter: ImageFilter.blur(sigmaX: 60, sigmaY: 60),
|
||||||
|
child: Container(
|
||||||
|
decoration:
|
||||||
|
BoxDecoration(color: Colors.black.withOpacity(0.25)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: onTap,
|
||||||
|
child: ImmichImage(
|
||||||
|
asset,
|
||||||
|
fit: BoxFit.fitWidth,
|
||||||
|
height: double.infinity,
|
||||||
|
width: double.infinity,
|
||||||
|
type: ThumbnailFormat.JPEG,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
top: 2.0,
|
||||||
|
left: 2.0,
|
||||||
|
child: IconButton(
|
||||||
|
onPressed: onClose,
|
||||||
|
icon: const Icon(Icons.close_rounded),
|
||||||
|
color: Colors.grey[400],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
right: 18.0,
|
||||||
|
top: 18.0,
|
||||||
|
child: Text(
|
||||||
|
rightCornerText ?? "",
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.grey[200],
|
||||||
|
fontSize: 12.0,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (showTitle)
|
||||||
|
Positioned(
|
||||||
|
left: 18.0,
|
||||||
|
bottom: 18.0,
|
||||||
|
child: buildTitle(),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
89
mobile/lib/modules/memories/ui/memory_lane.dart
Normal file
89
mobile/lib/modules/memories/ui/memory_lane.dart
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
import 'package:auto_route/auto_route.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/modules/memories/providers/memory.provider.dart';
|
||||||
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
|
import 'package:immich_mobile/shared/ui/immich_image.dart';
|
||||||
|
|
||||||
|
class MemoryLane extends HookConsumerWidget {
|
||||||
|
const MemoryLane({super.key});
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final memoryLaneFutureProvider = ref.watch(memoryFutureProvider);
|
||||||
|
|
||||||
|
final memoryLane = memoryLaneFutureProvider
|
||||||
|
.whenData(
|
||||||
|
(memories) => memories != null
|
||||||
|
? SizedBox(
|
||||||
|
height: 200,
|
||||||
|
child: ListView.builder(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
shrinkWrap: true,
|
||||||
|
itemCount: memories.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final memory = memories[index];
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 8.0, bottom: 8),
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
AutoRouter.of(context).push(
|
||||||
|
VerticalRouteView(
|
||||||
|
memories: memories,
|
||||||
|
memoryIndex: index,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
Card(
|
||||||
|
elevation: 3,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(13.0),
|
||||||
|
),
|
||||||
|
clipBehavior: Clip.hardEdge,
|
||||||
|
child: ColorFiltered(
|
||||||
|
colorFilter: ColorFilter.mode(
|
||||||
|
Colors.black.withOpacity(0.1),
|
||||||
|
BlendMode.darken,
|
||||||
|
),
|
||||||
|
child: ImmichImage(
|
||||||
|
memory.assets[0],
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
width: 130,
|
||||||
|
height: 200,
|
||||||
|
useGrayBoxPlaceholder: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
bottom: 16,
|
||||||
|
left: 16,
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
maxWidth: 114,
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
memory.title,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: const SizedBox(),
|
||||||
|
)
|
||||||
|
.value;
|
||||||
|
|
||||||
|
return memoryLane ?? const SizedBox();
|
||||||
|
}
|
||||||
|
}
|
140
mobile/lib/modules/memories/views/memory_page.dart
Normal file
140
mobile/lib/modules/memories/views/memory_page.dart
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
import 'package:auto_route/auto_route.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/modules/memories/models/memory.dart';
|
||||||
|
import 'package:immich_mobile/modules/memories/ui/memory_card.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
|
class MemoryPage extends HookConsumerWidget {
|
||||||
|
final List<Memory> memories;
|
||||||
|
final int memoryIndex;
|
||||||
|
|
||||||
|
const MemoryPage({
|
||||||
|
required this.memories,
|
||||||
|
required this.memoryIndex,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final memoryPageController = usePageController(initialPage: memoryIndex);
|
||||||
|
final memoryAssetPageController = usePageController();
|
||||||
|
final currentMemory = useState(memories[memoryIndex]);
|
||||||
|
final currentAssetPage = useState(0);
|
||||||
|
final assetProgress = useState(
|
||||||
|
"${currentAssetPage.value + 1}|${currentMemory.value.assets.length}",
|
||||||
|
);
|
||||||
|
const bgColor = Colors.black;
|
||||||
|
|
||||||
|
toNextMemory() {
|
||||||
|
memoryPageController.nextPage(
|
||||||
|
duration: const Duration(milliseconds: 500),
|
||||||
|
curve: Curves.easeIn,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
toNextAsset(int currentAssetIndex) {
|
||||||
|
(currentAssetIndex + 1 < currentMemory.value.assets.length)
|
||||||
|
? memoryAssetPageController.jumpToPage(
|
||||||
|
(currentAssetIndex + 1),
|
||||||
|
)
|
||||||
|
: toNextMemory();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateProgressText() {
|
||||||
|
assetProgress.value =
|
||||||
|
"${currentAssetPage.value + 1}|${currentMemory.value.assets.length}";
|
||||||
|
}
|
||||||
|
|
||||||
|
onMemoryChanged(int otherIndex) {
|
||||||
|
HapticFeedback.mediumImpact();
|
||||||
|
currentMemory.value = memories[otherIndex];
|
||||||
|
currentAssetPage.value = 0;
|
||||||
|
updateProgressText();
|
||||||
|
}
|
||||||
|
|
||||||
|
onAssetChanged(int otherIndex) {
|
||||||
|
HapticFeedback.selectionClick();
|
||||||
|
|
||||||
|
currentAssetPage.value = otherIndex;
|
||||||
|
updateProgressText();
|
||||||
|
}
|
||||||
|
|
||||||
|
buildBottomInfo() {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
currentMemory.value.title,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.grey[400],
|
||||||
|
fontSize: 11.0,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
DateFormat.yMMMMd().format(
|
||||||
|
currentMemory.value.assets[0].fileCreatedAt,
|
||||||
|
),
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 14.0,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: bgColor,
|
||||||
|
body: SafeArea(
|
||||||
|
child: PageView.builder(
|
||||||
|
scrollDirection: Axis.vertical,
|
||||||
|
controller: memoryPageController,
|
||||||
|
onPageChanged: onMemoryChanged,
|
||||||
|
itemCount: memories.length,
|
||||||
|
itemBuilder: (context, mIndex) {
|
||||||
|
// Build horizontal page
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: PageView.builder(
|
||||||
|
controller: memoryAssetPageController,
|
||||||
|
onPageChanged: onAssetChanged,
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
itemCount: memories[mIndex].assets.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final asset = memories[mIndex].assets[index];
|
||||||
|
return Container(
|
||||||
|
color: Colors.black,
|
||||||
|
child: MemoryCard(
|
||||||
|
asset: asset,
|
||||||
|
onTap: () => toNextAsset(index),
|
||||||
|
onClose: () => AutoRouter.of(context).pop(),
|
||||||
|
rightCornerText: assetProgress.value,
|
||||||
|
title: memories[mIndex].title,
|
||||||
|
showTitle: index == 0,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
buildBottomInfo(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,6 +6,8 @@ import 'package:immich_mobile/modules/album/views/album_viewer_page.dart';
|
||||||
import 'package:immich_mobile/modules/album/views/asset_selection_page.dart';
|
import 'package:immich_mobile/modules/album/views/asset_selection_page.dart';
|
||||||
import 'package:immich_mobile/modules/album/views/create_album_page.dart';
|
import 'package:immich_mobile/modules/album/views/create_album_page.dart';
|
||||||
import 'package:immich_mobile/modules/album/views/library_page.dart';
|
import 'package:immich_mobile/modules/album/views/library_page.dart';
|
||||||
|
import 'package:immich_mobile/modules/memories/models/memory.dart';
|
||||||
|
import 'package:immich_mobile/modules/memories/views/memory_page.dart';
|
||||||
import 'package:immich_mobile/modules/partner/views/partner_detail_page.dart';
|
import 'package:immich_mobile/modules/partner/views/partner_detail_page.dart';
|
||||||
import 'package:immich_mobile/modules/partner/views/partner_page.dart';
|
import 'package:immich_mobile/modules/partner/views/partner_page.dart';
|
||||||
import 'package:immich_mobile/modules/album/views/select_additional_user_for_sharing_page.dart';
|
import 'package:immich_mobile/modules/album/views/select_additional_user_for_sharing_page.dart';
|
||||||
|
@ -151,6 +153,7 @@ part 'router.gr.dart';
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
AutoRoute(page: AllPeoplePage, guards: [AuthGuard, DuplicateGuard]),
|
AutoRoute(page: AllPeoplePage, guards: [AuthGuard, DuplicateGuard]),
|
||||||
|
AutoRoute(page: MemoryPage, guards: [AuthGuard, DuplicateGuard]),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
class AppRouter extends _$AppRouter {
|
class AppRouter extends _$AppRouter {
|
||||||
|
|
|
@ -290,6 +290,17 @@ class _$AppRouter extends RootStackRouter {
|
||||||
child: const AllPeoplePage(),
|
child: const AllPeoplePage(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
VerticalRouteView.name: (routeData) {
|
||||||
|
final args = routeData.argsAs<VerticalRouteViewArgs>();
|
||||||
|
return MaterialPageX<dynamic>(
|
||||||
|
routeData: routeData,
|
||||||
|
child: MemoryPage(
|
||||||
|
memories: args.memories,
|
||||||
|
memoryIndex: args.memoryIndex,
|
||||||
|
key: args.key,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
HomeRoute.name: (routeData) {
|
HomeRoute.name: (routeData) {
|
||||||
return MaterialPageX<dynamic>(
|
return MaterialPageX<dynamic>(
|
||||||
routeData: routeData,
|
routeData: routeData,
|
||||||
|
@ -589,6 +600,14 @@ class _$AppRouter extends RootStackRouter {
|
||||||
duplicateGuard,
|
duplicateGuard,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
RouteConfig(
|
||||||
|
VerticalRouteView.name,
|
||||||
|
path: '/vertical-page-view',
|
||||||
|
guards: [
|
||||||
|
authGuard,
|
||||||
|
duplicateGuard,
|
||||||
|
],
|
||||||
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1281,6 +1300,45 @@ class AllPeopleRoute extends PageRouteInfo<void> {
|
||||||
static const String name = 'AllPeopleRoute';
|
static const String name = 'AllPeopleRoute';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// generated route for
|
||||||
|
/// [MemoryPage]
|
||||||
|
class VerticalRouteView extends PageRouteInfo<VerticalRouteViewArgs> {
|
||||||
|
VerticalRouteView({
|
||||||
|
required List<Memory> memories,
|
||||||
|
required int memoryIndex,
|
||||||
|
Key? key,
|
||||||
|
}) : super(
|
||||||
|
VerticalRouteView.name,
|
||||||
|
path: '/vertical-page-view',
|
||||||
|
args: VerticalRouteViewArgs(
|
||||||
|
memories: memories,
|
||||||
|
memoryIndex: memoryIndex,
|
||||||
|
key: key,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
static const String name = 'VerticalRouteView';
|
||||||
|
}
|
||||||
|
|
||||||
|
class VerticalRouteViewArgs {
|
||||||
|
const VerticalRouteViewArgs({
|
||||||
|
required this.memories,
|
||||||
|
required this.memoryIndex,
|
||||||
|
this.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final List<Memory> memories;
|
||||||
|
|
||||||
|
final int memoryIndex;
|
||||||
|
|
||||||
|
final Key? key;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'VerticalRouteViewArgs{memories: $memories, memoryIndex: $memoryIndex, key: $key}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// generated route for
|
/// generated route for
|
||||||
/// [HomePage]
|
/// [HomePage]
|
||||||
class HomeRoute extends PageRouteInfo<void> {
|
class HomeRoute extends PageRouteInfo<void> {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/modules/album/providers/album.provider.dart';
|
import 'package:immich_mobile/modules/album/providers/album.provider.dart';
|
||||||
|
import 'package:immich_mobile/modules/memories/providers/memory.provider.dart';
|
||||||
import 'package:immich_mobile/modules/search/providers/people.provider.dart';
|
import 'package:immich_mobile/modules/search/providers/people.provider.dart';
|
||||||
|
|
||||||
import 'package:immich_mobile/modules/search/providers/search_page_state.provider.dart';
|
import 'package:immich_mobile/modules/search/providers/search_page_state.provider.dart';
|
||||||
|
@ -43,6 +44,10 @@ class TabNavigationObserver extends AutoRouterObserver {
|
||||||
if (route.name == 'LibraryRoute') {
|
if (route.name == 'LibraryRoute') {
|
||||||
ref.read(albumProvider.notifier).getAllAlbums();
|
ref.read(albumProvider.notifier).getAllAlbums();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (route.name == 'HomeRoute') {
|
||||||
|
ref.invalidate(memoryFutureProvider);
|
||||||
|
}
|
||||||
ref.watch(serverInfoProvider.notifier).getServerVersion();
|
ref.watch(serverInfoProvider.notifier).getServerVersion();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import 'package:immich_mobile/shared/models/asset.dart';
|
||||||
import 'package:immich_mobile/shared/models/store.dart';
|
import 'package:immich_mobile/shared/models/store.dart';
|
||||||
import 'package:immich_mobile/utils/image_url_builder.dart';
|
import 'package:immich_mobile/utils/image_url_builder.dart';
|
||||||
import 'package:photo_manager/photo_manager.dart';
|
import 'package:photo_manager/photo_manager.dart';
|
||||||
|
import 'package:openapi/api.dart' as api;
|
||||||
|
|
||||||
/// Renders an Asset using local data if available, else remote data
|
/// Renders an Asset using local data if available, else remote data
|
||||||
class ImmichImage extends StatelessWidget {
|
class ImmichImage extends StatelessWidget {
|
||||||
|
@ -15,6 +16,7 @@ class ImmichImage extends StatelessWidget {
|
||||||
this.height,
|
this.height,
|
||||||
this.fit = BoxFit.cover,
|
this.fit = BoxFit.cover,
|
||||||
this.useGrayBoxPlaceholder = false,
|
this.useGrayBoxPlaceholder = false,
|
||||||
|
this.type = api.ThumbnailFormat.WEBP,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
final Asset? asset;
|
final Asset? asset;
|
||||||
|
@ -22,6 +24,7 @@ class ImmichImage extends StatelessWidget {
|
||||||
final double? width;
|
final double? width;
|
||||||
final double? height;
|
final double? height;
|
||||||
final BoxFit fit;
|
final BoxFit fit;
|
||||||
|
final api.ThumbnailFormat type;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -85,7 +88,7 @@ class ImmichImage extends StatelessWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
final String? token = Store.get(StoreKey.accessToken);
|
final String? token = Store.get(StoreKey.accessToken);
|
||||||
final String thumbnailRequestUrl = getThumbnailUrl(asset);
|
final String thumbnailRequestUrl = getThumbnailUrl(asset, type: type);
|
||||||
return CachedNetworkImage(
|
return CachedNetworkImage(
|
||||||
imageUrl: thumbnailRequestUrl,
|
imageUrl: thumbnailRequestUrl,
|
||||||
httpHeaders: {"Authorization": "Bearer $token"},
|
httpHeaders: {"Authorization": "Bearer $token"},
|
||||||
|
@ -105,7 +108,7 @@ class ImmichImage extends StatelessWidget {
|
||||||
}
|
}
|
||||||
return Transform.scale(
|
return Transform.scale(
|
||||||
scale: 0.2,
|
scale: 0.2,
|
||||||
child: CircularProgressIndicator(
|
child: CircularProgressIndicator.adaptive(
|
||||||
value: downloadProgress.progress,
|
value: downloadProgress.progress,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -81,6 +81,16 @@ flutter:
|
||||||
- asset: fonts/WorkSans.ttf
|
- asset: fonts/WorkSans.ttf
|
||||||
- asset: fonts/WorkSans-Italic.ttf
|
- asset: fonts/WorkSans-Italic.ttf
|
||||||
style: italic
|
style: italic
|
||||||
|
# - asset: fonts/WorkSans-Medium.ttf
|
||||||
|
# weight: 500
|
||||||
|
# - asset: fonts/WorkSans-SemiBold.ttf
|
||||||
|
# weight: 600
|
||||||
|
# - asset: fonts/WorkSans-Bold.ttf
|
||||||
|
# weight: 700
|
||||||
|
# - asset: fonts/WorkSans-ExtraBold.ttf
|
||||||
|
# weight: 800
|
||||||
|
# - asset: fonts/WorkSans-Black.ttf
|
||||||
|
# weight: 900
|
||||||
- family: SnowburstOne
|
- family: SnowburstOne
|
||||||
fonts:
|
fonts:
|
||||||
- asset: fonts/SnowburstOne.ttf
|
- asset: fonts/SnowburstOne.ttf
|
||||||
|
|
Loading…
Reference in a new issue