threads view
This commit is contained in:
parent
4a797f0f41
commit
5515fde880
|
@ -122,14 +122,14 @@ Future<Map<int, RelationshipModel?>> getRelationship(
|
|||
);
|
||||
|
||||
final response = await http.get(uri, headers: headers);
|
||||
if (response.statusCode == 200) {
|
||||
if (response.statusCode == 200 && jsonDecode(response.body).isNotEmpty) {
|
||||
return {
|
||||
response.statusCode:
|
||||
RelationshipModel.fromJson(jsonDecode(response.body)[0], identityName)
|
||||
};
|
||||
}
|
||||
|
||||
return {404: null};
|
||||
return {response.statusCode: null};
|
||||
}
|
||||
|
||||
Future<Map<int, AccountModel?>> searchModel(
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:loris/business_logic/timeline/timeline.dart';
|
||||
import 'package:loris/global.dart' as global;
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
class PostContext {
|
||||
final List<PostModel> ancestors;
|
||||
final List<PostModel> descendants;
|
||||
|
||||
PostContext({required this.ancestors, required this.descendants});
|
||||
}
|
||||
|
||||
// first returns ancestors, then decendants
|
||||
Future<MapEntry<int, PostContext?>> getContextForPost(PostModel model) async {
|
||||
final identity = global.settings!.identities[model.identity]!;
|
||||
var headers = identity.getAuthHeaders();
|
||||
headers.addAll(global.defaultHeaders);
|
||||
String id = model.reblogId ?? model.id;
|
||||
final uri = Uri(
|
||||
scheme: "https",
|
||||
host: identity.instanceUrl,
|
||||
path: "/api/v1/statuses/$id/context");
|
||||
|
||||
final r = await http.get(uri, headers: headers);
|
||||
if (r.statusCode != 200) {
|
||||
return MapEntry(r.statusCode, null);
|
||||
}
|
||||
|
||||
final json = jsonDecode(r.body);
|
||||
List<dynamic> ancestors = json["ancestors"]!;
|
||||
List<dynamic> descendants = json["descendants"]!;
|
||||
|
||||
return MapEntry(
|
||||
r.statusCode,
|
||||
PostContext(
|
||||
ancestors: ancestors
|
||||
.map((e) => PostModel.fromJson(e, model.identity))
|
||||
.toList(),
|
||||
descendants: descendants
|
||||
.map((e) => PostModel.fromJson(e, model.identity))
|
||||
.toList(),
|
||||
));
|
||||
}
|
|
@ -83,6 +83,8 @@ class PostModel implements Comparable {
|
|||
late bool reblogged;
|
||||
late AccountModel account;
|
||||
late AccountModel? rebloggedBy;
|
||||
late PostModel? reblog;
|
||||
late String? inReplyTo;
|
||||
late List<MediaAttachmentModel> attachments;
|
||||
late List<MentionModel> mentions = [];
|
||||
// exists if post is a reblog
|
||||
|
@ -97,8 +99,10 @@ class PostModel implements Comparable {
|
|||
rebloggedBy = AccountModel.fromJson(json["account"], identity);
|
||||
json = json["reblog"];
|
||||
reblogId = json["id"];
|
||||
reblog = PostModel.fromJson(json, identity);
|
||||
} else {
|
||||
rebloggedBy = null;
|
||||
reblog = null;
|
||||
}
|
||||
originalId = json["id"];
|
||||
uri = json["uri"] as String;
|
||||
|
@ -110,6 +114,7 @@ class PostModel implements Comparable {
|
|||
spoilerText = json["spoiler_text"] as String;
|
||||
favourited = json["favourited"] as bool;
|
||||
reblogged = json["reblogged"] as bool;
|
||||
inReplyTo = json["in_reply_to_id"];
|
||||
account = AccountModel.fromJson(json["account"], identity);
|
||||
attachments = [];
|
||||
List<dynamic> jsonAttachmentList = json["media_attachments"];
|
||||
|
|
|
@ -0,0 +1,138 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:loris/business_logic/posts/posts.dart';
|
||||
import 'package:loris/business_logic/timeline/timeline.dart';
|
||||
import 'package:loris/global.dart' as global;
|
||||
|
||||
import '../partials/post.dart';
|
||||
|
||||
class FullPostView extends StatefulWidget {
|
||||
const FullPostView({
|
||||
super.key,
|
||||
required this.originPostModel,
|
||||
});
|
||||
final PostModel originPostModel;
|
||||
|
||||
@override
|
||||
State<FullPostView> createState() => _FullPostViewState();
|
||||
}
|
||||
|
||||
class _FullPostViewState extends State<FullPostView> {
|
||||
bool loading = false;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SimpleDialog(
|
||||
children: [
|
||||
Container(
|
||||
constraints: global.getConstraints(context),
|
||||
width: global.getWidth(context),
|
||||
child: SingleChildScrollView(
|
||||
child: SingleFullPostDisplay(
|
||||
level: 0,
|
||||
model: widget.originPostModel.reblog ?? widget.originPostModel,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SingleFullPostDisplay extends StatefulWidget {
|
||||
const SingleFullPostDisplay({
|
||||
super.key,
|
||||
required this.level,
|
||||
required this.model,
|
||||
this.toBeDistributed,
|
||||
});
|
||||
final int level;
|
||||
final PostModel model;
|
||||
final List<PostModel>? toBeDistributed;
|
||||
|
||||
@override
|
||||
State<SingleFullPostDisplay> createState() => _SingleFullPostDisplayState();
|
||||
}
|
||||
|
||||
class _SingleFullPostDisplayState extends State<SingleFullPostDisplay> {
|
||||
bool loading = true;
|
||||
List<PostModel> ancestors = [];
|
||||
List<PostModel> descendants = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
if (widget.level == 0) {
|
||||
loadPosts();
|
||||
} else {
|
||||
setState(() {
|
||||
loading = false;
|
||||
});
|
||||
}
|
||||
super.initState();
|
||||
}
|
||||
|
||||
void loadPosts() async {
|
||||
final r = await getContextForPost(widget.model);
|
||||
if (r.value != null) {
|
||||
setState(() {
|
||||
if (widget.level == 0) {
|
||||
ancestors = r.value!.ancestors;
|
||||
}
|
||||
descendants = r.value!.descendants;
|
||||
});
|
||||
}
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
loading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
List<PostModel> toBeDistributed = widget.toBeDistributed ?? [];
|
||||
List<Post> ancestorWidgets = ancestors
|
||||
.map(
|
||||
(e) => Post(model: e),
|
||||
)
|
||||
.toList();
|
||||
|
||||
List<Widget> descendantsWidgets = [];
|
||||
|
||||
if (widget.toBeDistributed == null) {
|
||||
for (var element in descendants) {
|
||||
toBeDistributed.add(element);
|
||||
if (element.id == widget.model.id) {}
|
||||
}
|
||||
}
|
||||
|
||||
for (var element in toBeDistributed) {
|
||||
if (element.inReplyTo == widget.model.id) {
|
||||
descendantsWidgets.add(SingleFullPostDisplay(
|
||||
level: widget.level + 1,
|
||||
model: element,
|
||||
toBeDistributed: toBeDistributed,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
List<Widget> c = [];
|
||||
c.addAll(ancestorWidgets);
|
||||
c.add(Post(model: widget.model));
|
||||
c.addAll(descendantsWidgets);
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.fromLTRB(8, 0, 0, 0),
|
||||
decoration: BoxDecoration(
|
||||
border: widget.level == 0
|
||||
? const Border()
|
||||
: Border(
|
||||
left: BorderSide(
|
||||
width: 4,
|
||||
color: widget.level.isEven
|
||||
? Theme.of(context).colorScheme.secondary
|
||||
: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Column(children: c));
|
||||
}
|
||||
}
|
|
@ -46,7 +46,7 @@ class _ProfileViewState extends State<ProfileView> {
|
|||
}
|
||||
}
|
||||
|
||||
void addIdentity(String identityName, int i) async {
|
||||
Future<void> addIdentity(String identityName, int i) async {
|
||||
final m = await searchModel(identityName, widget.model.url);
|
||||
if (m.values.first != null) {
|
||||
if (mounted) {
|
||||
|
@ -56,7 +56,7 @@ class _ProfileViewState extends State<ProfileView> {
|
|||
}
|
||||
await addRelationship(m.values.first!);
|
||||
}
|
||||
if (i >= global.settings!.identities.length - 1) {
|
||||
if (i >= global.settings!.identities.length - 2) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
loading = false;
|
||||
|
@ -101,12 +101,10 @@ class _ProfileViewState extends State<ProfileView> {
|
|||
global.settings!.identities.entries,
|
||||
(element) async {
|
||||
if (element.key == widget.model.identity) {
|
||||
i++;
|
||||
addIdentity(element.key, i);
|
||||
return;
|
||||
}
|
||||
await addIdentity(element.key, i);
|
||||
i++;
|
||||
addIdentity(element.key, i);
|
||||
},
|
||||
);
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:loris/business_logic/notifications/notifs.dart';
|
||||
import 'package:loris/pages/profile_view/profile_view.dart';
|
||||
import 'package:loris/dialogues/profile_view.dart';
|
||||
import 'package:loris/partials/post.dart';
|
||||
import '../../global.dart' as global;
|
||||
|
||||
|
|
|
@ -2,8 +2,9 @@ import 'package:flutter/material.dart';
|
|||
import 'package:localization/localization.dart';
|
||||
import 'package:loris/business_logic/account/account.dart';
|
||||
import 'package:loris/business_logic/timeline/media.dart';
|
||||
import 'package:loris/dialogues/full_post_view.dart';
|
||||
import 'package:loris/dialogues/makepost.dart';
|
||||
import 'package:loris/pages/profile_view/profile_view.dart';
|
||||
import 'package:loris/dialogues/profile_view.dart';
|
||||
import 'package:loris/partials/interaction_button.dart';
|
||||
import 'package:loris/partials/media_attachment.dart';
|
||||
import 'package:loris/partials/post_options.dart';
|
||||
|
@ -35,12 +36,16 @@ class _PostState extends State<Post> {
|
|||
List<Widget> c = [];
|
||||
c.addAll([
|
||||
DisplayName(account: widget.model.account),
|
||||
PostBody(
|
||||
sensitive: widget.model.sensitive,
|
||||
content: widget.model.content,
|
||||
spoilerText: widget.model.spoilerText,
|
||||
media: widget.model.attachments,
|
||||
forceShow: !widget.hideSensitive,
|
||||
SizedBox(
|
||||
width: double.maxFinite,
|
||||
child: PostBody(
|
||||
model: widget.model,
|
||||
sensitive: widget.model.sensitive,
|
||||
content: widget.model.content,
|
||||
spoilerText: widget.model.spoilerText,
|
||||
media: widget.model.attachments,
|
||||
forceShow: !widget.hideSensitive,
|
||||
),
|
||||
),
|
||||
]);
|
||||
if (!widget.hideActionBar) {
|
||||
|
@ -192,13 +197,17 @@ class PostBody extends StatefulWidget {
|
|||
required this.content,
|
||||
required this.media,
|
||||
required this.forceShow,
|
||||
this.openInBrowser = false,
|
||||
Key? key,
|
||||
required this.model,
|
||||
}) : super(key: key);
|
||||
final String content;
|
||||
final String spoilerText;
|
||||
final bool sensitive;
|
||||
final bool forceShow;
|
||||
final List<MediaAttachmentModel> media;
|
||||
final bool openInBrowser;
|
||||
final tl.PostModel model;
|
||||
|
||||
@override
|
||||
State<PostBody> createState() => _PostBodyState();
|
||||
|
@ -217,62 +226,72 @@ class _PostBodyState extends State<PostBody> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.fromLTRB(0, 6, 0, 6),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Visibility(
|
||||
visible: widget.forceShow ? false : widget.sensitive,
|
||||
child: Column(
|
||||
children: [
|
||||
SelectableText.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
WidgetSpan(
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
visible = !visible;
|
||||
if (visible) {
|
||||
cwButtonIcon = const Icon(Icons.visibility_off);
|
||||
cwButtonText = "hide".i18n();
|
||||
} else {
|
||||
cwButtonText = "show".i18n();
|
||||
cwButtonIcon = const Icon(Icons.visibility);
|
||||
}
|
||||
});
|
||||
},
|
||||
icon: cwButtonIcon,
|
||||
label: Text(cwButtonText),
|
||||
),
|
||||
return InkWell(
|
||||
onTap: () => showDialog(
|
||||
context: context,
|
||||
builder: (context) => FullPostView(originPostModel: widget.model),
|
||||
),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.fromLTRB(0, 6, 0, 6),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Visibility(
|
||||
visible: widget.forceShow ? false : widget.sensitive,
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: double.maxFinite,
|
||||
child: SelectableText.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
WidgetSpan(
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
visible = !visible;
|
||||
if (visible) {
|
||||
cwButtonIcon =
|
||||
const Icon(Icons.visibility_off);
|
||||
cwButtonText = "hide".i18n();
|
||||
} else {
|
||||
cwButtonText = "show".i18n();
|
||||
cwButtonIcon = const Icon(Icons.visibility);
|
||||
}
|
||||
});
|
||||
},
|
||||
icon: cwButtonIcon,
|
||||
label: Text(cwButtonText),
|
||||
),
|
||||
),
|
||||
const WidgetSpan(
|
||||
child: SizedBox(
|
||||
width: 8,
|
||||
)),
|
||||
TextSpan(text: widget.spoilerText),
|
||||
],
|
||||
),
|
||||
const WidgetSpan(
|
||||
child: SizedBox(
|
||||
width: 8,
|
||||
)),
|
||||
TextSpan(text: widget.spoilerText),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
)
|
||||
],
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Visibility(
|
||||
visible: visible || widget.forceShow,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
PostTextRenderer(input: widget.content),
|
||||
MediaAttachments(models: widget.media),
|
||||
],
|
||||
Visibility(
|
||||
visible: visible || widget.forceShow,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
PostTextRenderer(input: widget.content),
|
||||
MediaAttachments(models: widget.media),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -320,6 +339,19 @@ class _PostActionBarState extends State<PostActionBar> {
|
|||
type: interactions.InteractionType.favorite,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
flex: 20,
|
||||
child: IconButton(
|
||||
tooltip: "show-in-full".i18n(),
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => FullPostView(
|
||||
originPostModel: widget.model,
|
||||
),
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.open_in_full))),
|
||||
Expanded(
|
||||
flex: 20,
|
||||
child: IconButton(
|
||||
|
@ -329,7 +361,7 @@ class _PostActionBarState extends State<PostActionBar> {
|
|||
},
|
||||
icon: const Icon(Icons.more_horiz),
|
||||
),
|
||||
)
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||
import 'package:localization/localization.dart';
|
||||
import 'package:loris/business_logic/interactions/interactions.dart';
|
||||
import 'package:loris/business_logic/timeline/timeline.dart';
|
||||
import 'package:loris/dialogues/full_post_view.dart';
|
||||
import 'package:loris/partials/interaction_button.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:clipboard/clipboard.dart';
|
||||
|
@ -49,6 +50,16 @@ class _PostOptionsState extends State<PostOptions> {
|
|||
const SizedBox(
|
||||
height: 24,
|
||||
),
|
||||
TextButton.icon(
|
||||
onPressed: () => showDialog(
|
||||
context: context,
|
||||
builder: (context) =>
|
||||
FullPostView(originPostModel: widget.model),
|
||||
),
|
||||
icon: const Icon(
|
||||
Icons.open_in_full,
|
||||
),
|
||||
label: Text("show-in-full".i18n())),
|
||||
TextButton.icon(
|
||||
onPressed: () async {
|
||||
FlutterClipboard.copy(widget.model.uri);
|
||||
|
|
Loading…
Reference in New Issue