Personal page

This commit is contained in:
LittleSheep 2024-05-03 18:20:32 +08:00
parent 083975bcd9
commit f96ca899b5
6 changed files with 309 additions and 44 deletions

View File

@ -0,0 +1,47 @@
class PersonalPage {
int id;
DateTime createdAt;
DateTime updatedAt;
DateTime? deletedAt;
String content;
String script;
String style;
Map<String, String>? links;
int accountId;
PersonalPage({
required this.id,
required this.createdAt,
required this.updatedAt,
this.deletedAt,
required this.content,
required this.script,
required this.style,
this.links,
required this.accountId,
});
factory PersonalPage.fromJson(Map<String, dynamic> json) => PersonalPage(
id: json['id'],
createdAt: DateTime.parse(json['created_at']),
updatedAt: DateTime.parse(json['updated_at']),
deletedAt: json['deleted_at'] != null ? DateTime.parse(json['deleted_at']) : null,
content: json['content'],
script: json['script'],
style: json['style'],
links: json['links'],
accountId: json['account_id'],
);
Map<String, dynamic> toJson() => {
'id': id,
'created_at': createdAt.toIso8601String(),
'updated_at': updatedAt.toIso8601String(),
'deleted_at': deletedAt?.toIso8601String(),
'content': content,
'script': script,
'style': style,
'links': links,
'account_id': accountId,
};
}

View File

@ -19,6 +19,7 @@ import 'package:solian/screens/posts/comment_editor.dart';
import 'package:solian/screens/posts/moment_editor.dart'; import 'package:solian/screens/posts/moment_editor.dart';
import 'package:solian/screens/posts/screen.dart'; import 'package:solian/screens/posts/screen.dart';
import 'package:solian/screens/auth/signin.dart'; import 'package:solian/screens/auth/signin.dart';
import 'package:solian/screens/users/userinfo.dart';
import 'package:solian/utils/theme.dart'; import 'package:solian/utils/theme.dart';
import 'package:solian/widgets/empty.dart'; import 'package:solian/widgets/empty.dart';
import 'package:solian/widgets/layouts/two_column.dart'; import 'package:solian/widgets/layouts/two_column.dart';
@ -155,7 +156,13 @@ abstract class SolianRouter {
name: 'account.personalize', name: 'account.personalize',
builder: (context, state) => const PersonalizeScreen(), builder: (context, state) => const PersonalizeScreen(),
), ),
]), ],
),
GoRoute(
path: '/users/:user',
name: 'users.info',
builder: (context, state) => UserInfoScreen(name: state.pathParameters['user'] as String),
),
], ],
); );

View File

@ -0,0 +1,162 @@
import 'dart:convert';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart';
import 'package:solian/models/account.dart';
import 'package:solian/models/personal_page.dart';
import 'package:solian/utils/service_url.dart';
import 'package:solian/utils/theme.dart';
import 'package:solian/widgets/account/account_avatar.dart';
import 'package:solian/widgets/account/personal_page_content.dart';
import 'package:solian/widgets/exts.dart';
import 'package:solian/widgets/scaffold.dart';
class UserInfoScreen extends StatefulWidget {
final String name;
const UserInfoScreen({super.key, required this.name});
@override
State<UserInfoScreen> createState() => _UserInfoScreenState();
}
class _UserInfoScreenState extends State<UserInfoScreen> {
final _client = Client();
Account? _userinfo;
PersonalPage? _page;
Future<Account> fetchUserinfo() async {
final res = await Future.wait([
_client.get(getRequestUri('passport', '/api/users/${widget.name}')),
_client.get(getRequestUri('passport', '/api/users/${widget.name}/page'))
], eagerError: true);
final mistakeRes = res.indexWhere((x) => x.statusCode != 200);
if (mistakeRes > -1) {
var message = utf8.decode(res[0].bodyBytes);
context.showErrorDialog(message);
throw Exception(message);
} else {
final info = Account.fromJson(jsonDecode(utf8.decode(res[0].bodyBytes)));
final page = PersonalPage.fromJson(jsonDecode(utf8.decode(res[1].bodyBytes)));
setState(() {
_userinfo = info;
_page = page;
});
return info;
}
}
String getAuthorDescribe() => _userinfo!.description.isNotEmpty ? _userinfo!.description : 'No description yet.';
@override
Widget build(BuildContext context) {
return IndentScaffold(
title: _userinfo?.nick ?? 'Loading...',
fixedAppBarColor: SolianTheme.isLargeScreen(context),
hideDrawer: true,
noSafeArea: true,
child: FutureBuilder(
future: fetchUserinfo(),
builder: (context, snapshot) {
if (!snapshot.hasData || snapshot.data == null) {
return const Center(child: CircularProgressIndicator());
}
return ListView(
padding: const EdgeInsets.symmetric(horizontal: 24),
children: [
const SizedBox(height: 8),
ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(8)),
child: AspectRatio(
aspectRatio: 16 / 5,
child: Container(
color: Theme.of(context).colorScheme.surfaceVariant,
child: _userinfo?.banner != null
? CachedNetworkImage(
imageUrl: getRequestUri('passport', '/api/avatar/${_userinfo!.banner}').toString(),
fit: BoxFit.cover,
progressIndicatorBuilder: (_, __, DownloadProgress loadingProgress) {
return Center(
child: CircularProgressIndicator(
value: loadingProgress.totalSize != null
? loadingProgress.downloaded / loadingProgress.totalSize!
: null,
),
);
},
)
: Container(),
),
),
),
Padding(
padding: const EdgeInsets.only(left: 16, right: 16, top: 20),
child: Row(
children: [
AccountAvatar(source: _userinfo!.avatar, radius: 32),
const SizedBox(width: 12),
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
textBaseline: TextBaseline.alphabetic,
crossAxisAlignment: CrossAxisAlignment.baseline,
children: [
Text(
_userinfo!.nick,
maxLines: 1,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
overflow: TextOverflow.ellipsis,
),
),
const SizedBox(width: 6),
Text(
'@${_userinfo!.name}',
maxLines: 1,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w400,
overflow: TextOverflow.ellipsis,
),
),
],
),
Text(
_userinfo!.description,
maxLines: 3,
style: const TextStyle(
fontSize: 14,
overflow: TextOverflow.ellipsis,
),
),
],
),
),
],
),
),
const Padding(
padding: EdgeInsets.symmetric(vertical: 8),
child: Divider(thickness: 0.3, indent: 4, endIndent: 4),
),
ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(8)),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: PersonalPageContent(item: _page!),
),
),
],
);
},
),
);
}
}

View File

@ -0,0 +1,28 @@
import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:solian/models/personal_page.dart';
import 'package:url_launcher/url_launcher_string.dart';
class PersonalPageContent extends StatelessWidget {
final PersonalPage item;
const PersonalPageContent({super.key, required this.item});
@override
Widget build(BuildContext context) {
return Markdown(
selectable: true,
data: item.content,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
padding: const EdgeInsets.all(0),
onTapLink: (text, href, title) async {
if (href == null) return;
await launchUrlString(
href,
mode: LaunchMode.externalApplication,
);
},
);
}
}

View File

@ -1,6 +1,9 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
import 'package:solian/models/post.dart'; import 'package:solian/models/post.dart';
import 'package:solian/router.dart';
import 'package:solian/utils/theme.dart'; import 'package:solian/utils/theme.dart';
import 'package:solian/widgets/account/account_avatar.dart'; import 'package:solian/widgets/account/account_avatar.dart';
import 'package:solian/widgets/posts/comment_list.dart'; import 'package:solian/widgets/posts/comment_list.dart';
@ -172,10 +175,18 @@ class _PostItemState extends State<PostItem> {
Row( Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
AccountAvatar( GestureDetector(
child: AccountAvatar(
source: widget.item.author.avatar, source: widget.item.author.avatar,
direct: true, direct: true,
), ),
onTap: () {
SolianRouter.router.pushNamed(
'users.info',
pathParameters: {'user': widget.item.author.name},
);
},
),
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -203,10 +214,18 @@ class _PostItemState extends State<PostItem> {
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
AccountAvatar( GestureDetector(
child: AccountAvatar(
source: widget.item.author.avatar, source: widget.item.author.avatar,
direct: true, direct: true,
), ),
onTap: () {
SolianRouter.router.pushNamed(
'users.info',
pathParameters: {'user': widget.item.author.name},
);
},
),
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,

View File

@ -41,6 +41,8 @@
</head> </head>
<body> <body>
<script> <script>
window.flutterWebRenderer = "html";
window.addEventListener('load', function(ev) { window.addEventListener('load', function(ev) {
// Download main.dart.js // Download main.dart.js
_flutter.loader.loadEntrypoint({ _flutter.loader.loadEntrypoint({