Compare commits
10 Commits
11a3d8f39b
...
8943f089f2
Author | SHA1 | Date | |
---|---|---|---|
8943f089f2 | |||
ba405770ed | |||
c25ae591b9 | |||
da5f3a24e7 | |||
718c715cae | |||
964210cbe4 | |||
bb5a10c4c4 | |||
0814c17407 | |||
d2ae4f3292 | |||
7e42d95904 |
@ -4,6 +4,10 @@ PODS:
|
|||||||
- Flutter
|
- Flutter
|
||||||
- image_picker_ios (0.0.1):
|
- image_picker_ios (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- media_kit_libs_ios_video (1.0.4):
|
||||||
|
- Flutter
|
||||||
|
- media_kit_native_event_loop (1.0.0):
|
||||||
|
- Flutter
|
||||||
- media_kit_video (0.0.1):
|
- media_kit_video (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- package_info_plus (0.4.5):
|
- package_info_plus (0.4.5):
|
||||||
@ -15,9 +19,6 @@ PODS:
|
|||||||
- Flutter
|
- Flutter
|
||||||
- url_launcher_ios (0.0.1):
|
- url_launcher_ios (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- video_player_avfoundation (0.0.1):
|
|
||||||
- Flutter
|
|
||||||
- FlutterMacOS
|
|
||||||
- volume_controller (0.0.1):
|
- volume_controller (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- wakelock_plus (0.0.1):
|
- wakelock_plus (0.0.1):
|
||||||
@ -29,12 +30,13 @@ DEPENDENCIES:
|
|||||||
- Flutter (from `Flutter`)
|
- Flutter (from `Flutter`)
|
||||||
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
|
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
|
||||||
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
||||||
|
- media_kit_libs_ios_video (from `.symlinks/plugins/media_kit_libs_ios_video/ios`)
|
||||||
|
- media_kit_native_event_loop (from `.symlinks/plugins/media_kit_native_event_loop/ios`)
|
||||||
- media_kit_video (from `.symlinks/plugins/media_kit_video/ios`)
|
- media_kit_video (from `.symlinks/plugins/media_kit_video/ios`)
|
||||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||||
- screen_brightness_ios (from `.symlinks/plugins/screen_brightness_ios/ios`)
|
- screen_brightness_ios (from `.symlinks/plugins/screen_brightness_ios/ios`)
|
||||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||||
- video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`)
|
|
||||||
- volume_controller (from `.symlinks/plugins/volume_controller/ios`)
|
- volume_controller (from `.symlinks/plugins/volume_controller/ios`)
|
||||||
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
|
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
|
||||||
- webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/ios`)
|
- webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/ios`)
|
||||||
@ -46,6 +48,10 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/flutter_secure_storage/ios"
|
:path: ".symlinks/plugins/flutter_secure_storage/ios"
|
||||||
image_picker_ios:
|
image_picker_ios:
|
||||||
:path: ".symlinks/plugins/image_picker_ios/ios"
|
:path: ".symlinks/plugins/image_picker_ios/ios"
|
||||||
|
media_kit_libs_ios_video:
|
||||||
|
:path: ".symlinks/plugins/media_kit_libs_ios_video/ios"
|
||||||
|
media_kit_native_event_loop:
|
||||||
|
:path: ".symlinks/plugins/media_kit_native_event_loop/ios"
|
||||||
media_kit_video:
|
media_kit_video:
|
||||||
:path: ".symlinks/plugins/media_kit_video/ios"
|
:path: ".symlinks/plugins/media_kit_video/ios"
|
||||||
package_info_plus:
|
package_info_plus:
|
||||||
@ -56,8 +62,6 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/screen_brightness_ios/ios"
|
:path: ".symlinks/plugins/screen_brightness_ios/ios"
|
||||||
url_launcher_ios:
|
url_launcher_ios:
|
||||||
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||||
video_player_avfoundation:
|
|
||||||
:path: ".symlinks/plugins/video_player_avfoundation/darwin"
|
|
||||||
volume_controller:
|
volume_controller:
|
||||||
:path: ".symlinks/plugins/volume_controller/ios"
|
:path: ".symlinks/plugins/volume_controller/ios"
|
||||||
wakelock_plus:
|
wakelock_plus:
|
||||||
@ -69,12 +73,13 @@ SPEC CHECKSUMS:
|
|||||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||||
flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be
|
flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be
|
||||||
image_picker_ios: b545a5f16c0fa88e3ecbbce3ed4de45567a8ec18
|
image_picker_ios: b545a5f16c0fa88e3ecbbce3ed4de45567a8ec18
|
||||||
media_kit_video: 26c5b265a4094a2df3e8d41e6724d9b964c13151
|
media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1
|
||||||
|
media_kit_native_event_loop: e6b2ab20cf0746eb1c33be961fcf79667304fa2a
|
||||||
|
media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e
|
||||||
package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c
|
package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c
|
||||||
path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c
|
path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c
|
||||||
screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625
|
screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625
|
||||||
url_launcher_ios: 6116280ddcfe98ab8820085d8d76ae7449447586
|
url_launcher_ios: 6116280ddcfe98ab8820085d8d76ae7449447586
|
||||||
video_player_avfoundation: 2b4384f3b157206b5e150a0083cdc0c905d260d3
|
|
||||||
volume_controller: 531ddf792994285c9b17f9d8a7e4dcdd29b3eae9
|
volume_controller: 531ddf792994285c9b17f9d8a7e4dcdd29b3eae9
|
||||||
wakelock_plus: 78ec7c5b202cab7761af8e2b2b3d0671be6c4ae1
|
wakelock_plus: 78ec7c5b202cab7761af8e2b2b3d0671be6c4ae1
|
||||||
webview_flutter_wkwebview: be0f0d33777f1bfd0c9fdcb594786704dbf65f36
|
webview_flutter_wkwebview: be0f0d33777f1bfd0c9fdcb594786704dbf65f36
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"solian": "Solian",
|
"solian": "Solian",
|
||||||
"explore": "Explore",
|
"explore": "Explore",
|
||||||
|
"chat": "Chat",
|
||||||
"account": "Account",
|
"account": "Account",
|
||||||
"signIn": "Sign In",
|
"signIn": "Sign In",
|
||||||
"signInCaption": "Sign in to create post, start a realm, message your friend and more!",
|
"signInCaption": "Sign in to create post, start a realm, message your friend and more!",
|
||||||
@ -12,16 +13,26 @@
|
|||||||
"edit": "Edit",
|
"edit": "Edit",
|
||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
"action": "Action",
|
"action": "Action",
|
||||||
|
"cancel": "Cancel",
|
||||||
"report": "Report",
|
"report": "Report",
|
||||||
|
"reaction": "Reaction",
|
||||||
|
"reactVerb": "React",
|
||||||
"post": "Post",
|
"post": "Post",
|
||||||
"postVerb": "Post",
|
"postVerb": "Post",
|
||||||
"comment": "Comment",
|
"comment": "Comment",
|
||||||
"attachment": "Attachment",
|
"attachment": "Attachment",
|
||||||
"attachmentAdd": "Add new attachment",
|
"attachmentAdd": "Add new attachment",
|
||||||
"pickPhoto": "Gallery photo",
|
"pickPhoto": "Gallery photo",
|
||||||
|
"takePhoto": "Capture photo",
|
||||||
|
"pickVideo": "Gallery video",
|
||||||
|
"takeVideo": "Record video",
|
||||||
"newMoment": "Record a moment",
|
"newMoment": "Record a moment",
|
||||||
"newComment": "Leave a comment",
|
"newComment": "Leave a comment",
|
||||||
"postIdentityNotify": "You will create this post as",
|
"postIdentityNotify": "You will create this post as",
|
||||||
"postContentPlaceholder": "What's happened?!",
|
"postContentPlaceholder": "What's happened?!",
|
||||||
"postDeleteConfirm": "Are you sure you want to delete this post? This operation cannot be revert!"
|
"postDeleteConfirm": "Are you sure you want to delete this post? This operation cannot be revert!",
|
||||||
|
"postEditNotify": "You are about editing a post that already published.",
|
||||||
|
"reactionAdded": "Your reaction has been added.",
|
||||||
|
"reactionRemoved": "Your reaction has been removed.",
|
||||||
|
"chatMessagePlaceholder": "Write a message..."
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"solian": "索链",
|
"solian": "索链",
|
||||||
"explore": "探索",
|
"explore": "探索",
|
||||||
|
"chat": "聊天",
|
||||||
"account": "账号",
|
"account": "账号",
|
||||||
"signIn": "登陆",
|
"signIn": "登陆",
|
||||||
"signInCaption": "登陆以发表帖子、文章、创建领域、和你的朋友聊天,以及获取更多功能!",
|
"signInCaption": "登陆以发表帖子、文章、创建领域、和你的朋友聊天,以及获取更多功能!",
|
||||||
@ -12,16 +13,26 @@
|
|||||||
"edit": "编辑",
|
"edit": "编辑",
|
||||||
"delete": "删除",
|
"delete": "删除",
|
||||||
"action": "操作",
|
"action": "操作",
|
||||||
|
"cancel": "取消",
|
||||||
"report": "举报",
|
"report": "举报",
|
||||||
|
"reaction": "反应",
|
||||||
|
"reactVerb": "作出反应",
|
||||||
"post": "帖子",
|
"post": "帖子",
|
||||||
"postVerb": "发表",
|
"postVerb": "发表",
|
||||||
"comment": "评论",
|
"comment": "评论",
|
||||||
"attachment": "附件",
|
"attachment": "附件",
|
||||||
"attachmentAdd": "附加新附件",
|
"attachmentAdd": "附加新附件",
|
||||||
"pickPhoto": "相册照片",
|
"pickPhoto": "相册照片",
|
||||||
|
"takePhoto": "拍摄照片",
|
||||||
|
"pickVideo": "相册视频",
|
||||||
|
"takeVideo": "拍摄视频",
|
||||||
"newMoment": "记录时刻",
|
"newMoment": "记录时刻",
|
||||||
"newComment": "留下评论",
|
"newComment": "留下评论",
|
||||||
"postIdentityNotify": "你将会以该身份发表本帖子",
|
"postIdentityNotify": "你将会以该身份发表本帖子",
|
||||||
"postContentPlaceholder": "发生什么事了?!",
|
"postContentPlaceholder": "发生什么事了?!",
|
||||||
"postDeleteConfirm": "你确定要删除这篇帖子吗?这意味着这个帖子将永远被我们丢弃在硬盘海中!该操作不可被反转!"
|
"postDeleteConfirm": "你确定要删除这篇帖子吗?这意味着这个帖子将永远被我们丢弃在硬盘海中!该操作不可被反转!",
|
||||||
|
"postEditNotify": "你正在修改一个已经发布了的帖子。",
|
||||||
|
"reactionAdded": "你的反应已被添加。",
|
||||||
|
"reactionRemoved": "你的反应已被移除。",
|
||||||
|
"chatMessagePlaceholder": "发条消息……"
|
||||||
}
|
}
|
@ -5,8 +5,10 @@ import 'package:solian/providers/navigation.dart';
|
|||||||
import 'package:solian/router.dart';
|
import 'package:solian/router.dart';
|
||||||
import 'package:solian/utils/timeago.dart';
|
import 'package:solian/utils/timeago.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
import 'package:solian/utils/video_player.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
|
initVideo();
|
||||||
initTimeAgo();
|
initTimeAgo();
|
||||||
|
|
||||||
runApp(const SolianApp());
|
runApp(const SolianApp());
|
||||||
|
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,
|
||||||
|
required this.emailAddress,
|
||||||
|
required this.powerLevel,
|
||||||
|
required 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,
|
||||||
|
};
|
||||||
|
}
|
63
lib/models/channel.dart
Normal file
63
lib/models/channel.dart
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
class Channel {
|
||||||
|
int id;
|
||||||
|
DateTime createdAt;
|
||||||
|
DateTime updatedAt;
|
||||||
|
DateTime? deletedAt;
|
||||||
|
String alias;
|
||||||
|
String name;
|
||||||
|
String description;
|
||||||
|
dynamic members;
|
||||||
|
dynamic messages;
|
||||||
|
dynamic calls;
|
||||||
|
int type;
|
||||||
|
int accountId;
|
||||||
|
int realmId;
|
||||||
|
|
||||||
|
Channel({
|
||||||
|
required this.id,
|
||||||
|
required this.createdAt,
|
||||||
|
required this.updatedAt,
|
||||||
|
this.deletedAt,
|
||||||
|
required this.alias,
|
||||||
|
required this.name,
|
||||||
|
required this.description,
|
||||||
|
this.members,
|
||||||
|
this.messages,
|
||||||
|
this.calls,
|
||||||
|
required this.type,
|
||||||
|
required this.accountId,
|
||||||
|
required 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"],
|
||||||
|
members: json["members"],
|
||||||
|
messages: json["messages"],
|
||||||
|
calls: json["calls"],
|
||||||
|
type: json["type"],
|
||||||
|
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,
|
||||||
|
"messages": messages,
|
||||||
|
"calls": calls,
|
||||||
|
"type": type,
|
||||||
|
"account_id": accountId,
|
||||||
|
"realm_id": realmId,
|
||||||
|
};
|
||||||
|
}
|
115
lib/models/message.dart
Normal file
115
lib/models/message.dart
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
import 'package:solian/models/account.dart';
|
||||||
|
import 'package:solian/models/channel.dart';
|
||||||
|
import 'package:solian/models/post.dart';
|
||||||
|
|
||||||
|
class Message {
|
||||||
|
int id;
|
||||||
|
DateTime createdAt;
|
||||||
|
DateTime updatedAt;
|
||||||
|
DateTime? deletedAt;
|
||||||
|
String content;
|
||||||
|
dynamic metadata;
|
||||||
|
int type;
|
||||||
|
List<Attachment>? attachments;
|
||||||
|
Channel? channel;
|
||||||
|
Sender sender;
|
||||||
|
int? replyId;
|
||||||
|
Message? replyTo;
|
||||||
|
int channelId;
|
||||||
|
int senderId;
|
||||||
|
|
||||||
|
Message({
|
||||||
|
required this.id,
|
||||||
|
required this.createdAt,
|
||||||
|
required this.updatedAt,
|
||||||
|
this.deletedAt,
|
||||||
|
required this.content,
|
||||||
|
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"],
|
||||||
|
content: json["content"],
|
||||||
|
metadata: json["metadata"],
|
||||||
|
type: json["type"],
|
||||||
|
attachments: List<Attachment>.from(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"]) : 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": 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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
}
|
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),
|
||||||
|
};
|
@ -1,5 +1,4 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
@ -8,9 +7,7 @@ import 'package:oauth2/oauth2.dart' as oauth2;
|
|||||||
import 'package:solian/utils/service_url.dart';
|
import 'package:solian/utils/service_url.dart';
|
||||||
|
|
||||||
class AuthProvider {
|
class AuthProvider {
|
||||||
AuthProvider() {
|
AuthProvider();
|
||||||
pickClient();
|
|
||||||
}
|
|
||||||
|
|
||||||
final deviceEndpoint =
|
final deviceEndpoint =
|
||||||
getRequestUri('passport', '/api/notifications/subscribe');
|
getRequestUri('passport', '/api/notifications/subscribe');
|
||||||
@ -26,7 +23,10 @@ class AuthProvider {
|
|||||||
static const storageKey = "identity";
|
static const storageKey = "identity";
|
||||||
static const profileKey = "profiles";
|
static const profileKey = "profiles";
|
||||||
|
|
||||||
|
/// Before use this variable to make request
|
||||||
|
/// **MAKE SURE YOU HAVE CALL THE isAuthorized() METHOD**
|
||||||
oauth2.Client? client;
|
oauth2.Client? client;
|
||||||
|
|
||||||
DateTime? lastRefreshedAt;
|
DateTime? lastRefreshedAt;
|
||||||
|
|
||||||
Future<bool> pickClient() async {
|
Future<bool> pickClient() async {
|
||||||
@ -83,9 +83,9 @@ class AuthProvider {
|
|||||||
|
|
||||||
Future<void> refreshToken() async {
|
Future<void> refreshToken() async {
|
||||||
if (client != null) {
|
if (client != null) {
|
||||||
final credentials = await client?.credentials.refresh(
|
final credentials = await client!.credentials.refresh(
|
||||||
identifier: clientId, secret: clientSecret, basicAuth: false);
|
identifier: clientId, secret: clientSecret, basicAuth: false);
|
||||||
client = oauth2.Client(credentials!,
|
client = oauth2.Client(credentials,
|
||||||
identifier: clientId, secret: clientSecret);
|
identifier: clientId, secret: clientSecret);
|
||||||
storage.write(key: storageKey, value: credentials.toJson());
|
storage.write(key: storageKey, value: credentials.toJson());
|
||||||
}
|
}
|
||||||
@ -106,14 +106,15 @@ class AuthProvider {
|
|||||||
Future<bool> isAuthorized() async {
|
Future<bool> isAuthorized() async {
|
||||||
const storage = FlutterSecureStorage();
|
const storage = FlutterSecureStorage();
|
||||||
if (await storage.containsKey(key: storageKey)) {
|
if (await storage.containsKey(key: storageKey)) {
|
||||||
if (client != null) {
|
if (client == null) {
|
||||||
if (lastRefreshedAt == null ||
|
await pickClient();
|
||||||
lastRefreshedAt!
|
}
|
||||||
.add(const Duration(minutes: 3))
|
if (lastRefreshedAt == null ||
|
||||||
.isBefore(DateTime.now())) {
|
DateTime.now()
|
||||||
await refreshToken();
|
.subtract(const Duration(minutes: 3))
|
||||||
lastRefreshedAt = DateTime.now();
|
.isAfter(lastRefreshedAt!)) {
|
||||||
}
|
await refreshToken();
|
||||||
|
lastRefreshedAt = DateTime.now();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:solian/models/post.dart';
|
import 'package:solian/models/post.dart';
|
||||||
import 'package:solian/screens/account.dart';
|
import 'package:solian/screens/account.dart';
|
||||||
|
import 'package:solian/screens/chat/chat.dart';
|
||||||
|
import 'package:solian/screens/chat/index.dart';
|
||||||
import 'package:solian/screens/explore.dart';
|
import 'package:solian/screens/explore.dart';
|
||||||
import 'package:solian/screens/posts/comment_editor.dart';
|
import 'package:solian/screens/posts/comment_editor.dart';
|
||||||
import 'package:solian/screens/posts/moment_editor.dart';
|
import 'package:solian/screens/posts/moment_editor.dart';
|
||||||
@ -13,6 +15,16 @@ final router = GoRouter(
|
|||||||
name: 'explore',
|
name: 'explore',
|
||||||
builder: (context, state) => const ExploreScreen(),
|
builder: (context, state) => const ExploreScreen(),
|
||||||
),
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/chat',
|
||||||
|
name: 'chat',
|
||||||
|
builder: (context, state) => const ChatIndexScreen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/chat/:channel',
|
||||||
|
name: 'chat.channel',
|
||||||
|
builder: (context, state) => ChatScreen(alias: state.pathParameters['channel'] as String),
|
||||||
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/account',
|
path: '/account',
|
||||||
name: 'account',
|
name: 'account',
|
||||||
|
129
lib/screens/chat/chat.dart
Normal file
129
lib/screens/chat/chat.dart
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
||||||
|
import 'package:provider/provider.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/utils/service_url.dart';
|
||||||
|
import 'package:solian/widgets/chat/message.dart';
|
||||||
|
import 'package:solian/widgets/chat/message_editor.dart';
|
||||||
|
import 'package:solian/widgets/indent_wrapper.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
|
class ChatScreen extends StatefulWidget {
|
||||||
|
final String alias;
|
||||||
|
|
||||||
|
const ChatScreen({super.key, required this.alias});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ChatScreen> createState() => _ChatScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ChatScreenState extends State<ChatScreen> {
|
||||||
|
Channel? _channelMeta;
|
||||||
|
|
||||||
|
final PagingController<int, Message> _pagingController = PagingController(firstPageKey: 0);
|
||||||
|
|
||||||
|
final http.Client _client = http.Client();
|
||||||
|
|
||||||
|
Future<void> fetchMetadata(BuildContext context) 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));
|
||||||
|
} else {
|
||||||
|
var message = utf8.decode(res.bodyBytes);
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text("Something went wrong... $message")),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> fetchMessages(int pageKey, BuildContext context) async {
|
||||||
|
final auth = context.read<AuthProvider>();
|
||||||
|
if (!await auth.isAuthorized()) return;
|
||||||
|
|
||||||
|
final offset = pageKey;
|
||||||
|
const take = 5;
|
||||||
|
|
||||||
|
var uri = getRequestUri(
|
||||||
|
'messaging',
|
||||||
|
'/api/channels/${widget.alias}/messages?take=$take&offset=$offset',
|
||||||
|
);
|
||||||
|
|
||||||
|
var res = await auth.client!.get(uri);
|
||||||
|
if (res.statusCode == 200) {
|
||||||
|
final result = PaginationResult.fromJson(jsonDecode(utf8.decode(res.bodyBytes)));
|
||||||
|
final items = result.data?.map((x) => Message.fromJson(x)).toList() ?? List.empty();
|
||||||
|
final isLastPage = (result.count - pageKey) < take;
|
||||||
|
if (isLastPage || result.data == null) {
|
||||||
|
_pagingController.appendLastPage(items);
|
||||||
|
} else {
|
||||||
|
final nextPageKey = pageKey + items.length;
|
||||||
|
_pagingController.appendPage(items, nextPageKey);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_pagingController.error = utf8.decode(res.bodyBytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool getMessageMergeable(Message? a, Message? b) {
|
||||||
|
if (a == null || b == null) return false;
|
||||||
|
if (a.senderId != b.senderId) return false;
|
||||||
|
return a.createdAt.difference(b.createdAt).inMinutes <= 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
Future.delayed(Duration.zero, () {
|
||||||
|
fetchMetadata(context);
|
||||||
|
});
|
||||||
|
|
||||||
|
_pagingController.addPageRequestListener((pageKey) => fetchMessages(pageKey, context));
|
||||||
|
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return IndentWrapper(
|
||||||
|
hideDrawer: true,
|
||||||
|
title: _channelMeta?.name ?? "Loading...",
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: PagedListView<int, Message>(
|
||||||
|
pagingController: _pagingController,
|
||||||
|
builderDelegate: PagedChildBuilderDelegate<Message>(
|
||||||
|
itemBuilder: (context, item, index) {
|
||||||
|
bool isMerged = false, hasMerged = false;
|
||||||
|
if (index > 0) {
|
||||||
|
isMerged = getMessageMergeable(_pagingController.itemList?[index - 1], item);
|
||||||
|
}
|
||||||
|
if (index + 1 < (_pagingController.itemList?.length ?? 0)) {
|
||||||
|
hasMerged = getMessageMergeable(item, _pagingController.itemList?[index + 1]);
|
||||||
|
}
|
||||||
|
return Container(
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
top: !isMerged ? 8 : 0,
|
||||||
|
bottom: !hasMerged ? 8 : 0,
|
||||||
|
left: 12,
|
||||||
|
right: 12,
|
||||||
|
),
|
||||||
|
child: ChatMessage(item: item, underMerged: isMerged),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ChatMessageEditor(channel: widget.alias),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
82
lib/screens/chat/index.dart
Normal file
82
lib/screens/chat/index.dart
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
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/utils/service_url.dart';
|
||||||
|
import 'package:solian/widgets/indent_wrapper.dart';
|
||||||
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
|
||||||
|
class ChatIndexScreen extends StatefulWidget {
|
||||||
|
const ChatIndexScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ChatIndexScreen> createState() => _ChatIndexScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ChatIndexScreenState extends State<ChatIndexScreen> {
|
||||||
|
List<Channel> _channels = List.empty();
|
||||||
|
|
||||||
|
Future<void> fetchChannels(BuildContext context) async {
|
||||||
|
final auth = context.read<AuthProvider>();
|
||||||
|
if (!await auth.isAuthorized()) return;
|
||||||
|
|
||||||
|
var uri = getRequestUri('messaging', '/api/channels/me/available');
|
||||||
|
|
||||||
|
var res = await auth.client!.get(uri);
|
||||||
|
if (res.statusCode == 200) {
|
||||||
|
final result = jsonDecode(utf8.decode(res.bodyBytes)) as List<dynamic>;
|
||||||
|
setState(() {
|
||||||
|
_channels = result.map((x) => Channel.fromJson(x)).toList();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
var message = utf8.decode(res.bodyBytes);
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text("Something went wrong... $message")),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
Future.delayed(Duration.zero, () {
|
||||||
|
fetchChannels(context);
|
||||||
|
});
|
||||||
|
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return IndentWrapper(
|
||||||
|
title: AppLocalizations.of(context)!.chat,
|
||||||
|
child: RefreshIndicator(
|
||||||
|
onRefresh: () => fetchChannels(context),
|
||||||
|
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: () {
|
||||||
|
router.pushNamed(
|
||||||
|
'chat.channel',
|
||||||
|
pathParameters: {
|
||||||
|
'channel': element.alias,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -19,8 +19,7 @@ class ExploreScreen extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _ExploreScreenState extends State<ExploreScreen> {
|
class _ExploreScreenState extends State<ExploreScreen> {
|
||||||
final PagingController<int, Post> _pagingController =
|
final PagingController<int, Post> _pagingController = PagingController(firstPageKey: 0);
|
||||||
PagingController(firstPageKey: 0);
|
|
||||||
|
|
||||||
final http.Client _client = http.Client();
|
final http.Client _client = http.Client();
|
||||||
|
|
||||||
@ -28,15 +27,12 @@ class _ExploreScreenState extends State<ExploreScreen> {
|
|||||||
final offset = pageKey;
|
final offset = pageKey;
|
||||||
const take = 5;
|
const take = 5;
|
||||||
|
|
||||||
var uri =
|
var uri = getRequestUri('interactive', '/api/feed?take=$take&offset=$offset');
|
||||||
getRequestUri('interactive', '/api/feed?take=$take&offset=$offset');
|
|
||||||
|
|
||||||
var res = await _client.get(uri);
|
var res = await _client.get(uri);
|
||||||
if (res.statusCode == 200) {
|
if (res.statusCode == 200) {
|
||||||
final result =
|
final result = PaginationResult.fromJson(jsonDecode(utf8.decode(res.bodyBytes)));
|
||||||
PaginationResult.fromJson(jsonDecode(utf8.decode(res.bodyBytes)));
|
final items = result.data?.map((x) => Post.fromJson(x)).toList() ?? List.empty();
|
||||||
final items =
|
|
||||||
result.data?.map((x) => Post.fromJson(x)).toList() ?? List.empty();
|
|
||||||
final isLastPage = (result.count - pageKey) < take;
|
final isLastPage = (result.count - pageKey) < take;
|
||||||
if (isLastPage || result.data == null) {
|
if (isLastPage || result.data == null) {
|
||||||
_pagingController.appendLastPage(items);
|
_pagingController.appendLastPage(items);
|
||||||
@ -77,8 +73,7 @@ class _ExploreScreenState extends State<ExploreScreen> {
|
|||||||
constraints: const BoxConstraints(maxWidth: 640),
|
constraints: const BoxConstraints(maxWidth: 640),
|
||||||
child: PagedListView<int, Post>.separated(
|
child: PagedListView<int, Post>.separated(
|
||||||
pagingController: _pagingController,
|
pagingController: _pagingController,
|
||||||
separatorBuilder: (context, index) =>
|
separatorBuilder: (context, index) => const Divider(thickness: 0.3),
|
||||||
const Divider(thickness: 0.3),
|
|
||||||
builderDelegate: PagedChildBuilderDelegate<Post>(
|
builderDelegate: PagedChildBuilderDelegate<Post>(
|
||||||
itemBuilder: (context, item, index) => GestureDetector(
|
itemBuilder: (context, item, index) => GestureDetector(
|
||||||
child: PostItem(
|
child: PostItem(
|
||||||
|
@ -12,14 +12,14 @@ import 'package:solian/widgets/indent_wrapper.dart';
|
|||||||
import 'package:solian/widgets/posts/attachment_editor.dart';
|
import 'package:solian/widgets/posts/attachment_editor.dart';
|
||||||
|
|
||||||
class CommentPostArguments {
|
class CommentPostArguments {
|
||||||
final Post related;
|
final Post? related;
|
||||||
final Post? editing;
|
final Post? editing;
|
||||||
|
|
||||||
CommentPostArguments({required this.related, this.editing});
|
CommentPostArguments({this.related, this.editing});
|
||||||
}
|
}
|
||||||
|
|
||||||
class CommentEditorScreen extends StatefulWidget {
|
class CommentEditorScreen extends StatefulWidget {
|
||||||
final Post related;
|
final Post? related;
|
||||||
final Post? editing;
|
final Post? editing;
|
||||||
|
|
||||||
const CommentEditorScreen({super.key, required this.related, this.editing});
|
const CommentEditorScreen({super.key, required this.related, this.editing});
|
||||||
@ -50,9 +50,10 @@ class _CommentEditorScreenState extends State<CommentEditorScreen> {
|
|||||||
final auth = context.read<AuthProvider>();
|
final auth = context.read<AuthProvider>();
|
||||||
if (!await auth.isAuthorized()) return;
|
if (!await auth.isAuthorized()) return;
|
||||||
|
|
||||||
final relatedDataset = '${widget.related.modelType}s';
|
final alias = widget.related?.alias ?? 'not-found';
|
||||||
|
final relatedDataset = '${widget.related?.modelType ?? 'comment'}s';
|
||||||
final uri = widget.editing == null
|
final uri = widget.editing == null
|
||||||
? getRequestUri('interactive', '/api/p/$relatedDataset/${widget.related.alias}/comments')
|
? getRequestUri('interactive', '/api/p/$relatedDataset/$alias/comments')
|
||||||
: getRequestUri('interactive', '/api/p/comments/${widget.editing!.id}');
|
: 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);
|
||||||
@ -78,6 +79,12 @@ class _CommentEditorScreenState extends State<CommentEditorScreen> {
|
|||||||
setState(() => _isSubmitting = false);
|
setState(() => _isSubmitting = false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void cancelEditing() {
|
||||||
|
if (Navigator.canPop(context)) {
|
||||||
|
Navigator.pop(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
if (widget.editing != null) {
|
if (widget.editing != null) {
|
||||||
@ -93,6 +100,20 @@ class _CommentEditorScreenState extends State<CommentEditorScreen> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final auth = context.read<AuthProvider>();
|
final auth = context.read<AuthProvider>();
|
||||||
|
|
||||||
|
final editingBanner = MaterialBanner(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 20),
|
||||||
|
leading: const Icon(Icons.edit_note),
|
||||||
|
backgroundColor: const Color(0xFFE0E0E0),
|
||||||
|
dividerColor: const Color.fromARGB(1, 0, 0, 0),
|
||||||
|
content: Text(AppLocalizations.of(context)!.postEditNotify),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
child: Text(AppLocalizations.of(context)!.cancel),
|
||||||
|
onPressed: () => cancelEditing(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
return IndentWrapper(
|
return IndentWrapper(
|
||||||
hideDrawer: true,
|
hideDrawer: true,
|
||||||
title: AppLocalizations.of(context)!.newComment,
|
title: AppLocalizations.of(context)!.newComment,
|
||||||
@ -145,6 +166,7 @@ class _CommentEditorScreenState extends State<CommentEditorScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
widget.editing != null ? editingBanner : Container(),
|
||||||
Container(
|
Container(
|
||||||
decoration: const BoxDecoration(
|
decoration: const BoxDecoration(
|
||||||
border: Border(
|
border: Border(
|
||||||
|
@ -69,6 +69,12 @@ class _MomentEditorScreenState extends State<MomentEditorScreen> {
|
|||||||
setState(() => _isSubmitting = false);
|
setState(() => _isSubmitting = false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void cancelEditing() {
|
||||||
|
if (Navigator.canPop(context)) {
|
||||||
|
Navigator.pop(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
if (widget.editing != null) {
|
if (widget.editing != null) {
|
||||||
@ -84,6 +90,20 @@ class _MomentEditorScreenState extends State<MomentEditorScreen> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final auth = context.read<AuthProvider>();
|
final auth = context.read<AuthProvider>();
|
||||||
|
|
||||||
|
final editingBanner = MaterialBanner(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 20),
|
||||||
|
leading: const Icon(Icons.edit_note),
|
||||||
|
backgroundColor: const Color(0xFFE0E0E0),
|
||||||
|
dividerColor: const Color.fromARGB(1, 0, 0, 0),
|
||||||
|
content: Text(AppLocalizations.of(context)!.postEditNotify),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
child: Text(AppLocalizations.of(context)!.cancel),
|
||||||
|
onPressed: () => cancelEditing(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
return IndentWrapper(
|
return IndentWrapper(
|
||||||
hideDrawer: true,
|
hideDrawer: true,
|
||||||
title: AppLocalizations.of(context)!.newMoment,
|
title: AppLocalizations.of(context)!.newMoment,
|
||||||
@ -121,8 +141,7 @@ class _MomentEditorScreenState extends State<MomentEditorScreen> {
|
|||||||
const Divider(thickness: 0.3),
|
const Divider(thickness: 0.3),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Container(
|
child: Container(
|
||||||
padding:
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
|
||||||
child: TextField(
|
child: TextField(
|
||||||
maxLines: null,
|
maxLines: null,
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
@ -130,12 +149,12 @@ class _MomentEditorScreenState extends State<MomentEditorScreen> {
|
|||||||
keyboardType: TextInputType.multiline,
|
keyboardType: TextInputType.multiline,
|
||||||
controller: _textController,
|
controller: _textController,
|
||||||
decoration: InputDecoration.collapsed(
|
decoration: InputDecoration.collapsed(
|
||||||
hintText:
|
hintText: AppLocalizations.of(context)!.postContentPlaceholder,
|
||||||
AppLocalizations.of(context)!.postContentPlaceholder,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
widget.editing != null ? editingBanner : Container(),
|
||||||
Container(
|
Container(
|
||||||
decoration: const BoxDecoration(
|
decoration: const BoxDecoration(
|
||||||
border: Border(
|
border: Border(
|
||||||
|
@ -44,6 +44,7 @@ class _PostScreenState extends State<PostScreen> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return IndentWrapper(
|
return IndentWrapper(
|
||||||
|
noSafeArea: true,
|
||||||
hideDrawer: true,
|
hideDrawer: true,
|
||||||
title: AppLocalizations.of(context)!.post,
|
title: AppLocalizations.of(context)!.post,
|
||||||
child: FutureBuilder(
|
child: FutureBuilder(
|
||||||
|
7
lib/utils/video_player.dart
Normal file
7
lib/utils/video_player.dart
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:media_kit/media_kit.dart';
|
||||||
|
|
||||||
|
void initVideo() {
|
||||||
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
MediaKit.ensureInitialized();
|
||||||
|
}
|
28
lib/widgets/chat/content.dart
Normal file
28
lib/widgets/chat/content.dart
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||||
|
import 'package:solian/models/message.dart';
|
||||||
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
|
|
||||||
|
class ChatMessageContent extends StatelessWidget {
|
||||||
|
final Message item;
|
||||||
|
|
||||||
|
const ChatMessageContent({super.key, required this.item});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Markdown(
|
||||||
|
selectable: true,
|
||||||
|
data: item.content,
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
padding: const EdgeInsets.all(0),
|
||||||
|
onTapLink: (text, href, title) async {
|
||||||
|
if (href == null) return;
|
||||||
|
await launchUrlString(
|
||||||
|
href,
|
||||||
|
mode: LaunchMode.externalApplication,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
73
lib/widgets/chat/message.dart
Normal file
73
lib/widgets/chat/message.dart
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:solian/models/message.dart';
|
||||||
|
import 'package:solian/widgets/chat/content.dart';
|
||||||
|
import 'package:solian/widgets/posts/content/attachment.dart';
|
||||||
|
import 'package:timeago/timeago.dart' as timeago;
|
||||||
|
|
||||||
|
class ChatMessage extends StatelessWidget {
|
||||||
|
final Message item;
|
||||||
|
final bool underMerged;
|
||||||
|
|
||||||
|
const ChatMessage({super.key, required this.item, required this.underMerged});
|
||||||
|
|
||||||
|
Widget renderAttachment() {
|
||||||
|
if (item.attachments != null && item.attachments!.isNotEmpty) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 6),
|
||||||
|
child: AttachmentList(items: item.attachments!, provider: 'messaging'),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Container();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final contentPart = Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 12, right: 12, top: 2),
|
||||||
|
child: ChatMessageContent(item: item),
|
||||||
|
);
|
||||||
|
|
||||||
|
final userinfoPart = Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
item.sender.account.nick,
|
||||||
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
Text(timeago.format(item.createdAt))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (underMerged) {
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
const SizedBox(width: 40),
|
||||||
|
Expanded(child: contentPart),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
CircleAvatar(
|
||||||
|
backgroundImage: NetworkImage(item.sender.account.avatar),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
userinfoPart,
|
||||||
|
contentPart,
|
||||||
|
renderAttachment(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
94
lib/widgets/chat/message_editor.dart
Normal file
94
lib/widgets/chat/message_editor.dart
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
import 'package:http/http.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:solian/models/message.dart';
|
||||||
|
import 'package:solian/providers/auth.dart';
|
||||||
|
import 'package:solian/utils/service_url.dart';
|
||||||
|
|
||||||
|
class ChatMessageEditor extends StatefulWidget {
|
||||||
|
final String channel;
|
||||||
|
final Message? editing;
|
||||||
|
|
||||||
|
const ChatMessageEditor({super.key, required this.channel, this.editing});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ChatMessageEditor> createState() => _ChatMessageEditorState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ChatMessageEditorState extends State<ChatMessageEditor> {
|
||||||
|
final _textController = TextEditingController();
|
||||||
|
|
||||||
|
bool _isSubmitting = false;
|
||||||
|
|
||||||
|
Future<void> sendMessage(BuildContext context) async {
|
||||||
|
if (_isSubmitting) return;
|
||||||
|
|
||||||
|
final auth = context.read<AuthProvider>();
|
||||||
|
if (!await auth.isAuthorized()) return;
|
||||||
|
|
||||||
|
final uri = widget.editing == null
|
||||||
|
? 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);
|
||||||
|
req.headers['Content-Type'] = 'application/json';
|
||||||
|
req.body = jsonEncode(<String, dynamic>{
|
||||||
|
'content': _textController.value.text,
|
||||||
|
});
|
||||||
|
|
||||||
|
setState(() => _isSubmitting = true);
|
||||||
|
var res = await Response.fromStream(await auth.client!.send(req));
|
||||||
|
if (res.statusCode != 200) {
|
||||||
|
var message = utf8.decode(res.bodyBytes);
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text("Something went wrong... $message")),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
setState(() => _isSubmitting = false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset() {
|
||||||
|
_textController.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
height: 56,
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
border: Border(
|
||||||
|
top: BorderSide(width: 0.3, color: Color(0xffdedede)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: TextField(
|
||||||
|
controller: _textController,
|
||||||
|
maxLines: null,
|
||||||
|
autofocus: true,
|
||||||
|
autocorrect: true,
|
||||||
|
keyboardType: TextInputType.text,
|
||||||
|
decoration: InputDecoration.collapsed(
|
||||||
|
hintText: AppLocalizations.of(context)!.chatMessagePlaceholder,
|
||||||
|
),
|
||||||
|
onSubmitted: (_) => sendMessage(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
style: TextButton.styleFrom(shape: const CircleBorder(), padding: const EdgeInsets.all(4)),
|
||||||
|
onPressed: !_isSubmitting ? () => sendMessage(context) : null,
|
||||||
|
child: const Icon(Icons.send),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -3,18 +3,29 @@ import 'package:solian/widgets/navigation_drawer.dart';
|
|||||||
|
|
||||||
class LayoutWrapper extends StatelessWidget {
|
class LayoutWrapper extends StatelessWidget {
|
||||||
final Widget? child;
|
final Widget? child;
|
||||||
|
final Widget? floatingActionButton;
|
||||||
|
final List<Widget>? appBarActions;
|
||||||
|
final bool? noSafeArea;
|
||||||
final String title;
|
final String title;
|
||||||
|
|
||||||
const LayoutWrapper({super.key, this.child, required this.title});
|
const LayoutWrapper({
|
||||||
|
super.key,
|
||||||
|
this.child,
|
||||||
|
required this.title,
|
||||||
|
this.floatingActionButton,
|
||||||
|
this.appBarActions,
|
||||||
|
this.noSafeArea,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final content = child ?? Container();
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
appBar: AppBar(title: Text(title), actions: appBarActions),
|
||||||
|
floatingActionButton: floatingActionButton,
|
||||||
drawer: const SolianNavigationDrawer(),
|
drawer: const SolianNavigationDrawer(),
|
||||||
appBar: AppBar(title: Text(title)),
|
body: (noSafeArea ?? false) ? content : SafeArea(child: content),
|
||||||
body: SafeArea(
|
|
||||||
child: child ?? Container(),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,19 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:solian/widgets/common_wrapper.dart';
|
||||||
import 'package:solian/widgets/navigation_drawer.dart';
|
import 'package:solian/widgets/navigation_drawer.dart';
|
||||||
|
|
||||||
class IndentWrapper extends StatelessWidget {
|
class IndentWrapper extends LayoutWrapper {
|
||||||
final Widget? child;
|
|
||||||
final Widget? floatingActionButton;
|
|
||||||
final List<Widget>? appBarActions;
|
|
||||||
final bool? hideDrawer;
|
final bool? hideDrawer;
|
||||||
final bool? noSafeArea;
|
|
||||||
final String title;
|
|
||||||
|
|
||||||
const IndentWrapper({super.key, this.child, required this.title, this.floatingActionButton, this.appBarActions, this.hideDrawer, this.noSafeArea});
|
const IndentWrapper({
|
||||||
|
super.key,
|
||||||
|
super.child,
|
||||||
|
required super.title,
|
||||||
|
super.floatingActionButton,
|
||||||
|
super.appBarActions,
|
||||||
|
this.hideDrawer,
|
||||||
|
super.noSafeArea,
|
||||||
|
}) : super();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -41,6 +41,13 @@ class _SolianNavigationDrawerState extends State<SolianNavigationDrawer> {
|
|||||||
),
|
),
|
||||||
"explore",
|
"explore",
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
NavigationDrawerDestination(
|
||||||
|
icon: const Icon(Icons.send),
|
||||||
|
label: Text(AppLocalizations.of(context)!.chat),
|
||||||
|
),
|
||||||
|
"chat",
|
||||||
|
),
|
||||||
(
|
(
|
||||||
NavigationDrawerDestination(
|
NavigationDrawerDestination(
|
||||||
icon: const Icon(Icons.account_circle),
|
icon: const Icon(Icons.account_circle),
|
||||||
|
@ -3,7 +3,6 @@ import 'dart:io';
|
|||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
|
|
||||||
import 'package:crypto/crypto.dart';
|
import 'package:crypto/crypto.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:http/http.dart';
|
import 'package:http/http.dart';
|
||||||
import 'package:image_picker/image_picker.dart';
|
import 'package:image_picker/image_picker.dart';
|
||||||
@ -17,8 +16,7 @@ class AttachmentEditor extends StatefulWidget {
|
|||||||
final List<Attachment> current;
|
final List<Attachment> current;
|
||||||
final void Function(List<Attachment> data) onUpdate;
|
final void Function(List<Attachment> data) onUpdate;
|
||||||
|
|
||||||
const AttachmentEditor(
|
const AttachmentEditor({super.key, required this.current, required this.onUpdate});
|
||||||
{super.key, required this.current, required this.onUpdate});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<AttachmentEditor> createState() => _AttachmentEditorState();
|
State<AttachmentEditor> createState() => _AttachmentEditorState();
|
||||||
@ -27,7 +25,7 @@ class AttachmentEditor extends StatefulWidget {
|
|||||||
class _AttachmentEditorState extends State<AttachmentEditor> {
|
class _AttachmentEditorState extends State<AttachmentEditor> {
|
||||||
final _imagePicker = ImagePicker();
|
final _imagePicker = ImagePicker();
|
||||||
|
|
||||||
bool isSubmitting = false;
|
bool _isSubmitting = false;
|
||||||
|
|
||||||
List<Attachment> _attachments = List.empty(growable: true);
|
List<Attachment> _attachments = List.empty(growable: true);
|
||||||
|
|
||||||
@ -35,19 +33,22 @@ class _AttachmentEditorState extends State<AttachmentEditor> {
|
|||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AttachmentEditorMethodPopup(
|
builder: (context) => AttachmentEditorMethodPopup(
|
||||||
pickImage: () => pickImageToUpload(context),
|
pickImage: () => pickImageToUpload(context, ImageSource.gallery),
|
||||||
|
takeImage: () => pickImageToUpload(context, ImageSource.camera),
|
||||||
|
pickVideo: () => pickVideoToUpload(context, ImageSource.gallery),
|
||||||
|
takeVideo: () => pickVideoToUpload(context, ImageSource.camera),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> pickImageToUpload(BuildContext context) async {
|
Future<void> pickImageToUpload(BuildContext context, ImageSource source) async {
|
||||||
final auth = context.read<AuthProvider>();
|
final auth = context.read<AuthProvider>();
|
||||||
if (!await auth.isAuthorized()) return;
|
if (!await auth.isAuthorized()) return;
|
||||||
|
|
||||||
final image = await _imagePicker.pickImage(source: ImageSource.gallery);
|
final image = await _imagePicker.pickImage(source: source);
|
||||||
if (image == null) return;
|
if (image == null) return;
|
||||||
|
|
||||||
setState(() => isSubmitting = true);
|
setState(() => _isSubmitting = true);
|
||||||
|
|
||||||
final file = File(image.path);
|
final file = File(image.path);
|
||||||
final hashcode = await calculateSha256(file);
|
final hashcode = await calculateSha256(file);
|
||||||
@ -63,7 +64,34 @@ class _AttachmentEditorState extends State<AttachmentEditor> {
|
|||||||
SnackBar(content: Text("Something went wrong... $err")),
|
SnackBar(content: Text("Something went wrong... $err")),
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
setState(() => isSubmitting = false);
|
setState(() => _isSubmitting = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> pickVideoToUpload(BuildContext context, ImageSource source) async {
|
||||||
|
final auth = context.read<AuthProvider>();
|
||||||
|
if (!await auth.isAuthorized()) return;
|
||||||
|
|
||||||
|
final image = await _imagePicker.pickVideo(source: source);
|
||||||
|
if (image == null) return;
|
||||||
|
|
||||||
|
setState(() => _isSubmitting = true);
|
||||||
|
|
||||||
|
final file = File(image.path);
|
||||||
|
final hashcode = await calculateSha256(file);
|
||||||
|
|
||||||
|
if (Navigator.canPop(context)) {
|
||||||
|
Navigator.pop(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await uploadAttachment(file, hashcode);
|
||||||
|
} catch (err) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text("Something went wrong... $err")),
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
setState(() => _isSubmitting = false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,7 +105,6 @@ class _AttachmentEditorState extends State<AttachmentEditor> {
|
|||||||
req.fields['hashcode'] = hashcode;
|
req.fields['hashcode'] = hashcode;
|
||||||
|
|
||||||
var res = await auth.client!.send(req);
|
var res = await auth.client!.send(req);
|
||||||
print(res);
|
|
||||||
if (res.statusCode == 200) {
|
if (res.statusCode == 200) {
|
||||||
var result = Attachment.fromJson(
|
var result = Attachment.fromJson(
|
||||||
jsonDecode(utf8.decode(await res.stream.toBytes()))["info"],
|
jsonDecode(utf8.decode(await res.stream.toBytes()))["info"],
|
||||||
@ -89,8 +116,7 @@ class _AttachmentEditorState extends State<AttachmentEditor> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> disposeAttachment(
|
Future<void> disposeAttachment(BuildContext context, Attachment item, int index) async {
|
||||||
BuildContext context, Attachment item, int index) async {
|
|
||||||
final auth = context.read<AuthProvider>();
|
final auth = context.read<AuthProvider>();
|
||||||
|
|
||||||
final req = MultipartRequest(
|
final req = MultipartRequest(
|
||||||
@ -98,7 +124,7 @@ class _AttachmentEditorState extends State<AttachmentEditor> {
|
|||||||
getRequestUri('interactive', '/api/attachments/${item.id}'),
|
getRequestUri('interactive', '/api/attachments/${item.id}'),
|
||||||
);
|
);
|
||||||
|
|
||||||
setState(() => isSubmitting = true);
|
setState(() => _isSubmitting = true);
|
||||||
var res = await auth.client!.send(req);
|
var res = await auth.client!.send(req);
|
||||||
if (res.statusCode == 200) {
|
if (res.statusCode == 200) {
|
||||||
setState(() => _attachments.removeAt(index));
|
setState(() => _attachments.removeAt(index));
|
||||||
@ -109,7 +135,7 @@ class _AttachmentEditorState extends State<AttachmentEditor> {
|
|||||||
SnackBar(content: Text("Something went wrong... $err")),
|
SnackBar(content: Text("Something went wrong... $err")),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
setState(() => isSubmitting = false);
|
setState(() => _isSubmitting = false);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> calculateSha256(File file) async {
|
Future<String> calculateSha256(File file) async {
|
||||||
@ -139,17 +165,7 @@ class _AttachmentEditorState extends State<AttachmentEditor> {
|
|||||||
if (bytes == 0) return '0 Bytes';
|
if (bytes == 0) return '0 Bytes';
|
||||||
const k = 1024;
|
const k = 1024;
|
||||||
final dm = decimals < 0 ? 0 : decimals;
|
final dm = decimals < 0 ? 0 : decimals;
|
||||||
final sizes = [
|
final sizes = ['Bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
|
||||||
'Bytes',
|
|
||||||
'KiB',
|
|
||||||
'MiB',
|
|
||||||
'GiB',
|
|
||||||
'TiB',
|
|
||||||
'PiB',
|
|
||||||
'EiB',
|
|
||||||
'ZiB',
|
|
||||||
'YiB'
|
|
||||||
];
|
|
||||||
final i = (math.log(bytes) / math.log(k)).floor().toInt();
|
final i = (math.log(bytes) / math.log(k)).floor().toInt();
|
||||||
return '${(bytes / math.pow(k, i)).toStringAsFixed(dm)} ${sizes[i]}';
|
return '${(bytes / math.pow(k, i)).toStringAsFixed(dm)} ${sizes[i]}';
|
||||||
}
|
}
|
||||||
@ -186,9 +202,7 @@ class _AttachmentEditorState extends State<AttachmentEditor> {
|
|||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.hasData && snapshot.data == true) {
|
if (snapshot.hasData && snapshot.data == true) {
|
||||||
return TextButton(
|
return TextButton(
|
||||||
onPressed: isSubmitting
|
onPressed: _isSubmitting ? null : () => viewAttachMethods(context),
|
||||||
? null
|
|
||||||
: () => viewAttachMethods(context),
|
|
||||||
style: TextButton.styleFrom(shape: const CircleBorder()),
|
style: TextButton.styleFrom(shape: const CircleBorder()),
|
||||||
child: const Icon(Icons.add_circle),
|
child: const Icon(Icons.add_circle),
|
||||||
);
|
);
|
||||||
@ -200,7 +214,7 @@ class _AttachmentEditorState extends State<AttachmentEditor> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
isSubmitting ? const LinearProgressIndicator() : Container(),
|
_isSubmitting ? const LinearProgressIndicator() : Container(),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ListView.separated(
|
child: ListView.separated(
|
||||||
itemCount: _attachments.length,
|
itemCount: _attachments.length,
|
||||||
@ -232,8 +246,7 @@ class _AttachmentEditorState extends State<AttachmentEditor> {
|
|||||||
foregroundColor: Colors.red,
|
foregroundColor: Colors.red,
|
||||||
),
|
),
|
||||||
child: const Icon(Icons.delete),
|
child: const Icon(Icons.delete),
|
||||||
onPressed: () =>
|
onPressed: () => disposeAttachment(context, element, index),
|
||||||
disposeAttachment(context, element, index),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -249,8 +262,17 @@ class _AttachmentEditorState extends State<AttachmentEditor> {
|
|||||||
|
|
||||||
class AttachmentEditorMethodPopup extends StatelessWidget {
|
class AttachmentEditorMethodPopup extends StatelessWidget {
|
||||||
final Function pickImage;
|
final Function pickImage;
|
||||||
|
final Function takeImage;
|
||||||
|
final Function pickVideo;
|
||||||
|
final Function takeVideo;
|
||||||
|
|
||||||
const AttachmentEditorMethodPopup({super.key, required this.pickImage});
|
const AttachmentEditorMethodPopup({
|
||||||
|
super.key,
|
||||||
|
required this.pickImage,
|
||||||
|
required this.takeImage,
|
||||||
|
required this.pickVideo,
|
||||||
|
required this.takeVideo,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -282,14 +304,58 @@ class AttachmentEditorMethodPopup extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.add_photo_alternate,
|
const Icon(Icons.add_photo_alternate, color: Colors.indigo),
|
||||||
color: Colors.indigo),
|
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(AppLocalizations.of(context)!.pickPhoto),
|
Text(AppLocalizations.of(context)!.pickPhoto),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
InkWell(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
onTap: () => takeImage(),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.camera_alt, color: Colors.indigo),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(AppLocalizations.of(context)!.takePhoto),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
InkWell(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
onTap: () => pickVideo(),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.camera, color: Colors.indigo),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(AppLocalizations.of(context)!.pickVideo),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
InkWell(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
onTap: () => takeVideo(),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.video_call, color: Colors.indigo),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(AppLocalizations.of(context)!.takeVideo),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -1,29 +1,28 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class AttachmentScreen extends StatelessWidget {
|
class AttachmentScreen extends StatelessWidget {
|
||||||
final String tag;
|
|
||||||
final String url;
|
final String url;
|
||||||
|
final String? tag;
|
||||||
|
|
||||||
const AttachmentScreen({super.key, required this.tag, required this.url});
|
const AttachmentScreen({super.key, this.tag, required this.url});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final image = SizedBox(
|
||||||
|
height: MediaQuery.of(context).size.height,
|
||||||
|
width: MediaQuery.of(context).size.width,
|
||||||
|
child: InteractiveViewer(
|
||||||
|
boundaryMargin: const EdgeInsets.all(128),
|
||||||
|
minScale: 0.1,
|
||||||
|
maxScale: 16.0,
|
||||||
|
child: Image.network(url, fit: BoxFit.contain),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: GestureDetector(
|
body: GestureDetector(
|
||||||
child: Center(
|
child: Center(
|
||||||
child: SizedBox(
|
child: tag != null ? Hero(tag: tag!, child: image) : image,
|
||||||
height: MediaQuery.of(context).size.height,
|
|
||||||
width: MediaQuery.of(context).size.width,
|
|
||||||
child: InteractiveViewer(
|
|
||||||
boundaryMargin: const EdgeInsets.all(128),
|
|
||||||
minScale: 0.1,
|
|
||||||
maxScale: 16.0,
|
|
||||||
child: Hero(
|
|
||||||
tag: tag,
|
|
||||||
child: Image.network(url, fit: BoxFit.contain),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
|
@ -3,6 +3,7 @@ import 'package:flutter_markdown/flutter_markdown.dart';
|
|||||||
import 'package:solian/models/post.dart';
|
import 'package:solian/models/post.dart';
|
||||||
import 'package:markdown/markdown.dart' as markdown;
|
import 'package:markdown/markdown.dart' as markdown;
|
||||||
import 'package:solian/utils/service_url.dart';
|
import 'package:solian/utils/service_url.dart';
|
||||||
|
import 'package:solian/widgets/posts/content/attachment.dart';
|
||||||
import 'package:url_launcher/url_launcher_string.dart';
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
|
|
||||||
class ArticleContent extends StatelessWidget {
|
class ArticleContent extends StatelessWidget {
|
||||||
@ -53,13 +54,17 @@ class ArticleContent extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
imageBuilder: (url, _, __) {
|
imageBuilder: (url, _, __) {
|
||||||
|
Uri uri;
|
||||||
if (url.toString().startsWith("/api/attachments")) {
|
if (url.toString().startsWith("/api/attachments")) {
|
||||||
return Image.network(
|
uri = getRequestUri('interactive', url.toString());
|
||||||
getRequestUri('interactive', url.toString())
|
|
||||||
.toString());
|
|
||||||
} else {
|
} else {
|
||||||
return Image.network(url.toString());
|
uri = url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return AttachmentItem(
|
||||||
|
type: 1,
|
||||||
|
url: uri.toString(),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -1,47 +1,58 @@
|
|||||||
import 'package:flutter/cupertino.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:chewie/chewie.dart';
|
import 'package:media_kit/media_kit.dart';
|
||||||
|
import 'package:media_kit_video/media_kit_video.dart';
|
||||||
import 'package:solian/models/post.dart';
|
import 'package:solian/models/post.dart';
|
||||||
import 'package:solian/utils/service_url.dart';
|
import 'package:solian/utils/service_url.dart';
|
||||||
import 'package:flutter_carousel_widget/flutter_carousel_widget.dart';
|
import 'package:flutter_carousel_widget/flutter_carousel_widget.dart';
|
||||||
import 'package:solian/widgets/posts/attachment_screen.dart';
|
import 'package:solian/widgets/posts/attachment_screen.dart';
|
||||||
import 'package:video_player/video_player.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
class AttachmentItem extends StatefulWidget {
|
class AttachmentItem extends StatefulWidget {
|
||||||
final Attachment item;
|
final int type;
|
||||||
|
final String url;
|
||||||
|
final String? tag;
|
||||||
final String? badge;
|
final String? badge;
|
||||||
|
|
||||||
const AttachmentItem({super.key, required this.item, this.badge});
|
const AttachmentItem({
|
||||||
|
super.key,
|
||||||
|
required this.type,
|
||||||
|
required this.url,
|
||||||
|
this.tag,
|
||||||
|
this.badge,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<AttachmentItem> createState() => _AttachmentItemState();
|
State<AttachmentItem> createState() => _AttachmentItemState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AttachmentItemState extends State<AttachmentItem> {
|
class _AttachmentItemState extends State<AttachmentItem> {
|
||||||
String getTag() => 'attachment-${widget.item.fileId}';
|
String getTag() => 'attachment-${widget.tag ?? const Uuid().v4()}';
|
||||||
|
|
||||||
Uri getFileUri() =>
|
late final _videoPlayer = Player(
|
||||||
getRequestUri('interactive', '/api/attachments/o/${widget.item.fileId}');
|
configuration: PlayerConfiguration(
|
||||||
|
title: "Attachment #${getTag()}",
|
||||||
VideoPlayerController? _vpController;
|
logLevel: MPVLogLevel.error,
|
||||||
ChewieController? _chewieController;
|
),
|
||||||
|
);
|
||||||
|
late final _videoController = VideoController(_videoPlayer);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
const borderRadius = Radius.circular(16);
|
const borderRadius = Radius.circular(8);
|
||||||
|
final tag = getTag();
|
||||||
|
|
||||||
Widget content;
|
Widget content;
|
||||||
|
|
||||||
if (widget.item.type == 1) {
|
if (widget.type == 1) {
|
||||||
content = GestureDetector(
|
content = GestureDetector(
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: const BorderRadius.all(borderRadius),
|
borderRadius: const BorderRadius.all(borderRadius),
|
||||||
child: Hero(
|
child: Hero(
|
||||||
tag: getTag(),
|
tag: tag,
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
Image.network(
|
Image.network(
|
||||||
getFileUri().toString(),
|
widget.url,
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: double.infinity,
|
height: double.infinity,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
@ -62,38 +73,25 @@ class _AttachmentItemState extends State<AttachmentItem> {
|
|||||||
context,
|
context,
|
||||||
MaterialPageRoute(builder: (_) {
|
MaterialPageRoute(builder: (_) {
|
||||||
return AttachmentScreen(
|
return AttachmentScreen(
|
||||||
tag: getTag(),
|
tag: tag,
|
||||||
url: getFileUri().toString(),
|
url: widget.url,
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
_vpController = VideoPlayerController.networkUrl(getFileUri());
|
_videoPlayer.open(
|
||||||
_chewieController = ChewieController(
|
Media(widget.url),
|
||||||
videoPlayerController: _vpController!,
|
play: false,
|
||||||
);
|
);
|
||||||
|
|
||||||
content = FutureBuilder(
|
content = ClipRRect(
|
||||||
future: () async {
|
borderRadius: const BorderRadius.all(borderRadius),
|
||||||
await _vpController?.initialize();
|
child: Video(
|
||||||
return true;
|
controller: _videoController,
|
||||||
}(),
|
key: Key(getTag()),
|
||||||
builder: (context, snapshot) {
|
),
|
||||||
if (snapshot.hasData) {
|
|
||||||
return ClipRRect(
|
|
||||||
borderRadius: const BorderRadius.all(borderRadius),
|
|
||||||
child: Chewie(
|
|
||||||
controller: _chewieController!,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return const Center(
|
|
||||||
child: CircularProgressIndicator(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,16 +110,18 @@ class _AttachmentItemState extends State<AttachmentItem> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_vpController?.dispose();
|
_videoPlayer.dispose();
|
||||||
_chewieController?.dispose();
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AttachmentList extends StatelessWidget {
|
class AttachmentList extends StatelessWidget {
|
||||||
final List<Attachment> items;
|
final List<Attachment> items;
|
||||||
|
final String provider;
|
||||||
|
|
||||||
const AttachmentList({super.key, required this.items});
|
const AttachmentList({super.key, required this.items, required this.provider});
|
||||||
|
|
||||||
|
Uri getFileUri(String fileId) => getRequestUri(provider, '/api/attachments/o/$fileId');
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -140,7 +140,11 @@ class AttachmentList extends StatelessWidget {
|
|||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||||
child: AttachmentItem(
|
child: AttachmentItem(
|
||||||
item: item, badge: items.length <= 1 ? null : badge),
|
type: item.type,
|
||||||
|
tag: item.fileId,
|
||||||
|
url: getFileUri(item.fileId).toString(),
|
||||||
|
badge: items.length <= 1 ? null : badge,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||||
import 'package:solian/models/post.dart';
|
import 'package:solian/models/post.dart';
|
||||||
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
|
|
||||||
class MomentContent extends StatelessWidget {
|
class MomentContent extends StatelessWidget {
|
||||||
final Post item;
|
final Post item;
|
||||||
@ -16,6 +17,13 @@ class MomentContent extends StatelessWidget {
|
|||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
padding: const EdgeInsets.all(0),
|
padding: const EdgeInsets.all(0),
|
||||||
|
onTapLink: (text, href, title) async {
|
||||||
|
if (href == null) return;
|
||||||
|
await launchUrlString(
|
||||||
|
href,
|
||||||
|
mode: LaunchMode.externalApplication,
|
||||||
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,28 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
||||||
import 'package:solian/models/post.dart';
|
import 'package:solian/models/post.dart';
|
||||||
|
import 'package:solian/widgets/posts/comment_list.dart';
|
||||||
import 'package:solian/widgets/posts/content/article.dart';
|
import 'package:solian/widgets/posts/content/article.dart';
|
||||||
import 'package:solian/widgets/posts/content/attachment.dart';
|
import 'package:solian/widgets/posts/content/attachment.dart';
|
||||||
import 'package:solian/widgets/posts/content/moment.dart';
|
import 'package:solian/widgets/posts/content/moment.dart';
|
||||||
import 'package:solian/widgets/posts/item_action.dart';
|
import 'package:solian/widgets/posts/item_action.dart';
|
||||||
|
import 'package:solian/widgets/posts/reaction_list.dart';
|
||||||
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
import 'package:timeago/timeago.dart' as timeago;
|
import 'package:timeago/timeago.dart' as timeago;
|
||||||
|
|
||||||
class PostItem extends StatefulWidget {
|
class PostItem extends StatefulWidget {
|
||||||
final Post item;
|
final Post item;
|
||||||
final bool? brief;
|
final bool? brief;
|
||||||
final Function? onUpdate;
|
final Function? onUpdate;
|
||||||
|
final Function? onDelete;
|
||||||
|
|
||||||
const PostItem({super.key, required this.item, this.brief, this.onUpdate});
|
const PostItem({
|
||||||
|
super.key,
|
||||||
|
required this.item,
|
||||||
|
this.brief,
|
||||||
|
this.onUpdate,
|
||||||
|
this.onDelete,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<PostItem> createState() => _PostItemState();
|
State<PostItem> createState() => _PostItemState();
|
||||||
@ -30,6 +41,36 @@ class _PostItemState extends State<PostItem> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void viewComments(BuildContext context) {
|
||||||
|
final PagingController<int, Post> commentPaging =
|
||||||
|
PagingController(firstPageKey: 0);
|
||||||
|
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
CommentListHeader(
|
||||||
|
related: widget.item,
|
||||||
|
paging: commentPaging,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: CustomScrollView(
|
||||||
|
slivers: [
|
||||||
|
CommentList(
|
||||||
|
related: widget.item,
|
||||||
|
dataset: '${widget.item.modelType}s',
|
||||||
|
paging: commentPaging,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget renderContent() {
|
Widget renderContent() {
|
||||||
switch (widget.item.modelType) {
|
switch (widget.item.modelType) {
|
||||||
case 'article':
|
case 'article':
|
||||||
@ -46,17 +87,56 @@ class _PostItemState extends State<PostItem> {
|
|||||||
widget.item.attachments!.isNotEmpty) {
|
widget.item.attachments!.isNotEmpty) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(top: 8),
|
padding: const EdgeInsets.only(top: 8),
|
||||||
child: AttachmentList(items: widget.item.attachments!),
|
child: AttachmentList(items: widget.item.attachments!, provider: 'interactive'),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return Container();
|
return Container();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget renderReactions() {
|
||||||
|
return Container(
|
||||||
|
height: 48,
|
||||||
|
padding: const EdgeInsets.only(top: 8, left: 4, right: 4),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
ActionChip(
|
||||||
|
avatar: const Icon(Icons.comment),
|
||||||
|
label: Text(widget.item.commentCount.toString()),
|
||||||
|
tooltip: AppLocalizations.of(context)!.comment,
|
||||||
|
onPressed: () => viewComments(context),
|
||||||
|
),
|
||||||
|
const VerticalDivider(thickness: 0.3, indent: 8, endIndent: 8),
|
||||||
|
Expanded(
|
||||||
|
child: ReactionList(
|
||||||
|
item: widget.item,
|
||||||
|
reactionList: reactionList,
|
||||||
|
onReact: (symbol, changes) {
|
||||||
|
setState(() {
|
||||||
|
if (!reactionList!.containsKey(symbol)) {
|
||||||
|
reactionList![symbol] = 0;
|
||||||
|
}
|
||||||
|
reactionList![symbol] += changes;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
String getAuthorDescribe() => widget.item.author.description.isNotEmpty
|
String getAuthorDescribe() => widget.item.author.description.isNotEmpty
|
||||||
? widget.item.author.description
|
? widget.item.author.description
|
||||||
: 'No description yet.';
|
: 'No description yet.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
reactionList = widget.item.reactionList;
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final headingParts = [
|
final headingParts = [
|
||||||
@ -75,89 +155,96 @@ class _PostItemState extends State<PostItem> {
|
|||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
Widget content;
|
||||||
|
|
||||||
if (widget.brief ?? true) {
|
if (widget.brief ?? true) {
|
||||||
return GestureDetector(
|
content = Padding(
|
||||||
child: Padding(
|
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
|
||||||
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
CircleAvatar(
|
|
||||||
backgroundImage: NetworkImage(widget.item.author.avatar),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
...headingParts,
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(
|
|
||||||
left: 12, right: 12, top: 4),
|
|
||||||
child: renderContent(),
|
|
||||||
),
|
|
||||||
renderAttachments(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
onLongPress: () {
|
|
||||||
viewActions(context);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return GestureDetector(
|
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Row(
|
||||||
padding: const EdgeInsets.only(left: 12, right: 12, top: 16),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
child: Row(
|
children: [
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
CircleAvatar(
|
||||||
children: [
|
backgroundImage: NetworkImage(widget.item.author.avatar),
|
||||||
CircleAvatar(
|
),
|
||||||
backgroundImage: NetworkImage(widget.item.author.avatar),
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
...headingParts,
|
||||||
|
Padding(
|
||||||
|
padding:
|
||||||
|
const EdgeInsets.only(left: 12, right: 12, top: 4),
|
||||||
|
child: renderContent(),
|
||||||
|
),
|
||||||
|
renderAttachments(),
|
||||||
|
renderReactions(),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
Expanded(
|
),
|
||||||
child: Column(
|
],
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
...headingParts,
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
|
||||||
child: Text(
|
|
||||||
getAuthorDescribe(),
|
|
||||||
maxLines: 1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const Padding(
|
|
||||||
padding: EdgeInsets.only(top: 6),
|
|
||||||
child: Divider(thickness: 0.3),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
|
||||||
child: renderContent(),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
|
||||||
child: renderAttachments(),
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
onLongPress: () {
|
);
|
||||||
viewActions(context);
|
} else {
|
||||||
},
|
content = Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 12, right: 12, top: 16),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
CircleAvatar(
|
||||||
|
backgroundImage: NetworkImage(widget.item.author.avatar),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
...headingParts,
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
|
child: Text(
|
||||||
|
getAuthorDescribe(),
|
||||||
|
maxLines: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Padding(
|
||||||
|
padding: EdgeInsets.only(top: 6),
|
||||||
|
child: Divider(thickness: 0.3),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||||
|
child: renderContent(),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
|
child: renderAttachments(),
|
||||||
|
),
|
||||||
|
ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
|
child: renderReactions(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return GestureDetector(
|
||||||
|
child: content,
|
||||||
|
onLongPress: () {
|
||||||
|
viewActions(context);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,13 +4,45 @@ import 'package:solian/models/post.dart';
|
|||||||
import 'package:solian/providers/auth.dart';
|
import 'package:solian/providers/auth.dart';
|
||||||
import 'package:solian/router.dart';
|
import 'package:solian/router.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
import 'package:solian/screens/posts/comment_editor.dart';
|
||||||
import 'package:solian/widgets/posts/item_deletion.dart';
|
import 'package:solian/widgets/posts/item_deletion.dart';
|
||||||
|
|
||||||
class PostItemAction extends StatelessWidget {
|
class PostItemAction extends StatelessWidget {
|
||||||
final Post item;
|
final Post item;
|
||||||
final Function? onUpdate;
|
final Function? onUpdate;
|
||||||
|
final Function? onDelete;
|
||||||
|
|
||||||
const PostItemAction({super.key, required this.item, this.onUpdate});
|
const PostItemAction({
|
||||||
|
super.key,
|
||||||
|
required this.item,
|
||||||
|
this.onUpdate,
|
||||||
|
this.onDelete,
|
||||||
|
});
|
||||||
|
|
||||||
|
void viewEditor() async {
|
||||||
|
bool ok = false;
|
||||||
|
switch (item.modelType) {
|
||||||
|
case 'article':
|
||||||
|
ok = await router.pushNamed(
|
||||||
|
'posts.articles.editor',
|
||||||
|
extra: item,
|
||||||
|
) as bool;
|
||||||
|
case 'moment':
|
||||||
|
ok = await router.pushNamed(
|
||||||
|
'posts.moments.editor',
|
||||||
|
extra: item,
|
||||||
|
) as bool;
|
||||||
|
case 'comment':
|
||||||
|
ok = await router.pushNamed(
|
||||||
|
'posts.comments.editor',
|
||||||
|
extra: CommentPostArguments(editing: item),
|
||||||
|
) as bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ok == true && onUpdate != null) {
|
||||||
|
onUpdate!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -32,21 +64,12 @@ class PostItemAction extends StatelessWidget {
|
|||||||
child: FutureBuilder(
|
child: FutureBuilder(
|
||||||
future: auth.getProfiles(),
|
future: auth.getProfiles(),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
print(snapshot);
|
|
||||||
if (snapshot.hasData) {
|
if (snapshot.hasData) {
|
||||||
final authorizedItems = [
|
final authorizedItems = [
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.edit),
|
leading: const Icon(Icons.edit),
|
||||||
title: Text(AppLocalizations.of(context)!.edit),
|
title: Text(AppLocalizations.of(context)!.edit),
|
||||||
onTap: () {
|
onTap: () => viewEditor(),
|
||||||
router
|
|
||||||
.pushNamed('posts.moments.editor', extra: item)
|
|
||||||
.then((did) {
|
|
||||||
if (did == true && onUpdate != null) {
|
|
||||||
onUpdate!();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.delete),
|
leading: const Icon(Icons.delete),
|
||||||
@ -59,7 +82,7 @@ class PostItemAction extends StatelessWidget {
|
|||||||
item: item,
|
item: item,
|
||||||
dataset: dataset,
|
dataset: dataset,
|
||||||
onDelete: (did) {
|
onDelete: (did) {
|
||||||
if(did == true && onUpdate != null) onUpdate!();
|
if (did == true && onDelete != null) onDelete!();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
145
lib/widgets/posts/reaction_action.dart
Normal file
145
lib/widgets/posts/reaction_action.dart
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:solian/models/reaction.dart';
|
||||||
|
import 'package:solian/providers/auth.dart';
|
||||||
|
import 'package:solian/utils/service_url.dart';
|
||||||
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
|
||||||
|
Future<void> doReact(
|
||||||
|
String dataset,
|
||||||
|
int id,
|
||||||
|
String symbol,
|
||||||
|
int attitude,
|
||||||
|
final void Function(String symbol, int num) onReact,
|
||||||
|
BuildContext context,
|
||||||
|
) async {
|
||||||
|
final auth = context.read<AuthProvider>();
|
||||||
|
if (!await auth.isAuthorized()) return;
|
||||||
|
|
||||||
|
var uri = getRequestUri(
|
||||||
|
'interactive',
|
||||||
|
'/api/p/$dataset/$id/react',
|
||||||
|
);
|
||||||
|
|
||||||
|
var res = await auth.client!.post(
|
||||||
|
uri,
|
||||||
|
headers: <String, String>{
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: jsonEncode(<String, dynamic>{
|
||||||
|
'symbol': symbol,
|
||||||
|
'attitude': attitude,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.statusCode == 201) {
|
||||||
|
onReact(symbol, 1);
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(AppLocalizations.of(context)!.reactionAdded),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else if (res.statusCode == 204) {
|
||||||
|
onReact(symbol, -1);
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(AppLocalizations.of(context)!.reactionRemoved),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
final message = utf8.decode(res.bodyBytes);
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text("Something went wrong... $message")),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Navigator.canPop(context)) {
|
||||||
|
Navigator.pop(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ReactionActionPopup extends StatefulWidget {
|
||||||
|
final String dataset;
|
||||||
|
final int id;
|
||||||
|
final void Function(String symbol, int num) onReact;
|
||||||
|
|
||||||
|
const ReactionActionPopup({
|
||||||
|
super.key,
|
||||||
|
required this.dataset,
|
||||||
|
required this.id,
|
||||||
|
required this.onReact,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ReactionActionPopup> createState() => _ReactionActionPopupState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ReactionActionPopupState extends State<ReactionActionPopup> {
|
||||||
|
bool _isSubmitting = false;
|
||||||
|
|
||||||
|
Future<void> doWidgetReact(
|
||||||
|
String symbol,
|
||||||
|
int attitude,
|
||||||
|
BuildContext context,
|
||||||
|
) async {
|
||||||
|
if (_isSubmitting) return;
|
||||||
|
|
||||||
|
setState(() => _isSubmitting = true);
|
||||||
|
await doReact(
|
||||||
|
widget.dataset,
|
||||||
|
widget.id,
|
||||||
|
symbol,
|
||||||
|
attitude,
|
||||||
|
widget.onReact,
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
setState(() => _isSubmitting = false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final reactEntries = reactions.entries.toList();
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.only(left: 8, right: 8, top: 20),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 8,
|
||||||
|
vertical: 12,
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
AppLocalizations.of(context)!.reaction,
|
||||||
|
style: Theme.of(context).textTheme.headlineSmall,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
_isSubmitting ? const LinearProgressIndicator() : Container(),
|
||||||
|
Expanded(
|
||||||
|
child: ListView.builder(
|
||||||
|
itemCount: reactions.length,
|
||||||
|
itemBuilder: (BuildContext context, int index) {
|
||||||
|
var info = reactEntries[index];
|
||||||
|
return InkWell(
|
||||||
|
onTap: () async {
|
||||||
|
await doWidgetReact(info.key, info.value.attitude, context);
|
||||||
|
},
|
||||||
|
child: ListTile(
|
||||||
|
title: Text(info.value.icon),
|
||||||
|
subtitle: Text(
|
||||||
|
":${info.key}:",
|
||||||
|
style: const TextStyle(fontFamily: "monospace"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
90
lib/widgets/posts/reaction_list.dart
Normal file
90
lib/widgets/posts/reaction_list.dart
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:solian/models/post.dart';
|
||||||
|
import 'package:solian/models/reaction.dart';
|
||||||
|
import 'package:solian/widgets/posts/reaction_action.dart';
|
||||||
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
|
||||||
|
class ReactionList extends StatefulWidget {
|
||||||
|
final Post item;
|
||||||
|
final Map<String, dynamic>? reactionList;
|
||||||
|
final void Function(String symbol, int num) onReact;
|
||||||
|
|
||||||
|
const ReactionList({
|
||||||
|
super.key,
|
||||||
|
required this.item,
|
||||||
|
this.reactionList,
|
||||||
|
required this.onReact,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ReactionList> createState() => _ReactionListState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ReactionListState extends State<ReactionList> {
|
||||||
|
bool _isSubmitting = false;
|
||||||
|
|
||||||
|
void viewReactMenu(BuildContext context) {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => ReactionActionPopup(
|
||||||
|
dataset: '${widget.item.modelType}s',
|
||||||
|
id: widget.item.id,
|
||||||
|
onReact: widget.onReact,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> doWidgetReact(
|
||||||
|
String symbol,
|
||||||
|
int attitude,
|
||||||
|
BuildContext context,
|
||||||
|
) async {
|
||||||
|
if (_isSubmitting) return;
|
||||||
|
|
||||||
|
setState(() => _isSubmitting = true);
|
||||||
|
await doReact(
|
||||||
|
'${widget.item.modelType}s',
|
||||||
|
widget.item.id,
|
||||||
|
symbol,
|
||||||
|
attitude,
|
||||||
|
widget.onReact,
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
setState(() => _isSubmitting = false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
const density = VisualDensity(horizontal: -4, vertical: -2);
|
||||||
|
|
||||||
|
final reactEntries = widget.reactionList?.entries ?? List.empty();
|
||||||
|
|
||||||
|
return ListView(
|
||||||
|
shrinkWrap: true,
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
children: [
|
||||||
|
...reactEntries.map((x) {
|
||||||
|
final info = reactions[x.key];
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 8),
|
||||||
|
child: ActionChip(
|
||||||
|
avatar: Text(info!.icon),
|
||||||
|
label: Text(x.value.toString()),
|
||||||
|
tooltip: ':${x.key}:',
|
||||||
|
visualDensity: density,
|
||||||
|
onPressed: _isSubmitting
|
||||||
|
? null
|
||||||
|
: () => doWidgetReact(x.key, info.attitude, context),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
ActionChip(
|
||||||
|
avatar: const Icon(Icons.add_reaction, color: Colors.teal),
|
||||||
|
label: Text(AppLocalizations.of(context)!.reactVerb),
|
||||||
|
visualDensity: density,
|
||||||
|
onPressed: () => viewReactMenu(context),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
#include <file_selector_linux/file_selector_plugin.h>
|
#include <file_selector_linux/file_selector_plugin.h>
|
||||||
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
|
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
|
||||||
|
#include <media_kit_libs_linux/media_kit_libs_linux_plugin.h>
|
||||||
#include <media_kit_video/media_kit_video_plugin.h>
|
#include <media_kit_video/media_kit_video_plugin.h>
|
||||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||||
|
|
||||||
@ -18,6 +19,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
|
|||||||
g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar =
|
g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin");
|
||||||
flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar);
|
flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar);
|
||||||
|
g_autoptr(FlPluginRegistrar) media_kit_libs_linux_registrar =
|
||||||
|
fl_plugin_registry_get_registrar_for_plugin(registry, "MediaKitLibsLinuxPlugin");
|
||||||
|
media_kit_libs_linux_plugin_register_with_registrar(media_kit_libs_linux_registrar);
|
||||||
g_autoptr(FlPluginRegistrar) media_kit_video_registrar =
|
g_autoptr(FlPluginRegistrar) media_kit_video_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "MediaKitVideoPlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "MediaKitVideoPlugin");
|
||||||
media_kit_video_plugin_register_with_registrar(media_kit_video_registrar);
|
media_kit_video_plugin_register_with_registrar(media_kit_video_registrar);
|
||||||
|
@ -5,11 +5,13 @@
|
|||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
file_selector_linux
|
file_selector_linux
|
||||||
flutter_secure_storage_linux
|
flutter_secure_storage_linux
|
||||||
|
media_kit_libs_linux
|
||||||
media_kit_video
|
media_kit_video
|
||||||
url_launcher_linux
|
url_launcher_linux
|
||||||
)
|
)
|
||||||
|
|
||||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||||
|
media_kit_native_event_loop
|
||||||
)
|
)
|
||||||
|
|
||||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||||
|
@ -7,22 +7,22 @@ import Foundation
|
|||||||
|
|
||||||
import file_selector_macos
|
import file_selector_macos
|
||||||
import flutter_secure_storage_macos
|
import flutter_secure_storage_macos
|
||||||
|
import media_kit_libs_macos_video
|
||||||
import media_kit_video
|
import media_kit_video
|
||||||
import package_info_plus
|
import package_info_plus
|
||||||
import path_provider_foundation
|
import path_provider_foundation
|
||||||
import screen_brightness_macos
|
import screen_brightness_macos
|
||||||
import url_launcher_macos
|
import url_launcher_macos
|
||||||
import video_player_avfoundation
|
|
||||||
import wakelock_plus
|
import wakelock_plus
|
||||||
|
|
||||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||||
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
||||||
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
||||||
|
MediaKitLibsMacosVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosVideoPlugin"))
|
||||||
MediaKitVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitVideoPlugin"))
|
MediaKitVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitVideoPlugin"))
|
||||||
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
||||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||||
ScreenBrightnessMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenBrightnessMacosPlugin"))
|
ScreenBrightnessMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenBrightnessMacosPlugin"))
|
||||||
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||||
FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin"))
|
|
||||||
WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin"))
|
WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin"))
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,10 @@ PODS:
|
|||||||
- flutter_secure_storage_macos (6.1.1):
|
- flutter_secure_storage_macos (6.1.1):
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- FlutterMacOS (1.0.0)
|
- FlutterMacOS (1.0.0)
|
||||||
|
- media_kit_libs_macos_video (1.0.4):
|
||||||
|
- FlutterMacOS
|
||||||
|
- media_kit_native_event_loop (1.0.0):
|
||||||
|
- FlutterMacOS
|
||||||
- media_kit_video (0.0.1):
|
- media_kit_video (0.0.1):
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- package_info_plus (0.0.1):
|
- package_info_plus (0.0.1):
|
||||||
@ -15,9 +19,6 @@ PODS:
|
|||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- url_launcher_macos (0.0.1):
|
- url_launcher_macos (0.0.1):
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- video_player_avfoundation (0.0.1):
|
|
||||||
- Flutter
|
|
||||||
- FlutterMacOS
|
|
||||||
- wakelock_plus (0.0.1):
|
- wakelock_plus (0.0.1):
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
|
|
||||||
@ -25,12 +26,13 @@ DEPENDENCIES:
|
|||||||
- file_selector_macos (from `Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos`)
|
- file_selector_macos (from `Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos`)
|
||||||
- flutter_secure_storage_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos`)
|
- flutter_secure_storage_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos`)
|
||||||
- FlutterMacOS (from `Flutter/ephemeral`)
|
- FlutterMacOS (from `Flutter/ephemeral`)
|
||||||
|
- media_kit_libs_macos_video (from `Flutter/ephemeral/.symlinks/plugins/media_kit_libs_macos_video/macos`)
|
||||||
|
- media_kit_native_event_loop (from `Flutter/ephemeral/.symlinks/plugins/media_kit_native_event_loop/macos`)
|
||||||
- media_kit_video (from `Flutter/ephemeral/.symlinks/plugins/media_kit_video/macos`)
|
- media_kit_video (from `Flutter/ephemeral/.symlinks/plugins/media_kit_video/macos`)
|
||||||
- package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`)
|
- package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`)
|
||||||
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
|
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
|
||||||
- screen_brightness_macos (from `Flutter/ephemeral/.symlinks/plugins/screen_brightness_macos/macos`)
|
- screen_brightness_macos (from `Flutter/ephemeral/.symlinks/plugins/screen_brightness_macos/macos`)
|
||||||
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
|
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
|
||||||
- video_player_avfoundation (from `Flutter/ephemeral/.symlinks/plugins/video_player_avfoundation/darwin`)
|
|
||||||
- wakelock_plus (from `Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos`)
|
- wakelock_plus (from `Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos`)
|
||||||
|
|
||||||
EXTERNAL SOURCES:
|
EXTERNAL SOURCES:
|
||||||
@ -40,6 +42,10 @@ EXTERNAL SOURCES:
|
|||||||
:path: Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos
|
:path: Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos
|
||||||
FlutterMacOS:
|
FlutterMacOS:
|
||||||
:path: Flutter/ephemeral
|
:path: Flutter/ephemeral
|
||||||
|
media_kit_libs_macos_video:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/media_kit_libs_macos_video/macos
|
||||||
|
media_kit_native_event_loop:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/media_kit_native_event_loop/macos
|
||||||
media_kit_video:
|
media_kit_video:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/media_kit_video/macos
|
:path: Flutter/ephemeral/.symlinks/plugins/media_kit_video/macos
|
||||||
package_info_plus:
|
package_info_plus:
|
||||||
@ -50,8 +56,6 @@ EXTERNAL SOURCES:
|
|||||||
:path: Flutter/ephemeral/.symlinks/plugins/screen_brightness_macos/macos
|
:path: Flutter/ephemeral/.symlinks/plugins/screen_brightness_macos/macos
|
||||||
url_launcher_macos:
|
url_launcher_macos:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
|
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
|
||||||
video_player_avfoundation:
|
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/video_player_avfoundation/darwin
|
|
||||||
wakelock_plus:
|
wakelock_plus:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos
|
:path: Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos
|
||||||
|
|
||||||
@ -59,12 +63,13 @@ SPEC CHECKSUMS:
|
|||||||
file_selector_macos: 468fb6b81fac7c0e88d71317f3eec34c3b008ff9
|
file_selector_macos: 468fb6b81fac7c0e88d71317f3eec34c3b008ff9
|
||||||
flutter_secure_storage_macos: d56e2d218c1130b262bef8b4a7d64f88d7f9c9ea
|
flutter_secure_storage_macos: d56e2d218c1130b262bef8b4a7d64f88d7f9c9ea
|
||||||
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
|
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
|
||||||
media_kit_video: f5bdcbfaef003c02251e50d44bb741aa96fb8a1e
|
media_kit_libs_macos_video: b3e2bbec2eef97c285f2b1baa7963c67c753fb82
|
||||||
|
media_kit_native_event_loop: 81fd5b45192b72f8b5b69eaf5b540f45777eb8d5
|
||||||
|
media_kit_video: c75b07f14d59706c775778e4dd47dd027de8d1e5
|
||||||
package_info_plus: fa739dd842b393193c5ca93c26798dff6e3d0e0c
|
package_info_plus: fa739dd842b393193c5ca93c26798dff6e3d0e0c
|
||||||
path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c
|
path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c
|
||||||
screen_brightness_macos: 2d6d3af2165592d9a55ffcd95b7550970e41ebda
|
screen_brightness_macos: 2d6d3af2165592d9a55ffcd95b7550970e41ebda
|
||||||
url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95
|
url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95
|
||||||
video_player_avfoundation: 2b4384f3b157206b5e150a0083cdc0c905d260d3
|
|
||||||
wakelock_plus: 4783562c9a43d209c458cb9b30692134af456269
|
wakelock_plus: 4783562c9a43d209c458cb9b30692134af456269
|
||||||
|
|
||||||
PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367
|
PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367
|
||||||
|
160
pubspec.lock
160
pubspec.lock
@ -41,14 +41,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.0"
|
version: "1.3.0"
|
||||||
chewie:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: chewie
|
|
||||||
sha256: e53da939709efb9aad0f3d72a69a8d05f889168b7a138af60ce78bab5c94b135
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.8.1"
|
|
||||||
clock:
|
clock:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -89,22 +81,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.3"
|
version: "3.0.3"
|
||||||
csslib:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: csslib
|
|
||||||
sha256: "706b5707578e0c1b4b7550f64078f0a0f19dec3f50a178ffae7006b0a9ca58fb"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.0.0"
|
|
||||||
cupertino_icons:
|
cupertino_icons:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: cupertino_icons
|
name: cupertino_icons
|
||||||
sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d
|
sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.6"
|
version: "1.0.8"
|
||||||
dbus:
|
dbus:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -199,10 +183,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_markdown
|
name: flutter_markdown
|
||||||
sha256: "31c12de79262b5431c5492e9c89948aa789158435f707d3519a7fdef6af28af7"
|
sha256: "04c4722cc36ec5af38acc38ece70d22d3c2123c61305d555750a091517bbe504"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.6.22+1"
|
version: "0.6.23"
|
||||||
flutter_plugin_android_lifecycle:
|
flutter_plugin_android_lifecycle:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -285,14 +269,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "13.2.4"
|
version: "13.2.4"
|
||||||
html:
|
hive:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: html
|
name: hive
|
||||||
sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a"
|
sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.15.4"
|
version: "2.2.3"
|
||||||
|
hive_flutter:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: hive_flutter
|
||||||
|
sha256: dca1da446b1d808a51689fb5d0c6c9510c0a2ba01e22805d492c73b68e33eecc
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.0"
|
||||||
http:
|
http:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -321,34 +313,34 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: image_picker
|
name: image_picker
|
||||||
sha256: "1f498d086203360cca099d20ffea2963f48c39ce91bdd8a3b6d4a045786b02c8"
|
sha256: fe9ee64ccb8d599a5dfb0e21cc6652232c610bcf667af4e79b9eb175cc30a7a5
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.8"
|
version: "1.1.0"
|
||||||
image_picker_android:
|
image_picker_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image_picker_android
|
name: image_picker_android
|
||||||
sha256: "844c6da4e4f2829dffdab97816bca09d0e0977e8dcef7450864aba4e07967a58"
|
sha256: "8e75431a62b7feb4fd55cb4a5c6f0ac4564460ec5dc09f9c4a0d50a5ce7c4cb9"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.8.9+6"
|
version: "0.8.10"
|
||||||
image_picker_for_web:
|
image_picker_for_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image_picker_for_web
|
name: image_picker_for_web
|
||||||
sha256: "6a1704fdd75022272e7e7a897a9068e9c2ff3cd6a66820bf3ded810633eac954"
|
sha256: "5d6eb13048cd47b60dbf1a5495424dea226c5faf3950e20bf8120a58efb5b5f3"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.3"
|
version: "3.0.4"
|
||||||
image_picker_ios:
|
image_picker_ios:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image_picker_ios
|
name: image_picker_ios
|
||||||
sha256: "917a5cadd67d052554cfb258595e54217de53fac5b52939426e26319a02e6297"
|
sha256: f4a6f62be96d6fd268f32a6bf8ef444cd8e3fff64d16923c6e6fe55e0c84a761
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.8.9+2"
|
version: "0.8.10"
|
||||||
image_picker_linux:
|
image_picker_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -470,13 +462,69 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "0.8.0"
|
version: "0.8.0"
|
||||||
media_kit:
|
media_kit:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: media_kit
|
name: media_kit
|
||||||
sha256: "3289062540e3b8b9746e5c50d95bd78a9289826b7227e253dff806d002b9e67a"
|
sha256: "3289062540e3b8b9746e5c50d95bd78a9289826b7227e253dff806d002b9e67a"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.10+1"
|
version: "1.1.10+1"
|
||||||
|
media_kit_libs_android_video:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: media_kit_libs_android_video
|
||||||
|
sha256: "9dd8012572e4aff47516e55f2597998f0a378e3d588d0fad0ca1f11a53ae090c"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.6"
|
||||||
|
media_kit_libs_ios_video:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: media_kit_libs_ios_video
|
||||||
|
sha256: b5382994eb37a4564c368386c154ad70ba0cc78dacdd3fb0cd9f30db6d837991
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.4"
|
||||||
|
media_kit_libs_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: media_kit_libs_linux
|
||||||
|
sha256: e186891c31daa6bedab4d74dcdb4e8adfccc7d786bfed6ad81fe24a3b3010310
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.3"
|
||||||
|
media_kit_libs_macos_video:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: media_kit_libs_macos_video
|
||||||
|
sha256: f26aa1452b665df288e360393758f84b911f70ffb3878032e1aabba23aa1032d
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.4"
|
||||||
|
media_kit_libs_video:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: media_kit_libs_video
|
||||||
|
sha256: "3688e0c31482074578652bf038ce6301a5d21e1eda6b54fc3117ffeb4bdba067"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.4"
|
||||||
|
media_kit_libs_windows_video:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: media_kit_libs_windows_video
|
||||||
|
sha256: "7bace5f35d9afcc7f9b5cdadb7541d2191a66bb3fc71bfa11c1395b3360f6122"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.9"
|
||||||
|
media_kit_native_event_loop:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: media_kit_native_event_loop
|
||||||
|
sha256: a605cf185499d14d58935b8784955a92a4bf0ff4e19a23de3d17a9106303930e
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.8"
|
||||||
media_kit_video:
|
media_kit_video:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -846,10 +894,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_web
|
name: url_launcher_web
|
||||||
sha256: "3692a459204a33e04bc94f5fb91158faf4f2c8903281ddd82915adecdb1a901d"
|
sha256: "8d9e750d8c9338601e709cd0885f95825086bd8b642547f26bda435aade95d8a"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.0"
|
version: "2.3.1"
|
||||||
url_launcher_windows:
|
url_launcher_windows:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -859,7 +907,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.1"
|
version: "3.1.1"
|
||||||
uuid:
|
uuid:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: uuid
|
name: uuid
|
||||||
sha256: "814e9e88f21a176ae1359149021870e87f7cddaf633ab678a5d2b0bff7fd1ba8"
|
sha256: "814e9e88f21a176ae1359149021870e87f7cddaf633ab678a5d2b0bff7fd1ba8"
|
||||||
@ -874,46 +922,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.4"
|
version: "2.1.4"
|
||||||
video_player:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: video_player
|
|
||||||
sha256: db6a72d8f4fd155d0189845678f55ad2fd54b02c10dcafd11c068dbb631286c0
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.8.6"
|
|
||||||
video_player_android:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: video_player_android
|
|
||||||
sha256: "821cff3446bbde255e8d03c12fe1f9810c69fee2c26c394545b13d824ba63c2e"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.4.13"
|
|
||||||
video_player_avfoundation:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: video_player_avfoundation
|
|
||||||
sha256: "00c49b1d68071341397cf760b982c1e26ed9232464c8506ee08378a5cca5070d"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.5.7"
|
|
||||||
video_player_platform_interface:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: video_player_platform_interface
|
|
||||||
sha256: "236454725fafcacf98f0f39af0d7c7ab2ce84762e3b63f2cbb3ef9a7e0550bc6"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "6.2.2"
|
|
||||||
video_player_web:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: video_player_web
|
|
||||||
sha256: "41245cef5ef29c4585dbabcbcbe9b209e34376642c7576cabf11b4ad9289d6e4"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.3.0"
|
|
||||||
vm_service:
|
vm_service:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -48,13 +48,15 @@ dependencies:
|
|||||||
timeago: ^3.6.1
|
timeago: ^3.6.1
|
||||||
flutter_carousel_widget: ^2.2.0
|
flutter_carousel_widget: ^2.2.0
|
||||||
media_kit_video: ^1.2.4
|
media_kit_video: ^1.2.4
|
||||||
chewie: ^1.8.1
|
|
||||||
video_player: ^2.8.6
|
|
||||||
flutter_secure_storage: ^9.0.0
|
flutter_secure_storage: ^9.0.0
|
||||||
oauth2: ^2.0.2
|
oauth2: ^2.0.2
|
||||||
webview_flutter: ^4.7.0
|
webview_flutter: ^4.7.0
|
||||||
crypto: ^3.0.3
|
crypto: ^3.0.3
|
||||||
image_picker: ^1.0.8
|
image_picker: ^1.0.8
|
||||||
|
uuid: ^4.4.0
|
||||||
|
media_kit: ^1.1.10+1
|
||||||
|
media_kit_libs_video: ^1.0.4
|
||||||
|
hive_flutter: ^1.1.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
#include <file_selector_windows/file_selector_windows.h>
|
#include <file_selector_windows/file_selector_windows.h>
|
||||||
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
|
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
|
||||||
|
#include <media_kit_libs_windows_video/media_kit_libs_windows_video_plugin_c_api.h>
|
||||||
#include <media_kit_video/media_kit_video_plugin_c_api.h>
|
#include <media_kit_video/media_kit_video_plugin_c_api.h>
|
||||||
#include <screen_brightness_windows/screen_brightness_windows_plugin.h>
|
#include <screen_brightness_windows/screen_brightness_windows_plugin.h>
|
||||||
#include <url_launcher_windows/url_launcher_windows.h>
|
#include <url_launcher_windows/url_launcher_windows.h>
|
||||||
@ -17,6 +18,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
|
|||||||
registry->GetRegistrarForPlugin("FileSelectorWindows"));
|
registry->GetRegistrarForPlugin("FileSelectorWindows"));
|
||||||
FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
|
FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
|
registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
|
||||||
|
MediaKitLibsWindowsVideoPluginCApiRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("MediaKitLibsWindowsVideoPluginCApi"));
|
||||||
MediaKitVideoPluginCApiRegisterWithRegistrar(
|
MediaKitVideoPluginCApiRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("MediaKitVideoPluginCApi"));
|
registry->GetRegistrarForPlugin("MediaKitVideoPluginCApi"));
|
||||||
ScreenBrightnessWindowsPluginRegisterWithRegistrar(
|
ScreenBrightnessWindowsPluginRegisterWithRegistrar(
|
||||||
|
@ -5,12 +5,14 @@
|
|||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
file_selector_windows
|
file_selector_windows
|
||||||
flutter_secure_storage_windows
|
flutter_secure_storage_windows
|
||||||
|
media_kit_libs_windows_video
|
||||||
media_kit_video
|
media_kit_video
|
||||||
screen_brightness_windows
|
screen_brightness_windows
|
||||||
url_launcher_windows
|
url_launcher_windows
|
||||||
)
|
)
|
||||||
|
|
||||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||||
|
media_kit_native_event_loop
|
||||||
)
|
)
|
||||||
|
|
||||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user