1
0
mirror of https://github.com/immich-app/immich.git synced 2025-08-08 23:07:06 +02:00

feat(mobile): new mobile UI (#12582)

This commit is contained in:
Alex
2024-10-10 15:44:14 +07:00
committed by GitHub
parent b59abdff3d
commit e9813315e7
56 changed files with 1960 additions and 1274 deletions

View File

@ -0,0 +1,161 @@
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/providers/partner.provider.dart';
import 'package:immich_mobile/services/partner.service.dart';
import 'package:immich_mobile/entities/user.entity.dart';
import 'package:immich_mobile/widgets/common/confirm_dialog.dart';
import 'package:immich_mobile/widgets/common/immich_toast.dart';
import 'package:immich_mobile/widgets/common/user_avatar.dart';
@RoutePage()
class PartnerPage extends HookConsumerWidget {
const PartnerPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final List<User> partners = ref.watch(partnerSharedByProvider);
final availableUsers = ref.watch(partnerAvailableProvider);
addNewUsersHandler() async {
final users = availableUsers.value;
if (users == null || users.isEmpty) {
ImmichToast.show(
context: context,
msg: "partner_page_no_more_users".tr(),
);
return;
}
final selectedUser = await showDialog<User>(
context: context,
builder: (context) {
return SimpleDialog(
title: const Text("partner_page_select_partner").tr(),
children: [
for (User u in users)
SimpleDialogOption(
onPressed: () => context.pop(u),
child: Row(
children: [
Padding(
padding: const EdgeInsets.only(right: 8),
child: userAvatar(context, u),
),
Text(u.name),
],
),
),
],
);
},
);
if (selectedUser != null) {
final ok =
await ref.read(partnerServiceProvider).addPartner(selectedUser);
if (ok) {
ref.invalidate(partnerSharedByProvider);
} else {
ImmichToast.show(
context: context,
msg: "partner_page_partner_add_failed".tr(),
toastType: ToastType.error,
);
}
}
}
onDeleteUser(User u) {
return showDialog(
context: context,
builder: (BuildContext context) {
return ConfirmDialog(
title: "partner_page_stop_sharing_title",
content: "partner_page_stop_sharing_content".tr(args: [u.name]),
onOk: () => ref.read(partnerServiceProvider).removePartner(u),
);
},
);
}
buildUserList(List<User> users) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(left: 16.0, top: 16.0),
child: Text(
"partner_page_shared_to_title",
style: context.textTheme.titleSmall?.copyWith(
color: context.colorScheme.onSurface.withAlpha(200),
),
).tr(),
),
if (users.isNotEmpty)
ListView.builder(
shrinkWrap: true,
itemCount: users.length,
itemBuilder: ((context, index) {
return ListTile(
leading: userAvatar(context, users[index]),
title: Text(
users[index].email,
style: context.textTheme.bodyLarge,
),
trailing: IconButton(
icon: const Icon(Icons.person_remove),
onPressed: () => onDeleteUser(users[index]),
),
);
}),
),
if (users.isEmpty)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: const Text(
"partner_page_empty_message",
style: TextStyle(fontSize: 14),
).tr(),
),
Align(
alignment: Alignment.center,
child: ElevatedButton.icon(
onPressed: availableUsers.whenOrNull(
data: (data) => addNewUsersHandler,
),
icon: const Icon(Icons.person_add),
label: const Text("partner_page_add_partner").tr(),
),
),
],
),
),
],
);
}
return Scaffold(
appBar: AppBar(
title: const Text("partners").tr(),
elevation: 0,
centerTitle: false,
actions: [
IconButton(
onPressed:
availableUsers.whenOrNull(data: (data) => addNewUsersHandler),
icon: const Icon(Icons.person_add),
tooltip: "partner_page_add_partner".tr(),
),
],
),
body: buildUserList(partners),
);
}
}

View File

@ -0,0 +1,120 @@
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/providers/multiselect.provider.dart';
import 'package:immich_mobile/providers/partner.provider.dart';
import 'package:immich_mobile/entities/user.entity.dart';
import 'package:immich_mobile/providers/asset.provider.dart';
import 'package:immich_mobile/widgets/asset_grid/multiselect_grid.dart';
import 'package:immich_mobile/widgets/common/immich_toast.dart';
@RoutePage()
class PartnerDetailPage extends HookConsumerWidget {
const PartnerDetailPage({super.key, required this.partner});
final User partner;
@override
Widget build(BuildContext context, WidgetRef ref) {
final inTimeline = useState(partner.inTimeline);
bool toggleInProcess = false;
useEffect(
() {
Future.microtask(
() async => {
await ref.read(assetProvider.notifier).getAllAsset(),
},
);
return null;
},
[],
);
void toggleInTimeline() async {
if (toggleInProcess) return;
toggleInProcess = true;
try {
final ok = await ref
.read(partnerSharedWithProvider.notifier)
.updatePartner(partner, inTimeline: !inTimeline.value);
if (ok) {
inTimeline.value = !inTimeline.value;
final action = inTimeline.value ? "shown on" : "hidden from";
ImmichToast.show(
context: context,
toastType: ToastType.success,
durationInSecond: 1,
msg: "${partner.name}'s assets $action your timeline",
);
} else {
ImmichToast.show(
context: context,
toastType: ToastType.error,
durationInSecond: 1,
msg: "Failed to toggle the timeline setting",
);
}
} finally {
toggleInProcess = false;
}
}
return Scaffold(
appBar: ref.watch(multiselectProvider)
? null
: AppBar(
title: Text(partner.name),
elevation: 0,
centerTitle: false,
),
body: MultiselectGrid(
topWidget: Padding(
padding: const EdgeInsets.only(left: 8.0, right: 8.0, top: 16.0),
child: Container(
decoration: BoxDecoration(
border: Border.all(
color: context.colorScheme.onSurface.withAlpha(10),
width: 1,
),
borderRadius: BorderRadius.circular(20),
gradient: LinearGradient(
colors: [
context.colorScheme.primary.withAlpha(10),
context.colorScheme.primary.withAlpha(15),
],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: ListTile(
title: Text(
"Show in timeline",
style: context.textTheme.titleSmall?.copyWith(
color: context.colorScheme.primary,
),
),
subtitle: Text(
"Show photos and videos from this user in your timeline",
style: context.textTheme.bodyMedium,
),
trailing: Switch(
value: inTimeline.value,
onChanged: (_) => toggleInTimeline(),
),
),
),
),
),
renderListProvider: assetsProvider(partner.isarId),
onRefresh: () => ref.read(assetProvider.notifier).getAllAsset(),
deleteEnabled: false,
favoriteEnabled: false,
),
);
}
}