refactor(mobile): Activities (#5990)

* refactor: autoroutex pushroute

* refactor: autoroutex popRoute

* refactor: autoroutex navigate and replace

* chore: add doc comments for extension methods

* refactor: Add LoggerMixin and refactor Album activities to use mixin

* refactor: Activity page

* chore: activity user from user constructor

* fix: update current asset after build method

* refactor: tests with similar structure as lib

* chore: remove avoid-declaring-call-method rule from dcm analysis

* test: fix proper expect order

* test: activity_statistics_provider_test

* test: activity_provider_test

* test: use proper matchers

* test: activity_text_field_test & dismissible_activity_test added

* test: add http mock to return transparent image

* test: download isar core libs during test

* test: add widget tags to widget test cases

* test: activity_tile_test

* build: currentAlbumProvider to generator

* movie add / remove like to activity input tile

* test: activities_page_test.dart

* chore: better error logs

* chore: dismissibleactivity as statelesswidget

---------

Co-authored-by: shalong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
This commit is contained in:
shenlong
2024-01-05 05:20:55 +00:00
committed by GitHub
parent d1e16025cf
commit af32183728
108 changed files with 2847 additions and 826 deletions

View File

@@ -46,18 +46,7 @@ class Activity {
type = dto.type == ActivityResponseDtoTypeEnum.comment
? ActivityType.comment
: ActivityType.like,
user = User(
email: dto.user.email,
name: dto.user.name,
profileImagePath: dto.user.profileImagePath,
id: dto.user.id,
// Placeholder values
isAdmin: false,
updatedAt: DateTime.now(),
isPartnerSharedBy: false,
isPartnerSharedWith: false,
memoryEnabled: false,
);
user = User.fromSimpleUserDto(dto.user);
@override
String toString() {
@@ -65,11 +54,10 @@ class Activity {
}
@override
bool operator ==(Object other) {
bool operator ==(covariant Activity other) {
if (identical(this, other)) return true;
return other is Activity &&
other.id == id &&
return other.id == id &&
other.assetId == assetId &&
other.comment == comment &&
other.createdAt == createdAt &&

View File

@@ -1,134 +1,67 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/activities/models/activity.model.dart';
import 'package:immich_mobile/modules/activities/services/activity.service.dart';
import 'package:immich_mobile/modules/activities/providers/activity_service.provider.dart';
import 'package:immich_mobile/modules/activities/providers/activity_statistics.provider.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
class ActivityNotifier extends StateNotifier<AsyncValue<List<Activity>>> {
final Ref _ref;
final ActivityService _activityService;
final String albumId;
final String? assetId;
part 'activity.provider.g.dart';
ActivityNotifier(
this._ref,
this._activityService,
this.albumId,
this.assetId,
) : super(
const AsyncData([]),
) {
fetchActivity();
}
Future<void> fetchActivity() async {
state = const AsyncLoading();
state = await AsyncValue.guard(
() => _activityService.getAllActivities(albumId, assetId),
);
/// Maintains the current list of all activities for <share-album-id, asset>
@riverpod
class AlbumActivity extends _$AlbumActivity {
@override
Future<List<Activity>> build(String albumId, [String? assetId]) async {
return ref
.watch(activityServiceProvider)
.getAllActivities(albumId, assetId: assetId);
}
Future<void> removeActivity(String id) async {
final activities = state.asData?.value ?? [];
if (await _activityService.removeActivity(id)) {
if (await ref.watch(activityServiceProvider).removeActivity(id)) {
final activities = state.valueOrNull ?? [];
final removedActivity = activities.firstWhere((a) => a.id == id);
activities.remove(removedActivity);
state = AsyncData(activities);
// Decrement activity count only for comments
if (removedActivity.type == ActivityType.comment) {
_ref
.read(
activityStatisticsStateProvider(
(albumId: albumId, assetId: assetId),
).notifier,
)
ref
.watch(activityStatisticsProvider(albumId, assetId).notifier)
.removeActivity();
}
}
}
Future<void> addComment(String comment) async {
final activity = await _activityService.addActivity(
albumId,
ActivityType.comment,
assetId: assetId,
comment: comment,
);
if (activity != null) {
Future<void> addLike() async {
final activity = await ref
.watch(activityServiceProvider)
.addActivity(albumId, ActivityType.like, assetId: assetId);
if (activity.hasValue) {
final activities = state.asData?.value ?? [];
state = AsyncData([...activities, activity]);
_ref
.read(
activityStatisticsStateProvider(
(albumId: albumId, assetId: assetId),
).notifier,
)
state = AsyncData([...activities, activity.requireValue]);
}
}
Future<void> addComment(String comment) async {
final activity = await ref.watch(activityServiceProvider).addActivity(
albumId,
ActivityType.comment,
assetId: assetId,
comment: comment,
);
if (activity.hasValue) {
final activities = state.valueOrNull ?? [];
state = AsyncData([...activities, activity.requireValue]);
ref
.watch(activityStatisticsProvider(albumId, assetId).notifier)
.addActivity();
// The previous addActivity call would increase the count of an asset if assetId != null
// To also increase the activity count of the album, calling it once again with assetId set to null
if (assetId != null) {
// Add a count to the current album's provider as well
_ref
.read(
activityStatisticsStateProvider(
(albumId: albumId, assetId: null),
).notifier,
)
.addActivity();
ref.watch(activityStatisticsProvider(albumId).notifier).addActivity();
}
}
}
Future<void> addLike() async {
final activity = await _activityService
.addActivity(albumId, ActivityType.like, assetId: assetId);
if (activity != null) {
final activities = state.asData?.value ?? [];
state = AsyncData([...activities, activity]);
}
}
}
class ActivityStatisticsNotifier extends StateNotifier<int> {
final String albumId;
final String? assetId;
final ActivityService _activityService;
ActivityStatisticsNotifier(this._activityService, this.albumId, this.assetId)
: super(0) {
fetchStatistics();
}
Future<void> fetchStatistics() async {
final count =
await _activityService.getStatistics(albumId, assetId: assetId);
if (mounted) {
state = count;
}
}
Future<void> addActivity() async {
state = state + 1;
}
Future<void> removeActivity() async {
state = state - 1;
}
}
typedef ActivityParams = ({String albumId, String? assetId});
final activityStateProvider = StateNotifierProvider.autoDispose
.family<ActivityNotifier, AsyncValue<List<Activity>>, ActivityParams>(
(ref, args) {
return ActivityNotifier(
ref,
ref.watch(activityServiceProvider),
args.albumId,
args.assetId,
);
});
final activityStatisticsStateProvider = StateNotifierProvider.autoDispose
.family<ActivityStatisticsNotifier, int, ActivityParams>((ref, args) {
return ActivityStatisticsNotifier(
ref.watch(activityServiceProvider),
args.albumId,
args.assetId,
);
});
/// Mock class for testing
abstract class AlbumActivityInternal extends _$AlbumActivity {}

View File

@@ -0,0 +1,209 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'activity.provider.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$albumActivityHash() => r'3b0d7acee4d41c84b3f220784c3b904c83f836e6';
/// Copied from Dart SDK
class _SystemHash {
_SystemHash._();
static int combine(int hash, int value) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + value);
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
return hash ^ (hash >> 6);
}
static int finish(int hash) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
// ignore: parameter_assignments
hash = hash ^ (hash >> 11);
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
}
}
abstract class _$AlbumActivity
extends BuildlessAutoDisposeAsyncNotifier<List<Activity>> {
late final String albumId;
late final String? assetId;
Future<List<Activity>> build(
String albumId, [
String? assetId,
]);
}
/// Maintains the current list of all activities for <share-album-id, asset>
///
/// Copied from [AlbumActivity].
@ProviderFor(AlbumActivity)
const albumActivityProvider = AlbumActivityFamily();
/// Maintains the current list of all activities for <share-album-id, asset>
///
/// Copied from [AlbumActivity].
class AlbumActivityFamily extends Family<AsyncValue<List<Activity>>> {
/// Maintains the current list of all activities for <share-album-id, asset>
///
/// Copied from [AlbumActivity].
const AlbumActivityFamily();
/// Maintains the current list of all activities for <share-album-id, asset>
///
/// Copied from [AlbumActivity].
AlbumActivityProvider call(
String albumId, [
String? assetId,
]) {
return AlbumActivityProvider(
albumId,
assetId,
);
}
@override
AlbumActivityProvider getProviderOverride(
covariant AlbumActivityProvider provider,
) {
return call(
provider.albumId,
provider.assetId,
);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'albumActivityProvider';
}
/// Maintains the current list of all activities for <share-album-id, asset>
///
/// Copied from [AlbumActivity].
class AlbumActivityProvider extends AutoDisposeAsyncNotifierProviderImpl<
AlbumActivity, List<Activity>> {
/// Maintains the current list of all activities for <share-album-id, asset>
///
/// Copied from [AlbumActivity].
AlbumActivityProvider(
String albumId, [
String? assetId,
]) : this._internal(
() => AlbumActivity()
..albumId = albumId
..assetId = assetId,
from: albumActivityProvider,
name: r'albumActivityProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$albumActivityHash,
dependencies: AlbumActivityFamily._dependencies,
allTransitiveDependencies:
AlbumActivityFamily._allTransitiveDependencies,
albumId: albumId,
assetId: assetId,
);
AlbumActivityProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.albumId,
required this.assetId,
}) : super.internal();
final String albumId;
final String? assetId;
@override
Future<List<Activity>> runNotifierBuild(
covariant AlbumActivity notifier,
) {
return notifier.build(
albumId,
assetId,
);
}
@override
Override overrideWith(AlbumActivity Function() create) {
return ProviderOverride(
origin: this,
override: AlbumActivityProvider._internal(
() => create()
..albumId = albumId
..assetId = assetId,
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
albumId: albumId,
assetId: assetId,
),
);
}
@override
AutoDisposeAsyncNotifierProviderElement<AlbumActivity, List<Activity>>
createElement() {
return _AlbumActivityProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is AlbumActivityProvider &&
other.albumId == albumId &&
other.assetId == assetId;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, albumId.hashCode);
hash = _SystemHash.combine(hash, assetId.hashCode);
return _SystemHash.finish(hash);
}
}
mixin AlbumActivityRef on AutoDisposeAsyncNotifierProviderRef<List<Activity>> {
/// The parameter `albumId` of this provider.
String get albumId;
/// The parameter `assetId` of this provider.
String? get assetId;
}
class _AlbumActivityProviderElement
extends AutoDisposeAsyncNotifierProviderElement<AlbumActivity,
List<Activity>> with AlbumActivityRef {
_AlbumActivityProviderElement(super.provider);
@override
String get albumId => (origin as AlbumActivityProvider).albumId;
@override
String? get assetId => (origin as AlbumActivityProvider).assetId;
}
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member

View File

@@ -0,0 +1,9 @@
import 'package:immich_mobile/modules/activities/services/activity.service.dart';
import 'package:immich_mobile/shared/providers/api.provider.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'activity_service.provider.g.dart';
@riverpod
ActivityService activityService(ActivityServiceRef ref) =>
ActivityService(ref.watch(apiServiceProvider));

View File

@@ -0,0 +1,25 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'activity_service.provider.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$activityServiceHash() => r'5dd4955d14f5bf01c00d7f8750d07e7ace7cc4b0';
/// See also [activityService].
@ProviderFor(activityService)
final activityServiceProvider = AutoDisposeProvider<ActivityService>.internal(
activityService,
name: r'activityServiceProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$activityServiceHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef ActivityServiceRef = AutoDisposeProviderRef<ActivityService>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member

View File

@@ -0,0 +1,24 @@
import 'package:immich_mobile/modules/activities/providers/activity_service.provider.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'activity_statistics.provider.g.dart';
/// Maintains the current number of comments by <shared-album, asset>
@riverpod
class ActivityStatistics extends _$ActivityStatistics {
@override
int build(String albumId, [String? assetId]) {
ref
.watch(activityServiceProvider)
.getStatistics(albumId, assetId: assetId)
.then((comments) => state = comments);
return 0;
}
void addActivity() => state = state + 1;
void removeActivity() => state = state - 1;
}
/// Mock class for testing
abstract class ActivityStatisticsInternal extends _$ActivityStatistics {}

View File

@@ -0,0 +1,208 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'activity_statistics.provider.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$activityStatisticsHash() =>
r'a5f7bbee1891c33b72919a34e632ca9ef9cd8dbf';
/// Copied from Dart SDK
class _SystemHash {
_SystemHash._();
static int combine(int hash, int value) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + value);
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
return hash ^ (hash >> 6);
}
static int finish(int hash) {
// ignore: parameter_assignments
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
// ignore: parameter_assignments
hash = hash ^ (hash >> 11);
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
}
}
abstract class _$ActivityStatistics extends BuildlessAutoDisposeNotifier<int> {
late final String albumId;
late final String? assetId;
int build(
String albumId, [
String? assetId,
]);
}
/// Maintains the current number of comments by <shared-album, asset>
///
/// Copied from [ActivityStatistics].
@ProviderFor(ActivityStatistics)
const activityStatisticsProvider = ActivityStatisticsFamily();
/// Maintains the current number of comments by <shared-album, asset>
///
/// Copied from [ActivityStatistics].
class ActivityStatisticsFamily extends Family<int> {
/// Maintains the current number of comments by <shared-album, asset>
///
/// Copied from [ActivityStatistics].
const ActivityStatisticsFamily();
/// Maintains the current number of comments by <shared-album, asset>
///
/// Copied from [ActivityStatistics].
ActivityStatisticsProvider call(
String albumId, [
String? assetId,
]) {
return ActivityStatisticsProvider(
albumId,
assetId,
);
}
@override
ActivityStatisticsProvider getProviderOverride(
covariant ActivityStatisticsProvider provider,
) {
return call(
provider.albumId,
provider.assetId,
);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'activityStatisticsProvider';
}
/// Maintains the current number of comments by <shared-album, asset>
///
/// Copied from [ActivityStatistics].
class ActivityStatisticsProvider
extends AutoDisposeNotifierProviderImpl<ActivityStatistics, int> {
/// Maintains the current number of comments by <shared-album, asset>
///
/// Copied from [ActivityStatistics].
ActivityStatisticsProvider(
String albumId, [
String? assetId,
]) : this._internal(
() => ActivityStatistics()
..albumId = albumId
..assetId = assetId,
from: activityStatisticsProvider,
name: r'activityStatisticsProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$activityStatisticsHash,
dependencies: ActivityStatisticsFamily._dependencies,
allTransitiveDependencies:
ActivityStatisticsFamily._allTransitiveDependencies,
albumId: albumId,
assetId: assetId,
);
ActivityStatisticsProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.albumId,
required this.assetId,
}) : super.internal();
final String albumId;
final String? assetId;
@override
int runNotifierBuild(
covariant ActivityStatistics notifier,
) {
return notifier.build(
albumId,
assetId,
);
}
@override
Override overrideWith(ActivityStatistics Function() create) {
return ProviderOverride(
origin: this,
override: ActivityStatisticsProvider._internal(
() => create()
..albumId = albumId
..assetId = assetId,
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
albumId: albumId,
assetId: assetId,
),
);
}
@override
AutoDisposeNotifierProviderElement<ActivityStatistics, int> createElement() {
return _ActivityStatisticsProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is ActivityStatisticsProvider &&
other.albumId == albumId &&
other.assetId == assetId;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, albumId.hashCode);
hash = _SystemHash.combine(hash, assetId.hashCode);
return _SystemHash.finish(hash);
}
}
mixin ActivityStatisticsRef on AutoDisposeNotifierProviderRef<int> {
/// The parameter `albumId` of this provider.
String get albumId;
/// The parameter `assetId` of this provider.
String? get assetId;
}
class _ActivityStatisticsProviderElement
extends AutoDisposeNotifierProviderElement<ActivityStatistics, int>
with ActivityStatisticsRef {
_ActivityStatisticsProviderElement(super.provider);
@override
String get albumId => (origin as ActivityStatisticsProvider).albumId;
@override
String? get assetId => (origin as ActivityStatisticsProvider).assetId;
}
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member

View File

@@ -1,67 +1,60 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/errors.dart';
import 'package:immich_mobile/mixins/error_logger.mixin.dart';
import 'package:immich_mobile/modules/activities/models/activity.model.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 activityServiceProvider =
Provider((ref) => ActivityService(ref.watch(apiServiceProvider)));
class ActivityService {
class ActivityService with ErrorLoggerMixin {
final ApiService _apiService;
final Logger _log = Logger("ActivityService");
@override
final Logger logger = Logger("ActivityService");
ActivityService(this._apiService);
Future<List<Activity>> getAllActivities(
String albumId,
String albumId, {
String? assetId,
) async {
try {
final list = await _apiService.activityApi
.getActivities(albumId, assetId: assetId);
return list != null ? list.map(Activity.fromDto).toList() : [];
} catch (e) {
_log.severe(
"failed to fetch activities for albumId - $albumId; assetId - $assetId -> $e",
);
rethrow;
}
}) async {
return logError(
() async {
final list = await _apiService.activityApi
.getActivities(albumId, assetId: assetId);
return list != null ? list.map(Activity.fromDto).toList() : [];
},
defaultValue: [],
);
}
Future<int> getStatistics(String albumId, {String? assetId}) async {
try {
final dto = await _apiService.activityApi
.getActivityStatistics(albumId, assetId: assetId);
return dto?.comments ?? 0;
} catch (e) {
_log.severe(
"failed to fetch activity statistics for albumId - $albumId; assetId - $assetId -> $e",
);
}
return 0;
return logError(
() async {
final dto = await _apiService.activityApi
.getActivityStatistics(albumId, assetId: assetId);
return dto?.comments ?? 0;
},
defaultValue: 0,
);
}
Future<bool> removeActivity(String id) async {
try {
await _apiService.activityApi.deleteActivity(id);
return true;
} catch (e) {
_log.severe(
"failed to remove activity id - $id -> $e",
);
}
return false;
return logError(
() async {
await _apiService.activityApi.deleteActivity(id);
return true;
},
defaultValue: false,
);
}
Future<Activity?> addActivity(
AsyncFuture<Activity> addActivity(
String albumId,
ActivityType type, {
String? assetId,
String? comment,
}) async {
try {
return guardError(() async {
final dto = await _apiService.activityApi.createActivity(
ActivityCreateDto(
albumId: albumId,
@@ -75,11 +68,7 @@ class ActivityService {
if (dto != null) {
return Activity.fromDto(dto);
}
} catch (e) {
_log.severe(
"failed to add activity for albumId - $albumId; assetId - $assetId -> $e",
);
}
return null;
throw NoResponseDtoError();
});
}
}

View File

@@ -1,6 +1,4 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:collection/collection.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
import 'package:hooks_riverpod/hooks_riverpod.dart';
@@ -8,236 +6,51 @@ import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/modules/activities/models/activity.model.dart';
import 'package:immich_mobile/modules/activities/providers/activity.provider.dart';
import 'package:immich_mobile/shared/models/store.dart';
import 'package:immich_mobile/shared/ui/confirm_dialog.dart';
import 'package:immich_mobile/shared/ui/user_circle_avatar.dart';
import 'package:immich_mobile/extensions/datetime_extensions.dart';
import 'package:immich_mobile/utils/image_url_builder.dart';
import 'package:immich_mobile/modules/activities/widgets/activity_text_field.dart';
import 'package:immich_mobile/modules/activities/widgets/activity_tile.dart';
import 'package:immich_mobile/modules/activities/widgets/dismissible_activity.dart';
import 'package:immich_mobile/modules/album/providers/current_album.provider.dart';
import 'package:immich_mobile/modules/asset_viewer/providers/current_asset.provider.dart';
import 'package:immich_mobile/shared/providers/user.provider.dart';
class ActivitiesPage extends HookConsumerWidget {
final String albumId;
final String? assetId;
final bool withAssetThumbs;
final String appBarTitle;
final bool isOwner;
final bool isReadOnly;
const ActivitiesPage(
this.albumId, {
this.appBarTitle = "",
this.assetId,
this.withAssetThumbs = true,
this.isOwner = false,
this.isReadOnly = false,
const ActivitiesPage({
super.key,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
final provider =
activityStateProvider((albumId: albumId, assetId: assetId));
final activities = ref.watch(provider);
final inputController = useTextEditingController();
final inputFocusNode = useFocusNode();
// Album has to be set in the provider before reaching this page
final album = ref.watch(currentAlbumProvider)!;
final asset = ref.watch(currentAssetProvider);
final user = ref.watch(currentUserProvider);
final activityNotifier = ref
.read(albumActivityProvider(album.remoteId!, asset?.remoteId).notifier);
final activities =
ref.watch(albumActivityProvider(album.remoteId!, asset?.remoteId));
final listViewScrollController = useScrollController();
final currentUser = Store.tryGet(StoreKey.currentUser);
useEffect(
() {
inputFocusNode.requestFocus();
return null;
},
[],
);
buildTitleWithTimestamp(Activity activity, {bool leftAlign = true}) {
final textColor = context.isDarkTheme ? Colors.white : Colors.black;
final textStyle = context.textTheme.bodyMedium
?.copyWith(color: textColor.withOpacity(0.6));
return Row(
mainAxisAlignment: leftAlign
? MainAxisAlignment.start
: MainAxisAlignment.spaceBetween,
mainAxisSize: leftAlign ? MainAxisSize.min : MainAxisSize.max,
children: [
Text(
activity.user.name,
style: textStyle,
overflow: TextOverflow.ellipsis,
),
if (leftAlign)
Text(
"",
style: textStyle,
),
Expanded(
child: Text(
activity.createdAt.copyWith().timeAgo(),
style: textStyle,
overflow: TextOverflow.ellipsis,
textAlign: leftAlign ? TextAlign.left : TextAlign.right,
),
),
],
);
}
buildAssetThumbnail(Activity activity) {
return withAssetThumbs && activity.assetId != null
? Container(
width: 40,
height: 30,
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(4)),
image: DecorationImage(
image: CachedNetworkImageProvider(
getThumbnailUrlForRemoteId(
activity.assetId!,
),
cacheKey: getThumbnailCacheKeyForRemoteId(
activity.assetId!,
),
headers: {
"Authorization":
'Bearer ${Store.get(StoreKey.accessToken)}',
},
),
fit: BoxFit.cover,
),
),
child: const SizedBox.shrink(),
)
: null;
}
buildTextField(String? likedId) {
final liked = likedId != null;
return Padding(
padding: const EdgeInsets.only(bottom: 10),
child: TextField(
controller: inputController,
enabled: !isReadOnly,
focusNode: inputFocusNode,
textInputAction: TextInputAction.send,
autofocus: false,
decoration: InputDecoration(
border: InputBorder.none,
focusedBorder: InputBorder.none,
prefixIcon: currentUser != null
? Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: UserCircleAvatar(
user: currentUser,
size: 30,
radius: 15,
),
)
: null,
suffixIcon: Padding(
padding: const EdgeInsets.only(right: 10),
child: IconButton(
icon: Icon(
liked
? Icons.favorite_rounded
: Icons.favorite_border_rounded,
),
onPressed: () async {
liked
? await ref
.read(provider.notifier)
.removeActivity(likedId)
: await ref.read(provider.notifier).addLike();
},
),
),
suffixIconColor: liked ? Colors.red[700] : null,
hintText: isReadOnly
? 'shared_album_activities_input_disable'.tr()
: 'shared_album_activities_input_hint'.tr(),
hintStyle: TextStyle(
fontWeight: FontWeight.normal,
fontSize: 14,
color: Colors.grey[600],
),
),
onEditingComplete: () async {
await ref.read(provider.notifier).addComment(inputController.text);
inputController.clear();
inputFocusNode.unfocus();
listViewScrollController.animateTo(
listViewScrollController.position.maxScrollExtent,
duration: const Duration(milliseconds: 800),
curve: Curves.fastOutSlowIn,
);
},
onTapOutside: (_) => inputFocusNode.unfocus(),
),
);
}
getDismissibleWidget(
Widget widget,
Activity activity,
bool canDelete,
) {
return Dismissible(
key: Key(activity.id),
dismissThresholds: const {
DismissDirection.horizontal: 0.7,
},
direction: DismissDirection.horizontal,
confirmDismiss: (direction) => canDelete
? showDialog(
context: context,
builder: (context) => ConfirmDialog(
onOk: () {},
title: "shared_album_activity_remove_title",
content: "shared_album_activity_remove_content",
ok: "delete_dialog_ok",
),
)
: Future.value(false),
onDismissed: (direction) async =>
await ref.read(provider.notifier).removeActivity(activity.id),
background: Container(
color: canDelete ? Colors.red[400] : Colors.grey[600],
alignment: AlignmentDirectional.centerStart,
child: canDelete
? const Padding(
padding: EdgeInsets.all(15),
child: Icon(
Icons.delete_sweep_rounded,
color: Colors.black,
),
)
: null,
),
secondaryBackground: Container(
color: canDelete ? Colors.red[400] : Colors.grey[600],
alignment: AlignmentDirectional.centerEnd,
child: canDelete
? const Padding(
padding: EdgeInsets.all(15),
child: Icon(
Icons.delete_sweep_rounded,
color: Colors.black,
),
)
: null,
),
child: widget,
Future<void> onAddComment(String comment) async {
await activityNotifier.addComment(comment);
// Scroll to the end of the list to show the newly added activity
listViewScrollController.animateTo(
listViewScrollController.position.maxScrollExtent + 200,
duration: const Duration(milliseconds: 600),
curve: Curves.fastOutSlowIn,
);
}
return Scaffold(
appBar: AppBar(title: Text(appBarTitle)),
appBar: AppBar(title: asset == null ? Text(album.name) : null),
body: activities.widgetWhen(
onData: (data) {
final liked = data.firstWhereOrNull(
(a) =>
a.type == ActivityType.like &&
a.user.id == currentUser?.id &&
a.assetId == assetId,
a.user.id == user?.id &&
a.assetId == asset?.remoteId,
);
return SafeArea(
@@ -245,9 +58,10 @@ class ActivitiesPage extends HookConsumerWidget {
children: [
ListView.builder(
controller: listViewScrollController,
// +1 to display an additional over-scroll space after the last element
itemCount: data.length + 1,
itemBuilder: (context, index) {
// Vertical gap after the last element
// Additional vertical gap after the last element
if (index == data.length) {
return const SizedBox(
height: 80,
@@ -255,45 +69,19 @@ class ActivitiesPage extends HookConsumerWidget {
}
final activity = data[index];
final canDelete =
activity.user.id == currentUser?.id || isOwner;
final canDelete = activity.user.id == user?.id ||
album.ownerId == user?.id;
return Padding(
padding: const EdgeInsets.all(5),
child: activity.type == ActivityType.comment
? getDismissibleWidget(
ListTile(
minVerticalPadding: 15,
leading: UserCircleAvatar(user: activity.user),
title: buildTitleWithTimestamp(
activity,
leftAlign: withAssetThumbs &&
activity.assetId != null,
),
titleAlignment: ListTileTitleAlignment.top,
trailing: buildAssetThumbnail(activity),
subtitle: Text(activity.comment!),
),
activity,
canDelete,
)
: getDismissibleWidget(
ListTile(
minVerticalPadding: 15,
leading: Container(
width: 44,
alignment: Alignment.center,
child: Icon(
Icons.favorite_rounded,
color: Colors.red[700],
),
),
title: buildTitleWithTimestamp(activity),
trailing: buildAssetThumbnail(activity),
),
activity,
canDelete,
),
child: DismissibleActivity(
activity.id,
ActivityTile(activity),
onDismiss: canDelete
? (activityId) async => await activityNotifier
.removeActivity(activity.id)
: null,
),
);
},
),
@@ -301,7 +89,11 @@ class ActivitiesPage extends HookConsumerWidget {
alignment: Alignment.bottomCenter,
child: Container(
color: context.scaffoldBackgroundColor,
child: buildTextField(liked?.id),
child: ActivityTextField(
isEnabled: album.activityEnabled,
likeId: liked?.id,
onSubmit: onAddComment,
),
),
),
],

View File

@@ -0,0 +1,105 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/activities/providers/activity.provider.dart';
import 'package:immich_mobile/modules/album/providers/current_album.provider.dart';
import 'package:immich_mobile/modules/asset_viewer/providers/current_asset.provider.dart';
import 'package:immich_mobile/shared/providers/user.provider.dart';
import 'package:immich_mobile/shared/ui/user_circle_avatar.dart';
class ActivityTextField extends HookConsumerWidget {
final bool isEnabled;
final String? likeId;
final Function(String) onSubmit;
const ActivityTextField({
required this.onSubmit,
this.isEnabled = true,
this.likeId,
super.key,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
final album = ref.watch(currentAlbumProvider)!;
final asset = ref.watch(currentAssetProvider);
final activityNotifier = ref
.read(albumActivityProvider(album.remoteId!, asset?.remoteId).notifier);
final user = ref.watch(currentUserProvider);
final inputController = useTextEditingController();
final inputFocusNode = useFocusNode();
final liked = likeId != null;
// Show keyboard immediately on activities open
useEffect(
() {
inputFocusNode.requestFocus();
return null;
},
[],
);
// Pass text to callback and reset controller
void onEditingComplete() {
onSubmit(inputController.text);
inputController.clear();
inputFocusNode.unfocus();
}
Future<void> addLike() async {
await activityNotifier.addLike();
}
Future<void> removeLike() async {
if (liked) {
await activityNotifier.removeActivity(likeId!);
}
}
return Padding(
padding: const EdgeInsets.only(bottom: 10),
child: TextField(
controller: inputController,
enabled: isEnabled,
focusNode: inputFocusNode,
textInputAction: TextInputAction.send,
autofocus: false,
decoration: InputDecoration(
border: InputBorder.none,
focusedBorder: InputBorder.none,
prefixIcon: user != null
? Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: UserCircleAvatar(
user: user,
size: 30,
radius: 15,
),
)
: null,
suffixIcon: Padding(
padding: const EdgeInsets.only(right: 10),
child: IconButton(
icon: Icon(
liked ? Icons.favorite_rounded : Icons.favorite_border_rounded,
),
onPressed: liked ? removeLike : addLike,
),
),
suffixIconColor: liked ? Colors.red[700] : null,
hintText: !isEnabled
? 'shared_album_activities_input_disable'.tr()
: 'shared_album_activities_input_hint'.tr(),
hintStyle: TextStyle(
fontWeight: FontWeight.normal,
fontSize: 14,
color: Colors.grey[600],
),
),
onEditingComplete: onEditingComplete,
onTapOutside: (_) => inputFocusNode.unfocus(),
),
);
}
}

View File

@@ -0,0 +1,116 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/datetime_extensions.dart';
import 'package:immich_mobile/modules/activities/models/activity.model.dart';
import 'package:immich_mobile/modules/asset_viewer/providers/current_asset.provider.dart';
import 'package:immich_mobile/shared/ui/immich_image.dart';
import 'package:immich_mobile/shared/ui/user_circle_avatar.dart';
class ActivityTile extends HookConsumerWidget {
final Activity activity;
const ActivityTile(this.activity, {super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final asset = ref.watch(currentAssetProvider);
final isLike = activity.type == ActivityType.like;
// Asset thumbnail is displayed when we are accessing activities from the album page
// currentAssetProvider will not be set until we open the gallery viewer
final showAssetThumbnail = asset == null && activity.assetId != null;
return ListTile(
minVerticalPadding: 15,
leading: isLike
? Container(
width: 44,
alignment: Alignment.center,
child: Icon(
Icons.favorite_rounded,
color: Colors.red[700],
),
)
: UserCircleAvatar(user: activity.user),
title: _ActivityTitle(
userName: activity.user.name,
createdAt: activity.createdAt.timeAgo(),
leftAlign: isLike || showAssetThumbnail,
),
// No subtitle for like, so center title
titleAlignment:
!isLike ? ListTileTitleAlignment.top : ListTileTitleAlignment.center,
trailing: showAssetThumbnail
? _ActivityAssetThumbnail(activity.assetId!)
: null,
subtitle: !isLike ? Text(activity.comment!) : null,
);
}
}
class _ActivityTitle extends StatelessWidget {
final String userName;
final String createdAt;
final bool leftAlign;
const _ActivityTitle({
required this.userName,
required this.createdAt,
required this.leftAlign,
});
@override
Widget build(BuildContext context) {
final textColor = context.isDarkTheme ? Colors.white : Colors.black;
final textStyle = context.textTheme.bodyMedium
?.copyWith(color: textColor.withOpacity(0.6));
return Row(
mainAxisAlignment:
leftAlign ? MainAxisAlignment.start : MainAxisAlignment.spaceBetween,
mainAxisSize: leftAlign ? MainAxisSize.min : MainAxisSize.max,
children: [
Text(
userName,
style: textStyle,
overflow: TextOverflow.ellipsis,
),
if (leftAlign)
Text(
"",
style: textStyle,
),
Expanded(
child: Text(
createdAt,
style: textStyle,
overflow: TextOverflow.ellipsis,
textAlign: leftAlign ? TextAlign.left : TextAlign.right,
),
),
],
);
}
}
class _ActivityAssetThumbnail extends StatelessWidget {
final String assetId;
const _ActivityAssetThumbnail(this.assetId);
@override
Widget build(BuildContext context) {
return Container(
width: 40,
height: 30,
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(4)),
image: DecorationImage(
image: ImmichImage.remoteThumbnailProviderForId(assetId),
fit: BoxFit.cover,
),
),
child: const SizedBox.shrink(),
);
}
}

View File

@@ -0,0 +1,75 @@
import 'package:flutter/material.dart';
import 'package:immich_mobile/modules/activities/widgets/activity_tile.dart';
import 'package:immich_mobile/shared/ui/confirm_dialog.dart';
/// Wraps an [ActivityTile] and makes it dismissible
class DismissibleActivity extends StatelessWidget {
final String activityId;
final ActivityTile body;
final Function(String)? onDismiss;
const DismissibleActivity(
this.activityId,
this.body, {
this.onDismiss,
super.key,
});
@override
Widget build(BuildContext context) {
return Dismissible(
key: Key(activityId),
dismissThresholds: const {
DismissDirection.horizontal: 0.7,
},
direction: DismissDirection.horizontal,
confirmDismiss: (direction) => onDismiss != null
? showDialog(
context: context,
builder: (context) => ConfirmDialog(
onOk: () {},
title: "shared_album_activity_remove_title",
content: "shared_album_activity_remove_content",
ok: "delete_dialog_ok",
),
)
: Future.value(false),
onDismissed: (_) async => onDismiss?.call(activityId),
// LTR
background: _DismissBackground(withDeleteIcon: onDismiss != null),
// RTL
secondaryBackground: _DismissBackground(
withDeleteIcon: onDismiss != null,
alignment: AlignmentDirectional.centerEnd,
),
child: body,
);
}
}
class _DismissBackground extends StatelessWidget {
final AlignmentDirectional alignment;
final bool withDeleteIcon;
const _DismissBackground({
required this.withDeleteIcon,
this.alignment = AlignmentDirectional.centerStart,
});
@override
Widget build(BuildContext context) {
return Container(
alignment: alignment,
color: withDeleteIcon ? Colors.red[400] : Colors.grey[600],
child: withDeleteIcon
? const Padding(
padding: EdgeInsets.all(15),
child: Icon(
Icons.delete_sweep_rounded,
color: Colors.black,
),
)
: null,
);
}
}

View File

@@ -7,7 +7,7 @@ part of 'album_sort_by_options.provider.dart';
// **************************************************************************
String _$albumSortByOptionsHash() =>
r'8d22fa8b7cbca2d3d7ed20a83bf00211dc948004';
r'dd8da5e730af555de1b86c3b157b6c93183523ac';
/// See also [AlbumSortByOptions].
@ProviderFor(AlbumSortByOptions)

View File

@@ -1,6 +1,15 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/shared/models/album.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
final currentAlbumProvider = StateProvider<Album?>((ref) {
return null;
});
part 'current_album.provider.g.dart';
@riverpod
class CurrentAlbum extends _$CurrentAlbum {
@override
Album? build() => null;
void set(Album? a) => state = a;
}
/// Mock class for testing
abstract class CurrentAlbumInternal extends _$CurrentAlbum {}

View File

@@ -0,0 +1,25 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'current_album.provider.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$currentAlbumHash() => r'61f00273d6b69da45add1532cc3d3a076ee55110';
/// See also [CurrentAlbum].
@ProviderFor(CurrentAlbum)
final currentAlbumProvider =
AutoDisposeNotifierProvider<CurrentAlbum, Album?>.internal(
CurrentAlbum.new,
name: r'currentAlbumProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product') ? null : _$currentAlbumHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$CurrentAlbum = AutoDisposeNotifier<Album?>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member

View File

@@ -1,3 +1,4 @@
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
@@ -104,7 +105,7 @@ class AddToAlbumBottomSheet extends HookConsumerWidget {
style: TextStyle(color: context.primaryColor),
),
onPressed: () {
context.autoPush(
context.pushRoute(
CreateAlbumRoute(
isSharedAlbum: false,
initialAssets: assets,

View File

@@ -1,3 +1,4 @@
import 'package:auto_route/auto_route.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
@@ -60,7 +61,7 @@ class AlbumThumbnailListTile extends StatelessWidget {
behavior: HitTestBehavior.opaque,
onTap: onTap ??
() {
context.autoPush(AlbumViewerRoute(albumId: album.id));
context.pushRoute(AlbumViewerRoute(albumId: album.id));
},
child: Padding(
padding: const EdgeInsets.only(bottom: 12.0),

View File

@@ -1,9 +1,10 @@
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/modules/activities/providers/activity.provider.dart';
import 'package:immich_mobile/modules/activities/providers/activity_statistics.provider.dart';
import 'package:immich_mobile/modules/album/providers/album.provider.dart';
import 'package:immich_mobile/modules/album/providers/album_viewer.provider.dart';
import 'package:immich_mobile/modules/album/providers/shared_album.provider.dart';
@@ -37,11 +38,7 @@ class AlbumViewerAppbar extends HookConsumerWidget
final isEditAlbum = ref.watch(albumViewerProvider).isEditAlbum;
final isProcessing = useProcessingOverlay();
final comments = album.shared
? ref.watch(
activityStatisticsStateProvider(
(albumId: album.remoteId!, assetId: null),
),
)
? ref.watch(activityStatisticsProvider(album.remoteId!))
: 0;
deleteAlbum() async {
@@ -52,11 +49,11 @@ class AlbumViewerAppbar extends HookConsumerWidget
success =
await ref.watch(sharedAlbumProvider.notifier).deleteAlbum(album);
context
.autoNavigate(const TabControllerRoute(children: [SharingRoute()]));
.navigateTo(const TabControllerRoute(children: [SharingRoute()]));
} else {
success = await ref.watch(albumProvider.notifier).deleteAlbum(album);
context
.autoNavigate(const TabControllerRoute(children: [LibraryRoute()]));
.navigateTo(const TabControllerRoute(children: [LibraryRoute()]));
}
if (!success) {
ImmichToast.show(
@@ -122,7 +119,7 @@ class AlbumViewerAppbar extends HookConsumerWidget
if (isSuccess) {
context
.autoNavigate(const TabControllerRoute(children: [SharingRoute()]));
.navigateTo(const TabControllerRoute(children: [SharingRoute()]));
} else {
context.pop();
ImmichToast.show(
@@ -175,7 +172,7 @@ class AlbumViewerAppbar extends HookConsumerWidget
ListTile(
leading: const Icon(Icons.share_rounded),
onTap: () {
context.autoPush(SharedLinkEditRoute(albumId: album.remoteId));
context.pushRoute(SharedLinkEditRoute(albumId: album.remoteId));
context.pop();
},
title: const Text(
@@ -185,7 +182,7 @@ class AlbumViewerAppbar extends HookConsumerWidget
),
ListTile(
leading: const Icon(Icons.settings_rounded),
onTap: () => context.autoNavigate(AlbumOptionsRoute(album: album)),
onTap: () => context.navigateTo(AlbumOptionsRoute(album: album)),
title: const Text(
"translated_text_options",
style: TextStyle(fontWeight: FontWeight.w500),
@@ -280,7 +277,7 @@ class AlbumViewerAppbar extends HookConsumerWidget
);
} else {
return IconButton(
onPressed: () async => await context.autoPop(),
onPressed: () async => await context.popRoute(),
icon: const Icon(Icons.arrow_back_ios_rounded),
splashRadius: 25,
);

View File

@@ -1,3 +1,4 @@
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
@@ -45,7 +46,7 @@ class AlbumOptionsPage extends HookConsumerWidget {
await ref.read(sharedAlbumProvider.notifier).leaveAlbum(album);
if (isSuccess) {
context.autoNavigate(
context.navigateTo(
const TabControllerRoute(children: [SharingRoute()]),
);
} else {
@@ -181,7 +182,7 @@ class AlbumOptionsPage extends HookConsumerWidget {
appBar: AppBar(
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios_new_rounded),
onPressed: () => context.autoPop(null),
onPressed: () => context.popRoute(null),
),
centerTitle: true,
title: Text("translated_text_options".tr()),

View File

@@ -1,3 +1,6 @@
import 'dart:async';
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
@@ -33,9 +36,12 @@ class AlbumViewerPage extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
FocusNode titleFocusNode = useFocusNode();
final album = ref.watch(albumWatcher(albumId));
// Listen provider to prevent autoDispose when navigating to other routes from within the viewer page
ref.listen(currentAlbumProvider, (_, __) {});
album.whenData(
(value) =>
Future((() => ref.read(currentAlbumProvider.notifier).state = value)),
(value) => Future.microtask(
() => ref.read(currentAlbumProvider.notifier).set(value),
),
);
final userId = ref.watch(authenticationProvider).userId;
final isProcessing = useProcessingOverlay();
@@ -62,7 +68,7 @@ class AlbumViewerPage extends HookConsumerWidget {
/// If they exist, add to selected asset state to show they are already selected.
void onAddPhotosPressed(Album albumInfo) async {
AssetSelectionPageResult? returnPayload =
await context.autoPush<AssetSelectionPageResult?>(
await context.pushRoute<AssetSelectionPageResult?>(
AssetSelectionRoute(
existingAssets: albumInfo.assets,
canDeselect: false,
@@ -84,7 +90,7 @@ class AlbumViewerPage extends HookConsumerWidget {
}
void onAddUsersPressed(Album album) async {
List<String>? sharedUserIds = await context.autoPush<List<String>?>(
List<String>? sharedUserIds = await context.pushRoute<List<String>?>(
SelectAdditionalUserForSharingRoute(album: album),
);
@@ -178,7 +184,7 @@ class AlbumViewerPage extends HookConsumerWidget {
Widget buildSharedUserIconsRow(Album album) {
return GestureDetector(
onTap: () => context.autoPush(AlbumOptionsRoute(album: album)),
onTap: () => context.pushRoute(AlbumOptionsRoute(album: album)),
child: SizedBox(
height: 50,
child: ListView.builder(
@@ -214,13 +220,8 @@ class AlbumViewerPage extends HookConsumerWidget {
onActivitiesPressed(Album album) {
if (album.remoteId != null) {
context.autoPush(
ActivitiesRoute(
albumId: album.remoteId!,
appBarTitle: album.name,
isOwner: userId == album.ownerId,
isReadOnly: !album.activityEnabled,
),
context.pushRoute(
const ActivitiesRoute(),
);
}
}

View File

@@ -1,3 +1,4 @@
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
@@ -36,7 +37,7 @@ class CreateAlbumPage extends HookConsumerWidget {
);
showSelectUserPage() async {
final bool? ok = await context.autoPush<bool?>(
final bool? ok = await context.pushRoute<bool?>(
SelectUserForSharingRoute(assets: selectedAssets.value),
);
if (ok == true) {
@@ -58,7 +59,7 @@ class CreateAlbumPage extends HookConsumerWidget {
onSelectPhotosButtonPressed() async {
AssetSelectionPageResult? selectedAsset =
await context.autoPush<AssetSelectionPageResult?>(
await context.pushRoute<AssetSelectionPageResult?>(
AssetSelectionRoute(
existingAssets: selectedAssets.value,
canDeselect: true,
@@ -202,7 +203,7 @@ class CreateAlbumPage extends HookConsumerWidget {
selectedAssets.value = {};
ref.watch(albumTitleProvider.notifier).clearAlbumTitle();
context.autoReplace(AlbumViewerRoute(albumId: newAlbum.id));
context.replaceRoute(AlbumViewerRoute(albumId: newAlbum.id));
}
}
@@ -214,7 +215,7 @@ class CreateAlbumPage extends HookConsumerWidget {
leading: IconButton(
onPressed: () {
selectedAssets.value = {};
context.autoPop();
context.popRoute();
},
icon: const Icon(Icons.close_rounded),
),

View File

@@ -1,3 +1,4 @@
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
@@ -102,7 +103,7 @@ class LibraryPage extends HookConsumerWidget {
return GestureDetector(
onTap: () =>
context.autoPush(CreateAlbumRoute(isSharedAlbum: false)),
context.pushRoute(CreateAlbumRoute(isSharedAlbum: false)),
child: Padding(
padding:
const EdgeInsets.only(bottom: 32), // Adjust padding to suit
@@ -190,7 +191,7 @@ class LibraryPage extends HookConsumerWidget {
Widget? shareTrashButton() {
return trashEnabled
? InkWell(
onTap: () => context.autoPush(const TrashRoute()),
onTap: () => context.pushRoute(const TrashRoute()),
borderRadius: const BorderRadius.all(Radius.circular(12)),
child: const Icon(
Icons.delete_rounded,
@@ -219,12 +220,12 @@ class LibraryPage extends HookConsumerWidget {
children: [
buildLibraryNavButton(
"library_page_favorites".tr(), Icons.favorite_border, () {
context.autoNavigate(const FavoritesRoute());
context.navigateTo(const FavoritesRoute());
}),
const SizedBox(width: 12.0),
buildLibraryNavButton(
"library_page_archive".tr(), Icons.archive_outlined, () {
context.autoNavigate(const ArchiveRoute());
context.navigateTo(const ArchiveRoute());
}),
],
),
@@ -270,7 +271,7 @@ class LibraryPage extends HookConsumerWidget {
return AlbumThumbnailCard(
album: sorted[index - 1],
onTap: () => context.autoPush(
onTap: () => context.pushRoute(
AlbumViewerRoute(
albumId: sorted[index - 1].id,
),
@@ -314,7 +315,7 @@ class LibraryPage extends HookConsumerWidget {
childCount: local.length,
(context, index) => AlbumThumbnailCard(
album: local[index],
onTap: () => context.autoPush(
onTap: () => context.pushRoute(
AlbumViewerRoute(
albumId: local[index].id,
),

View File

@@ -1,3 +1,4 @@
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
@@ -22,7 +23,7 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
final sharedUsersList = useState<Set<User>>({});
addNewUsersHandler() {
context.autoPop(sharedUsersList.value.map((e) => e.id).toList());
context.popRoute(sharedUsersList.value.map((e) => e.id).toList());
}
buildTileIcon(User user) {
@@ -123,7 +124,7 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
leading: IconButton(
icon: const Icon(Icons.close_rounded),
onPressed: () {
context.autoPop(null);
context.popRoute(null);
},
),
actions: [

View File

@@ -1,3 +1,4 @@
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
@@ -35,9 +36,9 @@ class SelectUserForSharingPage extends HookConsumerWidget {
await ref.watch(sharedAlbumProvider.notifier).getAllSharedAlbums();
// ref.watch(assetSelectionProvider.notifier).removeAll();
ref.watch(albumTitleProvider.notifier).clearAlbumTitle();
context.autoPop(true);
context.popRoute(true);
context
.autoNavigate(const TabControllerRoute(children: [SharingRoute()]));
.navigateTo(const TabControllerRoute(children: [SharingRoute()]));
}
ScaffoldMessenger(
@@ -151,7 +152,7 @@ class SelectUserForSharingPage extends HookConsumerWidget {
leading: IconButton(
icon: const Icon(Icons.close_rounded),
onPressed: () async {
context.autoPop();
context.popRoute();
},
),
actions: [

View File

@@ -1,3 +1,4 @@
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
@@ -48,11 +49,9 @@ class SharingPage extends HookConsumerWidget {
return AlbumThumbnailCard(
album: sharedAlbums[index],
showOwner: true,
onTap: () {
context.autoPush(
AlbumViewerRoute(albumId: sharedAlbums[index].id),
);
},
onTap: () => context.pushRoute(
AlbumViewerRoute(albumId: sharedAlbums[index].id),
),
);
},
childCount: sharedAlbums.length,
@@ -99,11 +98,8 @@ class SharingPage extends HookConsumerWidget {
style: context.textTheme.bodyMedium,
)
: null,
onTap: () {
context.autoPush(
AlbumViewerRoute(albumId: sharedAlbums[index].id),
);
},
onTap: () => context
.pushRoute(AlbumViewerRoute(albumId: sharedAlbums[index].id)),
);
},
childCount: sharedAlbums.length,
@@ -124,9 +120,8 @@ class SharingPage extends HookConsumerWidget {
children: [
Expanded(
child: ElevatedButton.icon(
onPressed: () {
context.autoPush(CreateAlbumRoute(isSharedAlbum: true));
},
onPressed: () =>
context.pushRoute(CreateAlbumRoute(isSharedAlbum: true)),
icon: const Icon(
Icons.photo_album_outlined,
size: 20,
@@ -144,7 +139,7 @@ class SharingPage extends HookConsumerWidget {
const SizedBox(width: 12.0),
Expanded(
child: ElevatedButton.icon(
onPressed: () => context.autoPush(const SharedLinkRoute()),
onPressed: () => context.pushRoute(const SharedLinkRoute()),
icon: const Icon(
Icons.link,
size: 20,
@@ -214,7 +209,7 @@ class SharingPage extends HookConsumerWidget {
Widget sharePartnerButton() {
return InkWell(
onTap: () => context.autoPush(const PartnerRoute()),
onTap: () => context.pushRoute(const PartnerRoute()),
borderRadius: const BorderRadius.all(Radius.circular(12)),
child: const Icon(
Icons.swap_horizontal_circle_rounded,

View File

@@ -1,7 +1,7 @@
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/modules/archive/providers/archive_asset_provider.dart';
import 'package:immich_mobile/modules/home/providers/multiselect.provider.dart';
import 'package:immich_mobile/shared/ui/asset_grid/multiselect_grid.dart';
@@ -16,7 +16,7 @@ class ArchivePage extends HookConsumerWidget {
final count = archivedAssets.value?.totalAssets.toString() ?? "?";
return AppBar(
leading: IconButton(
onPressed: () => context.autoPop(),
onPressed: () => context.popRoute(),
icon: const Icon(Icons.arrow_back_ios_rounded),
),
centerTitle: true,

View File

@@ -0,0 +1,15 @@
import 'package:immich_mobile/shared/models/asset.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'current_asset.provider.g.dart';
@riverpod
class CurrentAsset extends _$CurrentAsset {
@override
Asset? build() => null;
void set(Asset? a) => state = a;
}
/// Mock class for testing
abstract class CurrentAssetInternal extends _$CurrentAsset {}

View File

@@ -0,0 +1,25 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'current_asset.provider.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$currentAssetHash() => r'018d9f936991c48f06c11bf7e72130bba25806e2';
/// See also [CurrentAsset].
@ProviderFor(CurrentAsset)
final currentAssetProvider =
AutoDisposeNotifierProvider<CurrentAsset, Asset?>.internal(
CurrentAsset.new,
name: r'currentAssetProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product') ? null : _$currentAssetHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$CurrentAsset = AutoDisposeNotifier<Asset?>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member

View File

@@ -1,7 +1,7 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/modules/activities/providers/activity.provider.dart';
import 'package:immich_mobile/modules/activities/providers/activity_statistics.provider.dart';
import 'package:immich_mobile/modules/album/providers/current_album.provider.dart';
import 'package:immich_mobile/shared/models/asset.dart';
import 'package:immich_mobile/shared/providers/asset.provider.dart';
@@ -39,12 +39,8 @@ class TopControlAppBar extends HookConsumerWidget {
const double iconSize = 22.0;
final a = ref.watch(assetWatcher(asset)).value ?? asset;
final album = ref.watch(currentAlbumProvider);
final comments = album != null && album.remoteId != null
? ref.watch(
activityStatisticsStateProvider(
(albumId: album.remoteId!, assetId: asset.remoteId),
),
)
final comments = album != null
? ref.watch(activityStatisticsProvider(album.remoteId!, asset.remoteId))
: 0;
Widget buildFavoriteButton(a) {
@@ -149,7 +145,7 @@ class TopControlAppBar extends HookConsumerWidget {
Widget buildBackButton() {
return IconButton(
onPressed: () {
context.autoPop();
context.popRoute();
},
icon: Icon(
Icons.arrow_back_ios_new_rounded,

View File

@@ -11,6 +11,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/modules/album/providers/current_album.provider.dart';
import 'package:immich_mobile/modules/asset_viewer/providers/asset_stack.provider.dart';
import 'package:immich_mobile/modules/asset_viewer/providers/current_asset.provider.dart';
import 'package:immich_mobile/modules/asset_viewer/providers/show_controls.provider.dart';
import 'package:immich_mobile/modules/asset_viewer/providers/video_player_controls_provider.dart';
import 'package:immich_mobile/modules/album/ui/add_to_album_bottom_sheet.dart';
@@ -106,6 +107,19 @@ class GalleryViewerPage extends HookConsumerWidget {
bool isParent = stackIndex.value == -1 || stackIndex.value == 0;
// Listen provider to prevent autoDispose when navigating to other routes from within the gallery page
ref.listen(currentAssetProvider, (_, __) {});
useEffect(
() {
// Delay state update to after the execution of build method
Future.microtask(
() => ref.read(currentAssetProvider.notifier).set(asset()),
);
return null;
},
[asset()],
);
useEffect(
() {
isLoadPreview.value =
@@ -214,7 +228,7 @@ class GalleryViewerPage extends HookConsumerWidget {
if (isDeleted && isParent) {
if (totalAssets == 1) {
// Handle only one asset
context.autoPop();
context.popRoute();
} else {
// Go to next page otherwise
controller.nextPage(
@@ -298,7 +312,7 @@ class GalleryViewerPage extends HookConsumerWidget {
final ratio = d.dy / max(d.dx.abs(), 1);
if (d.dy > sensitivity && ratio > ratioThreshold) {
context.autoPop();
context.popRoute();
} else if (d.dy < -sensitivity && ratio < -ratioThreshold) {
showInfo();
}
@@ -311,7 +325,7 @@ class GalleryViewerPage extends HookConsumerWidget {
handleArchive(Asset asset) {
ref.watch(assetProvider.notifier).toggleArchive([asset]);
if (isParent) {
context.autoPop();
context.popRoute();
return;
}
removeAssetFromStack();
@@ -334,14 +348,7 @@ class GalleryViewerPage extends HookConsumerWidget {
handleActivities() {
if (album != null && album.shared && album.remoteId != null) {
context.autoPush(
ActivitiesRoute(
albumId: album.remoteId!,
assetId: asset().remoteId,
withAssetThumbs: false,
isOwner: isOwner,
),
);
context.pushRoute(const ActivitiesRoute());
}
}
@@ -517,7 +524,7 @@ class GalleryViewerPage extends HookConsumerWidget {
stackElements.elementAt(stackIndex.value),
);
ctx.pop();
context.autoPop();
context.popRoute();
},
title: const Text(
"viewer_stack_use_as_main_asset",
@@ -544,7 +551,7 @@ class GalleryViewerPage extends HookConsumerWidget {
childrenToRemove: [currentAsset],
);
ctx.pop();
context.autoPop();
context.popRoute();
} else {
await ref.read(assetStackServiceProvider).updateStack(
currentAsset,
@@ -572,7 +579,7 @@ class GalleryViewerPage extends HookConsumerWidget {
childrenToRemove: stack,
);
ctx.pop();
context.autoPop();
context.popRoute();
},
title: const Text(
"viewer_unstack",

View File

@@ -1,3 +1,4 @@
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@@ -201,7 +202,7 @@ class AlbumInfoCard extends HookConsumerWidget {
),
IconButton(
onPressed: () {
context.autoPush(
context.pushRoute(
AlbumPreviewRoute(album: albumInfo.albumEntity),
);
},

View File

@@ -1,3 +1,4 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
@@ -134,7 +135,7 @@ class AlbumInfoListTile extends HookConsumerWidget {
subtitle: Text(assetCount.value.toString()),
trailing: IconButton(
onPressed: () {
context.autoPush(
context.pushRoute(
AlbumPreviewRoute(album: albumInfo.albumEntity),
);
},

View File

@@ -1,5 +1,6 @@
import 'dart:io';
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
@@ -56,9 +57,7 @@ class CurrentUploadingAssetInfoBox extends HookConsumerWidget {
args: [ref.watch(errorBackupListProvider).length.toString()],
),
backgroundColor: Colors.white,
onPressed: () {
context.autoPush(const FailedBackupStatusRoute());
},
onPressed: () => context.pushRoute(const FailedBackupStatusRoute()),
);
}

View File

@@ -1,9 +1,9 @@
import 'dart:typed_data';
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
import 'package:photo_manager/photo_manager.dart';
@@ -53,7 +53,7 @@ class AlbumPreviewPage extends HookConsumerWidget {
],
),
leading: IconButton(
onPressed: () => context.autoPop(),
onPressed: () => context.popRoute(),
icon: const Icon(Icons.arrow_back_ios_new_rounded),
),
),

View File

@@ -1,3 +1,4 @@
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
@@ -193,7 +194,7 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
return Scaffold(
appBar: AppBar(
leading: IconButton(
onPressed: () => context.autoPop(),
onPressed: () => context.popRoute(),
icon: const Icon(Icons.arrow_back_ios_rounded),
),
title: const Text(

View File

@@ -1,6 +1,7 @@
import 'dart:io';
import 'dart:math';
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
@@ -151,7 +152,7 @@ class BackupControllerPage extends HookConsumerWidget {
),
trailing: ElevatedButton(
onPressed: () async {
await context.autoPush(const BackupAlbumSelectionRoute());
await context.pushRoute(const BackupAlbumSelectionRoute());
// waited until returning from selection
await ref
.read(backupProvider.notifier)
@@ -242,7 +243,7 @@ class BackupControllerPage extends HookConsumerWidget {
leading: IconButton(
onPressed: () {
ref.watch(websocketProvider.notifier).listenUploadEvent();
context.autoPop(true);
context.popRoute(true);
},
splashRadius: 24,
icon: const Icon(
@@ -253,7 +254,7 @@ class BackupControllerPage extends HookConsumerWidget {
Padding(
padding: const EdgeInsets.only(right: 8.0),
child: IconButton(
onPressed: () => context.autoPush(const BackupOptionsRoute()),
onPressed: () => context.pushRoute(const BackupOptionsRoute()),
splashRadius: 24,
icon: const Icon(
Icons.settings_outlined,

View File

@@ -1,5 +1,6 @@
import 'dart:io';
import 'package:auto_route/auto_route.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
@@ -487,9 +488,7 @@ class BackupOptionsPage extends HookConsumerWidget {
"Backup options",
),
leading: IconButton(
onPressed: () {
context.autoPop(true);
},
onPressed: () => context.popRoute(true),
splashRadius: 24,
icon: const Icon(
Icons.arrow_back_ios_rounded,

View File

@@ -1,3 +1,4 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
@@ -20,7 +21,7 @@ class FailedBackupStatusPage extends HookConsumerWidget {
),
leading: IconButton(
onPressed: () {
context.autoPop(true);
context.popRoute(true);
},
splashRadius: 24,
icon: const Icon(

View File

@@ -1,7 +1,7 @@
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/modules/favorite/providers/favorite_provider.dart';
import 'package:immich_mobile/modules/home/providers/multiselect.provider.dart';
import 'package:immich_mobile/shared/ui/asset_grid/multiselect_grid.dart';
@@ -14,7 +14,7 @@ class FavoritesPage extends HookConsumerWidget {
AppBar buildAppBar() {
return AppBar(
leading: IconButton(
onPressed: () => context.autoPop(),
onPressed: () => context.popRoute(),
icon: const Icon(Icons.arrow_back_ios_rounded),
),
centerTitle: true,

View File

@@ -1,3 +1,4 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
@@ -174,7 +175,7 @@ class ThumbnailImage extends StatelessWidget {
onSelect?.call();
}
} else {
context.autoPush(
context.pushRoute(
GalleryViewerRoute(
initialIndex: index,
loadAsset: loadAsset,

View File

@@ -1,4 +1,5 @@
import 'dart:io';
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
@@ -157,7 +158,7 @@ class LoginForm extends HookConsumerWidget {
// Resume backup (if enable) then navigate
if (ref.read(authenticationProvider).shouldChangePassword &&
!ref.read(authenticationProvider).isAdmin) {
context.autoPush(const ChangePasswordRoute());
context.pushRoute(const ChangePasswordRoute());
} else {
final hasPermission = await ref
.read(galleryPermissionNotifier.notifier)
@@ -166,7 +167,7 @@ class LoginForm extends HookConsumerWidget {
// Don't resume the backup until we have gallery permission
ref.read(backupProvider.notifier).resumeBackup();
}
context.autoReplace(const TabControllerRoute());
context.replaceRoute(const TabControllerRoute());
}
} else {
ImmichToast.show(
@@ -218,7 +219,7 @@ class LoginForm extends HookConsumerWidget {
if (permission.isGranted || permission.isLimited) {
ref.watch(backupProvider.notifier).resumeBackup();
}
context.autoReplace(const TabControllerRoute());
context.replaceRoute(const TabControllerRoute());
} else {
ImmichToast.show(
context: context,
@@ -264,7 +265,7 @@ class LoginForm extends HookConsumerWidget {
),
),
),
onPressed: () => context.autoPush(const SettingsRoute()),
onPressed: () => context.pushRoute(const SettingsRoute()),
icon: const Icon(Icons.settings_rounded),
label: const SizedBox.shrink(),
),

View File

@@ -1,3 +1,4 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
@@ -53,7 +54,7 @@ class LoginPage extends HookConsumerWidget {
),
),
onTap: () {
context.autoPush(const AppLogRoute());
context.pushRoute(const AppLogRoute());
},
),
],

View File

@@ -1,3 +1,4 @@
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
@@ -90,12 +91,12 @@ class MapLocationPickerPage extends HookConsumerWidget {
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: () => context.autoPop(selectedLatLng.value),
onPressed: () => context.popRoute(selectedLatLng.value),
child: const Text("map_location_picker_page_use_location")
.tr(),
),
ElevatedButton(
onPressed: () => context.autoPop(),
onPressed: () => context.popRoute(),
style: ElevatedButton.styleFrom(
backgroundColor: context.colorScheme.error,
),

View File

@@ -1,8 +1,8 @@
import 'dart:io';
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/modules/home/ui/asset_grid/disable_multi_select_button.dart';
import 'package:immich_mobile/modules/map/ui/map_settings_dialog.dart';
@@ -30,7 +30,7 @@ class MapAppBar extends HookWidget implements PreferredSizeWidget {
Padding(
padding: const EdgeInsets.only(left: 15, top: 15),
child: ElevatedButton(
onPressed: () => context.autoPop(),
onPressed: () => context.popRoute(),
style: ElevatedButton.styleFrom(
shape: const CircleBorder(),
padding: const EdgeInsets.all(12),

View File

@@ -1,6 +1,7 @@
import 'dart:async';
import 'dart:math' as math;
import 'package:auto_route/auto_route.dart';
import 'package:collection/collection.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
@@ -102,7 +103,7 @@ class MapPageState extends ConsumerState<MapPage> {
}
void openAssetInViewer(Asset asset) {
context.autoPush(
context.pushRoute(
GalleryViewerRoute(
initialIndex: 0,
loadAsset: (index) => asset,

View File

@@ -1,7 +1,7 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.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';
@@ -31,7 +31,7 @@ class MemoryLane extends HookConsumerWidget {
child: GestureDetector(
onTap: () {
HapticFeedback.heavyImpact();
context.autoPush(
context.pushRoute(
MemoryRoute(
memories: memories,
memoryIndex: index,

View File

@@ -1,8 +1,8 @@
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/extensions/build_context_extensions.dart';
import 'package:immich_mobile/modules/memories/models/memory.dart';
import 'package:immich_mobile/modules/memories/ui/memory_card.dart';
import 'package:immich_mobile/shared/models/asset.dart';
@@ -182,14 +182,14 @@ class MemoryPage extends HookConsumerWidget {
currentMemory.value.assets.length;
if (isLastAsset &&
(offset > notification.metrics.maxScrollExtent + 150)) {
context.autoPop();
context.popRoute();
return true;
}
}
// Horizontal scroll handling
if (notification.depth == 1 &&
(offset > notification.metrics.maxScrollExtent + 100)) {
context.autoPop();
context.popRoute();
return true;
}
}
@@ -244,7 +244,7 @@ class MemoryPage extends HookConsumerWidget {
child: MemoryCard(
asset: asset,
onTap: () => toNextAsset(index),
onClose: () => context.autoPop(),
onClose: () => context.popRoute(),
rightCornerText: assetProgress.value,
title: memories[mIndex].title,
showTitle: index == 0,

View File

@@ -1,3 +1,4 @@
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
@@ -16,7 +17,7 @@ class PermissionOnboardingPage extends HookConsumerWidget {
final PermissionStatus permission = ref.watch(galleryPermissionNotifier);
// Navigate to the main Tab Controller when permission is granted
void goToBackup() => context.autoReplace(const BackupControllerRoute());
void goToBackup() => context.replaceRoute(const BackupControllerRoute());
// When the permission is denied, we show a request permission page
buildRequestPermission() {
@@ -174,7 +175,7 @@ class PermissionOnboardingPage extends HookConsumerWidget {
),
TextButton(
child: const Text('permission_onboarding_back').tr(),
onPressed: () => context.autoPop(),
onPressed: () => context.popRoute(),
),
],
),

View File

@@ -1,3 +1,4 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
@@ -36,7 +37,7 @@ class PartnerList extends HookConsumerWidget {
color: context.primaryColor,
),
),
onTap: () => context.autoPush((PartnerDetailRoute(partner: p))),
onTap: () => context.pushRoute((PartnerDetailRoute(partner: p))),
);
}
}

View File

@@ -1,3 +1,4 @@
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
@@ -26,7 +27,7 @@ class CuratedPlacesRow extends CuratedRow {
final int actualContentIndex = isMapEnabled ? 1 : 0;
Widget buildMapThumbnail() {
return GestureDetector(
onTap: () => context.autoPush(
onTap: () => context.pushRoute(
const MapRoute(),
),
child: SizedBox.square(

View File

@@ -1,5 +1,5 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/modules/search/models/curated_content.dart';
import 'package:immich_mobile/modules/search/ui/thumbnail_with_info.dart';
import 'package:immich_mobile/routing/router.dart';
@@ -50,13 +50,13 @@ class ExploreGrid extends StatelessWidget {
borderRadius: 0,
onTap: () {
isPeople
? context.autoPush(
? context.pushRoute(
PersonResultRoute(
personId: content.id,
personName: content.label,
),
)
: context.autoPush(
: context.pushRoute(
SearchResultRoute(searchTerm: 'm:${content.label}'),
);
},

View File

@@ -1,8 +1,8 @@
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart';
import 'package:immich_mobile/modules/search/providers/all_motion_photos.provider.dart';
@@ -17,7 +17,7 @@ class AllMotionPhotosPage extends HookConsumerWidget {
appBar: AppBar(
title: const Text('motion_photos_page_title').tr(),
leading: IconButton(
onPressed: () => context.autoPop(),
onPressed: () => context.popRoute(),
icon: const Icon(Icons.arrow_back_ios_rounded),
),
),

View File

@@ -1,8 +1,8 @@
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/modules/search/providers/people.provider.dart';
import 'package:immich_mobile/modules/search/ui/explore_grid.dart';
@@ -19,7 +19,7 @@ class AllPeoplePage extends HookConsumerWidget {
'all_people_page_title',
).tr(),
leading: IconButton(
onPressed: () => context.autoPop(),
onPressed: () => context.popRoute(),
icon: const Icon(Icons.arrow_back_ios_rounded),
),
),

View File

@@ -1,8 +1,8 @@
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart';
import 'package:immich_mobile/modules/search/providers/all_video_assets.provider.dart';
@@ -17,7 +17,7 @@ class AllVideosPage extends HookConsumerWidget {
appBar: AppBar(
title: const Text('all_videos_page_title').tr(),
leading: IconButton(
onPressed: () => context.autoPop(),
onPressed: () => context.popRoute(),
icon: const Icon(Icons.arrow_back_ios_rounded),
),
),

View File

@@ -1,8 +1,8 @@
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/modules/search/models/curated_content.dart';
import 'package:immich_mobile/modules/search/providers/search_page_state.provider.dart';
import 'package:immich_mobile/modules/search/ui/explore_grid.dart';
@@ -22,7 +22,7 @@ class CuratedLocationPage extends HookConsumerWidget {
'curated_location_page_title',
).tr(),
leading: IconButton(
onPressed: () => context.autoPop(),
onPressed: () => context.popRoute(),
icon: const Icon(Icons.arrow_back_ios_rounded),
),
),

View File

@@ -1,3 +1,4 @@
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
@@ -101,7 +102,7 @@ class PersonResultPage extends HookConsumerWidget {
appBar: AppBar(
title: Text(name.value),
leading: IconButton(
onPressed: () => context.autoPop(),
onPressed: () => context.popRoute(),
icon: const Icon(Icons.arrow_back_ios_rounded),
),
actions: [

View File

@@ -1,8 +1,8 @@
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart';
import 'package:immich_mobile/modules/search/providers/recently_added.provider.dart';
@@ -17,7 +17,7 @@ class RecentlyAddedPage extends HookConsumerWidget {
appBar: AppBar(
title: const Text('recently_added_page_title').tr(),
leading: IconButton(
onPressed: () => context.autoPop(),
onPressed: () => context.popRoute(),
icon: const Icon(Icons.arrow_back_ios_rounded),
),
),

View File

@@ -1,4 +1,5 @@
import 'dart:math' as math;
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
@@ -52,7 +53,7 @@ class SearchPage extends HookConsumerWidget {
searchFocusNode.unfocus();
ref.watch(searchPageStateProvider.notifier).disableSearch();
context.autoPush(
context.pushRoute(
SearchResultRoute(
searchTerm: searchTerm,
),
@@ -79,7 +80,7 @@ class SearchPage extends HookConsumerWidget {
onData: (people) => CuratedPeopleRow(
content: people.take(12).toList(),
onTap: (content, index) {
context.autoPush(
context.pushRoute(
PersonResultRoute(
personId: content.id,
personName: content.label,
@@ -111,7 +112,7 @@ class SearchPage extends HookConsumerWidget {
.toList(),
imageSize: imageSize,
onTap: (content, index) {
context.autoPush(
context.pushRoute(
SearchResultRoute(
searchTerm: 'm:${content.label}',
),
@@ -139,13 +140,13 @@ class SearchPage extends HookConsumerWidget {
SearchRowTitle(
title: "search_page_people".tr(),
onViewAllPressed: () =>
context.autoPush(const AllPeopleRoute()),
context.pushRoute(const AllPeopleRoute()),
),
buildPeople(),
SearchRowTitle(
title: "search_page_places".tr(),
onViewAllPressed: () =>
context.autoPush(const CuratedLocationRoute()),
context.pushRoute(const CuratedLocationRoute()),
top: 0,
),
const SizedBox(height: 10.0),
@@ -168,7 +169,7 @@ class SearchPage extends HookConsumerWidget {
title:
Text('search_page_favorites', style: categoryTitleStyle)
.tr(),
onTap: () => context.autoPush(const FavoritesRoute()),
onTap: () => context.pushRoute(const FavoritesRoute()),
),
const CategoryDivider(),
ListTile(
@@ -180,7 +181,7 @@ class SearchPage extends HookConsumerWidget {
'search_page_recently_added',
style: categoryTitleStyle,
).tr(),
onTap: () => context.autoPush(const RecentlyAddedRoute()),
onTap: () => context.pushRoute(const RecentlyAddedRoute()),
),
const SizedBox(height: 24.0),
Padding(
@@ -200,7 +201,7 @@ class SearchPage extends HookConsumerWidget {
Icons.screenshot,
color: categoryIconColor,
),
onTap: () => context.autoPush(
onTap: () => context.pushRoute(
SearchResultRoute(
searchTerm: 'screenshots',
),
@@ -214,7 +215,7 @@ class SearchPage extends HookConsumerWidget {
Icons.photo_camera_front_outlined,
color: categoryIconColor,
),
onTap: () => context.autoPush(
onTap: () => context.pushRoute(
SearchResultRoute(
searchTerm: 'selfies',
),
@@ -228,7 +229,7 @@ class SearchPage extends HookConsumerWidget {
Icons.play_circle_outline,
color: categoryIconColor,
),
onTap: () => context.autoPush(const AllVideosRoute()),
onTap: () => context.pushRoute(const AllVideosRoute()),
),
const CategoryDivider(),
ListTile(
@@ -240,7 +241,7 @@ class SearchPage extends HookConsumerWidget {
Icons.motion_photos_on_outlined,
color: categoryIconColor,
),
onTap: () => context.autoPush(const AllMotionPhotosRoute()),
onTap: () => context.pushRoute(const AllMotionPhotosRoute()),
),
],
),

View File

@@ -1,3 +1,4 @@
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
@@ -185,7 +186,7 @@ class SearchResultPage extends HookConsumerWidget {
if (isNewSearch.value) {
isNewSearch.value = false;
} else {
context.autoPop(true);
context.popRoute(true);
}
},
icon: const Icon(Icons.arrow_back_ios_rounded),

View File

@@ -1,4 +1,5 @@
import 'dart:math' as math;
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@@ -210,8 +211,8 @@ class SharedLinkItem extends ConsumerWidget {
tapTargetSize:
MaterialTapTargetSize.shrinkWrap, // the '2023' part
),
onPressed: () =>
context.autoPush(SharedLinkEditRoute(existingLink: sharedLink)),
onPressed: () => context
.pushRoute(SharedLinkEditRoute(existingLink: sharedLink)),
),
IconButton(
splashRadius: 25,

View File

@@ -1,3 +1,4 @@
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@@ -317,7 +318,7 @@ class SharedLinkEditPage extends HookConsumerWidget {
alignment: Alignment.bottomRight,
child: ElevatedButton(
onPressed: () {
context.autoPop();
context.popRoute();
},
child: const Text(
"share_done",
@@ -417,7 +418,7 @@ class SharedLinkEditPage extends HookConsumerWidget {
changeExpiry: changeExpiry,
);
ref.invalidate(sharedLinksStateProvider);
context.autoPop();
context.popRoute();
}
return Scaffold(

View File

@@ -1,3 +1,4 @@
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
@@ -138,7 +139,7 @@ class TrashPage extends HookConsumerWidget {
return AppBar(
leading: IconButton(
onPressed: !selectionEnabledHook.value
? () => context.autoPop()
? () => context.popRoute()
: () {
selectionEnabledHook.value = false;
selection.value = {};