🎉 Initial Commit
This commit is contained in:
40
lib/main.dart
Normal file
40
lib/main.dart
Normal file
@@ -0,0 +1,40 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/route_manager.dart';
|
||||
import 'package:solian/router.dart';
|
||||
import 'package:solian/theme.dart';
|
||||
import 'package:solian/translations.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const SolianApp());
|
||||
}
|
||||
|
||||
class SolianApp extends StatelessWidget {
|
||||
const SolianApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GetMaterialApp.router(
|
||||
title: 'Solian',
|
||||
theme: SolianTheme.build(Brightness.light),
|
||||
darkTheme: SolianTheme.build(Brightness.dark),
|
||||
themeMode: ThemeMode.system,
|
||||
routerDelegate: AppRouter.instance.routerDelegate,
|
||||
routeInformationParser: AppRouter.instance.routeInformationParser,
|
||||
routeInformationProvider: AppRouter.instance.routeInformationProvider,
|
||||
translations: SolianMessages(),
|
||||
locale: Get.deviceLocale,
|
||||
fallbackLocale: const Locale('en', 'US'),
|
||||
builder: (context, child) {
|
||||
return Overlay(
|
||||
initialEntries: [
|
||||
OverlayEntry(
|
||||
builder: (context) => ScaffoldMessenger(
|
||||
child: child ?? Container(),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
59
lib/models/account.dart
Normal file
59
lib/models/account.dart
Normal file
@@ -0,0 +1,59 @@
|
||||
class Account {
|
||||
int id;
|
||||
DateTime createdAt;
|
||||
DateTime updatedAt;
|
||||
DateTime? deletedAt;
|
||||
String name;
|
||||
String nick;
|
||||
String avatar;
|
||||
String banner;
|
||||
String description;
|
||||
String? emailAddress;
|
||||
int powerLevel;
|
||||
int? externalId;
|
||||
|
||||
Account({
|
||||
required this.id,
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
this.deletedAt,
|
||||
required this.name,
|
||||
required this.nick,
|
||||
required this.avatar,
|
||||
required this.banner,
|
||||
required this.description,
|
||||
this.emailAddress,
|
||||
required this.powerLevel,
|
||||
this.externalId,
|
||||
});
|
||||
|
||||
factory Account.fromJson(Map<String, dynamic> json) => Account(
|
||||
id: json['id'],
|
||||
createdAt: DateTime.parse(json['created_at']),
|
||||
updatedAt: DateTime.parse(json['updated_at']),
|
||||
deletedAt: json['deleted_at'],
|
||||
name: json['name'],
|
||||
nick: json['nick'],
|
||||
avatar: json['avatar'],
|
||||
banner: json['banner'],
|
||||
description: json['description'],
|
||||
emailAddress: json['email_address'],
|
||||
powerLevel: json['power_level'],
|
||||
externalId: json['external_id'],
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'id': id,
|
||||
'created_at': createdAt.toIso8601String(),
|
||||
'updated_at': updatedAt.toIso8601String(),
|
||||
'deleted_at': deletedAt,
|
||||
'name': name,
|
||||
'nick': nick,
|
||||
'avatar': avatar,
|
||||
'banner': banner,
|
||||
'description': description,
|
||||
'email_address': emailAddress,
|
||||
'power_level': powerLevel,
|
||||
'external_id': externalId,
|
||||
};
|
||||
}
|
77
lib/models/attachment.dart
Normal file
77
lib/models/attachment.dart
Normal file
@@ -0,0 +1,77 @@
|
||||
import 'package:solian/models/account.dart';
|
||||
|
||||
class Attachment {
|
||||
int id;
|
||||
DateTime createdAt;
|
||||
DateTime updatedAt;
|
||||
dynamic deletedAt;
|
||||
String uuid;
|
||||
int size;
|
||||
String name;
|
||||
String alt;
|
||||
String usage;
|
||||
String mimetype;
|
||||
String hash;
|
||||
String destination;
|
||||
Map<String, dynamic>? metadata;
|
||||
bool isMature;
|
||||
Account account;
|
||||
int accountId;
|
||||
|
||||
Attachment({
|
||||
required this.id,
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
required this.deletedAt,
|
||||
required this.uuid,
|
||||
required this.size,
|
||||
required this.name,
|
||||
required this.alt,
|
||||
required this.usage,
|
||||
required this.mimetype,
|
||||
required this.hash,
|
||||
required this.destination,
|
||||
required this.metadata,
|
||||
required this.isMature,
|
||||
required this.account,
|
||||
required this.accountId,
|
||||
});
|
||||
|
||||
factory Attachment.fromJson(Map<String, dynamic> json) => Attachment(
|
||||
id: json["id"],
|
||||
createdAt: DateTime.parse(json["created_at"]),
|
||||
updatedAt: DateTime.parse(json["updated_at"]),
|
||||
deletedAt: json["deleted_at"],
|
||||
uuid: json["uuid"],
|
||||
size: json["size"],
|
||||
name: json["name"],
|
||||
alt: json["alt"],
|
||||
usage: json["usage"],
|
||||
mimetype: json["mimetype"],
|
||||
hash: json["hash"],
|
||||
destination: json["destination"],
|
||||
metadata: json["metadata"],
|
||||
isMature: json["is_mature"],
|
||||
account: Account.fromJson(json["account"]),
|
||||
accountId: json["account_id"],
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
"id": id,
|
||||
"created_at": createdAt.toIso8601String(),
|
||||
"updated_at": updatedAt.toIso8601String(),
|
||||
"deleted_at": deletedAt,
|
||||
"uuid": uuid,
|
||||
"size": size,
|
||||
"name": name,
|
||||
"alt": alt,
|
||||
"usage": usage,
|
||||
"mimetype": mimetype,
|
||||
"hash": hash,
|
||||
"destination": destination,
|
||||
"metadata": metadata,
|
||||
"is_mature": isMature,
|
||||
"account": account.toJson(),
|
||||
"account_id": accountId,
|
||||
};
|
||||
}
|
50
lib/models/call.dart
Normal file
50
lib/models/call.dart
Normal file
@@ -0,0 +1,50 @@
|
||||
import 'package:solian/models/channel.dart';
|
||||
|
||||
class Call {
|
||||
int id;
|
||||
DateTime createdAt;
|
||||
DateTime updatedAt;
|
||||
DateTime? deletedAt;
|
||||
DateTime? endedAt;
|
||||
String externalId;
|
||||
int founderId;
|
||||
int channelId;
|
||||
Channel channel;
|
||||
|
||||
Call({
|
||||
required this.id,
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
this.deletedAt,
|
||||
this.endedAt,
|
||||
required this.externalId,
|
||||
required this.founderId,
|
||||
required this.channelId,
|
||||
required this.channel,
|
||||
});
|
||||
|
||||
factory Call.fromJson(Map<String, dynamic> json) => Call(
|
||||
id: json['id'],
|
||||
createdAt: DateTime.parse(json['created_at']),
|
||||
updatedAt: DateTime.parse(json['updated_at']),
|
||||
deletedAt: json['deleted_at'],
|
||||
endedAt:
|
||||
json['ended_at'] != null ? DateTime.parse(json['ended_at']) : null,
|
||||
externalId: json['external_id'],
|
||||
founderId: json['founder_id'],
|
||||
channelId: json['channel_id'],
|
||||
channel: Channel.fromJson(json['channel']),
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'id': id,
|
||||
'created_at': createdAt.toIso8601String(),
|
||||
'updated_at': updatedAt.toIso8601String(),
|
||||
'deleted_at': deletedAt,
|
||||
'ended_at': endedAt?.toIso8601String(),
|
||||
'external_id': externalId,
|
||||
'founder_id': founderId,
|
||||
'channel_id': channelId,
|
||||
'channel': channel.toJson(),
|
||||
};
|
||||
}
|
107
lib/models/channel.dart
Normal file
107
lib/models/channel.dart
Normal file
@@ -0,0 +1,107 @@
|
||||
import 'package:solian/models/account.dart';
|
||||
|
||||
class Channel {
|
||||
int id;
|
||||
DateTime createdAt;
|
||||
DateTime updatedAt;
|
||||
DateTime? deletedAt;
|
||||
String alias;
|
||||
String name;
|
||||
String description;
|
||||
int type;
|
||||
Account account;
|
||||
int accountId;
|
||||
int? realmId;
|
||||
bool isEncrypted;
|
||||
|
||||
bool isAvailable = false;
|
||||
|
||||
Channel({
|
||||
required this.id,
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
this.deletedAt,
|
||||
required this.alias,
|
||||
required this.name,
|
||||
required this.description,
|
||||
required this.type,
|
||||
required this.account,
|
||||
required this.accountId,
|
||||
required this.isEncrypted,
|
||||
this.realmId,
|
||||
});
|
||||
|
||||
factory Channel.fromJson(Map<String, dynamic> json) => Channel(
|
||||
id: json['id'],
|
||||
createdAt: DateTime.parse(json['created_at']),
|
||||
updatedAt: DateTime.parse(json['updated_at']),
|
||||
deletedAt: json['deleted_at'],
|
||||
alias: json['alias'],
|
||||
name: json['name'],
|
||||
description: json['description'],
|
||||
type: json['type'],
|
||||
account: Account.fromJson(json['account']),
|
||||
accountId: json['account_id'],
|
||||
realmId: json['realm_id'],
|
||||
isEncrypted: json['is_encrypted'],
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'id': id,
|
||||
'created_at': createdAt.toIso8601String(),
|
||||
'updated_at': updatedAt.toIso8601String(),
|
||||
'deleted_at': deletedAt,
|
||||
'alias': alias,
|
||||
'name': name,
|
||||
'description': description,
|
||||
'type': type,
|
||||
'account': account,
|
||||
'account_id': accountId,
|
||||
'realm_id': realmId,
|
||||
'is_encrypted': isEncrypted,
|
||||
};
|
||||
}
|
||||
|
||||
class ChannelMember {
|
||||
int id;
|
||||
DateTime createdAt;
|
||||
DateTime updatedAt;
|
||||
DateTime? deletedAt;
|
||||
int channelId;
|
||||
int accountId;
|
||||
Account account;
|
||||
int notify;
|
||||
|
||||
ChannelMember({
|
||||
required this.id,
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
this.deletedAt,
|
||||
required this.channelId,
|
||||
required this.accountId,
|
||||
required this.account,
|
||||
required this.notify,
|
||||
});
|
||||
|
||||
factory ChannelMember.fromJson(Map<String, dynamic> json) => ChannelMember(
|
||||
id: json['id'],
|
||||
createdAt: DateTime.parse(json['created_at']),
|
||||
updatedAt: DateTime.parse(json['updated_at']),
|
||||
deletedAt: json['deleted_at'],
|
||||
channelId: json['channel_id'],
|
||||
accountId: json['account_id'],
|
||||
account: Account.fromJson(json['account']),
|
||||
notify: json['notify'],
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'id': id,
|
||||
'created_at': createdAt.toIso8601String(),
|
||||
'updated_at': updatedAt.toIso8601String(),
|
||||
'deleted_at': deletedAt,
|
||||
'channel_id': channelId,
|
||||
'account_id': accountId,
|
||||
'account': account.toJson(),
|
||||
'notify': notify,
|
||||
};
|
||||
}
|
61
lib/models/friendship.dart
Normal file
61
lib/models/friendship.dart
Normal file
@@ -0,0 +1,61 @@
|
||||
import 'package:solian/models/account.dart';
|
||||
|
||||
class Friendship {
|
||||
int id;
|
||||
DateTime createdAt;
|
||||
DateTime updatedAt;
|
||||
DateTime? deletedAt;
|
||||
int accountId;
|
||||
int relatedId;
|
||||
int? blockedBy;
|
||||
Account account;
|
||||
Account related;
|
||||
int status;
|
||||
|
||||
Friendship({
|
||||
required this.id,
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
this.deletedAt,
|
||||
required this.accountId,
|
||||
required this.relatedId,
|
||||
this.blockedBy,
|
||||
required this.account,
|
||||
required this.related,
|
||||
required this.status,
|
||||
});
|
||||
|
||||
factory Friendship.fromJson(Map<String, dynamic> json) => Friendship(
|
||||
id: json['id'],
|
||||
createdAt: DateTime.parse(json['created_at']),
|
||||
updatedAt: DateTime.parse(json['updated_at']),
|
||||
deletedAt: json['deleted_at'],
|
||||
accountId: json['account_id'],
|
||||
relatedId: json['related_id'],
|
||||
blockedBy: json['blocked_by'],
|
||||
account: Account.fromJson(json['account']),
|
||||
related: Account.fromJson(json['related']),
|
||||
status: json['status'],
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'id': id,
|
||||
'created_at': createdAt.toIso8601String(),
|
||||
'updated_at': updatedAt.toIso8601String(),
|
||||
'deleted_at': deletedAt,
|
||||
'account_id': accountId,
|
||||
'related_id': relatedId,
|
||||
'blocked_by': blockedBy,
|
||||
'account': account.toJson(),
|
||||
'related': related.toJson(),
|
||||
'status': status,
|
||||
};
|
||||
|
||||
Account getOtherside(int selfId) {
|
||||
if (accountId != selfId) {
|
||||
return account;
|
||||
} else {
|
||||
return related;
|
||||
}
|
||||
}
|
||||
}
|
32
lib/models/keypair.dart
Normal file
32
lib/models/keypair.dart
Normal file
@@ -0,0 +1,32 @@
|
||||
class Keypair {
|
||||
final String id;
|
||||
final String algorithm;
|
||||
final String publicKey;
|
||||
final String? privateKey;
|
||||
|
||||
final bool isOwned;
|
||||
|
||||
Keypair({
|
||||
required this.id,
|
||||
required this.algorithm,
|
||||
required this.publicKey,
|
||||
required this.privateKey,
|
||||
this.isOwned = false,
|
||||
});
|
||||
|
||||
factory Keypair.fromJson(Map<String, dynamic> json) => Keypair(
|
||||
id: json['id'],
|
||||
algorithm: json['algorithm'],
|
||||
publicKey: json['public_key'],
|
||||
privateKey: json['private_key'],
|
||||
isOwned: json['is_owned'],
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'id': id,
|
||||
'algorithm': algorithm,
|
||||
'public_key': publicKey,
|
||||
'private_key': privateKey,
|
||||
'is_owned': isOwned,
|
||||
};
|
||||
}
|
122
lib/models/message.dart
Normal file
122
lib/models/message.dart
Normal file
@@ -0,0 +1,122 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:solian/models/account.dart';
|
||||
import 'package:solian/models/channel.dart';
|
||||
|
||||
class Message {
|
||||
int id;
|
||||
DateTime createdAt;
|
||||
DateTime updatedAt;
|
||||
DateTime? deletedAt;
|
||||
String rawContent;
|
||||
Map<String, dynamic>? metadata;
|
||||
String type;
|
||||
List<String>? attachments;
|
||||
Channel? channel;
|
||||
Sender sender;
|
||||
int? replyId;
|
||||
Message? replyTo;
|
||||
int channelId;
|
||||
int senderId;
|
||||
|
||||
bool isSending = false;
|
||||
|
||||
Map<String, dynamic> get decodedContent {
|
||||
return jsonDecode(utf8.fuse(base64).decode(rawContent));
|
||||
}
|
||||
|
||||
Message({
|
||||
required this.id,
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
this.deletedAt,
|
||||
required this.rawContent,
|
||||
required this.metadata,
|
||||
required this.type,
|
||||
this.attachments,
|
||||
this.channel,
|
||||
required this.sender,
|
||||
required this.replyId,
|
||||
required this.replyTo,
|
||||
required this.channelId,
|
||||
required this.senderId,
|
||||
});
|
||||
|
||||
factory Message.fromJson(Map<String, dynamic> json) => Message(
|
||||
id: json['id'],
|
||||
createdAt: DateTime.parse(json['created_at']),
|
||||
updatedAt: DateTime.parse(json['updated_at']),
|
||||
deletedAt: json['deleted_at'],
|
||||
rawContent: json['content'],
|
||||
metadata: json['metadata'],
|
||||
type: json['type'],
|
||||
attachments: json['attachments'],
|
||||
channel: Channel.fromJson(json['channel']),
|
||||
sender: Sender.fromJson(json['sender']),
|
||||
replyId: json['reply_id'],
|
||||
replyTo: json['reply_to'] != null ? Message.fromJson(json['reply_to']) : null,
|
||||
channelId: json['channel_id'],
|
||||
senderId: json['sender_id'],
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'id': id,
|
||||
'created_at': createdAt.toIso8601String(),
|
||||
'updated_at': updatedAt.toIso8601String(),
|
||||
'deleted_at': deletedAt,
|
||||
'content': rawContent,
|
||||
'metadata': metadata,
|
||||
'type': type,
|
||||
'attachments': attachments,
|
||||
'channel': channel?.toJson(),
|
||||
'sender': sender.toJson(),
|
||||
'reply_id': replyId,
|
||||
'reply_to': replyTo?.toJson(),
|
||||
'channel_id': channelId,
|
||||
'sender_id': senderId,
|
||||
};
|
||||
}
|
||||
|
||||
class Sender {
|
||||
int id;
|
||||
DateTime createdAt;
|
||||
DateTime updatedAt;
|
||||
DateTime? deletedAt;
|
||||
Account account;
|
||||
int channelId;
|
||||
int accountId;
|
||||
int notify;
|
||||
|
||||
Sender({
|
||||
required this.id,
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
this.deletedAt,
|
||||
required this.account,
|
||||
required this.channelId,
|
||||
required this.accountId,
|
||||
required this.notify,
|
||||
});
|
||||
|
||||
factory Sender.fromJson(Map<String, dynamic> json) => Sender(
|
||||
id: json['id'],
|
||||
createdAt: DateTime.parse(json['created_at']),
|
||||
updatedAt: DateTime.parse(json['updated_at']),
|
||||
deletedAt: json['deleted_at'],
|
||||
account: Account.fromJson(json['account']),
|
||||
channelId: json['channel_id'],
|
||||
accountId: json['account_id'],
|
||||
notify: json['notify'],
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'id': id,
|
||||
'created_at': createdAt.toIso8601String(),
|
||||
'updated_at': updatedAt.toIso8601String(),
|
||||
'deleted_at': deletedAt,
|
||||
'account': account.toJson(),
|
||||
'channel_id': channelId,
|
||||
'account_id': accountId,
|
||||
'notify': notify,
|
||||
};
|
||||
}
|
79
lib/models/notification.dart
Executable file
79
lib/models/notification.dart
Executable file
@@ -0,0 +1,79 @@
|
||||
class Notification {
|
||||
int id;
|
||||
DateTime createdAt;
|
||||
DateTime updatedAt;
|
||||
DateTime? deletedAt;
|
||||
String subject;
|
||||
String content;
|
||||
List<Link>? links;
|
||||
bool isImportant;
|
||||
bool isRealtime;
|
||||
DateTime? readAt;
|
||||
int? senderId;
|
||||
int recipientId;
|
||||
|
||||
Notification({
|
||||
required this.id,
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
this.deletedAt,
|
||||
required this.subject,
|
||||
required this.content,
|
||||
this.links,
|
||||
required this.isImportant,
|
||||
required this.isRealtime,
|
||||
this.readAt,
|
||||
this.senderId,
|
||||
required this.recipientId,
|
||||
});
|
||||
|
||||
factory Notification.fromJson(Map<String, dynamic> json) => Notification(
|
||||
id: json['id'] ?? 0,
|
||||
createdAt: json['created_at'] == null ? DateTime.now() : DateTime.parse(json['created_at']),
|
||||
updatedAt: json['updated_at'] == null ? DateTime.now() : DateTime.parse(json['updated_at']),
|
||||
deletedAt: json['deleted_at'],
|
||||
subject: json['subject'],
|
||||
content: json['content'],
|
||||
links: json['links'] != null ? List<Link>.from(json['links'].map((x) => Link.fromJson(x))) : List.empty(),
|
||||
isImportant: json['is_important'],
|
||||
isRealtime: json['is_realtime'],
|
||||
readAt: json['read_at'],
|
||||
senderId: json['sender_id'],
|
||||
recipientId: json['recipient_id'],
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'id': id,
|
||||
'created_at': createdAt.toIso8601String(),
|
||||
'updated_at': updatedAt.toIso8601String(),
|
||||
'deleted_at': deletedAt,
|
||||
'subject': subject,
|
||||
'content': content,
|
||||
'links': links != null ? List<dynamic>.from(links!.map((x) => x.toJson())) : List.empty(),
|
||||
'is_important': isImportant,
|
||||
'is_realtime': isRealtime,
|
||||
'read_at': readAt,
|
||||
'sender_id': senderId,
|
||||
'recipient_id': recipientId,
|
||||
};
|
||||
}
|
||||
|
||||
class Link {
|
||||
String label;
|
||||
String url;
|
||||
|
||||
Link({
|
||||
required this.label,
|
||||
required this.url,
|
||||
});
|
||||
|
||||
factory Link.fromJson(Map<String, dynamic> json) => Link(
|
||||
label: json['label'],
|
||||
url: json['url'],
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'label': label,
|
||||
'url': url,
|
||||
};
|
||||
}
|
23
lib/models/packet.dart
Normal file
23
lib/models/packet.dart
Normal file
@@ -0,0 +1,23 @@
|
||||
class NetworkPackage {
|
||||
String method;
|
||||
String? message;
|
||||
Map<String, dynamic>? payload;
|
||||
|
||||
NetworkPackage({
|
||||
required this.method,
|
||||
this.message,
|
||||
this.payload,
|
||||
});
|
||||
|
||||
factory NetworkPackage.fromJson(Map<String, dynamic> json) => NetworkPackage(
|
||||
method: json['w'],
|
||||
message: json['m'],
|
||||
payload: json['p'],
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'w': method,
|
||||
'm': message,
|
||||
'p': payload,
|
||||
};
|
||||
}
|
17
lib/models/pagination.dart
Executable file
17
lib/models/pagination.dart
Executable file
@@ -0,0 +1,17 @@
|
||||
class PaginationResult {
|
||||
int count;
|
||||
List<dynamic>? data;
|
||||
|
||||
PaginationResult({
|
||||
required this.count,
|
||||
this.data,
|
||||
});
|
||||
|
||||
factory PaginationResult.fromJson(Map<String, dynamic> json) =>
|
||||
PaginationResult(count: json['count'], data: json['data']);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'count': count,
|
||||
'data': data,
|
||||
};
|
||||
}
|
47
lib/models/personal_page.dart
Normal file
47
lib/models/personal_page.dart
Normal 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,
|
||||
};
|
||||
}
|
106
lib/models/post.dart
Executable file
106
lib/models/post.dart
Executable file
@@ -0,0 +1,106 @@
|
||||
import 'package:solian/models/account.dart';
|
||||
import 'package:solian/models/realm.dart';
|
||||
|
||||
class Post {
|
||||
int id;
|
||||
DateTime createdAt;
|
||||
DateTime updatedAt;
|
||||
DateTime? deletedAt;
|
||||
String alias;
|
||||
String content;
|
||||
dynamic tags;
|
||||
dynamic categories;
|
||||
dynamic reactions;
|
||||
List<Post>? replies;
|
||||
List<String>? attachments;
|
||||
int? replyId;
|
||||
int? repostId;
|
||||
int? realmId;
|
||||
Post? replyTo;
|
||||
Post? repostTo;
|
||||
Realm? realm;
|
||||
DateTime? publishedAt;
|
||||
int authorId;
|
||||
Account author;
|
||||
int replyCount;
|
||||
int reactionCount;
|
||||
dynamic reactionList;
|
||||
|
||||
Post({
|
||||
required this.id,
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
required this.deletedAt,
|
||||
required this.alias,
|
||||
required this.content,
|
||||
required this.tags,
|
||||
required this.categories,
|
||||
required this.reactions,
|
||||
required this.replies,
|
||||
required this.attachments,
|
||||
required this.replyId,
|
||||
required this.repostId,
|
||||
required this.realmId,
|
||||
required this.replyTo,
|
||||
required this.repostTo,
|
||||
required this.realm,
|
||||
required this.publishedAt,
|
||||
required this.authorId,
|
||||
required this.author,
|
||||
required this.replyCount,
|
||||
required this.reactionCount,
|
||||
required this.reactionList,
|
||||
});
|
||||
|
||||
factory Post.fromJson(Map<String, dynamic> json) => Post(
|
||||
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,
|
||||
alias: json["alias"],
|
||||
content: json["content"],
|
||||
tags: json["tags"],
|
||||
categories: json["categories"],
|
||||
reactions: json["reactions"],
|
||||
replies: json["replies"],
|
||||
attachments: json["attachments"],
|
||||
replyId: json["reply_id"],
|
||||
repostId: json["repost_id"],
|
||||
realmId: json["realm_id"],
|
||||
replyTo: json["reply_to"] == null ? null : Post.fromJson(json["reply_to"]),
|
||||
repostTo: json["repost_to"],
|
||||
realm: json["realm"],
|
||||
publishedAt: json["published_at"],
|
||||
authorId: json["author_id"],
|
||||
author: Account.fromJson(json["author"]),
|
||||
replyCount: json["reply_count"],
|
||||
reactionCount: json["reaction_count"],
|
||||
reactionList: json["reaction_list"],
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
"id": id,
|
||||
"created_at": createdAt.toIso8601String(),
|
||||
"updated_at": updatedAt.toIso8601String(),
|
||||
"deleted_at": deletedAt,
|
||||
"alias": alias,
|
||||
"content": content,
|
||||
"tags": tags,
|
||||
"categories": categories,
|
||||
"reactions": reactions,
|
||||
"replies": replies,
|
||||
"attachments": attachments,
|
||||
"reply_id": replyId,
|
||||
"repost_id": repostId,
|
||||
"realm_id": realmId,
|
||||
"reply_to": replyTo?.toJson(),
|
||||
"repost_to": repostTo,
|
||||
"realm": realm,
|
||||
"published_at": publishedAt,
|
||||
"author_id": authorId,
|
||||
"author": author.toJson(),
|
||||
"reply_count": replyCount,
|
||||
"reaction_count": reactionCount,
|
||||
"reaction_list": reactionList,
|
||||
};
|
||||
}
|
16
lib/models/reaction.dart
Normal file
16
lib/models/reaction.dart
Normal file
@@ -0,0 +1,16 @@
|
||||
class ReactInfo {
|
||||
final String icon;
|
||||
final int attitude;
|
||||
|
||||
ReactInfo({required this.icon, required this.attitude});
|
||||
}
|
||||
|
||||
final Map<String, ReactInfo> reactions = {
|
||||
'thumb_up': ReactInfo(icon: '👍', attitude: 1),
|
||||
'thumb_down': ReactInfo(icon: '👎', attitude: 2),
|
||||
'just_okay': ReactInfo(icon: '😅', attitude: 0),
|
||||
'cry': ReactInfo(icon: '😭', attitude: 0),
|
||||
'confuse': ReactInfo(icon: '🧐', attitude: 0),
|
||||
'retard': ReactInfo(icon: '🤪', attitude: 0),
|
||||
'clap': ReactInfo(icon: '👏', attitude: 1),
|
||||
};
|
97
lib/models/realm.dart
Normal file
97
lib/models/realm.dart
Normal file
@@ -0,0 +1,97 @@
|
||||
import 'package:solian/models/account.dart';
|
||||
|
||||
class Realm {
|
||||
int id;
|
||||
DateTime createdAt;
|
||||
DateTime updatedAt;
|
||||
DateTime? deletedAt;
|
||||
String alias;
|
||||
String name;
|
||||
String description;
|
||||
bool isPublic;
|
||||
bool isCommunity;
|
||||
int accountId;
|
||||
|
||||
Realm({
|
||||
required this.id,
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
this.deletedAt,
|
||||
required this.alias,
|
||||
required this.name,
|
||||
required this.description,
|
||||
required this.isPublic,
|
||||
required this.isCommunity,
|
||||
required this.accountId,
|
||||
});
|
||||
|
||||
factory Realm.fromJson(Map<String, dynamic> json) => Realm(
|
||||
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,
|
||||
alias: json['alias'],
|
||||
name: json['name'],
|
||||
description: json['description'],
|
||||
isPublic: json['is_public'],
|
||||
isCommunity: json['is_community'],
|
||||
accountId: json['account_id'],
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'id': id,
|
||||
'created_at': createdAt.toIso8601String(),
|
||||
'updated_at': updatedAt.toIso8601String(),
|
||||
'deleted_at': deletedAt,
|
||||
'alias': alias,
|
||||
'name': name,
|
||||
'description': description,
|
||||
'is_public': isPublic,
|
||||
'is_community': isCommunity,
|
||||
'account_id': accountId,
|
||||
};
|
||||
}
|
||||
|
||||
class RealmMember {
|
||||
int id;
|
||||
DateTime createdAt;
|
||||
DateTime updatedAt;
|
||||
DateTime? deletedAt;
|
||||
int realmId;
|
||||
int accountId;
|
||||
Account account;
|
||||
int powerLevel;
|
||||
|
||||
RealmMember({
|
||||
required this.id,
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
this.deletedAt,
|
||||
required this.realmId,
|
||||
required this.accountId,
|
||||
required this.account,
|
||||
required this.powerLevel,
|
||||
});
|
||||
|
||||
factory RealmMember.fromJson(Map<String, dynamic> json) => RealmMember(
|
||||
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,
|
||||
realmId: json['realm_id'],
|
||||
accountId: json['account_id'],
|
||||
account: Account.fromJson(json['account']),
|
||||
powerLevel: json['power_level'],
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'id': id,
|
||||
'created_at': createdAt.toIso8601String(),
|
||||
'updated_at': updatedAt.toIso8601String(),
|
||||
'deleted_at': deletedAt,
|
||||
'realm_id': realmId,
|
||||
'account_id': accountId,
|
||||
'account': account.toJson(),
|
||||
'power_level': powerLevel,
|
||||
};
|
||||
}
|
11
lib/providers/content/post_explore.dart
Normal file
11
lib/providers/content/post_explore.dart
Normal file
@@ -0,0 +1,11 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:solian/services.dart';
|
||||
|
||||
class PostExploreProvider extends GetConnect {
|
||||
@override
|
||||
void onInit() {
|
||||
httpClient.baseUrl = ServiceFinder.services['interactive'];
|
||||
}
|
||||
|
||||
Future<Response> listPost(int page) => get('/api/feed?take=${10}&offset=${page * 10}');
|
||||
}
|
26
lib/router.dart
Normal file
26
lib/router.dart
Normal file
@@ -0,0 +1,26 @@
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:solian/screens/account.dart';
|
||||
import 'package:solian/screens/home.dart';
|
||||
import 'package:solian/shells/nav_shell.dart';
|
||||
|
||||
class AppRouter {
|
||||
static GoRouter instance = GoRouter(
|
||||
routes: [
|
||||
ShellRoute(
|
||||
builder: (context, state, child) => NavShell(state: state, child: child),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: "/",
|
||||
name: "home",
|
||||
builder: (context, state) => const HomeScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: "/account",
|
||||
name: "account",
|
||||
builder: (context, state) => const AccountScreen(),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
12
lib/screens/account.dart
Normal file
12
lib/screens/account.dart
Normal file
@@ -0,0 +1,12 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AccountScreen extends StatelessWidget {
|
||||
const AccountScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Center(
|
||||
child: Text("Woah account"),
|
||||
);
|
||||
}
|
||||
}
|
70
lib/screens/home.dart
Normal file
70
lib/screens/home.dart
Normal file
@@ -0,0 +1,70 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:solian/models/pagination.dart';
|
||||
import 'package:solian/models/post.dart';
|
||||
import 'package:solian/providers/content/post_explore.dart';
|
||||
import 'package:solian/widgets/posts/post_item.dart';
|
||||
|
||||
class HomeScreen extends StatefulWidget {
|
||||
const HomeScreen({super.key});
|
||||
|
||||
@override
|
||||
State<HomeScreen> createState() => _HomeScreenState();
|
||||
}
|
||||
|
||||
class _HomeScreenState extends State<HomeScreen> {
|
||||
int _pageKey = 0;
|
||||
int? _dataTotal;
|
||||
|
||||
bool _isFirstLoading = true;
|
||||
|
||||
final List<Post> _data = List.empty(growable: true);
|
||||
|
||||
getPosts() async {
|
||||
if (_dataTotal != null && _pageKey * 10 > _dataTotal!) return;
|
||||
|
||||
final PostExploreProvider provider = Get.find();
|
||||
final resp = await provider.listPost(_pageKey);
|
||||
final PaginationResult result = PaginationResult.fromJson(resp.body);
|
||||
|
||||
setState(() {
|
||||
final parsed = result.data?.map((e) => Post.fromJson(e));
|
||||
if (parsed != null) _data.addAll(parsed);
|
||||
_isFirstLoading = false;
|
||||
_dataTotal = result.count;
|
||||
_pageKey++;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
Get.lazyPut(() => PostExploreProvider());
|
||||
super.initState();
|
||||
|
||||
Future.delayed(Duration.zero, () => getPosts());
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_isFirstLoading) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
|
||||
return RefreshIndicator(
|
||||
onRefresh: () => getPosts(),
|
||||
child: ListView.separated(
|
||||
itemCount: _data.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final item = _data[index];
|
||||
return InkWell(
|
||||
child: PostItem(item: item).paddingSymmetric(horizontal: 18, vertical: 8),
|
||||
onTap: () {},
|
||||
);
|
||||
},
|
||||
separatorBuilder: (_, __) => const Divider(thickness: 0.3),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
10
lib/services.dart
Normal file
10
lib/services.dart
Normal file
@@ -0,0 +1,10 @@
|
||||
abstract class ServiceFinder {
|
||||
static const bool devFlag = true;
|
||||
|
||||
static Map<String, String> services = {
|
||||
'paperclip': devFlag ? 'http://localhost:8443' : 'https://usercontent.solsynth.dev',
|
||||
'passport': devFlag ? 'http://localhost:8444' : 'https://id.solsynth.dev',
|
||||
'interactive': devFlag ? 'http://localhost:8445' : 'https://co.solsynth.dev',
|
||||
'messaging': devFlag ? 'http://localhost:8446' : 'https://im.solsynth.dev',
|
||||
};
|
||||
}
|
35
lib/shells/nav_shell.dart
Normal file
35
lib/shells/nav_shell.dart
Normal file
@@ -0,0 +1,35 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:solian/theme.dart';
|
||||
import 'package:solian/widgets/navigation/app_navigation_bottom_bar.dart';
|
||||
import 'package:solian/widgets/navigation/app_navigation_rail.dart';
|
||||
|
||||
class NavShell extends StatelessWidget {
|
||||
final GoRouterState state;
|
||||
final Widget child;
|
||||
|
||||
const NavShell({super.key, required this.child, required this.state});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(state.topRoute?.name?.tr ?? 'page'.tr),
|
||||
centerTitle: false,
|
||||
elevation: SolianTheme.isLargeScreen(context) ? 1 : 0,
|
||||
titleSpacing: 24,
|
||||
),
|
||||
bottomNavigationBar: SolianTheme.isLargeScreen(context) ? null : const AppNavigationBottomBar(),
|
||||
body: SolianTheme.isLargeScreen(context)
|
||||
? Row(
|
||||
children: [
|
||||
const AppNavigationRail(),
|
||||
const VerticalDivider(thickness: 0.3, width: 1),
|
||||
Expanded(child: child),
|
||||
],
|
||||
)
|
||||
: child,
|
||||
);
|
||||
}
|
||||
}
|
14
lib/theme.dart
Normal file
14
lib/theme.dart
Normal file
@@ -0,0 +1,14 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
abstract class SolianTheme {
|
||||
static bool isLargeScreen(BuildContext context) =>
|
||||
MediaQuery.of(context).size.width > 640;
|
||||
|
||||
static ThemeData build(Brightness brightness) {
|
||||
return ThemeData(
|
||||
brightness: brightness,
|
||||
useMaterial3: true,
|
||||
colorScheme: ColorScheme.fromSeed(brightness: brightness, seedColor: Colors.indigo),
|
||||
);
|
||||
}
|
||||
}
|
17
lib/translations.dart
Normal file
17
lib/translations.dart
Normal file
@@ -0,0 +1,17 @@
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class SolianMessages extends Translations {
|
||||
@override
|
||||
Map<String, Map<String, String>> get keys => {
|
||||
'en_US': {
|
||||
'page': 'Page',
|
||||
'home': 'Home',
|
||||
'account': 'Account',
|
||||
},
|
||||
'zh_CN': {
|
||||
'page': '页面',
|
||||
'home': '首页',
|
||||
'account': '账号',
|
||||
}
|
||||
};
|
||||
}
|
21
lib/widgets/account/account_avatar.dart
Normal file
21
lib/widgets/account/account_avatar.dart
Normal file
@@ -0,0 +1,21 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:solian/services.dart';
|
||||
|
||||
class AccountAvatar extends StatelessWidget {
|
||||
final String content;
|
||||
final Color? color;
|
||||
final double? radius;
|
||||
|
||||
const AccountAvatar({super.key, required this.content, this.color, this.radius});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final direct = content.startsWith('http');
|
||||
|
||||
return CircleAvatar(
|
||||
radius: radius,
|
||||
backgroundColor: color,
|
||||
backgroundImage: NetworkImage(direct ? content : '${ServiceFinder.services['paperclip']}/api/attachments/$content'),
|
||||
);
|
||||
}
|
||||
}
|
25
lib/widgets/navigation/app_navigation.dart
Normal file
25
lib/widgets/navigation/app_navigation.dart
Normal file
@@ -0,0 +1,25 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/utils.dart';
|
||||
|
||||
abstract class AppNavigation {
|
||||
static List<AppNavigationDestination> destinations = [
|
||||
AppNavigationDestination(
|
||||
icon: const Icon(Icons.home),
|
||||
label: 'home'.tr,
|
||||
page: 'home',
|
||||
),
|
||||
AppNavigationDestination(
|
||||
icon: const Icon(Icons.account_circle),
|
||||
label: 'account'.tr,
|
||||
page: 'account',
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
class AppNavigationDestination {
|
||||
final Widget icon;
|
||||
final String label;
|
||||
final String page;
|
||||
|
||||
AppNavigationDestination({required this.icon, required this.label, required this.page});
|
||||
}
|
33
lib/widgets/navigation/app_navigation_bottom_bar.dart
Normal file
33
lib/widgets/navigation/app_navigation_bottom_bar.dart
Normal file
@@ -0,0 +1,33 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:solian/router.dart';
|
||||
import 'package:solian/widgets/navigation/app_navigation.dart';
|
||||
|
||||
class AppNavigationBottomBar extends StatefulWidget {
|
||||
const AppNavigationBottomBar({super.key});
|
||||
|
||||
@override
|
||||
State<AppNavigationBottomBar> createState() => _AppNavigationBottomBarState();
|
||||
}
|
||||
|
||||
class _AppNavigationBottomBarState extends State<AppNavigationBottomBar> {
|
||||
int _selectedIndex = 0;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BottomNavigationBar(
|
||||
items: AppNavigation.destinations.map(
|
||||
(e) => BottomNavigationBarItem(
|
||||
icon: e.icon,
|
||||
label: e.label,
|
||||
),
|
||||
).toList(),
|
||||
landscapeLayout: BottomNavigationBarLandscapeLayout.centered,
|
||||
currentIndex: _selectedIndex,
|
||||
showUnselectedLabels: false,
|
||||
onTap: (idx) {
|
||||
setState(() => _selectedIndex = idx);
|
||||
AppRouter.instance.goNamed(AppNavigation.destinations[idx].page);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
32
lib/widgets/navigation/app_navigation_rail.dart
Normal file
32
lib/widgets/navigation/app_navigation_rail.dart
Normal file
@@ -0,0 +1,32 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:solian/router.dart';
|
||||
import 'package:solian/widgets/navigation/app_navigation.dart';
|
||||
|
||||
class AppNavigationRail extends StatefulWidget {
|
||||
const AppNavigationRail({super.key});
|
||||
|
||||
@override
|
||||
State<AppNavigationRail> createState() => _AppNavigationRailState();
|
||||
}
|
||||
|
||||
class _AppNavigationRailState extends State<AppNavigationRail> {
|
||||
int _selectedIndex = 0;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return NavigationRail(
|
||||
destinations: AppNavigation.destinations.map(
|
||||
(e) => NavigationRailDestination(
|
||||
icon: e.icon,
|
||||
label: Text(e.label),
|
||||
),
|
||||
).toList(),
|
||||
labelType: NavigationRailLabelType.selected,
|
||||
selectedIndex: _selectedIndex,
|
||||
onDestinationSelected: (idx) {
|
||||
setState(() => _selectedIndex = idx);
|
||||
AppRouter.instance.pushNamed(AppNavigation.destinations[idx].page);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
45
lib/widgets/posts/post_item.dart
Normal file
45
lib/widgets/posts/post_item.dart
Normal file
@@ -0,0 +1,45 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||
import 'package:get/get_utils/get_utils.dart';
|
||||
import 'package:solian/models/post.dart';
|
||||
import 'package:solian/widgets/account/account_avatar.dart';
|
||||
import 'package:timeago/timeago.dart' show format;
|
||||
|
||||
class PostItem extends StatefulWidget {
|
||||
final Post item;
|
||||
|
||||
const PostItem({super.key, required this.item});
|
||||
|
||||
@override
|
||||
State<PostItem> createState() => _PostItemState();
|
||||
}
|
||||
|
||||
class _PostItemState extends State<PostItem> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
AccountAvatar(content: widget.item.author.avatar),
|
||||
Expanded(
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text(widget.item.author.nick, style: const TextStyle(fontWeight: FontWeight.bold)).paddingOnly(left: 8),
|
||||
Text(format(widget.item.createdAt, locale: 'en_short')).paddingOnly(left: 4),
|
||||
],
|
||||
),
|
||||
Markdown(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
data: widget.item.content,
|
||||
padding: const EdgeInsets.all(0),
|
||||
).paddingSymmetric(horizontal: 8),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user