profile view
This commit is contained in:
parent
44bdefd99a
commit
d976d9ec22
|
@ -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};
|
||||
}
|
||||
|
|
|
@ -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"]!
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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++) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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",
|
||||
);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in New Issue