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
+15
View File
@@ -51,6 +51,21 @@ class User {
avatarColor = dto.avatarColor.toAvatarColor(),
inTimeline = dto.inTimeline ?? false;
/// Base user dto used where the complete user object is not required
User.fromSimpleUserDto(UserDto dto)
: id = dto.id,
email = dto.email,
name = dto.name,
profileImagePath = dto.profileImagePath,
avatarColor = dto.avatarColor.toAvatarColor(),
// Fill the remaining fields with placeholders
isAdmin = false,
inTimeline = false,
memoryEnabled = false,
isPartnerSharedBy = false,
isPartnerSharedWith = false,
updatedAt = DateTime.now();
@Index(unique: true, replace: false, type: IndexType.hash)
String id;
DateTime updatedAt;
@@ -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,7 +91,7 @@ class ImmichAppBarDialog extends HookConsumerWidget {
return buildActionButton(
Icons.settings_rounded,
"profile_drawer_settings",
() => context.autoPush(const SettingsRoute()),
() => context.pushRoute(const SettingsRoute()),
);
}
@@ -98,7 +99,7 @@ class ImmichAppBarDialog extends HookConsumerWidget {
return buildActionButton(
Icons.assignment_outlined,
"profile_drawer_app_logs",
() => context.autoPush(const AppLogRoute()),
() => context.pushRoute(const AppLogRoute()),
);
}
@@ -121,7 +122,7 @@ class ImmichAppBarDialog extends HookConsumerWidget {
ref.watch(backupProvider.notifier).cancelBackup();
ref.watch(assetProvider.notifier).clearAllAsset();
ref.watch(websocketProvider.notifier).disconnect();
context.autoReplace(const LoginRoute());
context.replaceRoute(const LoginRoute());
},
);
},
@@ -1,12 +1,12 @@
import 'dart:async';
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.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/album/providers/album.provider.dart';
import 'package:immich_mobile/modules/album/providers/shared_album.provider.dart';
import 'package:immich_mobile/modules/album/services/album.service.dart';
@@ -158,7 +158,7 @@ class MultiselectGrid extends HookConsumerWidget {
final ids =
remoteSelection(errorMessage: "home_page_share_err_local".tr())
.map((e) => e.remoteId!);
context.autoPush(SharedLinkEditRoute(assetsList: ids.toList()));
context.pushRoute(SharedLinkEditRoute(assetsList: ids.toList()));
}
processing.value = false;
selectionEnabledHook.value = false;
@@ -301,7 +301,7 @@ class MultiselectGrid extends HookConsumerWidget {
ref.watch(sharedAlbumProvider.notifier).getAllSharedAlbums();
selectionEnabledHook.value = false;
context.autoPush(AlbumViewerRoute(albumId: result.id));
context.pushRoute(AlbumViewerRoute(albumId: result.id));
}
} finally {
processing.value = false;
+2 -1
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';
@@ -106,7 +107,7 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget {
final badgeBackground = isDarkTheme ? Colors.blueGrey[800] : Colors.white;
return InkWell(
onTap: () => context.autoPush(const BackupControllerRoute()),
onTap: () => context.pushRoute(const BackupControllerRoute()),
borderRadius: BorderRadius.circular(12),
child: Badge(
label: Container(
+13
View File
@@ -162,6 +162,19 @@ class ImmichImage extends StatelessWidget {
headers: authHeader,
);
/// TODO: refactor image providers to separate class
static CachedNetworkImageProvider remoteThumbnailProviderForId(
String assetId, {
api.ThumbnailFormat type = api.ThumbnailFormat.WEBP,
}) =>
CachedNetworkImageProvider(
getThumbnailUrlForRemoteId(assetId, type: type),
cacheKey: getThumbnailCacheKeyForRemoteId(assetId, type: type),
headers: {
"Authorization": 'Bearer ${Store.get(StoreKey.accessToken)}',
},
);
/// Precaches this asset for instant load the next time it is shown
static Future<void> precacheAsset(
Asset asset,
+2 -1
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';
@@ -97,7 +98,7 @@ class _LocationPicker extends HookWidget {
zoom: 6,
showAttribution: false,
onTap: (p0, p1) async {
final newLatLng = await context.autoPush<LatLng?>(
final newLatLng = await context.pushRoute<LatLng?>(
MapLocationPickerRoute(initialLatLng: latlng),
);
if (newLatLng != null) {
+11 -1
View File
@@ -5,8 +5,9 @@ import 'package:immich_mobile/extensions/build_context_extensions.dart';
// Error widget to be used in Scaffold when an AsyncError is received
class ScaffoldErrorBody extends StatelessWidget {
final bool withIcon;
final String? errorMsg;
const ScaffoldErrorBody({super.key, this.withIcon = true});
const ScaffoldErrorBody({super.key, this.withIcon = true, this.errorMsg});
@override
Widget build(BuildContext context) {
@@ -30,6 +31,15 @@ class ScaffoldErrorBody extends StatelessWidget {
),
),
),
if (withIcon && errorMsg != null)
Padding(
padding: const EdgeInsets.all(20),
child: Text(
errorMsg!,
style: context.textTheme.displaySmall,
textAlign: TextAlign.center,
),
),
],
);
}
+3 -2
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';
@@ -103,7 +104,7 @@ class AppLogPage extends HookConsumerWidget {
],
leading: IconButton(
onPressed: () {
context.autoPop();
context.popRoute();
},
icon: const Icon(
Icons.arrow_back_ios_new_rounded,
@@ -123,7 +124,7 @@ class AppLogPage extends HookConsumerWidget {
itemBuilder: (context, index) {
var logMessage = logMessages.value[index];
return ListTile(
onTap: () => context.autoPush(
onTap: () => context.pushRoute(
AppLogDetailRoute(
logMessage: logMessage,
),
+6 -6
View File
@@ -1,7 +1,7 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/modules/backup/providers/backup.provider.dart';
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
import 'package:immich_mobile/modules/onboarding/providers/gallery_permission.provider.dart';
@@ -57,14 +57,14 @@ class SplashScreenPage extends HookConsumerWidget {
stackTrace,
);
context.autoPush(const LoginRoute());
context.pushRoute(const LoginRoute());
}
}
// If the device is offline and there is a currentUser stored locallly
// Proceed into the app
if (deviceIsOffline && Store.tryGet(StoreKey.currentUser) != null) {
context.autoReplace(const TabControllerRoute());
context.replaceRoute(const TabControllerRoute());
} else if (isSuccess) {
// If device was able to login through the internet successfully
final hasPermission =
@@ -73,10 +73,10 @@ class SplashScreenPage extends HookConsumerWidget {
// Resume backup (if enable) then navigate
ref.watch(backupProvider.notifier).resumeBackup();
}
context.autoReplace(const TabControllerRoute());
context.replaceRoute(const TabControllerRoute());
} else {
// User was unable to login through either offline or online methods
context.autoReplace(const LoginRoute());
context.replaceRoute(const LoginRoute());
}
}
@@ -85,7 +85,7 @@ class SplashScreenPage extends HookConsumerWidget {
if (serverUrl != null && accessToken != null) {
performLoggingIn();
} else {
context.autoReplace(const LoginRoute());
context.replaceRoute(const LoginRoute());
}
return null;
},