profile view

This commit is contained in:
zoe 2022-09-04 20:03:24 +02:00
parent 44bdefd99a
commit d976d9ec22
12 changed files with 358 additions and 91 deletions

View File

@ -1,13 +1,125 @@
class AccountModel {
late String acct;
late String displayName;
late String avatar;
late String url;
import 'dart:convert';
AccountModel.fromJson(Map<String, dynamic> json) {
acct = json["acct"];
displayName = json["display_name"];
avatar = json["avatar"];
url = json["url"];
import 'package:http/http.dart' as http;
import 'package:loris/global.dart' as global;
class AccountModel {
final String acct;
final String displayName;
final String avatar;
final String url;
final String id;
final String note;
final String header;
final bool locked;
final bool? discoverable;
final List<dynamic>? fields;
final bool? bot;
final bool? suspended;
final String identity;
AccountModel({
required this.identity,
required this.note,
required this.header,
required this.locked,
this.discoverable,
this.fields,
this.bot,
this.suspended,
required this.acct,
required this.displayName,
required this.avatar,
required this.url,
required this.id,
});
factory AccountModel.fromJson(Map<String, dynamic> json, String identity) {
return AccountModel(
identity: identity,
id: json["id"],
acct: json["acct"],
displayName: json["display_name"],
avatar: json["avatar"],
url: json["url"],
note: json["note"],
discoverable: json["discoverable"],
header: json["header"],
locked: json["locked"],
bot: json["bot"],
fields: json["fields"],
suspended: json["suspended"],
);
}
}
class RelationshipModel {
final bool blockedBy;
final bool blocking;
final bool endorsed;
final bool followedBy;
final bool following;
final String id;
final bool muting;
final bool mutingNotifications;
final String note;
final bool notifying;
final bool requested;
final bool showingReblogs;
RelationshipModel({
required this.blockedBy,
required this.blocking,
required this.endorsed,
required this.followedBy,
required this.following,
required this.id,
required this.muting,
required this.mutingNotifications,
required this.note,
required this.notifying,
required this.requested,
required this.showingReblogs,
});
factory RelationshipModel.fromJson(Map<String, dynamic> json) {
return RelationshipModel(
blockedBy: json["blocked_by"],
blocking: json["blocking"],
endorsed: json["endorsed"],
followedBy: json["followed_by"],
following: json["following"],
id: json["id"],
muting: json["muting"],
mutingNotifications: json["muting_notifications"],
note: json["note"],
notifying: json["notifying"],
requested: json["requested"],
showingReblogs: json["showing_reblogs"]);
}
}
Future<Map<int, RelationshipModel?>> getRelationship(
String identityName, String id) async {
final identity = global.settings!.identities[identityName]!;
Map<String, String> headers = identity.getAuthHeaders();
headers.addAll(global.defaultHeaders);
final uri = Uri(
scheme: "https",
host: identity.instanceUrl,
path: "/api/v1/accounts/relationships",
queryParameters: {"id": id},
);
final response = await http.get(uri, headers: headers);
if (response.statusCode == 200) {
return {
response.statusCode:
RelationshipModel.fromJson(jsonDecode(response.body)[0])
};
}
return {404: null};
}

View File

@ -121,7 +121,10 @@ Future<bool> saveIdentity(
headers: headers,
);
if (response.statusCode == 200) {
final account = AccountModel.fromJson(jsonDecode(response.body));
final account = AccountModel.fromJson(
jsonDecode(response.body),
"${jsonDecode(response.body)["acct"]}@$baseurl",
);
await global.settings!.addNewIdentity("${account.acct}@$baseurl");
await global.settings!.saveActiveIdentity("${account.acct}@$baseurl");
await global.settings!.identities["${account.acct}@$baseurl"]!

View File

@ -18,7 +18,7 @@ class NotificationModel implements Comparable {
post = null;
time = json["created_at"];
id = json["id"];
account = AccountModel.fromJson(json["account"]);
account = AccountModel.fromJson(json["account"], identity);
type = NotificationType.values
.firstWhere((element) => element.param == json["type"]);
if (json["status"] != null) {

View File

@ -94,7 +94,7 @@ class PostModel implements Comparable {
createdAt = json["created_at"];
// in case of reblog
if (json["reblog"] != null) {
rebloggedBy = AccountModel.fromJson(json["account"]);
rebloggedBy = AccountModel.fromJson(json["account"], identity);
json = json["reblog"];
reblogId = json["id"];
} else {
@ -110,7 +110,7 @@ class PostModel implements Comparable {
spoilerText = json["spoiler_text"] as String;
favourited = json["favourited"] as bool;
reblogged = json["reblogged"] as bool;
account = AccountModel.fromJson(json["account"]);
account = AccountModel.fromJson(json["account"], identity);
attachments = [];
List<dynamic> jsonAttachmentList = json["media_attachments"];
for (int i = 0; i < jsonAttachmentList.length; i++) {

View File

@ -23,6 +23,17 @@ const List<String> bad = [
"freespeechextremist.com"
];
double getWidth(context) {
return (MediaQuery.of(context).size.width * settings!.postWidth) - 56;
}
BoxConstraints getConstraints(context) {
return BoxConstraints(
maxWidth: settings!.maxPostWidth,
minWidth: 375,
);
}
const List<Locale> availableLocales = [Locale("en", "US"), Locale("de")];
Settings? settings;

View File

@ -63,6 +63,11 @@
"day-4": "thursday",
"day-5": "friday",
"day-6": "saturday",
"day-7": "sunday"
"day-7": "sunday",
"user-is-bot": "this account is a bot",
"you-are-mufos": "you are mufos",
"they-follow-you": "they follow you",
"you-follow-them": "you follow them",
"you-do-not-follow-each-other": "you don't follow each other"
}

View File

@ -25,7 +25,7 @@ class _WebloginState extends State<Weblogin> {
}
final app = oauth.App.fromJson(jsonDecode(appresponse.body));
html.WindowBase? popupWin = html.window.open(
html.window.open(
oauth.getAuthUrl(widget.url, app).toString(),
"_blank",
);

View File

@ -1,5 +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/partials/post.dart';
import '../../global.dart' as global;
@ -31,41 +32,48 @@ class SingleNotif extends StatelessWidget {
),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
ProfilePic(url: model.account.avatar),
const SizedBox(
width: 8,
),
Expanded(
flex: 20,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SelectableText(
model.account.displayName,
style: Theme.of(context).textTheme.displaySmall,
),
SelectableText.rich(
TextSpan(
text: "${model.account.acct} ",
style: Theme.of(context).textTheme.bodySmall,
children: [
TextSpan(
text: model.type.actionName,
style: Theme.of(context).textTheme.bodyMedium)
],
),
),
],
InkWell(
onTap: () => showDialog(
context: context,
builder: (context) => ProfileView(model: model.account),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
ProfilePic(url: model.account.avatar),
const SizedBox(
width: 8,
),
),
Icon(
model.type.icon,
size: 64,
),
],
Expanded(
flex: 20,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SelectableText(
model.account.displayName,
style: Theme.of(context).textTheme.displaySmall,
),
SelectableText.rich(
TextSpan(
text: "${model.account.acct} ",
style: Theme.of(context).textTheme.bodySmall,
children: [
TextSpan(
text: model.type.actionName,
style:
Theme.of(context).textTheme.bodyMedium)
],
),
),
],
),
),
Icon(
model.type.icon,
size: 64,
),
],
),
),
const SizedBox(
height: 8,

View File

@ -1 +1,134 @@
import 'package:flutter/material.dart';
import 'package:localization/localization.dart';
import 'package:loris/business_logic/account/account.dart';
import 'package:loris/partials/post.dart';
import 'package:loris/partials/post_text_renderer.dart';
import 'package:loris/global.dart' as global;
class ProfileView extends StatefulWidget {
const ProfileView({
super.key,
required this.model,
});
final AccountModel model;
@override
State<ProfileView> createState() => _ProfileViewState();
}
class _ProfileViewState extends State<ProfileView> {
RelationshipModel? relationship;
static const d = SizedBox(
height: 8,
);
void update() async {
final r = await getRelationship(widget.model.identity, widget.model.id);
setState(() {
relationship = r.values.first;
});
}
@override
void initState() {
super.initState();
update();
}
@override
Widget build(BuildContext context) {
List<Widget> c = [
Image.network(
width: global.getWidth(context),
fit: BoxFit.fitWidth,
widget.model.header,
errorBuilder: (context, error, stackTrace) => const SizedBox.shrink(),
),
StatusIndicators(
model: widget.model,
relationship: relationship,
),
d,
DisplayName(
account: widget.model,
clickable: false,
),
d,
PostTextRenderer(input: widget.model.note),
];
return SimpleDialog(
alignment: Alignment.center,
contentPadding: const EdgeInsets.all(24),
children: [
Container(
constraints: global.getConstraints(context),
width: global.getWidth(context),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: c,
),
)
],
);
}
}
class StatusIndicators extends StatelessWidget {
const StatusIndicators({
super.key,
required this.model,
this.relationship,
});
final AccountModel model;
final RelationshipModel? relationship;
List<InlineSpan> getTextWithIcon(IconData icon, String t) {
return [
WidgetSpan(
child: Icon(icon),
alignment: PlaceholderAlignment.middle,
),
TextSpan(
text: t,
)
];
}
@override
Widget build(BuildContext context) {
List<InlineSpan> c = [];
if (relationship == null) {
c.add(const WidgetSpan(child: LinearProgressIndicator()));
} else {
// follow relationship
if (relationship!.followedBy && relationship!.following) {
c.addAll(getTextWithIcon(Icons.group, "you-are-mufos".i18n()));
} else if (relationship!.followedBy) {
c.addAll(getTextWithIcon(Icons.group, "they-follow-you".i18n()));
} else if (relationship!.following) {
c.addAll(getTextWithIcon(Icons.group, "you-follow-them".i18n()));
} else {
c.addAll(getTextWithIcon(
Icons.group, "you-do-not-follow-each-other".i18n()));
}
if (relationship!.requested) {
c.addAll(
getTextWithIcon(Icons.group_add, "pending-follow-request".i18n()));
}
}
// account is a bot
if (model.bot != null) {
if (model.bot!) {
c.addAll(getTextWithIcon(Icons.code, "user-is-bot".i18n()));
}
}
return SelectableText.rich(
TextSpan(children: c),
);
}
}

View File

@ -1,16 +1 @@
import 'package:flutter/src/widgets/container.dart';
import 'package:flutter/src/widgets/framework.dart';
class Search extends StatefulWidget {
const Search({super.key});
@override
State<Search> createState() => _SearchState();
}
class _SearchState extends State<Search> {
@override
Widget build(BuildContext context) {
return Container();
}
}

View File

@ -3,6 +3,7 @@ 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/makepost.dart';
import 'package:loris/pages/profile_view/profile_view.dart';
import 'package:loris/partials/interaction_button.dart';
import 'package:loris/partials/media_attachment.dart';
import 'package:loris/partials/post_options.dart';
@ -64,10 +65,12 @@ class DisplayName extends StatelessWidget {
const DisplayName({
required this.account,
this.isReblog = false,
this.clickable = true,
Key? key,
}) : super(key: key);
final AccountModel account;
final bool isReblog;
final bool clickable;
@override
Widget build(BuildContext context) {
@ -77,29 +80,39 @@ class DisplayName extends StatelessWidget {
} else {
usernameStyle = Theme.of(context).textTheme.displaySmall;
}
return Row(
children: [
ProfilePic(url: account.avatar),
const SizedBox(
width: 8,
),
Flexible(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
account.displayName,
style: usernameStyle,
),
Text(
account.acct,
style: Theme.of(context).textTheme.bodySmall,
),
],
return InkWell(
onTap: clickable
? () {
showDialog(
context: context,
builder: (context) => ProfileView(model: account),
);
}
: null,
child: Row(
children: [
ProfilePic(url: account.avatar),
const SizedBox(
width: 8,
),
),
],
Flexible(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SelectableText(
account.displayName,
style: usernameStyle,
),
SelectableText(
account.acct,
style: Theme.of(context).textTheme.bodySmall,
),
],
),
),
],
),
);
}
}

View File

@ -82,10 +82,7 @@ class _ThreadState extends State<Thread> {
width: (MediaQuery.of(context).size.width *
global.settings!.postWidth) -
56,
constraints: BoxConstraints(
maxWidth: global.settings!.maxPostWidth,
minWidth: 375,
),
constraints: global.getConstraints(context),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
border: