nc-photos/lib/widget/sharing_browser.dart

391 lines
13 KiB
Dart
Raw Normal View History

2021-10-06 22:32:36 +02:00
import 'package:cached_network_image/cached_network_image.dart';
2021-12-10 05:53:22 +01:00
import 'package:cached_network_image_platform_interface/cached_network_image_platform_interface.dart';
2021-10-06 22:32:36 +02:00
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:intl/intl.dart';
import 'package:kiwi/kiwi.dart';
2021-10-06 22:32:36 +02:00
import 'package:logging/logging.dart';
import 'package:nc_photos/account.dart';
import 'package:nc_photos/api/api.dart';
import 'package:nc_photos/api/api_util.dart' as api_util;
import 'package:nc_photos/app_localizations.dart';
import 'package:nc_photos/bloc/list_sharing.dart';
import 'package:nc_photos/cache_manager_util.dart';
import 'package:nc_photos/di_container.dart';
2021-10-12 12:15:58 +02:00
import 'package:nc_photos/entity/album.dart';
import 'package:nc_photos/entity/file.dart';
2021-10-12 12:15:58 +02:00
import 'package:nc_photos/entity/file/data_source.dart';
2021-10-06 22:32:36 +02:00
import 'package:nc_photos/entity/share.dart';
import 'package:nc_photos/exception_util.dart' as exception_util;
import 'package:nc_photos/iterable_extension.dart';
import 'package:nc_photos/k.dart' as k;
import 'package:nc_photos/object_extension.dart';
import 'package:nc_photos/or_null.dart';
import 'package:nc_photos/pref.dart';
2021-10-06 22:32:36 +02:00
import 'package:nc_photos/snack_bar_manager.dart';
import 'package:nc_photos/theme.dart';
import 'package:nc_photos/use_case/import_potential_shared_album.dart';
2021-10-12 12:15:58 +02:00
import 'package:nc_photos/widget/album_browser_util.dart' as album_browser_util;
2021-10-06 22:32:36 +02:00
import 'package:nc_photos/widget/empty_list_indicator.dart';
import 'package:nc_photos/widget/shared_file_viewer.dart';
import 'package:nc_photos/widget/unbounded_list_tile.dart';
2021-10-06 22:32:36 +02:00
class SharingBrowserArguments {
SharingBrowserArguments(this.account);
final Account account;
}
/// Show a list of all shares associated with this account
class SharingBrowser extends StatefulWidget {
static const routeName = "/sharing-browser";
static Route buildRoute(SharingBrowserArguments args) => MaterialPageRoute(
builder: (context) => SharingBrowser.fromArgs(args),
);
const SharingBrowser({
Key? key,
required this.account,
}) : super(key: key);
SharingBrowser.fromArgs(SharingBrowserArguments args, {Key? key})
: this(
key: key,
account: args.account,
);
@override
createState() => _SharingBrowserState();
final Account account;
}
class _SharingBrowserState extends State<SharingBrowser> {
@override
initState() {
super.initState();
2021-10-27 22:40:54 +02:00
if (Pref().isLabEnableSharedAlbumOr(false)) {
_importPotentialSharedAlbum().whenComplete(() {
_initBloc();
});
AccountPref.of(widget.account).run((obj) {
if (obj.hasNewSharedAlbumOr()) {
obj.setNewSharedAlbum(false);
}
});
} else {
_initBloc();
}
2021-10-06 22:32:36 +02:00
}
@override
build(BuildContext context) {
return AppTheme(
child: Scaffold(
body: BlocListener<ListSharingBloc, ListSharingBlocState>(
bloc: _bloc,
listener: (context, state) => _onStateChange(context, state),
child: BlocBuilder<ListSharingBloc, ListSharingBlocState>(
bloc: _bloc,
builder: (context, state) => _buildContent(context, state),
),
),
),
);
}
void _initBloc() {
if (_bloc.state is ListSharingBlocInit) {
_log.info("[_initBloc] Initialize bloc");
} else {
// process the current state
WidgetsBinding.instance!.addPostFrameCallback((_) {
setState(() {
_onStateChange(context, _bloc.state);
});
});
}
_reqQuery();
}
Widget _buildContent(BuildContext context, ListSharingBlocState state) {
if ((state is ListSharingBlocSuccess || state is ListSharingBlocFailure) &&
state.items.isEmpty) {
return _buildEmptyContent(context);
} else {
2021-10-09 21:35:04 +02:00
return Stack(
children: [
Theme(
data: Theme.of(context).copyWith(
colorScheme: Theme.of(context).colorScheme.copyWith(
secondary: AppTheme.getOverscrollIndicatorColor(context),
),
),
child: CustomScrollView(
slivers: [
SliverAppBar(
title: Text(L10n.global().collectionSharingLabel),
floating: true,
),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => _buildItem(context, _items[index]),
childCount: _items.length,
),
),
],
2021-10-06 22:32:36 +02:00
),
),
2021-10-09 21:35:04 +02:00
if (state is ListSharingBlocLoading)
const Align(
alignment: Alignment.bottomCenter,
child: LinearProgressIndicator(),
),
2021-10-06 22:32:36 +02:00
],
);
}
}
Widget _buildEmptyContent(BuildContext context) {
return Column(
children: [
AppBar(
title: Text(L10n.global().collectionSharingLabel),
elevation: 0,
),
Expanded(
child: EmptyListIndicator(
icon: Icons.share_outlined,
text: L10n.global().listEmptyText,
),
),
],
);
}
2021-10-12 12:15:58 +02:00
Widget _buildFileItem(BuildContext context, List<ListSharingItem> shares) {
assert(shares.first is ListSharingFile);
final dateStr =
_getDateFormat(context).format(shares.first.share.stime.toLocal());
final firstItem = shares.first as ListSharingFile;
2021-10-11 19:06:49 +02:00
return _ListTile(
leading: shares.first.share.itemType == ShareItemType.folder
2021-10-17 12:16:23 +02:00
? SizedBox(
2021-10-12 12:15:58 +02:00
height: _leadingSize,
width: _leadingSize,
2021-10-11 19:07:34 +02:00
child: Icon(
Icons.folder,
size: 32,
2021-10-17 12:16:23 +02:00
color: AppTheme.getUnfocusedIconColor(context),
2021-10-11 19:07:34 +02:00
),
2021-10-11 19:06:49 +02:00
)
: CachedNetworkImage(
2021-10-12 12:15:58 +02:00
width: _leadingSize,
height: _leadingSize,
2021-10-11 19:06:49 +02:00
cacheManager: ThumbnailCacheManager.inst,
imageUrl: api_util.getFilePreviewUrl(
2021-10-12 12:15:58 +02:00
widget.account, firstItem.file,
2021-10-11 19:06:49 +02:00
width: k.photoThumbSize, height: k.photoThumbSize),
httpHeaders: {
"Authorization":
Api.getAuthorizationHeaderValue(widget.account),
},
fadeInDuration: const Duration(),
filterQuality: FilterQuality.high,
imageRenderMethodForWeb: ImageRenderMethodForWeb.HttpGet,
errorWidget: (context, url, error) => Icon(
2021-12-19 19:28:44 +01:00
Icons.folder,
size: 32,
color: AppTheme.getUnfocusedIconColor(context),
),
2021-10-11 19:06:49 +02:00
),
label: shares.first.share.filename,
2021-11-12 22:13:02 +01:00
description: shares.first.share.uidOwner == widget.account.username
? L10n.global().fileLastSharedDescription(dateStr)
: L10n.global().fileLastSharedByOthersDescription(
shares.first.share.displaynameOwner, dateStr),
2021-10-11 19:06:49 +02:00
trailing: (shares.any((element) => element.share.url?.isNotEmpty == true))
2021-10-17 12:16:23 +02:00
? Icon(
Icons.link,
color: AppTheme.getUnfocusedIconColor(context),
)
2021-10-11 19:06:49 +02:00
: null,
2021-10-06 22:32:36 +02:00
onTap: () {
Navigator.of(context).pushNamed(SharedFileViewer.routeName,
arguments: SharedFileViewerArguments(
widget.account,
2021-10-12 12:15:58 +02:00
firstItem.file,
2021-10-06 22:32:36 +02:00
shares.map((e) => e.share).toList(),
));
},
);
}
2021-10-12 12:15:58 +02:00
Widget _buildAlbumItem(BuildContext context, List<ListSharingItem> shares) {
assert(shares.first is ListSharingAlbum);
final dateStr =
_getDateFormat(context).format(shares.first.share.stime.toLocal());
final firstItem = shares.first as ListSharingAlbum;
final cover = firstItem.album.coverProvider.getCover(firstItem.album);
return _ListTile(
leading: cover == null
2021-11-18 16:41:16 +01:00
? SizedBox(
height: _leadingSize,
width: _leadingSize,
child: Icon(
2021-12-19 19:28:44 +01:00
Icons.photo_album,
2021-11-18 16:41:16 +01:00
size: 32,
color: AppTheme.getUnfocusedIconColor(context),
),
2021-10-12 12:15:58 +02:00
)
: CachedNetworkImage(
width: _leadingSize,
height: _leadingSize,
cacheManager: ThumbnailCacheManager.inst,
imageUrl: api_util.getFilePreviewUrl(widget.account, cover,
width: k.photoThumbSize, height: k.photoThumbSize),
httpHeaders: {
"Authorization":
Api.getAuthorizationHeaderValue(widget.account),
},
fadeInDuration: const Duration(),
filterQuality: FilterQuality.high,
imageRenderMethodForWeb: ImageRenderMethodForWeb.HttpGet,
2021-10-17 12:16:23 +02:00
errorWidget: (context, url, error) => Icon(
2021-12-19 19:28:44 +01:00
Icons.photo_album,
2021-10-12 12:15:58 +02:00
size: 32,
2021-10-17 12:16:23 +02:00
color: AppTheme.getUnfocusedIconColor(context),
2021-10-12 12:15:58 +02:00
),
),
label: firstItem.album.name,
2021-11-12 22:13:02 +01:00
description: shares.first.share.uidOwner == widget.account.username
? L10n.global().fileLastSharedDescription(dateStr)
: L10n.global().albumLastSharedByOthersDescription(
shares.first.share.displaynameOwner, dateStr),
2021-10-17 12:16:23 +02:00
trailing: Icon(
Icons.photo_album_outlined,
color: AppTheme.getUnfocusedIconColor(context),
),
2021-10-12 12:15:58 +02:00
onTap: () =>
_onAlbumShareItemTap(context, shares.first as ListSharingAlbum),
);
}
Widget _buildItem(BuildContext context, List<ListSharingItem> shares) {
if (shares.first is ListSharingFile) {
return _buildFileItem(context, shares);
} else if (shares.first is ListSharingAlbum) {
return _buildAlbumItem(context, shares);
} else {
2021-10-17 12:16:53 +02:00
throw StateError("Unknown item type: ${shares.first.runtimeType}");
2021-10-12 12:15:58 +02:00
}
}
2021-10-06 22:32:36 +02:00
void _onStateChange(BuildContext context, ListSharingBlocState state) {
if (state is ListSharingBlocInit) {
_items = [];
} else if (state is ListSharingBlocSuccess ||
state is ListSharingBlocLoading) {
_transformItems(state.items);
} else if (state is ListSharingBlocFailure) {
_transformItems(state.items);
SnackBarManager().showSnackBar(SnackBar(
content: Text(exception_util.toUserString(state.exception)),
duration: k.snackBarDurationNormal,
));
}
}
2021-10-12 12:15:58 +02:00
Future<void> _onAlbumShareItemTap(
BuildContext context, ListSharingAlbum share) {
2021-11-21 14:36:13 +01:00
return album_browser_util.push(context, widget.account, share.album);
2021-10-12 12:15:58 +02:00
}
2021-10-06 22:32:36 +02:00
void _transformItems(List<ListSharingItem> items) {
// group shares of the same file
final map = <String, List<ListSharingItem>>{};
for (final i in items) {
2021-11-12 22:13:02 +01:00
final isSharedByMe = (i.share.uidOwner == widget.account.username);
2021-10-11 19:03:37 +02:00
final groupKey = "${i.share.path}?$isSharedByMe";
map[groupKey] ??= <ListSharingItem>[];
map[groupKey]!.add(i);
2021-10-06 22:32:36 +02:00
}
// sort the sub-lists
for (final list in map.values) {
list.sort((a, b) => b.share.stime.compareTo(a.share.stime));
}
// then sort the map and convert it to list
_items = map.entries
.sorted((a, b) =>
b.value.first.share.stime.compareTo(a.value.first.share.stime))
.map((e) => e.value)
.toList();
}
void _reqQuery() {
_bloc.add(ListSharingBlocQuery(widget.account));
}
Future<List<Album>> _importPotentialSharedAlbum() async {
final c = KiwiContainer().resolve<DiContainer>().copyWith(
// don't want the potential albums to be cached at this moment
fileRepo: OrNull(const FileRepo(FileWebdavDataSource())),
albumRepo: OrNull(AlbumRepo(AlbumRemoteDataSource())),
);
try {
return await ImportPotentialSharedAlbum(c)(
2021-12-05 13:02:22 +01:00
widget.account, AccountPref.of(widget.account));
} catch (e, stackTrace) {
_log.shout(
"[_importPotentialSharedAlbum] Failed while ImportPotentialSharedAlbum",
e,
stackTrace);
return [];
}
}
2021-10-06 22:32:36 +02:00
late final _bloc = ListSharingBloc.of(widget.account);
var _items = <List<ListSharingItem>>[];
static final _log = Logger("widget.sharing_browser._SharingBrowserState");
}
2021-10-11 19:06:49 +02:00
class _ListTile extends StatelessWidget {
const _ListTile({
required this.leading,
required this.label,
required this.description,
this.trailing,
required this.onTap,
});
@override
build(BuildContext context) {
return UnboundedListTile(
leading: leading,
title: Text(
label,
maxLines: 1,
overflow: TextOverflow.ellipsis,
2021-10-11 19:06:49 +02:00
),
subtitle: Text(description),
trailing: trailing,
onTap: onTap,
2021-10-11 19:06:49 +02:00
);
}
final Widget leading;
final String label;
final String description;
final Widget? trailing;
final VoidCallback onTap;
}
2021-10-12 12:15:58 +02:00
const _leadingSize = 56.0;
DateFormat _getDateFormat(BuildContext context) => DateFormat(
DateFormat.YEAR_ABBR_MONTH_DAY,
Localizations.localeOf(context).languageCode);