threads view

This commit is contained in:
zoe 2022-09-06 23:42:09 +02:00
parent 4a797f0f41
commit 5515fde880
9 changed files with 296 additions and 68 deletions

View File

@ -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(

View File

@ -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(),
));
}

View File

View File

@ -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"];

View File

@ -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));
}
}

View File

@ -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);
},
);
}

View File

@ -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;

View File

@ -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),
),
)
),
],
);
}

View File

@ -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);