✨ Basic large screen support
This commit is contained in:
parent
fceb3edbc6
commit
b39c8c770e
@ -28,32 +28,32 @@ class Account {
|
||||
});
|
||||
|
||||
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"],
|
||||
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,
|
||||
'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,
|
||||
};
|
||||
}
|
||||
|
@ -28,32 +28,32 @@ class Author {
|
||||
});
|
||||
|
||||
factory Author.fromJson(Map<String, dynamic> json) => Author(
|
||||
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"],
|
||||
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,
|
||||
'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,
|
||||
};
|
||||
}
|
||||
|
@ -25,28 +25,28 @@ class Call {
|
||||
});
|
||||
|
||||
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"],
|
||||
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"]),
|
||||
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(),
|
||||
'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(),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -32,35 +32,35 @@ class Channel {
|
||||
});
|
||||
|
||||
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"],
|
||||
members: json["members"],
|
||||
calls: json["calls"],
|
||||
type: json["type"],
|
||||
account: Account.fromJson(json["account"]),
|
||||
accountId: json["account_id"],
|
||||
realmId: json["realm_id"],
|
||||
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'],
|
||||
members: json['members'],
|
||||
calls: json['calls'],
|
||||
type: json['type'],
|
||||
account: Account.fromJson(json['account']),
|
||||
accountId: json['account_id'],
|
||||
realmId: json['realm_id'],
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
"id": id,
|
||||
"created_at": createdAt.toIso8601String(),
|
||||
"updated_at": updatedAt.toIso8601String(),
|
||||
"deleted_at": deletedAt,
|
||||
"alias": alias,
|
||||
"name": name,
|
||||
"description": description,
|
||||
"members": members,
|
||||
"calls": calls,
|
||||
"type": type,
|
||||
"account": account,
|
||||
"account_id": accountId,
|
||||
"realm_id": realmId,
|
||||
'id': id,
|
||||
'created_at': createdAt.toIso8601String(),
|
||||
'updated_at': updatedAt.toIso8601String(),
|
||||
'deleted_at': deletedAt,
|
||||
'alias': alias,
|
||||
'name': name,
|
||||
'description': description,
|
||||
'members': members,
|
||||
'calls': calls,
|
||||
'type': type,
|
||||
'account': account,
|
||||
'account_id': accountId,
|
||||
'realm_id': realmId,
|
||||
};
|
||||
}
|
||||
|
||||
@ -86,24 +86,24 @@ class ChannelMember {
|
||||
});
|
||||
|
||||
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"],
|
||||
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,
|
||||
'id': id,
|
||||
'created_at': createdAt.toIso8601String(),
|
||||
'updated_at': updatedAt.toIso8601String(),
|
||||
'deleted_at': deletedAt,
|
||||
'channel_id': channelId,
|
||||
'account_id': accountId,
|
||||
'account': account.toJson(),
|
||||
'notify': notify,
|
||||
};
|
||||
}
|
||||
|
@ -26,29 +26,29 @@ class Friendship {
|
||||
});
|
||||
|
||||
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"],
|
||||
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,
|
||||
'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) {
|
||||
|
@ -36,42 +36,42 @@ class Message {
|
||||
});
|
||||
|
||||
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"],
|
||||
content: json["content"],
|
||||
metadata: json["metadata"],
|
||||
type: json["type"],
|
||||
id: json['id'],
|
||||
createdAt: DateTime.parse(json['created_at']),
|
||||
updatedAt: DateTime.parse(json['updated_at']),
|
||||
deletedAt: json['deleted_at'],
|
||||
content: json['content'],
|
||||
metadata: json['metadata'],
|
||||
type: json['type'],
|
||||
attachments: List<Attachment>.from(
|
||||
json["attachments"]?.map((x) => Attachment.fromJson(x)) ??
|
||||
json['attachments']?.map((x) => Attachment.fromJson(x)) ??
|
||||
List.empty()),
|
||||
channel: Channel.fromJson(json["channel"]),
|
||||
sender: Sender.fromJson(json["sender"]),
|
||||
replyId: json["reply_id"],
|
||||
replyTo: json["reply_to"] != null
|
||||
? Message.fromJson(json["reply_to"])
|
||||
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"],
|
||||
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": content,
|
||||
"metadata": metadata,
|
||||
"type": type,
|
||||
"attachments": List<dynamic>.from(
|
||||
'id': id,
|
||||
'created_at': createdAt.toIso8601String(),
|
||||
'updated_at': updatedAt.toIso8601String(),
|
||||
'deleted_at': deletedAt,
|
||||
'content': content,
|
||||
'metadata': metadata,
|
||||
'type': type,
|
||||
'attachments': List<dynamic>.from(
|
||||
attachments?.map((x) => x.toJson()) ?? List.empty()),
|
||||
"channel": channel?.toJson(),
|
||||
"sender": sender.toJson(),
|
||||
"reply_id": replyId,
|
||||
"reply_to": replyTo?.toJson(),
|
||||
"channel_id": channelId,
|
||||
"sender_id": senderId,
|
||||
'channel': channel?.toJson(),
|
||||
'sender': sender.toJson(),
|
||||
'reply_id': replyId,
|
||||
'reply_to': replyTo?.toJson(),
|
||||
'channel_id': channelId,
|
||||
'sender_id': senderId,
|
||||
};
|
||||
}
|
||||
|
||||
@ -97,24 +97,24 @@ class Sender {
|
||||
});
|
||||
|
||||
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"],
|
||||
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,
|
||||
'id': id,
|
||||
'created_at': createdAt.toIso8601String(),
|
||||
'updated_at': updatedAt.toIso8601String(),
|
||||
'deleted_at': deletedAt,
|
||||
'account': account.toJson(),
|
||||
'channel_id': channelId,
|
||||
'account_id': accountId,
|
||||
'notify': notify,
|
||||
};
|
||||
}
|
||||
|
@ -28,37 +28,37 @@ class Notification {
|
||||
});
|
||||
|
||||
factory Notification.fromJson(Map<String, dynamic> json) => Notification(
|
||||
id: json["id"],
|
||||
createdAt: DateTime.parse(json["created_at"]),
|
||||
updatedAt: 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)))
|
||||
id: json['id'],
|
||||
createdAt: DateTime.parse(json['created_at']),
|
||||
updatedAt: 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"],
|
||||
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
|
||||
'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,
|
||||
'is_important': isImportant,
|
||||
'is_realtime': isRealtime,
|
||||
'read_at': readAt,
|
||||
'sender_id': senderId,
|
||||
'recipient_id': recipientId,
|
||||
};
|
||||
}
|
||||
|
||||
@ -72,12 +72,12 @@ class Link {
|
||||
});
|
||||
|
||||
factory Link.fromJson(Map<String, dynamic> json) => Link(
|
||||
label: json["label"],
|
||||
url: json["url"],
|
||||
label: json['label'],
|
||||
url: json['url'],
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
"label": label,
|
||||
"url": url,
|
||||
'label': label,
|
||||
'url': url,
|
||||
};
|
||||
}
|
||||
|
@ -10,14 +10,14 @@ class NetworkPackage {
|
||||
});
|
||||
|
||||
factory NetworkPackage.fromJson(Map<String, dynamic> json) => NetworkPackage(
|
||||
method: json["w"],
|
||||
message: json["m"],
|
||||
payload: json["p"],
|
||||
method: json['w'],
|
||||
message: json['m'],
|
||||
payload: json['p'],
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
"w": method,
|
||||
"m": message,
|
||||
"p": payload,
|
||||
'w': method,
|
||||
'm': message,
|
||||
'p': payload,
|
||||
};
|
||||
}
|
||||
|
@ -8,10 +8,10 @@ class PaginationResult {
|
||||
});
|
||||
|
||||
factory PaginationResult.fromJson(Map<String, dynamic> json) =>
|
||||
PaginationResult(count: json["count"], data: json["data"]);
|
||||
PaginationResult(count: json['count'], data: json['data']);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
"count": count,
|
||||
"data": data,
|
||||
'count': count,
|
||||
'data': data,
|
||||
};
|
||||
}
|
||||
|
@ -18,6 +18,8 @@ class Post {
|
||||
List<Attachment>? attachments;
|
||||
Map<String, dynamic>? reactionList;
|
||||
|
||||
String get dataset => '${modelType}s';
|
||||
|
||||
Post({
|
||||
required this.id,
|
||||
required this.createdAt,
|
||||
@ -38,46 +40,46 @@ class Post {
|
||||
});
|
||||
|
||||
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"],
|
||||
alias: json["alias"],
|
||||
title: json["title"],
|
||||
description: json["description"],
|
||||
content: json["content"],
|
||||
modelType: json["model_type"],
|
||||
commentCount: json["comment_count"],
|
||||
reactionCount: json["reaction_count"],
|
||||
authorId: json["author_id"],
|
||||
realmId: json["realm_id"],
|
||||
author: Author.fromJson(json["author"]),
|
||||
attachments: json["attachments"] != null
|
||||
id: json['id'],
|
||||
createdAt: DateTime.parse(json['created_at']),
|
||||
updatedAt: DateTime.parse(json['updated_at']),
|
||||
deletedAt: json['deleted_at'],
|
||||
alias: json['alias'],
|
||||
title: json['title'],
|
||||
description: json['description'],
|
||||
content: json['content'],
|
||||
modelType: json['model_type'],
|
||||
commentCount: json['comment_count'],
|
||||
reactionCount: json['reaction_count'],
|
||||
authorId: json['author_id'],
|
||||
realmId: json['realm_id'],
|
||||
author: Author.fromJson(json['author']),
|
||||
attachments: json['attachments'] != null
|
||||
? List<Attachment>.from(
|
||||
json["attachments"].map((x) => Attachment.fromJson(x)))
|
||||
json['attachments'].map((x) => Attachment.fromJson(x)))
|
||||
: List.empty(),
|
||||
reactionList: json["reaction_list"],
|
||||
reactionList: json['reaction_list'],
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
"id": id,
|
||||
"created_at": createdAt.toIso8601String(),
|
||||
"updated_at": updatedAt.toIso8601String(),
|
||||
"deleted_at": deletedAt,
|
||||
"alias": alias,
|
||||
"title": title,
|
||||
"description": description,
|
||||
"content": content,
|
||||
"model_type": modelType,
|
||||
"comment_count": commentCount,
|
||||
"reaction_count": reactionCount,
|
||||
"author_id": authorId,
|
||||
"realm_id": realmId,
|
||||
"author": author.toJson(),
|
||||
"attachments": attachments == null
|
||||
'id': id,
|
||||
'created_at': createdAt.toIso8601String(),
|
||||
'updated_at': updatedAt.toIso8601String(),
|
||||
'deleted_at': deletedAt,
|
||||
'alias': alias,
|
||||
'title': title,
|
||||
'description': description,
|
||||
'content': content,
|
||||
'model_type': modelType,
|
||||
'comment_count': commentCount,
|
||||
'reaction_count': reactionCount,
|
||||
'author_id': authorId,
|
||||
'realm_id': realmId,
|
||||
'author': author.toJson(),
|
||||
'attachments': attachments == null
|
||||
? List.empty()
|
||||
: List<dynamic>.from(attachments!.map((x) => x.toJson())),
|
||||
"reaction_list": reactionList,
|
||||
'reaction_list': reactionList,
|
||||
};
|
||||
}
|
||||
|
||||
@ -117,38 +119,38 @@ class Attachment {
|
||||
});
|
||||
|
||||
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"],
|
||||
fileId: json["file_id"],
|
||||
filesize: json["filesize"],
|
||||
filename: json["filename"],
|
||||
mimetype: json["mimetype"],
|
||||
type: json["type"],
|
||||
externalUrl: json["external_url"],
|
||||
author: Author.fromJson(json["author"]),
|
||||
articleId: json["article_id"],
|
||||
momentId: json["moment_id"],
|
||||
commentId: json["comment_id"],
|
||||
authorId: json["author_id"],
|
||||
id: json['id'],
|
||||
createdAt: DateTime.parse(json['created_at']),
|
||||
updatedAt: DateTime.parse(json['updated_at']),
|
||||
deletedAt: json['deleted_at'],
|
||||
fileId: json['file_id'],
|
||||
filesize: json['filesize'],
|
||||
filename: json['filename'],
|
||||
mimetype: json['mimetype'],
|
||||
type: json['type'],
|
||||
externalUrl: json['external_url'],
|
||||
author: Author.fromJson(json['author']),
|
||||
articleId: json['article_id'],
|
||||
momentId: json['moment_id'],
|
||||
commentId: json['comment_id'],
|
||||
authorId: json['author_id'],
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
"id": id,
|
||||
"created_at": createdAt.toIso8601String(),
|
||||
"updated_at": updatedAt.toIso8601String(),
|
||||
"deleted_at": deletedAt,
|
||||
"file_id": fileId,
|
||||
"filesize": filesize,
|
||||
"filename": filename,
|
||||
"mimetype": mimetype,
|
||||
"type": type,
|
||||
"external_url": externalUrl,
|
||||
"author": author.toJson(),
|
||||
"article_id": articleId,
|
||||
"moment_id": momentId,
|
||||
"comment_id": commentId,
|
||||
"author_id": authorId,
|
||||
'id': id,
|
||||
'created_at': createdAt.toIso8601String(),
|
||||
'updated_at': updatedAt.toIso8601String(),
|
||||
'deleted_at': deletedAt,
|
||||
'file_id': fileId,
|
||||
'filesize': filesize,
|
||||
'filename': filename,
|
||||
'mimetype': mimetype,
|
||||
'type': type,
|
||||
'external_url': externalUrl,
|
||||
'author': author.toJson(),
|
||||
'article_id': articleId,
|
||||
'moment_id': momentId,
|
||||
'comment_id': commentId,
|
||||
'author_id': authorId,
|
||||
};
|
||||
}
|
||||
|
@ -14,12 +14,12 @@ class AuthProvider extends ChangeNotifier {
|
||||
final userinfoEndpoint = getRequestUri('passport', '/api/users/me');
|
||||
final redirectUrl = Uri.parse('solian://auth');
|
||||
|
||||
static const clientId = "solian";
|
||||
static const clientSecret = "_F4%q2Eea3";
|
||||
static const clientId = 'solian';
|
||||
static const clientSecret = '_F4%q2Eea3';
|
||||
|
||||
static const storage = FlutterSecureStorage();
|
||||
static const storageKey = "identity";
|
||||
static const profileKey = "profiles";
|
||||
static const storageKey = 'identity';
|
||||
static const profileKey = 'profiles';
|
||||
|
||||
/// Before use this variable to make request
|
||||
/// **MAKE SURE YOU HAVE CALL THE isAuthorized() METHOD**
|
||||
@ -57,7 +57,7 @@ class AuthProvider extends ChangeNotifier {
|
||||
password,
|
||||
identifier: clientId,
|
||||
secret: clientSecret,
|
||||
scopes: ["openid"],
|
||||
scopes: ['openid'],
|
||||
basicAuth: false,
|
||||
);
|
||||
}
|
||||
@ -115,6 +115,6 @@ class AuthProvider extends ChangeNotifier {
|
||||
|
||||
Future<dynamic> getProfiles() async {
|
||||
const storage = FlutterSecureStorage();
|
||||
return jsonDecode(await storage.read(key: profileKey) ?? "{}");
|
||||
return jsonDecode(await storage.read(key: profileKey) ?? '{}');
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:livekit_client/livekit_client.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
@ -17,9 +18,11 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
class ChatProvider extends ChangeNotifier {
|
||||
bool isOpened = false;
|
||||
bool isShown = false;
|
||||
bool isCallShown = false;
|
||||
|
||||
ChatCallInstance? call;
|
||||
Call? ongoingCall;
|
||||
Channel? focusChannel;
|
||||
ChatCallInstance? currentCall;
|
||||
|
||||
Future<WebSocketChannel?> connect(AuthProvider auth) async {
|
||||
if (auth.client == null) await auth.loadClient();
|
||||
@ -43,17 +46,51 @@ class ChatProvider extends ChangeNotifier {
|
||||
return channel;
|
||||
}
|
||||
|
||||
bool handleCall(Call call, Channel channel,
|
||||
{Function? onUpdate, Function? onDispose}) {
|
||||
if (this.call != null) return false;
|
||||
Future<Channel> fetchChannel(String alias) async {
|
||||
final Client client = Client();
|
||||
|
||||
this.call = ChatCallInstance(
|
||||
var uri = getRequestUri('messaging', '/api/channels/$alias');
|
||||
var res = await client.get(uri);
|
||||
if (res.statusCode == 200) {
|
||||
final result = jsonDecode(utf8.decode(res.bodyBytes));
|
||||
focusChannel = Channel.fromJson(result);
|
||||
notifyListeners();
|
||||
return focusChannel!;
|
||||
} else {
|
||||
var message = utf8.decode(res.bodyBytes);
|
||||
throw Exception(message);
|
||||
}
|
||||
}
|
||||
|
||||
Future<Call?> fetchOngoingCall(String alias) async {
|
||||
final Client client = Client();
|
||||
|
||||
var uri = getRequestUri('messaging', '/api/channels/$alias/calls/ongoing');
|
||||
var res = await client.get(uri);
|
||||
if (res.statusCode == 200) {
|
||||
final result = jsonDecode(utf8.decode(res.bodyBytes));
|
||||
ongoingCall = Call.fromJson(result);
|
||||
notifyListeners();
|
||||
return ongoingCall;
|
||||
} else if (res.statusCode != 404) {
|
||||
var message = utf8.decode(res.bodyBytes);
|
||||
throw Exception(message);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
bool handleCallJoin(Call call, Channel channel,
|
||||
{Function? onUpdate, Function? onDispose}) {
|
||||
if (currentCall != null) return false;
|
||||
|
||||
currentCall = ChatCallInstance(
|
||||
onUpdate: () {
|
||||
notifyListeners();
|
||||
if (onUpdate != null) onUpdate();
|
||||
},
|
||||
onDispose: () {
|
||||
this.call = null;
|
||||
currentCall = null;
|
||||
notifyListeners();
|
||||
if (onDispose != null) onDispose();
|
||||
},
|
||||
@ -64,8 +101,13 @@ class ChatProvider extends ChangeNotifier {
|
||||
return true;
|
||||
}
|
||||
|
||||
void setShown(bool state) {
|
||||
isShown = state;
|
||||
void setOngoingCall(Call? item) {
|
||||
ongoingCall = item;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void setCallShown(bool state) {
|
||||
isCallShown = state;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
@ -118,8 +160,9 @@ class ChatCallInstance {
|
||||
}
|
||||
|
||||
Future<void> checkPermissions() async {
|
||||
if (lkPlatformIs(PlatformType.macOS) || lkPlatformIs(PlatformType.linux))
|
||||
if (lkPlatformIs(PlatformType.macOS) || lkPlatformIs(PlatformType.linux)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await Permission.camera.request();
|
||||
await Permission.microphone.request();
|
||||
@ -133,7 +176,7 @@ class ChatCallInstance {
|
||||
final auth = context.read<AuthProvider>();
|
||||
if (!await auth.isAuthorized()) {
|
||||
onDispose();
|
||||
throw Exception("unauthorized");
|
||||
throw Exception('unauthorized');
|
||||
}
|
||||
|
||||
var uri = getRequestUri(
|
||||
|
@ -29,7 +29,7 @@ class NotifyProvider extends ChangeNotifier {
|
||||
const androidSettings = AndroidInitializationSettings('app_icon');
|
||||
const darwinSettings = DarwinInitializationSettings(
|
||||
notificationCategories: [
|
||||
DarwinNotificationCategory("general"),
|
||||
DarwinNotificationCategory('general'),
|
||||
],
|
||||
);
|
||||
const linuxSettings =
|
||||
@ -46,8 +46,9 @@ class NotifyProvider extends ChangeNotifier {
|
||||
}
|
||||
|
||||
Future<void> requestPermissions() async {
|
||||
if (lkPlatformIs(PlatformType.macOS) || lkPlatformIs(PlatformType.linux))
|
||||
if (lkPlatformIs(PlatformType.macOS) || lkPlatformIs(PlatformType.linux)) {
|
||||
return;
|
||||
}
|
||||
await Permission.notification.request();
|
||||
}
|
||||
|
||||
|
@ -97,7 +97,7 @@ class NameCard extends StatelessWidget {
|
||||
Future<Widget> renderAvatar(BuildContext context) async {
|
||||
final auth = context.read<AuthProvider>();
|
||||
final profiles = await auth.getProfiles();
|
||||
return AccountAvatar(source: profiles["picture"], direct: true);
|
||||
return AccountAvatar(source: profiles['picture'], direct: true);
|
||||
}
|
||||
|
||||
Future<Column> renderLabel(BuildContext context) async {
|
||||
@ -107,13 +107,13 @@ class NameCard extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
profiles["nick"],
|
||||
profiles['nick'],
|
||||
style: const TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
Text(profiles["email"])
|
||||
Text(profiles['email'])
|
||||
],
|
||||
);
|
||||
}
|
||||
|
@ -157,8 +157,9 @@ class _FriendScreenState extends State<FriendScreen> {
|
||||
DismissDirection getDismissDirection(Friendship relation) {
|
||||
if (relation.status == 2) return DismissDirection.endToStart;
|
||||
if (relation.status == 1) return DismissDirection.startToEnd;
|
||||
if (relation.status == 0 && relation.relatedId != _selfId)
|
||||
if (relation.status == 0 && relation.relatedId != _selfId) {
|
||||
return DismissDirection.startToEnd;
|
||||
}
|
||||
return DismissDirection.horizontal;
|
||||
}
|
||||
|
||||
|
@ -1,41 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:webview_flutter/webview_flutter.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
class AuthorizationScreen extends StatelessWidget {
|
||||
final Uri authorizationUrl;
|
||||
|
||||
const AuthorizationScreen(this.authorizationUrl, {super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(AppLocalizations.of(context)!.signIn),
|
||||
),
|
||||
body: Stack(children: [
|
||||
WebViewWidget(
|
||||
controller: WebViewController()
|
||||
..setJavaScriptMode(JavaScriptMode.unrestricted)
|
||||
..setBackgroundColor(Colors.white)
|
||||
..setNavigationDelegate(NavigationDelegate(
|
||||
onNavigationRequest: (NavigationRequest request) {
|
||||
if (request.url.startsWith('solian')) {
|
||||
Navigator.of(context).pop(request.url);
|
||||
WebViewCookieManager().clearCookies();
|
||||
return NavigationDecision.prevent;
|
||||
} else if (request.url.contains("sign-up")) {
|
||||
launchUrl(Uri.parse(request.url));
|
||||
return NavigationDecision.prevent;
|
||||
}
|
||||
return NavigationDecision.navigate;
|
||||
},
|
||||
))
|
||||
..loadRequest(authorizationUrl)
|
||||
..clearCache(),
|
||||
),
|
||||
]),
|
||||
);
|
||||
}
|
||||
}
|
@ -24,14 +24,14 @@ class _ChatCallState extends State<ChatCall> {
|
||||
|
||||
late ChatProvider _chat;
|
||||
|
||||
ChatCallInstance get _call => _chat.call!;
|
||||
ChatCallInstance get _call => _chat.currentCall!;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_chat.setShown(true);
|
||||
_chat.setCallShown(true);
|
||||
});
|
||||
}
|
||||
|
||||
@ -40,13 +40,13 @@ class _ChatCallState extends State<ChatCall> {
|
||||
_chat = context.watch<ChatProvider>();
|
||||
if (!_isHandled) {
|
||||
_isHandled = true;
|
||||
if (_chat.handleCall(widget.call, widget.call.channel)) {
|
||||
_chat.call?.init();
|
||||
if (_chat.handleCallJoin(widget.call, widget.call.channel)) {
|
||||
_chat.currentCall?.init();
|
||||
}
|
||||
}
|
||||
|
||||
Widget content;
|
||||
if (_chat.call == null) {
|
||||
if (_chat.currentCall == null) {
|
||||
content = const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
@ -136,7 +136,7 @@ class _ChatCallState extends State<ChatCall> {
|
||||
|
||||
@override
|
||||
void deactivate() {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => _chat.setShown(false));
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => _chat.setCallShown(false));
|
||||
super.deactivate();
|
||||
}
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ class _ChannelEditorScreenState extends State<ChannelEditorScreen> {
|
||||
? getRequestUri('messaging', '/api/channels')
|
||||
: getRequestUri('messaging', '/api/channels/${widget.editing!.id}');
|
||||
|
||||
final req = Request(widget.editing == null ? "POST" : "PUT", uri);
|
||||
final req = Request(widget.editing == null ? 'POST' : 'PUT', uri);
|
||||
req.headers['Content-Type'] = 'application/json';
|
||||
req.body = jsonEncode(<String, dynamic>{
|
||||
'alias': _aliasController.value.text.toLowerCase(),
|
||||
|
@ -4,69 +4,52 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_animate/flutter_animate.dart';
|
||||
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:solian/models/call.dart';
|
||||
import 'package:solian/models/channel.dart';
|
||||
import 'package:solian/models/message.dart';
|
||||
import 'package:solian/models/pagination.dart';
|
||||
import 'package:solian/providers/auth.dart';
|
||||
import 'package:solian/providers/chat.dart';
|
||||
import 'package:solian/router.dart';
|
||||
import 'package:solian/utils/service_url.dart';
|
||||
import 'package:solian/widgets/chat/channel_action.dart';
|
||||
import 'package:solian/widgets/chat/maintainer.dart';
|
||||
import 'package:solian/widgets/chat/message.dart';
|
||||
import 'package:solian/widgets/chat/message_action.dart';
|
||||
import 'package:solian/widgets/chat/message_editor.dart';
|
||||
import 'package:solian/widgets/exts.dart';
|
||||
import 'package:solian/widgets/indent_wrapper.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
class ChatScreen extends StatefulWidget {
|
||||
class ChatScreen extends StatelessWidget {
|
||||
final String alias;
|
||||
|
||||
const ChatScreen({super.key, required this.alias});
|
||||
|
||||
@override
|
||||
State<ChatScreen> createState() => _ChatScreenState();
|
||||
Widget build(BuildContext context) {
|
||||
return IndentWrapper(
|
||||
title: AppLocalizations.of(context)!.post,
|
||||
noSafeArea: true,
|
||||
hideDrawer: true,
|
||||
child: ChatScreenWidget(
|
||||
alias: alias,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ChatScreenState extends State<ChatScreen> {
|
||||
Call? _ongoingCall;
|
||||
Channel? _channelMeta;
|
||||
class ChatScreenWidget extends StatefulWidget {
|
||||
final String alias;
|
||||
|
||||
const ChatScreenWidget({super.key, required this.alias});
|
||||
|
||||
@override
|
||||
State<ChatScreenWidget> createState() => _ChatScreenWidgetState();
|
||||
}
|
||||
|
||||
class _ChatScreenWidgetState extends State<ChatScreenWidget> {
|
||||
bool _isReady = false;
|
||||
|
||||
final PagingController<int, Message> _pagingController = PagingController(firstPageKey: 0);
|
||||
|
||||
final http.Client _client = http.Client();
|
||||
|
||||
Future<Channel> fetchMetadata() async {
|
||||
var uri = getRequestUri('messaging', '/api/channels/${widget.alias}');
|
||||
var res = await _client.get(uri);
|
||||
if (res.statusCode == 200) {
|
||||
final result = jsonDecode(utf8.decode(res.bodyBytes));
|
||||
setState(() => _channelMeta = Channel.fromJson(result));
|
||||
return _channelMeta!;
|
||||
} else {
|
||||
var message = utf8.decode(res.bodyBytes);
|
||||
context.showErrorDialog(message);
|
||||
throw Exception(message);
|
||||
}
|
||||
}
|
||||
|
||||
Future<Call?> fetchCall() async {
|
||||
var uri = getRequestUri('messaging', '/api/channels/${widget.alias}/calls/ongoing');
|
||||
var res = await _client.get(uri);
|
||||
if (res.statusCode == 200) {
|
||||
final result = jsonDecode(utf8.decode(res.bodyBytes));
|
||||
setState(() => _ongoingCall = Call.fromJson(result));
|
||||
return _ongoingCall;
|
||||
} else if (res.statusCode != 404) {
|
||||
var message = utf8.decode(res.bodyBytes);
|
||||
context.showErrorDialog(message);
|
||||
throw Exception(message);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
late final ChatProvider _chat;
|
||||
|
||||
Future<void> fetchMessages(int pageKey, BuildContext context) async {
|
||||
final auth = context.read<AuthProvider>();
|
||||
@ -142,11 +125,6 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
Future.delayed(Duration.zero, () {
|
||||
fetchMetadata();
|
||||
fetchCall();
|
||||
});
|
||||
|
||||
_pagingController.addPageRequestListener((pageKey) => fetchMessages(pageKey, context));
|
||||
|
||||
super.initState();
|
||||
@ -180,6 +158,11 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
if (!_isReady) {
|
||||
_isReady = true;
|
||||
_chat = context.watch<ChatProvider>();
|
||||
}
|
||||
|
||||
final callBanner = MaterialBanner(
|
||||
padding: const EdgeInsets.only(top: 4, bottom: 4, left: 20),
|
||||
leading: const Icon(Icons.call_received),
|
||||
@ -192,7 +175,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
onPressed: () {
|
||||
router.pushNamed(
|
||||
'chat.channel.call',
|
||||
extra: _ongoingCall,
|
||||
extra: _chat.ongoingCall,
|
||||
pathParameters: {'channel': widget.alias},
|
||||
);
|
||||
},
|
||||
@ -200,69 +183,52 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
],
|
||||
);
|
||||
|
||||
return IndentWrapper(
|
||||
hideDrawer: true,
|
||||
title: _channelMeta?.name ?? 'Loading...',
|
||||
appBarActions: _channelMeta != null
|
||||
? [
|
||||
ChannelCallAction(
|
||||
call: _ongoingCall,
|
||||
channel: _channelMeta!,
|
||||
onUpdate: () => fetchMetadata(),
|
||||
),
|
||||
ChannelManageAction(
|
||||
channel: _channelMeta!,
|
||||
onUpdate: () => fetchMetadata(),
|
||||
),
|
||||
]
|
||||
: [],
|
||||
child: FutureBuilder(
|
||||
future: fetchMetadata(),
|
||||
builder: (context, snapshot) {
|
||||
if (!snapshot.hasData || snapshot.data == null) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
return FutureBuilder(
|
||||
future: _chat.fetchChannel(widget.alias),
|
||||
builder: (context, snapshot) {
|
||||
if (!snapshot.hasData || snapshot.data == null) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
return ChatMaintainer(
|
||||
channel: snapshot.data!,
|
||||
child: Stack(
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: PagedListView<int, Message>(
|
||||
reverse: true,
|
||||
pagingController: _pagingController,
|
||||
builderDelegate: PagedChildBuilderDelegate<Message>(
|
||||
animateTransitions: true,
|
||||
transitionDuration: 500.ms,
|
||||
itemBuilder: chatHistoryBuilder,
|
||||
noItemsFoundIndicatorBuilder: (_) => Container(),
|
||||
),
|
||||
return ChatMaintainer(
|
||||
channel: snapshot.data!,
|
||||
child: Stack(
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: PagedListView<int, Message>(
|
||||
reverse: true,
|
||||
pagingController: _pagingController,
|
||||
builderDelegate: PagedChildBuilderDelegate<Message>(
|
||||
animateTransitions: true,
|
||||
transitionDuration: 500.ms,
|
||||
itemBuilder: chatHistoryBuilder,
|
||||
noItemsFoundIndicatorBuilder: (_) => Container(),
|
||||
),
|
||||
),
|
||||
ChatMessageEditor(
|
||||
channel: widget.alias,
|
||||
editing: _editingItem,
|
||||
replying: _replyingItem,
|
||||
onReset: () => setState(() {
|
||||
_editingItem = null;
|
||||
_replyingItem = null;
|
||||
}),
|
||||
),
|
||||
],
|
||||
),
|
||||
_ongoingCall != null ? callBanner.animate().slideY() : Container(),
|
||||
],
|
||||
),
|
||||
onInsertMessage: (message) => addMessage(message),
|
||||
onUpdateMessage: (message) => updateMessage(message),
|
||||
onDeleteMessage: (message) => deleteMessage(message),
|
||||
onCallStarted: (call) => setState(() => _ongoingCall = call),
|
||||
onCallEnded: () => setState(() => _ongoingCall = null),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
ChatMessageEditor(
|
||||
channel: widget.alias,
|
||||
editing: _editingItem,
|
||||
replying: _replyingItem,
|
||||
onReset: () => setState(() {
|
||||
_editingItem = null;
|
||||
_replyingItem = null;
|
||||
}),
|
||||
),
|
||||
],
|
||||
),
|
||||
_chat.ongoingCall != null ? callBanner.animate().slideY() : Container(),
|
||||
],
|
||||
),
|
||||
onInsertMessage: (message) => addMessage(message),
|
||||
onUpdateMessage: (message) => updateMessage(message),
|
||||
onDeleteMessage: (message) => deleteMessage(message),
|
||||
onCallStarted: (call) => _chat.setOngoingCall(call),
|
||||
onCallEnded: () => _chat.setOngoingCall(null),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -5,8 +5,10 @@ import 'package:provider/provider.dart';
|
||||
import 'package:solian/models/channel.dart';
|
||||
import 'package:solian/providers/auth.dart';
|
||||
import 'package:solian/router.dart';
|
||||
import 'package:solian/screens/chat/chat.dart';
|
||||
import 'package:solian/utils/service_url.dart';
|
||||
import 'package:solian/widgets/chat/chat_new.dart';
|
||||
import 'package:solian/widgets/empty.dart';
|
||||
import 'package:solian/widgets/exts.dart';
|
||||
import 'package:solian/widgets/indent_wrapper.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
@ -21,6 +23,68 @@ class ChatIndexScreen extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _ChatIndexScreenState extends State<ChatIndexScreen> {
|
||||
Channel? _selectedChannel;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final screenWidth = MediaQuery.of(context).size.width;
|
||||
final isLargeScreen = screenWidth >= 600;
|
||||
|
||||
return IndentWrapper(
|
||||
title: AppLocalizations.of(context)!.chat,
|
||||
appBarActions: const [NotificationButton()],
|
||||
fixedAppBarColor: isLargeScreen,
|
||||
child: isLargeScreen
|
||||
? Row(
|
||||
children: [
|
||||
Flexible(
|
||||
flex: 2,
|
||||
child: ChatIndexScreenWidget(
|
||||
onSelect: (item) {
|
||||
setState(() => _selectedChannel = item);
|
||||
},
|
||||
),
|
||||
),
|
||||
const VerticalDivider(thickness: 0.3, width: 0.3),
|
||||
Flexible(
|
||||
flex: 4,
|
||||
child: _selectedChannel == null
|
||||
? const SelectionEmptyWidget()
|
||||
: ChatScreenWidget(
|
||||
key: Key('c${_selectedChannel!.id}'),
|
||||
alias: _selectedChannel!.alias,
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: ChatIndexScreenWidget(
|
||||
onSelect: (item) async {
|
||||
final result = await router.pushNamed(
|
||||
'chat.channel',
|
||||
pathParameters: {
|
||||
'channel': item.alias,
|
||||
},
|
||||
);
|
||||
switch (result) {
|
||||
case 'refresh':
|
||||
// fetchChannels();
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ChatIndexScreenWidget extends StatefulWidget {
|
||||
final Function(Channel item) onSelect;
|
||||
|
||||
const ChatIndexScreenWidget({super.key, required this.onSelect});
|
||||
|
||||
@override
|
||||
State<ChatIndexScreenWidget> createState() => _ChatIndexScreenWidgetState();
|
||||
}
|
||||
|
||||
class _ChatIndexScreenWidgetState extends State<ChatIndexScreenWidget> {
|
||||
List<Channel> _channels = List.empty();
|
||||
|
||||
Future<void> fetchChannels() async {
|
||||
@ -61,9 +125,7 @@ class _ChatIndexScreenState extends State<ChatIndexScreen> {
|
||||
Widget build(BuildContext context) {
|
||||
final auth = context.read<AuthProvider>();
|
||||
|
||||
return IndentWrapper(
|
||||
title: AppLocalizations.of(context)!.chat,
|
||||
appBarActions: const [NotificationButton()],
|
||||
return Scaffold(
|
||||
floatingActionButton: FutureBuilder(
|
||||
future: auth.isAuthorized(),
|
||||
builder: (context, snapshot) {
|
||||
@ -78,43 +140,33 @@ class _ChatIndexScreenState extends State<ChatIndexScreen> {
|
||||
}
|
||||
},
|
||||
),
|
||||
child: FutureBuilder(
|
||||
future: auth.isAuthorized(),
|
||||
builder: (context, snapshot) {
|
||||
if (!snapshot.hasData || !snapshot.data!) {
|
||||
return const SignInRequiredScreen();
|
||||
}
|
||||
body: FutureBuilder(
|
||||
future: auth.isAuthorized(),
|
||||
builder: (context, snapshot) {
|
||||
if (!snapshot.hasData || !snapshot.data!) {
|
||||
return const SignInRequiredScreen();
|
||||
}
|
||||
|
||||
return RefreshIndicator(
|
||||
onRefresh: () => fetchChannels(),
|
||||
child: ListView.builder(
|
||||
itemCount: _channels.length,
|
||||
itemBuilder: (context, index) {
|
||||
final element = _channels[index];
|
||||
return ListTile(
|
||||
leading: const CircleAvatar(
|
||||
backgroundColor: Colors.indigo,
|
||||
child: Icon(Icons.tag, color: Colors.white),
|
||||
),
|
||||
title: Text(element.name),
|
||||
subtitle: Text(element.description),
|
||||
onTap: () async {
|
||||
final result = await router.pushNamed(
|
||||
'chat.channel',
|
||||
pathParameters: {
|
||||
'channel': element.alias,
|
||||
},
|
||||
);
|
||||
switch (result) {
|
||||
case 'refresh':
|
||||
fetchChannels();
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}),
|
||||
return RefreshIndicator(
|
||||
onRefresh: () => fetchChannels(),
|
||||
child: ListView.builder(
|
||||
itemCount: _channels.length,
|
||||
itemBuilder: (context, index) {
|
||||
final element = _channels[index];
|
||||
return ListTile(
|
||||
leading: const CircleAvatar(
|
||||
backgroundColor: Colors.indigo,
|
||||
child: Icon(Icons.tag, color: Colors.white),
|
||||
),
|
||||
title: Text(element.name),
|
||||
subtitle: Text(element.description),
|
||||
onTap: () => widget.onSelect(element),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -6,10 +6,12 @@ import 'package:solian/models/pagination.dart';
|
||||
import 'package:solian/models/post.dart';
|
||||
import 'package:solian/providers/auth.dart';
|
||||
import 'package:solian/router.dart';
|
||||
import 'package:solian/screens/posts/screen.dart';
|
||||
import 'package:solian/utils/service_url.dart';
|
||||
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:solian/widgets/empty.dart';
|
||||
import 'package:solian/widgets/indent_wrapper.dart';
|
||||
import 'package:solian/widgets/notification_notifier.dart';
|
||||
import 'package:solian/widgets/posts/item.dart';
|
||||
@ -22,8 +24,68 @@ class ExploreScreen extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _ExploreScreenState extends State<ExploreScreen> {
|
||||
final PagingController<int, Post> _pagingController =
|
||||
PagingController(firstPageKey: 0);
|
||||
Post? _selectedPost;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final screenWidth = MediaQuery.of(context).size.width;
|
||||
final isLargeScreen = screenWidth >= 600;
|
||||
|
||||
return IndentWrapper(
|
||||
noSafeArea: true,
|
||||
fixedAppBarColor: isLargeScreen,
|
||||
appBarActions: const [NotificationButton()],
|
||||
title: AppLocalizations.of(context)!.explore,
|
||||
child: isLargeScreen
|
||||
? Row(
|
||||
children: [
|
||||
Flexible(
|
||||
flex: 2,
|
||||
child: ExploreScreenWidget(
|
||||
onSelect: (item) {
|
||||
setState(() => _selectedPost = item);
|
||||
},
|
||||
),
|
||||
),
|
||||
const VerticalDivider(thickness: 0.3, width: 0.3),
|
||||
Flexible(
|
||||
flex: 4,
|
||||
child: _selectedPost == null
|
||||
? const SelectionEmptyWidget()
|
||||
: PostScreenWidget(
|
||||
key: Key('p${_selectedPost!.id}'),
|
||||
dataset: _selectedPost!.dataset,
|
||||
alias: _selectedPost!.alias,
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: ExploreScreenWidget(
|
||||
onSelect: (item) {
|
||||
router.pushNamed(
|
||||
'posts.screen',
|
||||
pathParameters: {
|
||||
'alias': item.alias,
|
||||
'dataset': item.dataset,
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ExploreScreenWidget extends StatefulWidget {
|
||||
final Function(Post item) onSelect;
|
||||
|
||||
const ExploreScreenWidget({super.key, required this.onSelect});
|
||||
|
||||
@override
|
||||
State<ExploreScreenWidget> createState() => _ExploreScreenWidgetState();
|
||||
}
|
||||
|
||||
class _ExploreScreenWidgetState extends State<ExploreScreenWidget> {
|
||||
final PagingController<int, Post> _pagingController = PagingController(firstPageKey: 0);
|
||||
|
||||
final http.Client _client = http.Client();
|
||||
|
||||
@ -31,15 +93,12 @@ class _ExploreScreenState extends State<ExploreScreen> {
|
||||
final offset = pageKey;
|
||||
const take = 5;
|
||||
|
||||
var uri =
|
||||
getRequestUri('interactive', '/api/feed?take=$take&offset=$offset');
|
||||
var uri = getRequestUri('interactive', '/api/feed?take=$take&offset=$offset');
|
||||
|
||||
var res = await _client.get(uri);
|
||||
if (res.statusCode == 200) {
|
||||
final result =
|
||||
PaginationResult.fromJson(jsonDecode(utf8.decode(res.bodyBytes)));
|
||||
final items =
|
||||
result.data?.map((x) => Post.fromJson(x)).toList() ?? List.empty();
|
||||
final result = PaginationResult.fromJson(jsonDecode(utf8.decode(res.bodyBytes)));
|
||||
final items = result.data?.map((x) => Post.fromJson(x)).toList() ?? List.empty();
|
||||
final isLastPage = (result.count - pageKey) < take;
|
||||
if (isLastPage || result.data == null) {
|
||||
_pagingController.appendLastPage(items);
|
||||
@ -63,8 +122,7 @@ class _ExploreScreenState extends State<ExploreScreen> {
|
||||
Widget build(BuildContext context) {
|
||||
final auth = context.read<AuthProvider>();
|
||||
|
||||
return IndentWrapper(
|
||||
noSafeArea: true,
|
||||
return Scaffold(
|
||||
floatingActionButton: FutureBuilder(
|
||||
future: auth.isAuthorized(),
|
||||
builder: (context, snapshot) {
|
||||
@ -72,7 +130,7 @@ class _ExploreScreenState extends State<ExploreScreen> {
|
||||
return FloatingActionButton(
|
||||
child: const Icon(Icons.edit),
|
||||
onPressed: () async {
|
||||
final did = await router.pushNamed("posts.moments.editor");
|
||||
final did = await router.pushNamed('posts.moments.editor');
|
||||
if (did == true) _pagingController.refresh();
|
||||
},
|
||||
);
|
||||
@ -81,9 +139,7 @@ class _ExploreScreenState extends State<ExploreScreen> {
|
||||
}
|
||||
},
|
||||
),
|
||||
appBarActions: const [NotificationButton()],
|
||||
title: AppLocalizations.of(context)!.explore,
|
||||
child: RefreshIndicator(
|
||||
body: RefreshIndicator(
|
||||
onRefresh: () => Future.sync(
|
||||
() => _pagingController.refresh(),
|
||||
),
|
||||
@ -93,15 +149,7 @@ class _ExploreScreenState extends State<ExploreScreen> {
|
||||
itemBuilder: (context, item, index) => PostItem(
|
||||
item: item,
|
||||
onUpdate: () => _pagingController.refresh(),
|
||||
onTap: () {
|
||||
router.pushNamed(
|
||||
'posts.screen',
|
||||
pathParameters: {
|
||||
'alias': item.alias,
|
||||
'dataset': '${item.modelType}s',
|
||||
},
|
||||
);
|
||||
},
|
||||
onTap: () => widget.onSelect(item),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -97,7 +97,7 @@ class NotificationItem extends StatelessWidget {
|
||||
padding: const EdgeInsets.only(
|
||||
left: 16, right: 16, top: 34, bottom: 12),
|
||||
child: Text(
|
||||
"Links",
|
||||
'Links',
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
),
|
||||
@ -151,7 +151,7 @@ class NotificationItem extends StatelessWidget {
|
||||
text: item.subject,
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
const TextSpan(text: " is marked as read")
|
||||
const TextSpan(text: ' is marked as read')
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -65,7 +65,7 @@ class _CommentEditorScreenState extends State<CommentEditorScreen> {
|
||||
? getRequestUri('interactive', '/api/p/$relatedDataset/$alias/comments')
|
||||
: getRequestUri('interactive', '/api/p/comments/${widget.editing!.id}');
|
||||
|
||||
final req = Request(widget.editing == null ? "POST" : "PUT", uri);
|
||||
final req = Request(widget.editing == null ? 'POST' : 'PUT', uri);
|
||||
req.headers['Content-Type'] = 'application/json';
|
||||
req.body = jsonEncode(<String, dynamic>{
|
||||
'alias': _alias,
|
||||
@ -140,12 +140,12 @@ class _CommentEditorScreenState extends State<CommentEditorScreen> {
|
||||
if (snapshot.hasData) {
|
||||
var userinfo = snapshot.data;
|
||||
return ListTile(
|
||||
title: Text(userinfo["nick"]),
|
||||
title: Text(userinfo['nick']),
|
||||
subtitle: Text(
|
||||
AppLocalizations.of(context)!.postIdentityNotify,
|
||||
),
|
||||
leading: AccountAvatar(
|
||||
source: userinfo["picture"],
|
||||
source: userinfo['picture'],
|
||||
direct: true,
|
||||
),
|
||||
);
|
||||
|
@ -55,7 +55,7 @@ class _MomentEditorScreenState extends State<MomentEditorScreen> {
|
||||
? getRequestUri('interactive', '/api/p/moments')
|
||||
: getRequestUri('interactive', '/api/p/moments/${widget.editing!.id}');
|
||||
|
||||
final req = Request(widget.editing == null ? "POST" : "PUT", uri);
|
||||
final req = Request(widget.editing == null ? 'POST' : 'PUT', uri);
|
||||
req.headers['Content-Type'] = 'application/json';
|
||||
req.body = jsonEncode(<String, dynamic>{
|
||||
'alias': _alias,
|
||||
@ -130,12 +130,12 @@ class _MomentEditorScreenState extends State<MomentEditorScreen> {
|
||||
if (snapshot.hasData) {
|
||||
var userinfo = snapshot.data;
|
||||
return ListTile(
|
||||
title: Text(userinfo["nick"]),
|
||||
title: Text(userinfo['nick']),
|
||||
subtitle: Text(
|
||||
AppLocalizations.of(context)!.postIdentityNotify,
|
||||
),
|
||||
leading: AccountAvatar(
|
||||
source: userinfo["picture"],
|
||||
source: userinfo['picture'],
|
||||
direct: true,
|
||||
),
|
||||
);
|
||||
|
@ -11,25 +11,43 @@ import 'package:solian/widgets/posts/comment_list.dart';
|
||||
import 'package:solian/widgets/posts/item.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
class PostScreen extends StatefulWidget {
|
||||
class PostScreen extends StatelessWidget {
|
||||
final String dataset;
|
||||
final String alias;
|
||||
|
||||
const PostScreen({super.key, required this.alias, required this.dataset});
|
||||
|
||||
@override
|
||||
State<PostScreen> createState() => _PostScreenState();
|
||||
Widget build(BuildContext context) {
|
||||
return IndentWrapper(
|
||||
title: AppLocalizations.of(context)!.post,
|
||||
noSafeArea: true,
|
||||
hideDrawer: true,
|
||||
child: PostScreenWidget(
|
||||
dataset: dataset,
|
||||
alias: alias,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _PostScreenState extends State<PostScreen> {
|
||||
class PostScreenWidget extends StatefulWidget {
|
||||
final String dataset;
|
||||
final String alias;
|
||||
|
||||
const PostScreenWidget({super.key, required this.dataset, required this.alias});
|
||||
|
||||
@override
|
||||
State<PostScreenWidget> createState() => _PostScreenWidgetState();
|
||||
}
|
||||
|
||||
class _PostScreenWidgetState extends State<PostScreenWidget> {
|
||||
final _client = http.Client();
|
||||
|
||||
final PagingController<int, Post> _commentPagingController =
|
||||
PagingController(firstPageKey: 0);
|
||||
final PagingController<int, Post> _commentPagingController = PagingController(firstPageKey: 0);
|
||||
|
||||
Future<Post?> fetchPost(BuildContext context) async {
|
||||
final uri = getRequestUri(
|
||||
'interactive', '/api/p/${widget.dataset}/${widget.alias}');
|
||||
final uri = getRequestUri('interactive', '/api/p/${widget.dataset}/${widget.alias}');
|
||||
final res = await _client.get(uri);
|
||||
if (res.statusCode != 200) {
|
||||
final err = utf8.decode(res.bodyBytes);
|
||||
@ -42,43 +60,38 @@ class _PostScreenState extends State<PostScreen> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return IndentWrapper(
|
||||
noSafeArea: true,
|
||||
hideDrawer: true,
|
||||
title: AppLocalizations.of(context)!.post,
|
||||
child: FutureBuilder(
|
||||
future: fetchPost(context),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData && snapshot.data != null) {
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
SliverToBoxAdapter(
|
||||
child: PostItem(
|
||||
item: snapshot.data!,
|
||||
brief: false,
|
||||
ripple: false,
|
||||
),
|
||||
return FutureBuilder(
|
||||
future: fetchPost(context),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData && snapshot.data != null) {
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
SliverToBoxAdapter(
|
||||
child: PostItem(
|
||||
item: snapshot.data!,
|
||||
brief: false,
|
||||
ripple: false,
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: CommentListHeader(
|
||||
related: snapshot.data!,
|
||||
paging: _commentPagingController,
|
||||
),
|
||||
),
|
||||
CommentList(
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: CommentListHeader(
|
||||
related: snapshot.data!,
|
||||
dataset: widget.dataset,
|
||||
paging: _commentPagingController,
|
||||
),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
CommentList(
|
||||
related: snapshot.data!,
|
||||
dataset: widget.dataset,
|
||||
paging: _commentPagingController,
|
||||
),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -14,7 +14,7 @@ class CallOverlay extends StatelessWidget {
|
||||
|
||||
final chat = context.watch<ChatProvider>();
|
||||
|
||||
if (chat.isShown || chat.call == null) {
|
||||
if (chat.isCallShown || chat.currentCall == null) {
|
||||
return Container();
|
||||
}
|
||||
|
||||
@ -54,8 +54,8 @@ class CallOverlay extends StatelessWidget {
|
||||
onTap: () {
|
||||
router.pushNamed(
|
||||
'chat.channel.call',
|
||||
extra: chat.call!.info,
|
||||
pathParameters: {'channel': chat.call!.channel.alias},
|
||||
extra: chat.currentCall!.info,
|
||||
pathParameters: {'channel': chat.currentCall!.channel.alias},
|
||||
);
|
||||
},
|
||||
);
|
||||
|
@ -73,9 +73,9 @@ class _ControlsWidgetState extends State<ControlsWidget> {
|
||||
if (await context.showDisconnectDialog() != true) return;
|
||||
|
||||
final chat = context.read<ChatProvider>();
|
||||
if (chat.call != null) {
|
||||
chat.call!.deactivate();
|
||||
chat.call!.dispose();
|
||||
if (chat.currentCall != null) {
|
||||
chat.currentCall!.deactivate();
|
||||
chat.currentCall!.dispose();
|
||||
router.pop();
|
||||
}
|
||||
}
|
||||
|
@ -55,23 +55,19 @@ class _ChatMaintainerState extends State<ChatMaintainer> {
|
||||
switch (result.method) {
|
||||
case 'messages.new':
|
||||
final payload = Message.fromJson(result.payload!);
|
||||
if (payload.channelId == widget.channel.id)
|
||||
widget.onInsertMessage(payload);
|
||||
if (payload.channelId == widget.channel.id) widget.onInsertMessage(payload);
|
||||
break;
|
||||
case 'messages.update':
|
||||
final payload = Message.fromJson(result.payload!);
|
||||
if (payload.channelId == widget.channel.id)
|
||||
widget.onUpdateMessage(payload);
|
||||
if (payload.channelId == widget.channel.id) widget.onUpdateMessage(payload);
|
||||
break;
|
||||
case 'messages.burnt':
|
||||
final payload = Message.fromJson(result.payload!);
|
||||
if (payload.channelId == widget.channel.id)
|
||||
widget.onDeleteMessage(payload);
|
||||
if (payload.channelId == widget.channel.id) widget.onDeleteMessage(payload);
|
||||
break;
|
||||
case 'calls.new':
|
||||
final payload = Call.fromJson(result.payload!);
|
||||
if (payload.channelId == widget.channel.id)
|
||||
widget.onCallStarted(payload);
|
||||
if (payload.channelId == widget.channel.id) widget.onCallStarted(payload);
|
||||
break;
|
||||
case 'calls.end':
|
||||
final payload = Call.fromJson(result.payload!);
|
||||
|
@ -56,7 +56,7 @@ class _ChatMessageEditorState extends State<ChatMessageEditor> {
|
||||
? getRequestUri('messaging', '/api/channels/${widget.channel}/messages')
|
||||
: getRequestUri('messaging', '/api/channels/${widget.channel}/messages/${widget.editing!.id}');
|
||||
|
||||
final req = Request(widget.editing == null ? "POST" : "PUT", uri);
|
||||
final req = Request(widget.editing == null ? 'POST' : 'PUT', uri);
|
||||
req.headers['Content-Type'] = 'application/json';
|
||||
req.body = jsonEncode(<String, dynamic>{
|
||||
'content': _textController.value.text,
|
||||
@ -163,7 +163,6 @@ class _ChatMessageEditorState extends State<ChatMessageEditor> {
|
||||
focusNode: _focusNode,
|
||||
controller: _textController,
|
||||
maxLines: null,
|
||||
autofocus: true,
|
||||
autocorrect: true,
|
||||
keyboardType: TextInputType.text,
|
||||
decoration: InputDecoration.collapsed(
|
||||
|
@ -22,7 +22,11 @@ class LayoutWrapper extends StatelessWidget {
|
||||
final content = child ?? Container();
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text(title), actions: appBarActions),
|
||||
appBar: AppBar(
|
||||
title: Text(title),
|
||||
actions: appBarActions,
|
||||
centerTitle: false,
|
||||
),
|
||||
floatingActionButton: floatingActionButton,
|
||||
drawer: const SolianNavigationDrawer(),
|
||||
body: noSafeArea ? content : SafeArea(child: content),
|
||||
|
23
lib/widgets/empty.dart
Normal file
23
lib/widgets/empty.dart
Normal file
@ -0,0 +1,23 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
class SelectionEmptyWidget extends StatelessWidget {
|
||||
const SelectionEmptyWidget({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Image.asset('assets/logo.png', width: 64, height: 64),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.appName,
|
||||
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w900),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -9,8 +9,8 @@ extension SolianCommonExtensions on BuildContext {
|
||||
return message
|
||||
.split(' ')
|
||||
.map((element) =>
|
||||
"${element[0].toUpperCase()}${element.substring(1).toLowerCase()}")
|
||||
.join(" ");
|
||||
'${element[0].toUpperCase()}${element.substring(1).toLowerCase()}')
|
||||
.join(' ');
|
||||
}
|
||||
|
||||
return showDialog<void>(
|
||||
|
@ -5,6 +5,7 @@ import 'package:solian/widgets/navigation_drawer.dart';
|
||||
|
||||
class IndentWrapper extends LayoutWrapper {
|
||||
final bool hideDrawer;
|
||||
final bool fixedAppBarColor;
|
||||
|
||||
const IndentWrapper({
|
||||
super.key,
|
||||
@ -13,6 +14,7 @@ class IndentWrapper extends LayoutWrapper {
|
||||
super.floatingActionButton,
|
||||
super.appBarActions,
|
||||
this.hideDrawer = false,
|
||||
this.fixedAppBarColor = false,
|
||||
super.noSafeArea = false,
|
||||
}) : super();
|
||||
|
||||
@ -30,6 +32,8 @@ class IndentWrapper extends LayoutWrapper {
|
||||
: null,
|
||||
title: Text(title),
|
||||
actions: appBarActions,
|
||||
centerTitle: false,
|
||||
elevation: fixedAppBarColor ? 4 : null,
|
||||
),
|
||||
floatingActionButton: floatingActionButton,
|
||||
drawer: const SolianNavigationDrawer(),
|
||||
|
@ -39,21 +39,21 @@ class _SolianNavigationDrawerState extends State<SolianNavigationDrawer> {
|
||||
icon: const Icon(Icons.explore),
|
||||
label: Text(AppLocalizations.of(context)!.explore),
|
||||
),
|
||||
"explore",
|
||||
'explore',
|
||||
),
|
||||
(
|
||||
NavigationDrawerDestination(
|
||||
icon: const Icon(Icons.send),
|
||||
label: Text(AppLocalizations.of(context)!.chat),
|
||||
),
|
||||
"chat",
|
||||
'chat',
|
||||
),
|
||||
(
|
||||
NavigationDrawerDestination(
|
||||
icon: const Icon(Icons.account_circle),
|
||||
label: Text(AppLocalizations.of(context)!.account),
|
||||
),
|
||||
"account",
|
||||
'account',
|
||||
),
|
||||
];
|
||||
|
||||
@ -69,7 +69,7 @@ class _SolianNavigationDrawerState extends State<SolianNavigationDrawer> {
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Image.asset("assets/logo.png", width: 26, height: 26),
|
||||
Image.asset('assets/logo.png', width: 26, height: 26),
|
||||
const SizedBox(width: 10),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.appName,
|
||||
|
@ -85,7 +85,7 @@ class _NotificationButtonState extends State<NotificationButton> {
|
||||
child: IconButton(
|
||||
icon: const Icon(Icons.notifications),
|
||||
onPressed: () {
|
||||
router.pushNamed("notification");
|
||||
router.pushNamed('notification');
|
||||
},
|
||||
),
|
||||
);
|
||||
|
@ -112,7 +112,7 @@ class _AttachmentEditorState extends State<AttachmentEditor> {
|
||||
var res = await auth.client!.send(req);
|
||||
if (res.statusCode == 200) {
|
||||
var result = Attachment.fromJson(
|
||||
jsonDecode(utf8.decode(await res.stream.toBytes()))["info"],
|
||||
jsonDecode(utf8.decode(await res.stream.toBytes()))['info'],
|
||||
);
|
||||
setState(() => _attachments.add(result));
|
||||
widget.onUpdate(_attachments);
|
||||
@ -252,7 +252,7 @@ class _AttachmentEditorState extends State<AttachmentEditor> {
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
Text(
|
||||
"${getFileType(element)} · ${formatBytes(element.filesize)}",
|
||||
'${getFileType(element)} · ${formatBytes(element.filesize)}',
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -121,7 +121,7 @@ class CommentListHeader extends StatelessWidget {
|
||||
return TextButton(
|
||||
onPressed: () async {
|
||||
final did = await router.pushNamed(
|
||||
"posts.comments.editor",
|
||||
'posts.comments.editor',
|
||||
extra: CommentPostArguments(related: related),
|
||||
);
|
||||
if (did == true) paging.refresh();
|
||||
|
@ -55,7 +55,7 @@ class ArticleContent extends StatelessWidget {
|
||||
},
|
||||
imageBuilder: (url, _, __) {
|
||||
Uri uri;
|
||||
if (url.toString().startsWith("/api/attachments")) {
|
||||
if (url.toString().startsWith('/api/attachments')) {
|
||||
uri = getRequestUri('interactive', url.toString());
|
||||
} else {
|
||||
uri = url;
|
||||
|
@ -30,7 +30,7 @@ class _AttachmentItemState extends State<AttachmentItem> {
|
||||
|
||||
late final _videoPlayer = Player(
|
||||
configuration: PlayerConfiguration(
|
||||
title: "Attachment #${getTag()}",
|
||||
title: 'Attachment #${getTag()}',
|
||||
logLevel: MPVLogLevel.error,
|
||||
),
|
||||
);
|
||||
|
@ -13,8 +13,8 @@ import 'package:timeago/timeago.dart' as timeago;
|
||||
|
||||
class PostItem extends StatefulWidget {
|
||||
final Post item;
|
||||
final bool? brief;
|
||||
final bool? ripple;
|
||||
final bool brief;
|
||||
final bool ripple;
|
||||
final Function? onUpdate;
|
||||
final Function? onDelete;
|
||||
final Function? onTap;
|
||||
@ -22,8 +22,8 @@ class PostItem extends StatefulWidget {
|
||||
const PostItem({
|
||||
super.key,
|
||||
required this.item,
|
||||
this.brief,
|
||||
this.ripple,
|
||||
this.brief = true,
|
||||
this.ripple = true,
|
||||
this.onUpdate,
|
||||
this.onDelete,
|
||||
this.onTap,
|
||||
@ -79,9 +79,9 @@ class _PostItemState extends State<PostItem> {
|
||||
Widget renderContent() {
|
||||
switch (widget.item.modelType) {
|
||||
case 'article':
|
||||
return ArticleContent(item: widget.item, brief: widget.brief ?? true);
|
||||
return ArticleContent(item: widget.item, brief: widget.brief);
|
||||
default:
|
||||
return MomentContent(item: widget.item, brief: widget.brief ?? true);
|
||||
return MomentContent(item: widget.item, brief: widget.brief);
|
||||
}
|
||||
}
|
||||
|
||||
@ -163,7 +163,7 @@ class _PostItemState extends State<PostItem> {
|
||||
|
||||
Widget content;
|
||||
|
||||
if (widget.brief ?? true) {
|
||||
if (widget.brief) {
|
||||
content = Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
|
||||
child: Column(
|
||||
@ -199,7 +199,7 @@ class _PostItemState extends State<PostItem> {
|
||||
content = Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 12, right: 12, top: 16),
|
||||
padding: const EdgeInsets.only(left: 20, right: 20, top: 16),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@ -230,17 +230,17 @@ class _PostItemState extends State<PostItem> {
|
||||
child: Divider(thickness: 0.3),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8),
|
||||
child: renderContent(),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: renderAttachments(),
|
||||
),
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 2),
|
||||
child: renderReactions(),
|
||||
),
|
||||
),
|
||||
@ -248,9 +248,7 @@ class _PostItemState extends State<PostItem> {
|
||||
);
|
||||
}
|
||||
|
||||
final ripple = widget.ripple ?? true;
|
||||
|
||||
if (ripple) {
|
||||
if (widget.ripple) {
|
||||
return InkWell(
|
||||
child: content,
|
||||
onTap: () {
|
||||
|
@ -134,8 +134,8 @@ class _ReactionActionPopupState extends State<ReactionActionPopup> {
|
||||
child: ListTile(
|
||||
title: Text(info.value.icon),
|
||||
subtitle: Text(
|
||||
":${info.key}:",
|
||||
style: const TextStyle(fontFamily: "monospace"),
|
||||
':${info.key}:',
|
||||
style: const TextStyle(fontFamily: 'monospace'),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user