Add text labels to albums

This commit is contained in:
Ming Ming 2021-07-09 18:07:37 +08:00
parent 62e72bdf81
commit 444491114e
4 changed files with 491 additions and 150 deletions

View file

@ -28,6 +28,8 @@ abstract class AlbumItem {
switch (type) {
case AlbumFileItem._type:
return AlbumFileItem.fromJson(content.cast<String, dynamic>());
case AlbumLabelItem._type:
return AlbumLabelItem.fromJson(content.cast<String, dynamic>());
default:
_log.shout("[fromJson] Unknown type: $type");
throw ArgumentError.value(type, "type");
@ -38,6 +40,8 @@ abstract class AlbumItem {
String getType() {
if (this is AlbumFileItem) {
return AlbumFileItem._type;
} else if (this is AlbumLabelItem) {
return AlbumLabelItem._type;
} else {
throw StateError("Unknwon subtype");
}
@ -102,3 +106,38 @@ class AlbumFileItem extends AlbumItem with EquatableMixin {
static const _type = "file";
}
class AlbumLabelItem extends AlbumItem with EquatableMixin {
AlbumLabelItem({
@required this.text,
});
factory AlbumLabelItem.fromJson(Map<String, dynamic> json) {
return AlbumLabelItem(
text: json["text"],
);
}
@override
toString() {
return "$runtimeType {"
"text: '$text', "
"}";
}
@override
toContentJson() {
return {
"text": text,
};
}
@override
get props => [
text,
];
final String text;
static const _type = "label";
}

View file

@ -551,6 +551,10 @@
"@albumEditDragRearrangeNotification": {
"description": "Instructions on how to rearrange photos"
},
"albumAddTextTooltip": "Add text",
"@albumAddTextTooltip": {
"description": "Add some text that display between photos to an album"
},
"changelogTitle": "Changelog",
"@changelogTitle": {

View file

@ -24,6 +24,7 @@ import 'package:nc_photos/widget/album_viewer_mixin.dart';
import 'package:nc_photos/widget/draggable_item_list_mixin.dart';
import 'package:nc_photos/widget/photo_list_item.dart';
import 'package:nc_photos/widget/selectable_item_stream_list_mixin.dart';
import 'package:nc_photos/widget/simple_input_dialog.dart';
import 'package:nc_photos/widget/viewer.dart';
import 'package:quiver/iterables.dart';
@ -93,6 +94,9 @@ class _AlbumViewerState extends State<AlbumViewer>
enterEditMode() {
super.enterEditMode();
_editAlbum = _album.copyWith();
setState(() {
_transformItems();
});
if (!SessionStorage().hasShowDragRearrangeNotification) {
SnackBarManager().showSnackBar(SnackBar(
@ -230,6 +234,11 @@ class _AlbumViewerState extends State<AlbumViewer>
Widget _buildEditAppBar(BuildContext context) {
return buildEditAppBar(context, widget.account, widget.album, actions: [
IconButton(
icon: Icon(Icons.text_fields),
tooltip: AppLocalizations.of(context).albumAddTextTooltip,
onPressed: _onEditAppBarAddTextPressed,
),
IconButton(
icon: Icon(Icons.sort_by_alpha),
tooltip: AppLocalizations.of(context).sortTooltip,
@ -429,6 +438,50 @@ class _AlbumViewerState extends State<AlbumViewer>
});
}
void _onEditAppBarAddTextPressed() {
showDialog(
context: context,
builder: (context) => SimpleInputDialog(),
).then((value) {
if (value == null) {
return;
}
_editAlbum = _editAlbum.copyWith(
provider: AlbumStaticProvider(
items: [
AlbumLabelItem(text: value),
..._sortedItems,
],
),
);
setState(() {
_transformItems();
});
});
}
void _onLabelItemEditPressed(AlbumLabelItem item, int index) {
showDialog(
context: context,
builder: (context) => SimpleInputDialog(
initialText: item.text,
),
).then((value) {
if (value == null) {
return;
}
_sortedItems[index] = AlbumLabelItem(text: value);
_editAlbum = _editAlbum.copyWith(
provider: AlbumStaticProvider(
items: _sortedItems,
),
);
setState(() {
_transformItems();
});
});
}
void _transformItems() {
if (_editAlbum != null) {
// edit mode
@ -491,6 +544,29 @@ class _AlbumViewerState extends State<AlbumViewer>
_log.shout(
"[_transformItems] Unsupported file format: ${item.file.contentType}");
}
} else if (item is AlbumLabelItem) {
if (isEditMode) {
yield _EditLabelListItem(
index: i,
text: item.text,
onEditPressed: () => _onLabelItemEditPressed(item, i),
onDropBefore: (dropItem) =>
_onItemMoved((dropItem as _ListItem).index, i, true),
onDropAfter: (dropItem) =>
_onItemMoved((dropItem as _ListItem).index, i, false),
onDragStarted: () {
_isDragging = true;
},
onDragEndedAny: () {
_isDragging = false;
},
);
} else {
yield _LabelListItem(
index: i,
text: item.text,
);
}
}
}
}()
@ -582,6 +658,9 @@ abstract class _ListItem implements SelectableItem, DraggableItem {
@override
get staggeredTile => const StaggeredTile.count(1, 1);
@override
buildDragFeedbackWidget(BuildContext context) => null;
@override
toString() {
return "$runtimeType {"
@ -662,3 +741,88 @@ class _VideoListItem extends _ListItem {
final Account account;
final String previewUrl;
}
class _LabelListItem extends _ListItem {
_LabelListItem({
@required int index,
@required this.text,
DragTargetAccept<DraggableItem> onDropBefore,
DragTargetAccept<DraggableItem> onDropAfter,
VoidCallback onDragStarted,
VoidCallback onDragEndedAny,
}) : super(
index: index,
onDropBefore: onDropBefore,
onDropAfter: onDropAfter,
onDragStarted: onDragStarted,
onDragEndedAny: onDragEndedAny,
);
@override
get staggeredTile => const StaggeredTile.extent(99, 56);
@override
buildWidget(BuildContext context) {
return Container(
alignment: AlignmentDirectional.centerStart,
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(
text,
style: Theme.of(context).textTheme.subtitle1,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
);
}
final String text;
}
class _EditLabelListItem extends _LabelListItem {
_EditLabelListItem({
@required int index,
@required String text,
@required this.onEditPressed,
DragTargetAccept<DraggableItem> onDropBefore,
DragTargetAccept<DraggableItem> onDropAfter,
VoidCallback onDragStarted,
VoidCallback onDragEndedAny,
}) : super(
index: index,
text: text,
onDropBefore: onDropBefore,
onDropAfter: onDropAfter,
onDragStarted: onDragStarted,
onDragEndedAny: onDragEndedAny,
);
@override
buildWidget(BuildContext context) {
return Stack(
children: [
// needed to expand the touch sensitive area to the whole row
Container(
color: Colors.transparent,
),
super.buildWidget(context),
PositionedDirectional(
top: 0,
bottom: 0,
end: 0,
child: IconButton(
icon: Icon(Icons.edit),
tooltip: AppLocalizations.of(context).editTooltip,
onPressed: onEditPressed,
),
),
],
);
}
@override
buildDragFeedbackWidget(BuildContext context) {
return super.buildWidget(context);
}
final VoidCallback onEditPressed;
}

View file

@ -75,61 +75,107 @@ void main() {
));
});
test("AlbumStaticProvider", () {
final json = <String, dynamic>{
"version": Album.version,
"lastUpdated": "2020-01-02T03:04:05.678901Z",
"name": "",
"provider": <String, dynamic>{
"type": "static",
"content": <String, dynamic>{
"items": [
<String, dynamic>{
"type": "file",
"content": <String, dynamic>{
"file": <String, dynamic>{
"path": "remote.php/dav/files/admin/test1.jpg",
group("AlbumStaticProvider", () {
test("AlbumFileItem", () {
final json = <String, dynamic>{
"version": Album.version,
"lastUpdated": "2020-01-02T03:04:05.678901Z",
"name": "",
"provider": <String, dynamic>{
"type": "static",
"content": <String, dynamic>{
"items": [
<String, dynamic>{
"type": "file",
"content": <String, dynamic>{
"file": <String, dynamic>{
"path": "remote.php/dav/files/admin/test1.jpg",
},
},
},
},
<String, dynamic>{
"type": "file",
"content": <String, dynamic>{
"file": <String, dynamic>{
"path": "remote.php/dav/files/admin/test2.jpg",
<String, dynamic>{
"type": "file",
"content": <String, dynamic>{
"file": <String, dynamic>{
"path": "remote.php/dav/files/admin/test2.jpg",
},
},
},
},
],
},
},
"coverProvider": <String, dynamic>{
"type": "auto",
"content": <String, dynamic>{},
},
"sortProvider": <String, dynamic>{
"type": "null",
"content": <String, dynamic>{},
},
};
expect(
Album.fromJson(json),
Album(
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
name: "",
provider: AlbumStaticProvider(
items: [
AlbumFileItem(
file: File(path: "remote.php/dav/files/admin/test1.jpg"),
),
AlbumFileItem(
file: File(path: "remote.php/dav/files/admin/test2.jpg"),
),
],
),
coverProvider: AlbumAutoCoverProvider(),
sortProvider: AlbumNullSortProvider(),
));
},
},
"coverProvider": <String, dynamic>{
"type": "auto",
"content": <String, dynamic>{},
},
"sortProvider": <String, dynamic>{
"type": "null",
"content": <String, dynamic>{},
},
};
expect(
Album.fromJson(json),
Album(
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
name: "",
provider: AlbumStaticProvider(
items: [
AlbumFileItem(
file: File(path: "remote.php/dav/files/admin/test1.jpg"),
),
AlbumFileItem(
file: File(path: "remote.php/dav/files/admin/test2.jpg"),
),
],
),
coverProvider: AlbumAutoCoverProvider(),
sortProvider: AlbumNullSortProvider(),
));
});
test("AlbumLabelItem", () {
final json = <String, dynamic>{
"version": Album.version,
"lastUpdated": "2020-01-02T03:04:05.678901Z",
"name": "",
"provider": <String, dynamic>{
"type": "static",
"content": <String, dynamic>{
"items": [
<String, dynamic>{
"type": "label",
"content": <String, dynamic>{
"text": "Testing",
},
},
],
},
},
"coverProvider": <String, dynamic>{
"type": "auto",
"content": <String, dynamic>{},
},
"sortProvider": <String, dynamic>{
"type": "null",
"content": <String, dynamic>{},
},
};
expect(
Album.fromJson(json),
Album(
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
name: "",
provider: AlbumStaticProvider(
items: [
AlbumLabelItem(
text: "Testing",
),
],
),
coverProvider: AlbumAutoCoverProvider(),
sortProvider: AlbumNullSortProvider(),
));
});
});
test("AlbumAutoCoverProvider", () {
@ -310,58 +356,102 @@ void main() {
});
});
test("AlbumStaticProvider", () {
final album = Album(
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
name: "",
provider: AlbumStaticProvider(
items: [
AlbumFileItem(
file: File(path: "remote.php/dav/files/admin/test1.jpg"),
),
AlbumFileItem(
file: File(path: "remote.php/dav/files/admin/test2.jpg"),
),
],
),
coverProvider: AlbumAutoCoverProvider(),
sortProvider: AlbumNullSortProvider(),
);
expect(album.toRemoteJson(), <String, dynamic>{
"version": Album.version,
"lastUpdated": "2020-01-02T03:04:05.678901Z",
"name": "",
"provider": <String, dynamic>{
"type": "static",
"content": <String, dynamic>{
"items": [
<String, dynamic>{
"type": "file",
"content": <String, dynamic>{
"file": <String, dynamic>{
"path": "remote.php/dav/files/admin/test1.jpg",
},
},
},
<String, dynamic>{
"type": "file",
"content": <String, dynamic>{
"file": <String, dynamic>{
"path": "remote.php/dav/files/admin/test2.jpg",
},
},
},
group("AlbumStaticProvider", () {
test("AlbumFileItem", () {
final album = Album(
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
name: "",
provider: AlbumStaticProvider(
items: [
AlbumFileItem(
file: File(path: "remote.php/dav/files/admin/test1.jpg"),
),
AlbumFileItem(
file: File(path: "remote.php/dav/files/admin/test2.jpg"),
),
],
),
coverProvider: AlbumAutoCoverProvider(),
sortProvider: AlbumNullSortProvider(),
);
expect(album.toRemoteJson(), <String, dynamic>{
"version": Album.version,
"lastUpdated": "2020-01-02T03:04:05.678901Z",
"name": "",
"provider": <String, dynamic>{
"type": "static",
"content": <String, dynamic>{
"items": [
<String, dynamic>{
"type": "file",
"content": <String, dynamic>{
"file": <String, dynamic>{
"path": "remote.php/dav/files/admin/test1.jpg",
},
},
},
<String, dynamic>{
"type": "file",
"content": <String, dynamic>{
"file": <String, dynamic>{
"path": "remote.php/dav/files/admin/test2.jpg",
},
},
},
],
},
},
},
"coverProvider": <String, dynamic>{
"type": "auto",
"content": <String, dynamic>{},
},
"sortProvider": <String, dynamic>{
"type": "null",
"content": <String, dynamic>{},
},
"coverProvider": <String, dynamic>{
"type": "auto",
"content": <String, dynamic>{},
},
"sortProvider": <String, dynamic>{
"type": "null",
"content": <String, dynamic>{},
},
});
});
test("AlbumLabelItem", () {
final album = Album(
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
name: "",
provider: AlbumStaticProvider(
items: [
AlbumLabelItem(
text: "Testing",
),
],
),
coverProvider: AlbumAutoCoverProvider(),
sortProvider: AlbumNullSortProvider(),
);
expect(album.toRemoteJson(), <String, dynamic>{
"version": Album.version,
"lastUpdated": "2020-01-02T03:04:05.678901Z",
"name": "",
"provider": <String, dynamic>{
"type": "static",
"content": <String, dynamic>{
"items": [
<String, dynamic>{
"type": "label",
"content": <String, dynamic>{
"text": "Testing",
},
},
],
},
},
"coverProvider": <String, dynamic>{
"type": "auto",
"content": <String, dynamic>{},
},
"sortProvider": <String, dynamic>{
"type": "null",
"content": <String, dynamic>{},
},
});
});
});
@ -500,58 +590,102 @@ void main() {
});
});
test("AlbumStaticProvider", () {
final album = Album(
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
name: "",
provider: AlbumStaticProvider(
items: [
AlbumFileItem(
file: File(path: "remote.php/dav/files/admin/test1.jpg"),
),
AlbumFileItem(
file: File(path: "remote.php/dav/files/admin/test2.jpg"),
),
],
),
coverProvider: AlbumAutoCoverProvider(),
sortProvider: AlbumNullSortProvider(),
);
expect(album.toAppDbJson(), <String, dynamic>{
"version": Album.version,
"lastUpdated": "2020-01-02T03:04:05.678901Z",
"name": "",
"provider": <String, dynamic>{
"type": "static",
"content": <String, dynamic>{
"items": [
<String, dynamic>{
"type": "file",
"content": <String, dynamic>{
"file": <String, dynamic>{
"path": "remote.php/dav/files/admin/test1.jpg",
},
},
},
<String, dynamic>{
"type": "file",
"content": <String, dynamic>{
"file": <String, dynamic>{
"path": "remote.php/dav/files/admin/test2.jpg",
},
},
},
group("AlbumStaticProvider", () {
test("AlbumFileItem", () {
final album = Album(
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
name: "",
provider: AlbumStaticProvider(
items: [
AlbumFileItem(
file: File(path: "remote.php/dav/files/admin/test1.jpg"),
),
AlbumFileItem(
file: File(path: "remote.php/dav/files/admin/test2.jpg"),
),
],
),
coverProvider: AlbumAutoCoverProvider(),
sortProvider: AlbumNullSortProvider(),
);
expect(album.toAppDbJson(), <String, dynamic>{
"version": Album.version,
"lastUpdated": "2020-01-02T03:04:05.678901Z",
"name": "",
"provider": <String, dynamic>{
"type": "static",
"content": <String, dynamic>{
"items": [
<String, dynamic>{
"type": "file",
"content": <String, dynamic>{
"file": <String, dynamic>{
"path": "remote.php/dav/files/admin/test1.jpg",
},
},
},
<String, dynamic>{
"type": "file",
"content": <String, dynamic>{
"file": <String, dynamic>{
"path": "remote.php/dav/files/admin/test2.jpg",
},
},
},
],
},
},
},
"coverProvider": <String, dynamic>{
"type": "auto",
"content": <String, dynamic>{},
},
"sortProvider": <String, dynamic>{
"type": "null",
"content": <String, dynamic>{},
},
"coverProvider": <String, dynamic>{
"type": "auto",
"content": <String, dynamic>{},
},
"sortProvider": <String, dynamic>{
"type": "null",
"content": <String, dynamic>{},
},
});
});
test("AlbumLabelItem", () {
final album = Album(
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5, 678, 901),
name: "",
provider: AlbumStaticProvider(
items: [
AlbumLabelItem(
text: "Testing",
),
],
),
coverProvider: AlbumAutoCoverProvider(),
sortProvider: AlbumNullSortProvider(),
);
expect(album.toAppDbJson(), <String, dynamic>{
"version": Album.version,
"lastUpdated": "2020-01-02T03:04:05.678901Z",
"name": "",
"provider": <String, dynamic>{
"type": "static",
"content": <String, dynamic>{
"items": [
<String, dynamic>{
"type": "label",
"content": <String, dynamic>{
"text": "Testing",
},
},
],
},
},
"coverProvider": <String, dynamic>{
"type": "auto",
"content": <String, dynamic>{},
},
"sortProvider": <String, dynamic>{
"type": "null",
"content": <String, dynamic>{},
},
});
});
});