mirror of
https://gitlab.com/mysocialportal/relatica
synced 2024-10-18 13:33:32 +00:00
Merge branch 'status-privacy-overhaul' into 'main'
Status privacy overhaul See merge request mysocialportal/relatica!48
This commit is contained in:
commit
e3967b89bc
6 changed files with 287 additions and 80 deletions
|
@ -5,7 +5,7 @@ import 'package:logging/logging.dart';
|
|||
import '../../globals.dart';
|
||||
import '../../models/connection.dart';
|
||||
import '../../models/timeline_entry.dart';
|
||||
import '../../models/visibility.dart';
|
||||
import '../../models/visibility.dart' as v;
|
||||
import '../../routes.dart';
|
||||
import '../../services/auth_service.dart';
|
||||
import '../../services/connections_manager.dart';
|
||||
|
@ -14,6 +14,7 @@ import '../../utils/active_profile_selector.dart';
|
|||
import '../../utils/dateutils.dart';
|
||||
import '../image_control.dart';
|
||||
import '../padding.dart';
|
||||
import '../visibility_dialog.dart';
|
||||
|
||||
class StatusHeaderControl extends StatelessWidget {
|
||||
static final _logger = Logger('$StatusHeaderControl');
|
||||
|
@ -39,22 +40,22 @@ class StatusHeaderControl extends StatelessWidget {
|
|||
.getForProfile(activeProfile)
|
||||
.transform((s) => s.getForPost(entry.id)?.resharers.firstOrNull)
|
||||
.getValueOrElse(() => null);
|
||||
|
||||
getIt<ActiveProfileSelector<ConnectionsManager>>()
|
||||
final manager = getIt<ActiveProfileSelector<ConnectionsManager>>()
|
||||
.getForProfile(activeProfile)
|
||||
.match(
|
||||
onSuccess: (manager) {
|
||||
author =
|
||||
manager.getById(entry.authorId).getValueOrElse(() => Connection());
|
||||
.fold(
|
||||
onSuccess: (m) => m,
|
||||
onError: (error) {
|
||||
_logger.severe('Error getting connection manager: $error');
|
||||
return null;
|
||||
});
|
||||
|
||||
author = manager!.getById(entry.authorId).getValueOrElse(
|
||||
() => Connection(),
|
||||
);
|
||||
reshareAuthor = reshareId == null
|
||||
? Connection()
|
||||
: manager.getById(reshareId).getValueOrElse(() => Connection());
|
||||
},
|
||||
onError: (error) {
|
||||
_logger.severe('Error getting connections manageR: $error');
|
||||
author = Connection();
|
||||
reshareAuthor = Connection();
|
||||
},
|
||||
: manager.getById(reshareId).getValueOrElse(
|
||||
() => Connection(),
|
||||
);
|
||||
|
||||
return Column(
|
||||
|
@ -122,14 +123,18 @@ class StatusHeaderControl extends StatelessWidget {
|
|||
ElapsedDateUtils.epochSecondsToString(entry.backdatedTimestamp),
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
const HorizontalPadding(),
|
||||
Icon(
|
||||
entry.visibility.type == VisibilityType.public
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
await showVisibilityDialog(context, manager, entry.visibility);
|
||||
},
|
||||
icon: Icon(
|
||||
entry.visibility.type == v.VisibilityType.public
|
||||
? Icons.public
|
||||
: Icons.lock,
|
||||
color: Theme.of(context).hintColor,
|
||||
size: Theme.of(context).textTheme.bodySmall?.fontSize,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
|
146
lib/controls/visibility_dialog.dart
Normal file
146
lib/controls/visibility_dialog.dart
Normal file
|
@ -0,0 +1,146 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../models/visibility.dart' as v;
|
||||
import '../services/connections_manager.dart';
|
||||
|
||||
Future<bool?> showVisibilityDialog(
|
||||
BuildContext context,
|
||||
ConnectionsManager cm,
|
||||
v.Visibility visibility,
|
||||
) async {
|
||||
final circlesMap = {for (var item in cm.getMyCircles()) item.id: item};
|
||||
|
||||
final allowedCircles = visibility.allowedCircleIds.map((c) {
|
||||
if (c == '~') {
|
||||
return 'Followers';
|
||||
}
|
||||
|
||||
return circlesMap[c]?.name ?? 'Circle #$c';
|
||||
}).toList();
|
||||
|
||||
final excludedCircles = visibility.excludedCircleIds.map((c) {
|
||||
if (c == '~') {
|
||||
return 'Followers';
|
||||
}
|
||||
|
||||
return circlesMap[c]?.name ?? 'Circle #$c';
|
||||
}).toList();
|
||||
|
||||
final allowedUsers = visibility.allowedUserIds
|
||||
.map(
|
||||
(u) => cm.getById(u).fold(
|
||||
onSuccess: (connection) => connection.handle,
|
||||
onError: (_) => 'User $u',
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
|
||||
final excludedUsers = visibility.excludedUserIds
|
||||
.map(
|
||||
(u) => cm.getById(u).fold(
|
||||
onSuccess: (connection) => connection.handle,
|
||||
onError: (_) => 'User $u',
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
|
||||
return showDialog<bool>(
|
||||
context: context,
|
||||
barrierDismissible: true,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
content: SizedBox(
|
||||
width: MediaQuery.of(context).size.width * 0.8,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Visibility Details',
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyLarge
|
||||
?.copyWith(decoration: TextDecoration.underline),
|
||||
),
|
||||
if (visibility.type == v.VisibilityType.public) ...[
|
||||
const Text('Public')
|
||||
],
|
||||
if (visibility.type != v.VisibilityType.public) ...[
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Allowed Users: ',
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
allowedUsers.isEmpty
|
||||
? 'Empty'
|
||||
: allowedUsers.join(', '),
|
||||
softWrap: true,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Allowed Circles: ',
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
Text(
|
||||
allowedCircles.isEmpty
|
||||
? 'Empty'
|
||||
: allowedCircles.join(','),
|
||||
softWrap: true,
|
||||
)
|
||||
],
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Excluded Users: ',
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
Text(
|
||||
excludedUsers.isEmpty ? 'Empty' : excludedUsers.join(','),
|
||||
softWrap: true,
|
||||
)
|
||||
],
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Excluded Circles: ',
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
Text(
|
||||
excludedCircles.isEmpty
|
||||
? 'Empty'
|
||||
: excludedCircles.join(','),
|
||||
softWrap: true,
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
ElevatedButton(
|
||||
child: const Text('Dismiss'),
|
||||
onPressed: () {
|
||||
Navigator.pop(context, true); // showDialog() returns true
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
|
@ -843,7 +843,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': visibility.toCreateStatusValue(),
|
||||
'visibility': visibility.toCreateStatusValue(inReplyToId.isNotEmpty),
|
||||
'friendica': {
|
||||
'title': '',
|
||||
},
|
||||
|
|
|
@ -15,6 +15,7 @@ import '../controls/login_aware_cached_network_image.dart';
|
|||
import '../controls/padding.dart';
|
||||
import '../controls/standard_appbar.dart';
|
||||
import '../controls/timeline/status_header_control.dart';
|
||||
import '../controls/visibility_dialog.dart';
|
||||
import '../globals.dart';
|
||||
import '../models/circle_data.dart';
|
||||
import '../models/exec_error.dart';
|
||||
|
@ -24,6 +25,7 @@ import '../models/media_attachment_uploads/new_entry_media_items.dart';
|
|||
import '../models/timeline_entry.dart';
|
||||
import '../models/visibility.dart';
|
||||
import '../serializers/friendica/link_preview_friendica_extensions.dart';
|
||||
import '../services/connections_manager.dart';
|
||||
import '../services/feature_version_checker.dart';
|
||||
import '../services/timeline_manager.dart';
|
||||
import '../utils/active_profile_selector.dart';
|
||||
|
@ -566,13 +568,22 @@ class _EditorScreenState extends State<EditorScreen> {
|
|||
|
||||
Widget buildVisibilitySelector(BuildContext context) {
|
||||
if (widget.forEditing || widget.parentId.isNotEmpty) {
|
||||
final cm = context
|
||||
.read<ActiveProfileSelector<ConnectionsManager>>()
|
||||
.activeEntry
|
||||
.value;
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
const Text('Visibility:'),
|
||||
const HorizontalPadding(),
|
||||
visibility.type == VisibilityType.public
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
await showVisibilityDialog(context, cm, visibility);
|
||||
},
|
||||
icon: visibility.type == VisibilityType.public
|
||||
? const Icon(Icons.public)
|
||||
: const Icon(Icons.lock),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
@ -583,34 +594,37 @@ class _EditorScreenState extends State<EditorScreen> {
|
|||
.getValueOrElse(() => []);
|
||||
circles.sort((g1, g2) => g1.name.compareTo(g2.name));
|
||||
|
||||
final circleMenuItems = <DropdownMenuEntry<CircleData>>[];
|
||||
circleMenuItems.add(DropdownMenuEntry(
|
||||
final circleMenuItems = <DropdownMenuItem<CircleData>>[];
|
||||
circleMenuItems.add(DropdownMenuItem(
|
||||
value: CircleData.followersPseudoCircle,
|
||||
label: CircleData.followersPseudoCircle.name));
|
||||
circleMenuItems.add(DropdownMenuEntry(
|
||||
value: CircleData('', ''), label: '-', enabled: false));
|
||||
circleMenuItems.addAll(circles.map((g) => DropdownMenuEntry(
|
||||
child: Text(CircleData.followersPseudoCircle.name)));
|
||||
circleMenuItems.add(DropdownMenuItem(
|
||||
value: CircleData('', ''), child: const Divider(), enabled: false));
|
||||
circleMenuItems.addAll(circles.map((g) => DropdownMenuItem(
|
||||
value: g,
|
||||
label: g.name,
|
||||
child: Text(g.name),
|
||||
)));
|
||||
if (!circles.contains(currentCircle)) {
|
||||
if (currentCircle != CircleData.followersPseudoCircle &&
|
||||
!circles.contains(currentCircle)) {
|
||||
currentCircle = null;
|
||||
}
|
||||
return Row(
|
||||
children: [
|
||||
const Text('Visibility:'),
|
||||
const HorizontalPadding(),
|
||||
DropdownMenu<VisibilityType>(
|
||||
initialSelection: visibility.type,
|
||||
enabled: !widget.forEditing,
|
||||
onSelected: (value) {
|
||||
DropdownButton<VisibilityType>(
|
||||
value: visibility.type,
|
||||
onChanged: widget.forEditing
|
||||
? null
|
||||
: (value) {
|
||||
setState(() {
|
||||
if (value == VisibilityType.public) {
|
||||
visibility = Visibility.public();
|
||||
return;
|
||||
}
|
||||
|
||||
if (value == VisibilityType.private && currentCircle == null) {
|
||||
if (value == VisibilityType.private &&
|
||||
currentCircle == null) {
|
||||
visibility = Visibility.private();
|
||||
return;
|
||||
}
|
||||
|
@ -621,19 +635,22 @@ class _EditorScreenState extends State<EditorScreen> {
|
|||
);
|
||||
});
|
||||
},
|
||||
dropdownMenuEntries: VisibilityType.values
|
||||
.map((v) => DropdownMenuEntry(
|
||||
items: VisibilityType.values
|
||||
.map((v) => DropdownMenuItem(
|
||||
value: v,
|
||||
label: v.toLabel(),
|
||||
child: Text(v.toLabel()),
|
||||
))
|
||||
.toList(),
|
||||
),
|
||||
const HorizontalPadding(),
|
||||
if (visibility.type == VisibilityType.private)
|
||||
DropdownMenu<CircleData>(
|
||||
enabled: !widget.forEditing,
|
||||
initialSelection: currentCircle,
|
||||
onSelected: (value) {
|
||||
Expanded(
|
||||
child: DropdownButton<CircleData>(
|
||||
value: currentCircle,
|
||||
isExpanded: true,
|
||||
onChanged: widget.forEditing
|
||||
? null
|
||||
: (value) {
|
||||
setState(() {
|
||||
currentCircle = value;
|
||||
visibility = Visibility(
|
||||
|
@ -643,7 +660,8 @@ class _EditorScreenState extends State<EditorScreen> {
|
|||
);
|
||||
});
|
||||
},
|
||||
dropdownMenuEntries: circleMenuItems,
|
||||
items: circleMenuItems,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
|
|
@ -71,9 +71,33 @@ extension TimelineEntryMastodonExtensions on TimelineEntry {
|
|||
: 0;
|
||||
|
||||
final youReshared = json['reblogged'] ?? false;
|
||||
final visibility = ['public', 'unlisted'].contains(json['visibility'])
|
||||
? Visibility.public()
|
||||
: Visibility.private();
|
||||
late final Visibility visibility;
|
||||
final visibilityString = json['visibility'];
|
||||
if (visibilityString == 'public') {
|
||||
visibility = Visibility.public();
|
||||
} else if (visibilityString == 'private') {
|
||||
final allowedUserIds =
|
||||
json['friendica']?['visibility']?['allow_cid'] as List<dynamic>? ??
|
||||
[];
|
||||
final excludedUserIds =
|
||||
json['friendica']?['visibility']?['deny_cid'] as List<dynamic>? ?? [];
|
||||
final allowedCircleIds =
|
||||
json['friendica']?['visibility']?['allow_gid'] as List<dynamic>? ??
|
||||
[];
|
||||
final excludedCircleIds =
|
||||
json['friendica']?['visibility']?['deny_gid'] as List<dynamic>? ?? [];
|
||||
visibility = Visibility(
|
||||
type: VisibilityType.private,
|
||||
allowedUserIds: allowedUserIds.map((e) => e.toString()).toList(),
|
||||
excludedUserIds: excludedUserIds.map((e) => e.toString()).toList(),
|
||||
allowedCircleIds: allowedCircleIds.map((e) => e.toString()).toList(),
|
||||
excludedCircleIds: excludedCircleIds.map((e) => e.toString()).toList(),
|
||||
);
|
||||
} else if (visibilityString == 'unlisted') {
|
||||
visibility = Visibility.private();
|
||||
} else {
|
||||
visibility = Visibility.private();
|
||||
}
|
||||
|
||||
const title = '';
|
||||
final body = json['content'] ?? '';
|
||||
|
|
|
@ -3,20 +3,34 @@ import 'package:relatica/models/circle_data.dart';
|
|||
import '../../models/visibility.dart';
|
||||
|
||||
extension VisibilityMastodonExtensions on Visibility {
|
||||
String toCreateStatusValue() {
|
||||
String toCreateStatusValue(bool onComment) {
|
||||
if (type == VisibilityType.public) {
|
||||
return 'public';
|
||||
}
|
||||
|
||||
if (hasDetails) {
|
||||
final circleId = allowedCircleIds.first;
|
||||
if (!onComment && hasDetails) {
|
||||
final circleId =
|
||||
allowedCircleIds.firstOrNull ?? allowedUserIds.firstOrNull;
|
||||
if (circleId == CircleData.followersPseudoCircle.id) {
|
||||
return 'private';
|
||||
}
|
||||
|
||||
return circleId;
|
||||
return circleId ?? 'private';
|
||||
}
|
||||
|
||||
if (onComment && !hasDetails && type == VisibilityType.private) {
|
||||
return 'direct';
|
||||
}
|
||||
|
||||
return 'private';
|
||||
}
|
||||
|
||||
Map<String, dynamic> friendicaExtensionVisibilityJson() {
|
||||
return {
|
||||
"allow_cid": allowedUserIds,
|
||||
"deny_cid": excludedUserIds,
|
||||
"allow_gid": allowedCircleIds,
|
||||
"deny_gid": excludedCircleIds,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue