Add Visibility object concept throughout, including image displays

codemagic-setup
Hank Grabowski 2023-03-20 14:30:51 -04:00
rodzic a7f3bb26ad
commit aafe6dea7c
15 zmienionych plików z 226 dodań i 78 usunięć

Wyświetl plik

@ -42,8 +42,6 @@ class _StatusControlState extends State<FlattenedTreeEntryControl> {
TimelineEntry get entry => item.timelineEntry;
bool get isPublic => entry.isPublic;
bool get isPost => entry.parentId.isEmpty;
bool get hasComments => entry.engagementSummary.repliesCount > 0;

Wyświetl plik

@ -44,8 +44,6 @@ class _PostControlState extends State<PostControl> {
TimelineEntry get entry => item.entry;
bool get isPublic => item.entry.isPublic;
@override
void initState() {
super.initState();

Wyświetl plik

@ -5,6 +5,7 @@ import 'package:logging/logging.dart';
import '../../globals.dart';
import '../../models/connection.dart';
import '../../models/timeline_entry.dart';
import '../../models/visibility.dart';
import '../../routes.dart';
import '../../services/connections_manager.dart';
import '../../utils/active_profile_selector.dart';
@ -106,7 +107,9 @@ class StatusHeaderControl extends StatelessWidget {
),
const HorizontalPadding(),
Icon(
entry.isPublic ? Icons.public : Icons.lock,
entry.visibility.type == VisibilityType.public
? Icons.public
: Icons.lock,
color: Theme.of(context).hintColor,
size: Theme.of(context).textTheme.caption?.fontSize,
),

Wyświetl plik

@ -651,6 +651,7 @@ class StatusesClient extends FriendicaClient {
if (spoilerText.isNotEmpty) 'spoiler_text': spoilerText,
if (inReplyToId.isNotEmpty) 'in_reply_to_id': inReplyToId,
if (mediaIds.isNotEmpty) 'media_ids': mediaIds,
'visibility': 'unlisted',
'friendica': {
'title': '',
},

Wyświetl plik

@ -1,3 +1,5 @@
import 'visibility.dart';
class ImageEntry {
final String id;
final String album;
@ -7,18 +9,21 @@ class ImageEntry {
final DateTime created;
final int height;
final int width;
final Visibility visibility;
final List<ImageEntryScale> scales;
ImageEntry(
{required this.id,
required this.album,
required this.filename,
required this.description,
required this.thumbnailUrl,
required this.created,
required this.height,
required this.width,
required this.scales});
ImageEntry({
required this.id,
required this.album,
required this.filename,
required this.description,
required this.thumbnailUrl,
required this.created,
required this.height,
required this.width,
required this.visibility,
required this.scales,
});
@override
bool operator ==(Object other) =>

Wyświetl plik

@ -1,8 +1,9 @@
import 'package:path/path.dart' as p;
import 'package:relatica/models/image_entry.dart';
import '../globals.dart';
import 'attachment_media_type_enum.dart';
import 'image_entry.dart';
import 'visibility.dart';
class MediaAttachment {
static final _graphicsExtensions = ['jpg', 'png', 'gif', 'tif'];
@ -26,16 +27,20 @@ class MediaAttachment {
final String description;
MediaAttachment(
{required this.id,
required this.uri,
required this.creationTimestamp,
required this.metadata,
required this.thumbnailUri,
required this.fullFileUri,
required this.title,
required this.explicitType,
required this.description});
final Visibility visibility;
MediaAttachment({
required this.id,
required this.uri,
required this.creationTimestamp,
required this.metadata,
required this.thumbnailUri,
required this.fullFileUri,
required this.title,
required this.explicitType,
required this.description,
required this.visibility,
});
MediaAttachment.randomBuilt()
: id = randomId(),
@ -46,7 +51,11 @@ class MediaAttachment {
thumbnailUri = Uri.parse('${randomId()}.jpg'),
description = 'Random description ${randomId()}',
explicitType = AttachmentMediaType.image,
metadata = {'value1': randomId(), 'value2': randomId()};
metadata = {
'value1': randomId(),
'value2': randomId(),
},
visibility = Visibility.public();
MediaAttachment.blank()
: id = '',
@ -57,19 +66,8 @@ class MediaAttachment {
title = '',
fullFileUri = Uri(),
description = '',
metadata = {};
factory MediaAttachment.fromMastodonJson(Map<String, dynamic> json) =>
MediaAttachment(
id: json['id'] ?? '',
uri: Uri.parse(json['url'] ?? 'http://localhost'),
creationTimestamp: 0,
metadata: {},
thumbnailUri: Uri.parse(json['url'] ?? 'http://localhost'),
title: '',
fullFileUri: Uri.parse(json['remote_url'] ?? 'http://localhost'),
explicitType: AttachmentMediaType.parse(json['type']),
description: json['description'] ?? '');
metadata = {},
visibility = Visibility.public();
@override
String toString() {
@ -86,6 +84,7 @@ class MediaAttachment {
created: DateTime.fromMillisecondsSinceEpoch(creationTimestamp),
height: 0,
width: 0,
visibility: visibility,
scales: []);
}

Wyświetl plik

@ -5,6 +5,7 @@ import 'link_data.dart';
import 'link_preview_data.dart';
import 'location_data.dart';
import 'media_attachment.dart';
import 'visibility.dart';
class TimelineEntry {
final String id;
@ -33,7 +34,7 @@ class TimelineEntry {
final bool youReshared;
final bool isPublic;
final Visibility visibility;
final String author;
@ -64,7 +65,7 @@ class TimelineEntry {
this.backdatedTimestamp = 0,
this.modificationTimestamp = 0,
this.youReshared = false,
this.isPublic = true,
Visibility? visibility,
this.body = '',
this.title = '',
this.spoilerText = '',
@ -82,7 +83,8 @@ class TimelineEntry {
this.dislikes = const [],
this.mediaAttachments = const [],
this.engagementSummary = const EngagementSummary(),
this.linkPreviewData});
this.linkPreviewData})
: visibility = visibility ?? Visibility.public();
TimelineEntry.randomBuilt()
: creationTimestamp = DateTime.now().millisecondsSinceEpoch,
@ -90,7 +92,9 @@ class TimelineEntry {
modificationTimestamp = DateTime.now().millisecondsSinceEpoch,
id = randomId(),
youReshared = DateTime.now().second ~/ 2 == 0 ? true : false,
isPublic = DateTime.now().second ~/ 2 == 0 ? true : false,
visibility = DateTime.now().second ~/ 2 == 0
? Visibility.public()
: Visibility.private(),
parentId = randomId(),
externalLink = 'Random external link ${randomId()}',
body = 'Random post text ${randomId()}',
@ -116,7 +120,7 @@ class TimelineEntry {
int? backdatedTimestamp,
int? modificationTimestamp,
bool? isReshare,
bool? isPublic,
Visibility? visibility,
String? id,
String? parentId,
String? externalLink,
@ -145,7 +149,7 @@ class TimelineEntry {
modificationTimestamp ?? this.modificationTimestamp,
id: id ?? this.id,
youReshared: isReshare ?? this.youReshared,
isPublic: isPublic ?? this.isPublic,
visibility: visibility ?? this.visibility,
parentId: parentId ?? this.parentId,
externalLink: externalLink ?? this.externalLink,
body: body ?? this.body,
@ -195,7 +199,7 @@ class TimelineEntry {
title == other.title &&
spoilerText == other.spoilerText &&
youReshared == other.youReshared &&
isPublic == other.isPublic &&
visibility == other.visibility &&
author == other.author &&
authorId == other.authorId &&
externalLink == other.externalLink &&
@ -222,7 +226,7 @@ class TimelineEntry {
title.hashCode ^
spoilerText.hashCode ^
youReshared.hashCode ^
isPublic.hashCode ^
visibility.hashCode ^
author.hashCode ^
authorId.hashCode ^
externalLink.hashCode ^

Wyświetl plik

@ -0,0 +1,57 @@
enum VisibilityType {
public,
private,
}
class Visibility {
final VisibilityType type;
final List<String> allowedUserIds;
final List<String> excludedUserIds;
final List<String> allowedGroupIds;
final List<String> excludedGroupIds;
bool get hasDetails =>
allowedUserIds.isNotEmpty ||
excludedUserIds.isNotEmpty ||
allowedGroupIds.isNotEmpty ||
excludedGroupIds.isNotEmpty;
const Visibility({
required this.type,
this.allowedUserIds = const [],
this.excludedUserIds = const [],
this.allowedGroupIds = const [],
this.excludedGroupIds = const [],
});
factory Visibility.public() => const Visibility(
type: VisibilityType.public,
);
factory Visibility.private() => const Visibility(
type: VisibilityType.private,
);
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is Visibility &&
runtimeType == other.runtimeType &&
type == other.type &&
allowedUserIds == other.allowedUserIds &&
excludedUserIds == other.excludedUserIds &&
allowedGroupIds == other.allowedGroupIds &&
excludedGroupIds == other.excludedGroupIds;
@override
int get hashCode =>
type.hashCode ^
allowedUserIds.hashCode ^
excludedUserIds.hashCode ^
allowedGroupIds.hashCode ^
excludedGroupIds.hashCode;
}

Wyświetl plik

@ -6,6 +6,7 @@ import '../controls/login_aware_cached_network_image.dart';
import '../controls/standard_appbar.dart';
import '../controls/status_and_refresh_button.dart';
import '../globals.dart';
import '../models/visibility.dart';
import '../serializers/friendica/image_entry_friendica_extensions.dart';
import '../services/gallery_service.dart';
import '../services/network_status_service.dart';
@ -122,10 +123,23 @@ class GalleryScreen extends StatelessWidget {
);
}));
},
child: LoginAwareCachedNetworkImage(
width: thumbnailDimension,
height: thumbnailDimension,
imageUrl: image.thumbnailUrl,
child: Card(
child: Stack(
children: [
LoginAwareCachedNetworkImage(
width: thumbnailDimension,
height: thumbnailDimension,
imageUrl: image.thumbnailUrl,
),
Positioned(
bottom: 5.0,
right: 5.0,
child: Icon(image.visibility.type == VisibilityType.public
? Icons.public
: Icons.lock),
),
],
),
),
),
);

Wyświetl plik

@ -1,6 +1,7 @@
import '../../models/attachment_media_type_enum.dart';
import '../../models/image_entry.dart';
import '../../models/media_attachment.dart';
import 'visibility_friendica_extensions.dart';
extension ImageEntryFriendicaExtension on ImageEntry {
static ImageEntry fromJson(Map<String, dynamic> json) => ImageEntry(
@ -12,6 +13,7 @@ extension ImageEntryFriendicaExtension on ImageEntry {
created: DateTime.tryParse(json['created']) ?? DateTime(0),
height: json['height'],
width: json['width'],
visibility: VisibilityFriendicaExtensions.fromJson(json),
scales: (json['scales'] as List<dynamic>? ?? [])
.map((scaleJson) => _scaleFromJson(scaleJson))
.toList());
@ -30,14 +32,16 @@ extension ImageEntryFriendicaExtension on ImageEntry {
final thumbUri = Uri.parse(thumbnailUrl);
final fullFileUri = scales.first.link;
return MediaAttachment(
id: id,
uri: fullFileUri,
fullFileUri: fullFileUri,
creationTimestamp: created.millisecondsSinceEpoch,
metadata: {},
thumbnailUri: thumbUri,
title: filename,
explicitType: AttachmentMediaType.image,
description: description);
id: id,
uri: fullFileUri,
fullFileUri: fullFileUri,
creationTimestamp: created.millisecondsSinceEpoch,
metadata: {},
thumbnailUri: thumbUri,
title: filename,
explicitType: AttachmentMediaType.image,
description: description,
visibility: visibility,
);
}
}

Wyświetl plik

@ -1,8 +1,12 @@
import '../../models/attachment_media_type_enum.dart';
import '../../models/media_attachment.dart';
import '../../models/visibility.dart';
extension MediaAttachmentFriendicaExtensions on MediaAttachment {
static MediaAttachment fromJson(Map<String, dynamic> json) {
static MediaAttachment fromJson(
Map<String, dynamic> json,
Visibility visibility,
) {
final id = json['id'];
final uri = Uri.parse(json['url']);
const creationTimestamp = 0;
@ -18,14 +22,16 @@ extension MediaAttachmentFriendicaExtensions on MediaAttachment {
const description = '';
return MediaAttachment(
id: id,
uri: uri,
fullFileUri: uri,
creationTimestamp: creationTimestamp,
metadata: metadata,
thumbnailUri: thumbnailUri,
title: title,
explicitType: explicitType,
description: description);
id: id,
uri: uri,
fullFileUri: uri,
creationTimestamp: creationTimestamp,
metadata: metadata,
thumbnailUri: thumbnailUri,
title: title,
explicitType: explicitType,
description: description,
visibility: visibility,
);
}
}

Wyświetl plik

@ -2,6 +2,7 @@ import 'package:logging/logging.dart';
import '../../models/location_data.dart';
import '../../models/timeline_entry.dart';
import '../../models/visibility.dart';
import '../../utils/dateutils.dart';
import 'connection_friendica_extensions.dart';
import 'media_attachment_friendica_extensions.dart';
@ -22,7 +23,8 @@ extension TimelineEntryFriendicaExtensions on TimelineEntry {
: 0;
final id = json['id_str'] ?? '';
final isReshare = json.containsKey('retweeted_status');
final isPublic = json['friendica_private'] == 'false';
final isPublic = !(json['friendica_private'] ?? false);
final visibility = isPublic ? Visibility.public() : Visibility.private();
final parentId = json['in_reply_to_status_id_str'] ?? '';
final parentAuthor = json['in_reply_to_screen_name'] ?? '';
final parentAuthorId = json['in_reply_to_user_id_str'] ?? '';
@ -35,7 +37,7 @@ extension TimelineEntryFriendicaExtensions on TimelineEntry {
final modificationTimestamp = timestamp;
final backdatedTimestamp = timestamp;
final mediaAttachments = (json['attachments'] as List<dynamic>? ?? [])
.map((j) => MediaAttachmentFriendicaExtensions.fromJson(j))
.map((j) => MediaAttachmentFriendicaExtensions.fromJson(j, visibility))
.toList();
final likes =
(json['friendica_activities']?['like'] as List<dynamic>? ?? [])
@ -53,7 +55,7 @@ extension TimelineEntryFriendicaExtensions on TimelineEntry {
locationData: actualLocationData,
body: body,
youReshared: isReshare,
isPublic: isPublic,
visibility: visibility,
id: id,
parentId: parentId,
parentAuthorId: parentAuthorId,

Wyświetl plik

@ -0,0 +1,33 @@
import '../../models/visibility.dart';
extension VisibilityFriendicaExtensions on Visibility {
static Visibility fromJson(Map<String, dynamic> json) {
final allowedUserIds = _parseAcl(json['allow_cid']);
final excludedGroupIds = _parseAcl(json['deny_cid']);
final allowedGroupIds = _parseAcl(json['allow_gid']);
final excludedUserIds = _parseAcl(json['deny_cid']);
final topLevelPrivate = json['friendica_private'];
late final VisibilityType type;
if (topLevelPrivate == null) {
type = allowedUserIds.isEmpty &&
excludedUserIds.isEmpty &&
allowedGroupIds.isEmpty &&
excludedGroupIds.isEmpty
? VisibilityType.public
: VisibilityType.private;
} else {
type = topLevelPrivate ? VisibilityType.public : VisibilityType.private;
}
return Visibility(
type: type,
allowedUserIds: allowedUserIds,
excludedUserIds: excludedUserIds,
allowedGroupIds: allowedGroupIds,
excludedGroupIds: excludedGroupIds,
);
}
static List<String> _parseAcl(String? acl) =>
acl?.split(RegExp('[><]')).where((e) => e.isNotEmpty).toList() ?? [];
}

Wyświetl plik

@ -0,0 +1,20 @@
import '../../models/attachment_media_type_enum.dart';
import '../../models/media_attachment.dart';
import '../../models/visibility.dart';
extension MediaAttachmentMastodonExtension on MediaAttachment {
static MediaAttachment fromJson(
Map<String, dynamic> json, Visibility visibility) {
return MediaAttachment(
id: json['id'] ?? '',
uri: Uri.parse(json['url'] ?? 'http://localhost'),
creationTimestamp: 0,
metadata: {},
thumbnailUri: Uri.parse(json['url'] ?? 'http://localhost'),
title: '',
fullFileUri: Uri.parse(json['remote_url'] ?? 'http://localhost'),
explicitType: AttachmentMediaType.parse(json['type']),
description: json['description'] ?? '',
visibility: visibility);
}
}

Wyświetl plik

@ -4,8 +4,8 @@ import '../../globals.dart';
import '../../models/engagement_summary.dart';
import '../../models/link_data.dart';
import '../../models/location_data.dart';
import '../../models/media_attachment.dart';
import '../../models/timeline_entry.dart';
import '../../models/visibility.dart';
import '../../services/connections_manager.dart';
import '../../services/hashtag_service.dart';
import '../../utils/active_profile_selector.dart';
@ -13,6 +13,7 @@ import '../../utils/dateutils.dart';
import 'connection_mastodon_extensions.dart';
import 'hashtag_mastodon_extensions.dart';
import 'link_preview_mastodon_extensions.dart';
import 'media_attachment_mastodon_extension.dart';
final _logger = Logger('TimelineEntryMastodonExtensions');
@ -29,7 +30,9 @@ extension TimelineEntryMastodonExtensions on TimelineEntry {
: 0;
final id = json['id'] ?? '';
final youReshared = json['reblogged'] ?? false;
final isPublic = json['visibility'] == 'public';
final visibility = ['public', 'unlisted'].contains(json['visibility'])
? Visibility.public()
: Visibility.private();
final parentId = json['in_reply_to_id'] ?? '';
final parentAuthor = json['in_reply_to_account_id'] ?? '';
final parentAuthorId = json['in_reply_to_account_id'] ?? '';
@ -47,7 +50,8 @@ extension TimelineEntryMastodonExtensions on TimelineEntry {
? <LinkData>[]
: [LinkData.fromMastodonJson(json['card'])];
final mediaAttachments = (json['media_attachments'] as List<dynamic>? ?? [])
.map((json) => MediaAttachment.fromMastodonJson(json))
.map((json) =>
MediaAttachmentMastodonExtension.fromJson(json, visibility))
.toList();
final favoritesCount = json['favourites_count'] ?? 0;
final repliesCount = json['replies_count'] ?? 0;
@ -100,7 +104,7 @@ extension TimelineEntryMastodonExtensions on TimelineEntry {
spoilerText: spoilerText,
body: body,
youReshared: youReshared,
isPublic: isPublic,
visibility: visibility,
id: id,
parentId: parentId,
parentAuthorId: parentAuthorId,