Compare commits

..

19 Commits

Author SHA1 Message Date
d87e67bd17 Subscriptions 2024-09-17 02:14:23 +08:00
06aa1fb359 🐛 Fix post last read at 2024-09-17 01:23:49 +08:00
62733bf29f 💄 Optimize featured reply style 2024-09-16 23:39:15 +08:00
ce16de9c71 Featured replies on post 2024-09-16 23:35:44 +08:00
47eb6cbc66 Chat list will also show wild group channel 2024-09-16 21:09:19 +08:00
029e72fb0b Improve sticker loading 2024-09-16 21:00:19 +08:00
152efd97a0 💄 Unified design of single attachment uploader 2024-09-16 20:33:34 +08:00
ad1dc064e6 🚀 Launch 1.2.1+39 2024-09-16 20:15:36 +08:00
675b5dea5d 💫 Optimize region animations 2024-09-16 20:06:15 +08:00
5941cb9fd5 🐛 Fix messages loading 2024-09-16 19:50:49 +08:00
e11bf204af 🐛 Fix web login error by the cors issue 2024-09-16 18:12:30 +08:00
8a2d94cedf 🚀 Launch 1.2.1+38 2024-09-16 12:04:21 +08:00
780f7c22bc 💄 Better user agent 2024-09-16 11:57:16 +08:00
c18ce88993 Brand new sign in flow 2024-09-16 02:37:20 +08:00
73456fcff6 ♻️ Full screen signin and signup 2024-09-15 23:32:15 +08:00
8e8be52658 🐛 Fix web uploading 2024-09-15 22:52:20 +08:00
df22b65777 💄 Fix style issue 2024-09-15 18:31:04 +08:00
1437414b7f Improve chat loading speed 2024-09-15 18:25:04 +08:00
c1ff317c66 🚑 Able to use database on web 2024-09-15 18:02:27 +08:00
94 changed files with 15424 additions and 1027 deletions

View File

@@ -3,6 +3,7 @@
"hide": "Hide", "hide": "Hide",
"okay": "Okay", "okay": "Okay",
"next": "Next", "next": "Next",
"prev": "Previous",
"reset": "Reset", "reset": "Reset",
"page": "Page", "page": "Page",
"home": "Home", "home": "Home",
@@ -70,8 +71,12 @@
"forgotPassword": "Forgot password", "forgotPassword": "Forgot password",
"email": "Email", "email": "Email",
"username": "Username", "username": "Username",
"usernameInputHint": "Also supports email and phone number",
"nickname": "Nickname", "nickname": "Nickname",
"password": "Password", "password": "Password",
"passwordOneTime": "One-time-password",
"passwordInputHint": "Forgot your password? Go back to the first step to reset your password",
"passwordOneTimeInputHint": "Check your inbox or authorizer for a verification code",
"title": "Title", "title": "Title",
"description": "Description", "description": "Description",
"birthday": "Birthday", "birthday": "Birthday",
@@ -103,6 +108,11 @@
"signinRiskDetected": "Risk detected, click Next to open a webpage and signin through it to pass security check.", "signinRiskDetected": "Risk detected, click Next to open a webpage and signin through it to pass security check.",
"signinResetPasswordHint": "Please enter username to request reset password.", "signinResetPasswordHint": "Please enter username to request reset password.",
"signinResetPasswordSent": "Reset password request sent, check your inbox!", "signinResetPasswordSent": "Reset password request sent, check your inbox!",
"signinPickFactor": "Pick a way\nfor verification",
"signinEnterPassword": "Enter your\npassword",
"signinMultiFactor": "@n step(s) verifications",
"authFactorEmail": "Email One-time-password",
"authFactorPassword": "Password",
"signup": "Sign up", "signup": "Sign up",
"signupGreeting": "Welcome onboard", "signupGreeting": "Welcome onboard",
"signupCaption": "Create an account on Solarpass and then get the access of entire Solar Network!", "signupCaption": "Create an account on Solarpass and then get the access of entire Solar Network!",
@@ -147,6 +157,9 @@
"postListNews": "News", "postListNews": "News",
"postListFriends": "Friends", "postListFriends": "Friends",
"postListShuffle": "Random", "postListShuffle": "Random",
"attachmentThumbnail": "Thumbnail",
"attachmentThumbnailAttachmentNew": "Upload thumbnail",
"attachmentThumbnailAttachment": "Attachment serial number",
"postEditorModeStory": "Post a post", "postEditorModeStory": "Post a post",
"postEditorModeArticle": "Post an article", "postEditorModeArticle": "Post an article",
"postEditor": "Post editor", "postEditor": "Post editor",
@@ -397,5 +410,8 @@
"userLevel13": "Immortal", "userLevel13": "Immortal",
"postBrowsingIn": "Browsing in @region", "postBrowsingIn": "Browsing in @region",
"needRestartToApply": "Restart the application to take effect", "needRestartToApply": "Restart the application to take effect",
"holdToSeeDetail": "Long press / Mouse hover to see detail" "holdToSeeDetail": "Long press / Mouse hover to see detail",
"subscribe": "Subscribe",
"subscribed": "Subscribed",
"unsubscribe": "Unsubscribe"
} }

View File

@@ -4,6 +4,7 @@
"okay": "确认", "okay": "确认",
"home": "首页", "home": "首页",
"next": "下一步", "next": "下一步",
"prev": "上一步",
"reset": "重置", "reset": "重置",
"cancel": "取消", "cancel": "取消",
"confirm": "确认", "confirm": "确认",
@@ -75,8 +76,12 @@
"forgotPassword": "忘记密码", "forgotPassword": "忘记密码",
"email": "邮件地址", "email": "邮件地址",
"username": "用户名", "username": "用户名",
"usernameInputHint": "同时支持邮箱 / 电话号码",
"nickname": "显示名", "nickname": "显示名",
"password": "密码", "password": "密码",
"passwordOneTime": "一次性验证码",
"passwordInputHint": "忘记密码了?回到第一步以重置密码",
"passwordOneTimeInputHint": "检查你的收件箱或是授权器获得以验证码",
"title": "标题", "title": "标题",
"description": "简介", "description": "简介",
"birthday": "生日", "birthday": "生日",
@@ -108,6 +113,11 @@
"signinRiskDetected": "检测到风险,点击下一步按钮来打开一个网页,并通过在其上面登录来通过安全检查。", "signinRiskDetected": "检测到风险,点击下一步按钮来打开一个网页,并通过在其上面登录来通过安全检查。",
"signinResetPasswordHint": "请先填写用户名以发送重置密码请求。", "signinResetPasswordHint": "请先填写用户名以发送重置密码请求。",
"signinResetPasswordSent": "重置密码请求已发送,在绑定邮件收件箱可收取一份包含重置密码链接的邮件。", "signinResetPasswordSent": "重置密码请求已发送,在绑定邮件收件箱可收取一份包含重置密码链接的邮件。",
"signinPickFactor": "选择一个\n验证方式",
"signinEnterPassword": "输入密码\n或验证码",
"signinMultiFactor": "@n 步验证",
"authFactorEmail": "邮箱一次性密码",
"authFactorPassword": "账户密码",
"signup": "注册", "signup": "注册",
"signupGreeting": "欢迎加入\nSolar Network", "signupGreeting": "欢迎加入\nSolar Network",
"signupCaption": "在 Solarpass 注册一个账号以获得整个 Solar Network 的存取权!", "signupCaption": "在 Solarpass 注册一个账号以获得整个 Solar Network 的存取权!",
@@ -158,6 +168,9 @@
"postListNews": "新鲜事", "postListNews": "新鲜事",
"postListFriends": "好友圈", "postListFriends": "好友圈",
"postListShuffle": "打乱看", "postListShuffle": "打乱看",
"attachmentThumbnail": "附件缩略图",
"attachmentThumbnailAttachmentNew": "上传附件作为缩略图",
"attachmentThumbnailAttachment": "附件序列号",
"postNew": "创建新帖子", "postNew": "创建新帖子",
"postNewInRealmHint": "在领域 @realm 里发表新帖子", "postNewInRealmHint": "在领域 @realm 里发表新帖子",
"postAction": "发表", "postAction": "发表",
@@ -398,5 +411,8 @@
"userLevel13": "万古流芳", "userLevel13": "万古流芳",
"postBrowsingIn": "浏览 @region 内的帖子中", "postBrowsingIn": "浏览 @region 内的帖子中",
"needRestartToApply": "需要重启应用来生效", "needRestartToApply": "需要重启应用来生效",
"holdToSeeDetail": "长按 / 鼠标悬浮来查看详情" "holdToSeeDetail": "长按 / 鼠标悬浮来查看详情",
"subscribe": "订阅",
"subscribed": "已订阅",
"unsubscribe": "取消订阅"
} }

View File

@@ -9,7 +9,6 @@ import 'package:solian/providers/auth.dart';
import 'package:solian/providers/content/channel.dart'; import 'package:solian/providers/content/channel.dart';
import 'package:solian/providers/content/realm.dart'; import 'package:solian/providers/content/realm.dart';
import 'package:solian/providers/relation.dart'; import 'package:solian/providers/relation.dart';
import 'package:solian/providers/stickers.dart';
import 'package:solian/providers/theme_switcher.dart'; import 'package:solian/providers/theme_switcher.dart';
import 'package:solian/providers/websocket.dart'; import 'package:solian/providers/websocket.dart';
import 'package:solian/services.dart'; import 'package:solian/services.dart';
@@ -71,7 +70,7 @@ class _BootstrapperShellState extends State<BootstrapperShell> {
( (
label: 'bsCheckingServer', label: 'bsCheckingServer',
action: () async { action: () async {
final client = ServiceFinder.configureClient('dealer'); final client = await ServiceFinder.configureClient('dealer');
final resp = await client.get('/.well-known'); final resp = await client.get('/.well-known');
if (resp.statusCode != null && resp.statusCode != 200) { if (resp.statusCode != null && resp.statusCode != 200) {
setState(() { setState(() {
@@ -115,7 +114,6 @@ class _BootstrapperShellState extends State<BootstrapperShell> {
final AuthProvider auth = Get.find(); final AuthProvider auth = Get.find();
try { try {
await Future.wait([ await Future.wait([
Get.find<StickerProvider>().refreshAvailableStickers(),
if (auth.isAuthorized.isTrue) if (auth.isAuthorized.isTrue)
Get.find<ChannelProvider>().refreshAvailableChannel(), Get.find<ChannelProvider>().refreshAvailableChannel(),
if (auth.isAuthorized.isTrue) if (auth.isAuthorized.isTrue)

View File

@@ -1,7 +1,8 @@
import 'dart:math' as math;
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:solian/models/channel.dart'; import 'package:solian/models/channel.dart';
import 'package:solian/models/event.dart'; import 'package:solian/models/event.dart';
import 'package:solian/platform.dart';
import 'package:solian/providers/database/database.dart'; import 'package:solian/providers/database/database.dart';
import 'package:solian/providers/database/services/messages.dart'; import 'package:solian/providers/database/services/messages.dart';
@@ -31,80 +32,56 @@ class ChatEventController {
this.channel = channel; this.channel = channel;
this.scope = scope; this.scope = scope;
syncLocal(channel); const firstTake = 20;
const furtherTake = 100;
isLoading.value = true; isLoading.value = true;
if (PlatformInfo.isWeb) { await syncLocal(channel, take: firstTake);
final result = await src.fetchRemoteEvents(
channel,
scope,
depth: 1,
offset: 0,
);
totalEvents.value = result?.$2 ?? 0;
if (result != null) {
for (final x in result.$1.reversed) {
final entry = LocalMessageEventTableData(
id: x.id,
channelId: x.channelId,
createdAt: x.createdAt,
data: x,
);
insertEvent(entry);
applyEvent(entry);
}
}
} else {
final result = await src.pullRemoteEvents(
channel,
scope: scope,
depth: 1,
);
totalEvents.value = result?.$2 ?? 0;
await syncLocal(channel);
}
isLoading.value = false; isLoading.value = false;
// Take a small range of messages to check is local database up to date
var isUpToDate = true;
final result =
await src.pullRemoteEvents(channel, scope: scope, take: firstTake);
totalEvents.value = result?.$2 ?? 0;
if ((result?.$1.length ?? 0) > 0) {
final minId = result!.$1.map((x) => x.id).reduce(math.min);
isUpToDate = await src.getEventFromLocal(minId) != null;
}
syncLocal(channel, take: firstTake);
if (!isUpToDate) {
// Loading more content due to isn't up to date
final result =
await src.pullRemoteEvents(channel, scope: scope, take: furtherTake);
totalEvents.value = result?.$2 ?? 0;
syncLocal(channel, take: furtherTake);
}
} }
Future<void> loadEvents(Channel channel, String scope) async { Future<void> loadEvents(Channel channel, String scope) async {
const take = 20;
final offset = currentEvents.length;
isLoading.value = true; isLoading.value = true;
if (PlatformInfo.isWeb) { await syncLocal(channel, take: take, offset: offset);
final result = await src.fetchRemoteEvents( src
channel, .pullRemoteEvents(channel, scope: scope, take: take, offset: offset)
scope, .then((result) {
depth: 3,
offset: currentEvents.length,
);
if (result != null) {
totalEvents.value = result.$2;
for (final x in result.$1.reversed) {
final entry = LocalMessageEventTableData(
id: x.id,
channelId: x.channelId,
createdAt: x.createdAt,
data: x,
);
currentEvents.add(entry);
applyEvent(entry);
}
}
} else {
final result = await src.pullRemoteEvents(
channel,
depth: 3,
scope: scope,
offset: currentEvents.length,
);
totalEvents.value = result?.$2 ?? 0; totalEvents.value = result?.$2 ?? 0;
await syncLocal(channel); syncLocal(channel, take: take, offset: offset);
} });
isLoading.value = false; isLoading.value = false;
} }
Future<bool> syncLocal(Channel channel) async { Future<bool> syncLocal(Channel channel,
if (PlatformInfo.isWeb) return false; {required int take, int offset = 0}) async {
final data = await src.listEvents(channel); final data = await src.listEvents(channel, take: take, offset: offset);
currentEvents.replaceRange(0, currentEvents.length, data); if (currentEvents.length >= offset + take) {
currentEvents.replaceRange(offset, offset + take, data);
} else {
currentEvents.insertAll(currentEvents.length, data);
}
for (final x in data.reversed) { for (final x in data.reversed) {
applyEvent(x); applyEvent(x);
} }
@@ -113,16 +90,7 @@ class ChatEventController {
receiveEvent(Event remote) async { receiveEvent(Event remote) async {
LocalMessageEventTableData entry; LocalMessageEventTableData entry;
if (PlatformInfo.isWeb) { entry = await src.receiveEvent(remote);
entry = LocalMessageEventTableData(
id: remote.id,
channelId: remote.channelId,
createdAt: remote.createdAt,
data: remote,
);
} else {
entry = await src.receiveEvent(remote);
}
totalEvents.value++; totalEvents.value++;
insertEvent(entry); insertEvent(entry);

View File

@@ -20,6 +20,7 @@ import 'package:solian/providers/last_read.dart';
import 'package:solian/providers/link_expander.dart'; import 'package:solian/providers/link_expander.dart';
import 'package:solian/providers/navigation.dart'; import 'package:solian/providers/navigation.dart';
import 'package:solian/providers/stickers.dart'; import 'package:solian/providers/stickers.dart';
import 'package:solian/providers/subscription.dart';
import 'package:solian/providers/theme_switcher.dart'; import 'package:solian/providers/theme_switcher.dart';
import 'package:solian/providers/websocket.dart'; import 'package:solian/providers/websocket.dart';
import 'package:solian/providers/auth.dart'; import 'package:solian/providers/auth.dart';
@@ -151,6 +152,7 @@ class SolianApp extends StatelessWidget {
Get.lazyPut(() => LinkExpandProvider()); Get.lazyPut(() => LinkExpandProvider());
Get.lazyPut(() => DailySignProvider()); Get.lazyPut(() => DailySignProvider());
Get.lazyPut(() => LastReadProvider()); Get.lazyPut(() => LastReadProvider());
Get.lazyPut(() => SubscriptionProvider());
Get.find<WebSocketProvider>().requestPermissions(); Get.find<WebSocketProvider>().requestPermissions();
} }

View File

@@ -1,4 +1,4 @@
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
part 'account.g.dart'; part 'account.g.dart';

View File

@@ -1,4 +1,4 @@
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
part 'account_status.g.dart'; part 'account_status.g.dart';

View File

@@ -1,4 +1,4 @@
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:solian/models/account.dart'; import 'package:solian/models/account.dart';
part 'attachment.g.dart'; part 'attachment.g.dart';

103
lib/models/auth.dart Normal file
View File

@@ -0,0 +1,103 @@
import 'package:json_annotation/json_annotation.dart';
import 'package:solian/models/account.dart';
part 'auth.g.dart';
@JsonSerializable()
class AuthResult {
bool isFinished;
AuthTicket ticket;
AuthResult({
required this.isFinished,
required this.ticket,
});
factory AuthResult.fromJson(Map<String, dynamic> json) =>
_$AuthResultFromJson(json);
Map<String, dynamic> toJson() => _$AuthResultToJson(this);
}
@JsonSerializable()
class AuthTicket {
int id;
DateTime createdAt;
DateTime updatedAt;
DateTime? deletedAt;
String location;
String ipAddress;
String userAgent;
int stepRemain;
List<String> claims;
List<String> audiences;
@JsonKey(defaultValue: [])
List<int> factorTrail;
String? grantToken;
String? accessToken;
String? refreshToken;
DateTime? expiredAt;
DateTime? availableAt;
DateTime? lastGrantAt;
String? nonce;
int? clientId;
Account account;
int accountId;
AuthTicket({
required this.id,
required this.createdAt,
required this.updatedAt,
required this.deletedAt,
required this.location,
required this.ipAddress,
required this.userAgent,
required this.stepRemain,
required this.claims,
required this.audiences,
required this.factorTrail,
required this.grantToken,
required this.accessToken,
required this.refreshToken,
required this.expiredAt,
required this.availableAt,
required this.lastGrantAt,
required this.nonce,
required this.clientId,
required this.account,
required this.accountId,
});
factory AuthTicket.fromJson(Map<String, dynamic> json) =>
_$AuthTicketFromJson(json);
Map<String, dynamic> toJson() => _$AuthTicketToJson(this);
}
@JsonSerializable()
class AuthFactor {
int id;
DateTime createdAt;
DateTime updatedAt;
DateTime? deletedAt;
int type;
Map<String, dynamic>? config;
Account account;
int accountId;
AuthFactor({
required this.id,
required this.createdAt,
required this.updatedAt,
required this.deletedAt,
required this.type,
required this.config,
required this.account,
required this.accountId,
});
factory AuthFactor.fromJson(Map<String, dynamic> json) =>
_$AuthFactorFromJson(json);
Map<String, dynamic> toJson() => _$AuthFactorToJson(this);
}

105
lib/models/auth.g.dart Normal file
View File

@@ -0,0 +1,105 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'auth.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
AuthResult _$AuthResultFromJson(Map<String, dynamic> json) => AuthResult(
isFinished: json['is_finished'] as bool,
ticket: AuthTicket.fromJson(json['ticket'] as Map<String, dynamic>),
);
Map<String, dynamic> _$AuthResultToJson(AuthResult instance) =>
<String, dynamic>{
'is_finished': instance.isFinished,
'ticket': instance.ticket.toJson(),
};
AuthTicket _$AuthTicketFromJson(Map<String, dynamic> json) => AuthTicket(
id: (json['id'] as num).toInt(),
createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
deletedAt: json['deleted_at'] == null
? null
: DateTime.parse(json['deleted_at'] as String),
location: json['location'] as String,
ipAddress: json['ip_address'] as String,
userAgent: json['user_agent'] as String,
stepRemain: (json['step_remain'] as num).toInt(),
claims:
(json['claims'] as List<dynamic>).map((e) => e as String).toList(),
audiences:
(json['audiences'] as List<dynamic>).map((e) => e as String).toList(),
factorTrail: (json['factor_trail'] as List<dynamic>?)
?.map((e) => (e as num).toInt())
.toList() ??
[],
grantToken: json['grant_token'] as String?,
accessToken: json['access_token'] as String?,
refreshToken: json['refresh_token'] as String?,
expiredAt: json['expired_at'] == null
? null
: DateTime.parse(json['expired_at'] as String),
availableAt: json['available_at'] == null
? null
: DateTime.parse(json['available_at'] as String),
lastGrantAt: json['last_grant_at'] == null
? null
: DateTime.parse(json['last_grant_at'] as String),
nonce: json['nonce'] as String?,
clientId: (json['client_id'] as num?)?.toInt(),
account: Account.fromJson(json['account'] as Map<String, dynamic>),
accountId: (json['account_id'] as num).toInt(),
);
Map<String, dynamic> _$AuthTicketToJson(AuthTicket instance) =>
<String, dynamic>{
'id': instance.id,
'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(),
'location': instance.location,
'ip_address': instance.ipAddress,
'user_agent': instance.userAgent,
'step_remain': instance.stepRemain,
'claims': instance.claims,
'audiences': instance.audiences,
'factor_trail': instance.factorTrail,
'grant_token': instance.grantToken,
'access_token': instance.accessToken,
'refresh_token': instance.refreshToken,
'expired_at': instance.expiredAt?.toIso8601String(),
'available_at': instance.availableAt?.toIso8601String(),
'last_grant_at': instance.lastGrantAt?.toIso8601String(),
'nonce': instance.nonce,
'client_id': instance.clientId,
'account': instance.account.toJson(),
'account_id': instance.accountId,
};
AuthFactor _$AuthFactorFromJson(Map<String, dynamic> json) => AuthFactor(
id: (json['id'] as num).toInt(),
createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
deletedAt: json['deleted_at'] == null
? null
: DateTime.parse(json['deleted_at'] as String),
type: (json['type'] as num).toInt(),
config: json['config'] as Map<String, dynamic>?,
account: Account.fromJson(json['account'] as Map<String, dynamic>),
accountId: (json['account_id'] as num).toInt(),
);
Map<String, dynamic> _$AuthFactorToJson(AuthFactor instance) =>
<String, dynamic>{
'id': instance.id,
'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(),
'type': instance.type,
'config': instance.config,
'account': instance.account.toJson(),
'account_id': instance.accountId,
};

View File

@@ -1,4 +1,4 @@
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:livekit_client/livekit_client.dart'; import 'package:livekit_client/livekit_client.dart';
import 'package:solian/models/channel.dart'; import 'package:solian/models/channel.dart';

View File

@@ -1,4 +1,4 @@
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:solian/models/account.dart'; import 'package:solian/models/account.dart';
import 'package:solian/models/realm.dart'; import 'package:solian/models/realm.dart';

View File

@@ -1,4 +1,4 @@
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:solian/models/account.dart'; import 'package:solian/models/account.dart';

View File

@@ -1,4 +1,4 @@
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:solian/models/channel.dart'; import 'package:solian/models/channel.dart';
part 'event.g.dart'; part 'event.g.dart';

View File

@@ -1,4 +1,4 @@
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
part 'link.g.dart'; part 'link.g.dart';

View File

@@ -1,4 +1,4 @@
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
part 'notification.g.dart'; part 'notification.g.dart';

View File

@@ -1,10 +1,10 @@
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
part 'packet.g.dart'; part 'packet.g.dart';
@JsonSerializable() @JsonSerializable()
class NetworkPackage { class NetworkPackage {
@JsonKey(name: 'w') @JsonKey(name: 'w', defaultValue: 'unknown')
String method; String method;
@JsonKey(name: 'e') @JsonKey(name: 'e')
String? endpoint; String? endpoint;

View File

@@ -8,7 +8,7 @@ part of 'packet.dart';
NetworkPackage _$NetworkPackageFromJson(Map<String, dynamic> json) => NetworkPackage _$NetworkPackageFromJson(Map<String, dynamic> json) =>
NetworkPackage( NetworkPackage(
method: json['w'] as String, method: json['w'] as String? ?? 'unknown',
endpoint: json['e'] as String?, endpoint: json['e'] as String?,
message: json['m'] as String?, message: json['m'] as String?,
payload: json['p'] as Map<String, dynamic>?, payload: json['p'] as Map<String, dynamic>?,

View File

@@ -1,4 +1,4 @@
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
part 'pagination.g.dart'; part 'pagination.g.dart';

View File

@@ -1,4 +1,4 @@
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:solian/models/account.dart'; import 'package:solian/models/account.dart';
import 'package:solian/models/post_categories.dart'; import 'package:solian/models/post_categories.dart';
import 'package:solian/models/realm.dart'; import 'package:solian/models/realm.dart';

View File

@@ -1,4 +1,4 @@
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
part 'post_categories.g.dart'; part 'post_categories.g.dart';

View File

@@ -1,4 +1,4 @@
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:solian/models/account.dart'; import 'package:solian/models/account.dart';
part 'realm.g.dart'; part 'realm.g.dart';

View File

@@ -1,4 +1,4 @@
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:solian/models/account.dart'; import 'package:solian/models/account.dart';
part 'relations.g.dart'; part 'relations.g.dart';

View File

@@ -1,4 +1,4 @@
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:solian/models/account.dart'; import 'package:solian/models/account.dart';
import 'package:solian/models/attachment.dart'; import 'package:solian/models/attachment.dart';
import 'package:solian/services.dart'; import 'package:solian/services.dart';

View File

@@ -0,0 +1,41 @@
import 'package:json_annotation/json_annotation.dart';
import 'package:solian/models/account.dart';
import 'package:solian/models/post_categories.dart';
part 'subscription.g.dart';
@JsonSerializable()
class Subscription {
int id;
DateTime createdAt;
DateTime updatedAt;
DateTime? deletedAt;
int followerId;
Account follower;
int? accountId;
Account? account;
int? tagId;
Tag? tag;
int? categoryId;
Category? category;
Subscription({
required this.id,
required this.createdAt,
required this.updatedAt,
required this.deletedAt,
required this.followerId,
required this.follower,
required this.accountId,
required this.account,
required this.tagId,
required this.tag,
required this.categoryId,
required this.category,
});
factory Subscription.fromJson(Map<String, dynamic> json) =>
_$SubscriptionFromJson(json);
Map<String, dynamic> toJson() => _$SubscriptionToJson(this);
}

View File

@@ -0,0 +1,46 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'subscription.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Subscription _$SubscriptionFromJson(Map<String, dynamic> json) => Subscription(
id: (json['id'] as num).toInt(),
createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
deletedAt: json['deleted_at'] == null
? null
: DateTime.parse(json['deleted_at'] as String),
followerId: (json['follower_id'] as num).toInt(),
follower: Account.fromJson(json['follower'] as Map<String, dynamic>),
accountId: (json['account_id'] as num?)?.toInt(),
account: json['account'] == null
? null
: Account.fromJson(json['account'] as Map<String, dynamic>),
tagId: (json['tag_id'] as num?)?.toInt(),
tag: json['tag'] == null
? null
: Tag.fromJson(json['tag'] as Map<String, dynamic>),
categoryId: (json['category_id'] as num?)?.toInt(),
category: json['category'] == null
? null
: Category.fromJson(json['category'] as Map<String, dynamic>),
);
Map<String, dynamic> _$SubscriptionToJson(Subscription instance) =>
<String, dynamic>{
'id': instance.id,
'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
'deleted_at': instance.deletedAt?.toIso8601String(),
'follower_id': instance.followerId,
'follower': instance.follower.toJson(),
'account_id': instance.accountId,
'account': instance.account?.toJson(),
'tag_id': instance.tagId,
'tag': instance.tag?.toJson(),
'category_id': instance.categoryId,
'category': instance.category?.toJson(),
};

View File

@@ -37,7 +37,7 @@ class StatusProvider extends GetConnect {
final AuthProvider auth = Get.find(); final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) throw const UnauthorizedException(); if (auth.isAuthorized.isFalse) throw const UnauthorizedException();
final client = auth.configureClient('auth'); final client = await auth.configureClient('auth');
return await client.get('/users/me/status'); return await client.get('/users/me/status');
} }
@@ -56,7 +56,7 @@ class StatusProvider extends GetConnect {
final AuthProvider auth = Get.find(); final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) throw const UnauthorizedException(); if (auth.isAuthorized.isFalse) throw const UnauthorizedException();
final client = auth.configureClient('auth'); final client = await auth.configureClient('auth');
final payload = { final payload = {
'type': type, 'type': type,
@@ -85,7 +85,7 @@ class StatusProvider extends GetConnect {
final AuthProvider auth = Get.find(); final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) throw const UnauthorizedException(); if (auth.isAuthorized.isFalse) throw const UnauthorizedException();
final client = auth.configureClient('auth'); final client = await auth.configureClient('auth');
final resp = await client.delete('/users/me/status'); final resp = await client.delete('/users/me/status');
if (resp.statusCode != 200) { if (resp.statusCode != 200) {

View File

@@ -9,6 +9,7 @@ import 'package:get/get_connect/http/src/request/request.dart';
import 'package:solian/background.dart'; import 'package:solian/background.dart';
import 'package:solian/exceptions/request.dart'; import 'package:solian/exceptions/request.dart';
import 'package:solian/exceptions/unauthorized.dart'; import 'package:solian/exceptions/unauthorized.dart';
import 'package:solian/models/auth.dart';
import 'package:solian/providers/database/database.dart'; import 'package:solian/providers/database/database.dart';
import 'package:solian/providers/websocket.dart'; import 'package:solian/providers/websocket.dart';
import 'package:solian/services.dart'; import 'package:solian/services.dart';
@@ -114,14 +115,14 @@ class AuthProvider extends GetConnect {
return request; return request;
} }
GetConnect configureClient( Future<GetConnect> configureClient(
String service, { String service, {
timeout = const Duration(seconds: 5), timeout = const Duration(seconds: 5),
}) { }) async {
final client = GetConnect( final client = GetConnect(
maxAuthRetries: 3, maxAuthRetries: 3,
timeout: timeout, timeout: timeout,
userAgent: 'Solian/1.1', userAgent: await ServiceFinder.getUserAgent(),
sendUserAgent: true, sendUserAgent: true,
); );
client.httpClient.addAuthenticator(requestAuthenticator); client.httpClient.addAuthenticator(requestAuthenticator);
@@ -148,27 +149,13 @@ class AuthProvider extends GetConnect {
Future<TokenSet> signin( Future<TokenSet> signin(
BuildContext context, BuildContext context,
String username, AuthTicket ticket,
String password,
) async { ) async {
userProfile.value = null; userProfile.value = null;
final client = ServiceFinder.configureClient('auth');
// Create ticket
final resp = await client.post('/auth', {
'username': username,
'password': password,
});
if (resp.statusCode != 200) {
throw RequestException(resp);
} else if (resp.body['is_finished'] == false) {
throw RiskyAuthenticateException(resp.body['ticket']['id']);
}
// Assign token // Assign token
final tokenResp = await post('/auth/token', { final tokenResp = await post('/auth/token', {
'code': resp.body['ticket']['grant_token'], 'code': ticket.grantToken!,
'grant_type': 'grant_token', 'grant_type': 'grant_token',
}); });
if (tokenResp.statusCode != 200) { if (tokenResp.statusCode != 200) {
@@ -217,7 +204,7 @@ class AuthProvider extends GetConnect {
Future<void> refreshUserProfile() async { Future<void> refreshUserProfile() async {
if (!isAuthorized.value) return; if (!isAuthorized.value) return;
final client = configureClient('auth'); final client = await configureClient('auth');
final resp = await client.get('/users/me'); final resp = await client.get('/users/me');
if (resp.statusCode != 200) { if (resp.statusCode != 200) {
throw RequestException(resp); throw RequestException(resp);

View File

@@ -92,7 +92,7 @@ class ChatCallProvider extends GetxController {
final AuthProvider auth = Get.find(); final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) throw const UnauthorizedException(); if (auth.isAuthorized.isFalse) throw const UnauthorizedException();
final client = auth.configureClient('messaging'); final client = await auth.configureClient('messaging');
final resp = await client.post( final resp = await client.post(
'/channels/global/${channel.value!.alias}/calls/ongoing/token', '/channels/global/${channel.value!.alias}/calls/ongoing/token',

View File

@@ -93,7 +93,7 @@ class AttachmentProvider extends GetConnect {
final AuthProvider auth = Get.find(); final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) throw const UnauthorizedException(); if (auth.isAuthorized.isFalse) throw const UnauthorizedException();
final client = auth.configureClient( final client = await auth.configureClient(
'uc', 'uc',
timeout: const Duration(minutes: 3), timeout: const Duration(minutes: 3),
); );
@@ -135,7 +135,7 @@ class AttachmentProvider extends GetConnect {
final AuthProvider auth = Get.find(); final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) throw const UnauthorizedException(); if (auth.isAuthorized.isFalse) throw const UnauthorizedException();
final client = auth.configureClient('uc'); final client = await auth.configureClient('uc');
final fileAlt = basename(path).contains('.') final fileAlt = basename(path).contains('.')
? basename(path).substring(0, basename(path).lastIndexOf('.')) ? basename(path).substring(0, basename(path).lastIndexOf('.'))
@@ -173,7 +173,7 @@ class AttachmentProvider extends GetConnect {
final AuthProvider auth = Get.find(); final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) throw const UnauthorizedException(); if (auth.isAuthorized.isFalse) throw const UnauthorizedException();
final client = auth.configureClient( final client = await auth.configureClient(
'uc', 'uc',
timeout: const Duration(minutes: 3), timeout: const Duration(minutes: 3),
); );
@@ -198,7 +198,7 @@ class AttachmentProvider extends GetConnect {
final AuthProvider auth = Get.find(); final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) throw const UnauthorizedException(); if (auth.isAuthorized.isFalse) throw const UnauthorizedException();
final client = auth.configureClient('files'); final client = await auth.configureClient('files');
var resp = await client.put('/attachments/$id', { var resp = await client.put('/attachments/$id', {
'alt': alt, 'alt': alt,
@@ -217,7 +217,7 @@ class AttachmentProvider extends GetConnect {
final AuthProvider auth = Get.find(); final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) throw const UnauthorizedException(); if (auth.isAuthorized.isFalse) throw const UnauthorizedException();
final client = auth.configureClient('files'); final client = await auth.configureClient('files');
var resp = await client.delete('/attachments/$id'); var resp = await client.delete('/attachments/$id');
if (resp.statusCode != 200) { if (resp.statusCode != 200) {

View File

@@ -33,7 +33,7 @@ class ChannelProvider extends GetxController {
final AuthProvider auth = Get.find(); final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) throw const UnauthorizedException(); if (auth.isAuthorized.isFalse) throw const UnauthorizedException();
final client = auth.configureClient('messaging'); final client = await auth.configureClient('messaging');
final resp = await client.get('/channels/$realm/$alias'); final resp = await client.get('/channels/$realm/$alias');
if (resp.statusCode != 200) { if (resp.statusCode != 200) {
@@ -48,7 +48,7 @@ class ChannelProvider extends GetxController {
final AuthProvider auth = Get.find(); final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) throw const UnauthorizedException(); if (auth.isAuthorized.isFalse) throw const UnauthorizedException();
final client = auth.configureClient('messaging'); final client = await auth.configureClient('messaging');
final resp = await client.get('/channels/$realm/$alias/me'); final resp = await client.get('/channels/$realm/$alias/me');
if (resp.statusCode != 200) { if (resp.statusCode != 200) {
@@ -63,7 +63,7 @@ class ChannelProvider extends GetxController {
final AuthProvider auth = Get.find(); final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) throw const UnauthorizedException(); if (auth.isAuthorized.isFalse) throw const UnauthorizedException();
final client = auth.configureClient('messaging'); final client = await auth.configureClient('messaging');
final resp = await client.get('/channels/$realm/$alias/calls/ongoing'); final resp = await client.get('/channels/$realm/$alias/calls/ongoing');
if (resp.statusCode == 404) { if (resp.statusCode == 404) {
@@ -79,7 +79,7 @@ class ChannelProvider extends GetxController {
final AuthProvider auth = Get.find(); final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) throw const UnauthorizedException(); if (auth.isAuthorized.isFalse) throw const UnauthorizedException();
final client = auth.configureClient('messaging'); final client = await auth.configureClient('messaging');
final resp = await client.get('/channels/$scope'); final resp = await client.get('/channels/$scope');
if (resp.statusCode != 200) { if (resp.statusCode != 200) {
@@ -93,7 +93,7 @@ class ChannelProvider extends GetxController {
final AuthProvider auth = Get.find(); final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) throw const UnauthorizedException(); if (auth.isAuthorized.isFalse) throw const UnauthorizedException();
final client = auth.configureClient('messaging'); final client = await auth.configureClient('messaging');
final resp = await client.get('/channels/$realm/me/available'); final resp = await client.get('/channels/$realm/me/available');
if (resp.statusCode != 200) { if (resp.statusCode != 200) {
@@ -107,7 +107,7 @@ class ChannelProvider extends GetxController {
final AuthProvider auth = Get.find(); final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) throw const UnauthorizedException(); if (auth.isAuthorized.isFalse) throw const UnauthorizedException();
final client = auth.configureClient('messaging'); final client = await auth.configureClient('messaging');
final resp = await client.post('/channels/$scope', payload); final resp = await client.post('/channels/$scope', payload);
if (resp.statusCode != 200) { if (resp.statusCode != 200) {
@@ -132,7 +132,7 @@ class ChannelProvider extends GetxController {
if (related == null) return null; if (related == null) return null;
final prof = auth.userProfile.value!; final prof = auth.userProfile.value!;
final client = auth.configureClient('messaging'); final client = await auth.configureClient('messaging');
final resp = await client.post('/channels/$scope/dm', { final resp = await client.post('/channels/$scope/dm', {
'alias': const Uuid().v4().replaceAll('-', '').substring(0, 12), 'alias': const Uuid().v4().replaceAll('-', '').substring(0, 12),
@@ -153,7 +153,7 @@ class ChannelProvider extends GetxController {
final AuthProvider auth = Get.find(); final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) throw const UnauthorizedException(); if (auth.isAuthorized.isFalse) throw const UnauthorizedException();
final client = auth.configureClient('messaging'); final client = await auth.configureClient('messaging');
final resp = await client.put('/channels/$scope/$id', payload); final resp = await client.put('/channels/$scope/$id', payload);
if (resp.statusCode != 200) { if (resp.statusCode != 200) {

View File

@@ -1,6 +1,7 @@
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:solian/exceptions/request.dart'; import 'package:solian/exceptions/request.dart';
import 'package:solian/exceptions/unauthorized.dart'; import 'package:solian/exceptions/unauthorized.dart';
import 'package:solian/models/post.dart';
import 'package:solian/providers/auth.dart'; import 'package:solian/providers/auth.dart';
import 'package:solian/services.dart'; import 'package:solian/services.dart';
@@ -14,9 +15,9 @@ class PostProvider extends GetConnect {
GetConnect client; GetConnect client;
final AuthProvider auth = Get.find(); final AuthProvider auth = Get.find();
if (auth.isAuthorized.value) { if (auth.isAuthorized.value) {
client = auth.configureClient('co'); client = await auth.configureClient('co');
} else { } else {
client = ServiceFinder.configureClient('co'); client = await ServiceFinder.configureClient('co');
} }
final resp = await client.get('/whats-new?pivot=$pivot'); final resp = await client.get('/whats-new?pivot=$pivot');
if (resp.statusCode != 200) { if (resp.statusCode != 200) {
@@ -36,9 +37,9 @@ class PostProvider extends GetConnect {
if (realm != null) 'realm=$realm', if (realm != null) 'realm=$realm',
]; ];
if (auth.isAuthorized.value) { if (auth.isAuthorized.value) {
client = auth.configureClient('co'); client = await auth.configureClient('co');
} else { } else {
client = ServiceFinder.configureClient('co'); client = await ServiceFinder.configureClient('co');
} }
final resp = await client.get( final resp = await client.get(
channel == null channel == null
@@ -60,7 +61,7 @@ class PostProvider extends GetConnect {
'take=${10}', 'take=${10}',
'offset=$page', 'offset=$page',
]; ];
final client = auth.configureClient('interactive'); final client = await auth.configureClient('interactive');
final resp = await client.get('/posts/drafts?${queries.join('&')}'); final resp = await client.get('/posts/drafts?${queries.join('&')}');
if (resp.statusCode != 200) { if (resp.statusCode != 200) {
throw RequestException(resp); throw RequestException(resp);
@@ -96,6 +97,15 @@ class PostProvider extends GetConnect {
return resp; return resp;
} }
Future<List<Post>> listPostFeaturedReply(String alias, {int take = 1}) async {
final resp = await get('/posts/$alias/replies/featured?take=$take');
if (resp.statusCode != 200) {
throw RequestException(resp);
}
return List<Post>.from(resp.body.map((x) => Post.fromJson(x)));
}
Future<Response> getPost(String alias) async { Future<Response> getPost(String alias) async {
final resp = await get('/posts/$alias'); final resp = await get('/posts/$alias');
if (resp.statusCode != 200) { if (resp.statusCode != 200) {

View File

@@ -25,7 +25,7 @@ class RealmProvider extends GetxController {
final AuthProvider auth = Get.find(); final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) throw const UnauthorizedException(); if (auth.isAuthorized.isFalse) throw const UnauthorizedException();
final client = auth.configureClient('auth'); final client = await auth.configureClient('auth');
final resp = await client.get('/realms/$alias'); final resp = await client.get('/realms/$alias');
if (resp.statusCode != 200) { if (resp.statusCode != 200) {
@@ -39,7 +39,7 @@ class RealmProvider extends GetxController {
final AuthProvider auth = Get.find(); final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) throw const UnauthorizedException(); if (auth.isAuthorized.isFalse) throw const UnauthorizedException();
final client = auth.configureClient('auth'); final client = await auth.configureClient('auth');
final resp = await client.get('/realms/me/available'); final resp = await client.get('/realms/me/available');
if (resp.statusCode != 200) { if (resp.statusCode != 200) {

View File

@@ -10,7 +10,7 @@ class DailySignProvider extends GetxController {
final AuthProvider auth = Get.find(); final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) throw const UnauthorizedException(); if (auth.isAuthorized.isFalse) throw const UnauthorizedException();
final client = auth.configureClient('id'); final client = await auth.configureClient('id');
final resp = await client.get('/daily?take=$take'); final resp = await client.get('/daily?take=$take');
if (resp.statusCode != 200 && resp.statusCode != 404) { if (resp.statusCode != 200 && resp.statusCode != 404) {
@@ -30,7 +30,7 @@ class DailySignProvider extends GetxController {
final AuthProvider auth = Get.find(); final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) throw const UnauthorizedException(); if (auth.isAuthorized.isFalse) throw const UnauthorizedException();
final client = auth.configureClient('id'); final client = await auth.configureClient('id');
final resp = await client.get('/daily/today'); final resp = await client.get('/daily/today');
if (resp.statusCode != 200 && resp.statusCode != 404) { if (resp.statusCode != 200 && resp.statusCode != 404) {
@@ -46,7 +46,7 @@ class DailySignProvider extends GetxController {
final AuthProvider auth = Get.find(); final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) throw const UnauthorizedException(); if (auth.isAuthorized.isFalse) throw const UnauthorizedException();
final client = auth.configureClient('id'); final client = await auth.configureClient('id');
final resp = await client.post('/daily', {}); final resp = await client.post('/daily', {});
if (resp.statusCode != 200) { if (resp.statusCode != 200) {

View File

@@ -20,7 +20,13 @@ class AppDatabase extends _$AppDatabase {
int get schemaVersion => 1; int get schemaVersion => 1;
static QueryExecutor _openConnection() { static QueryExecutor _openConnection() {
return driftDatabase(name: 'solar_network_local_db'); return driftDatabase(
name: 'solar_network_local_db',
web: DriftWebOptions(
sqlite3Wasm: Uri.parse('sqlite3.wasm'),
driftWorker: Uri.parse('drift_worker.dart.js'),
),
);
} }
static Future<int> getDatabaseSize() async { static Future<int> getDatabaseSize() async {

View File

@@ -12,7 +12,7 @@ class MessagesFetchingProvider extends GetxController {
final AuthProvider auth = Get.find(); final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) return null; if (auth.isAuthorized.isFalse) return null;
final client = auth.configureClient('messaging'); final client = await auth.configureClient('messaging');
final resp = await client.get( final resp = await client.get(
'/whats-new?pivot=$pivot&take=$take', '/whats-new?pivot=$pivot&take=$take',
@@ -33,7 +33,7 @@ class MessagesFetchingProvider extends GetxController {
final AuthProvider auth = Get.find(); final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) return null; if (auth.isAuthorized.isFalse) return null;
final client = auth.configureClient('messaging'); final client = await auth.configureClient('messaging');
final resp = await client.get( final resp = await client.get(
'/channels/$scope/${channel.alias}/events/$id', '/channels/$scope/${channel.alias}/events/$id',
@@ -51,19 +51,13 @@ class MessagesFetchingProvider extends GetxController {
Future<(List<Event>, int)?> fetchRemoteEvents( Future<(List<Event>, int)?> fetchRemoteEvents(
Channel channel, Channel channel,
String scope, { String scope, {
required int depth,
bool Function(List<Event> items)? onBrake,
take = 10, take = 10,
offset = 0, offset = 0,
}) async { }) async {
if (depth <= 0) {
return null;
}
final AuthProvider auth = Get.find(); final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) return null; if (auth.isAuthorized.isFalse) return null;
final client = auth.configureClient('messaging'); final client = await auth.configureClient('messaging');
final resp = await client.get( final resp = await client.get(
'/channels/$scope/${channel.alias}/events?take=$take&offset=$offset', '/channels/$scope/${channel.alias}/events?take=$take&offset=$offset',
@@ -77,21 +71,7 @@ class MessagesFetchingProvider extends GetxController {
final result = final result =
response.data?.map((e) => Event.fromJson(e)).toList() ?? List.empty(); response.data?.map((e) => Event.fromJson(e)).toList() ?? List.empty();
if (onBrake != null && onBrake(result)) { return (result, response.count);
return (result, response.count);
}
final expandResult = (await fetchRemoteEvents(
channel,
scope,
depth: depth - 1,
take: take,
offset: offset + result.length,
))
?.$1 ??
List.empty();
return ([...result, ...expandResult], response.count);
} }
Future<LocalMessageEventTableData> receiveEvent(Event remote) async { Future<LocalMessageEventTableData> receiveEvent(Event remote) async {
@@ -149,24 +129,24 @@ class MessagesFetchingProvider extends GetxController {
return await receiveEvent(remoteRecord); return await receiveEvent(remoteRecord);
} }
Future<LocalMessageEventTableData?> getEventFromLocal(int id) async {
final database = Get.find<DatabaseProvider>().database;
final localRecord = await (database.select(database.localMessageEventTable)
..where((x) => x.id.equals(id)))
.getSingleOrNull();
return localRecord;
}
/// Pull the remote events to local database /// Pull the remote events to local database
Future<(List<Event>, int)?> pullRemoteEvents(Channel channel, Future<(List<Event>, int)?> pullRemoteEvents(Channel channel,
{String scope = 'global', depth = 10, offset = 0}) async { {String scope = 'global', take = 10, offset = 0}) async {
final database = Get.find<DatabaseProvider>().database; final database = Get.find<DatabaseProvider>().database;
final lastOne = await (database.select(database.localMessageEventTable)
..where((x) => x.channelId.equals(channel.id))
..orderBy([(t) => OrderingTerm.desc(t.id)])
..limit(1))
.getSingleOrNull();
final data = await fetchRemoteEvents( final data = await fetchRemoteEvents(
channel, channel,
scope, scope,
depth: depth,
offset: offset, offset: offset,
onBrake: (items) { take: take,
return items.any((x) => x.id == lastOne?.id);
},
); );
if (data != null) { if (data != null) {
await database.batch((batch) { await database.batch((batch) {
@@ -185,11 +165,13 @@ class MessagesFetchingProvider extends GetxController {
return data; return data;
} }
Future<List<LocalMessageEventTableData>> listEvents(Channel channel) async { Future<List<LocalMessageEventTableData>> listEvents(Channel channel,
{required int take, int offset = 0}) async {
final database = Get.find<DatabaseProvider>().database; final database = Get.find<DatabaseProvider>().database;
return await (database.select(database.localMessageEventTable) return await (database.select(database.localMessageEventTable)
..where((x) => x.channelId.equals(channel.id)) ..where((x) => x.channelId.equals(channel.id))
..orderBy([(t) => OrderingTerm.desc(t.id)])) ..orderBy([(t) => OrderingTerm.desc(t.id)])
..limit(take, offset: offset))
.get(); .get();
} }

View File

@@ -12,7 +12,7 @@ class LinkExpandProvider extends GetxController {
log('[LinkExpander] Expanding link... $url'); log('[LinkExpander] Expanding link... $url');
final target = utf8.fuse(base64).encode(url); final target = utf8.fuse(base64).encode(url);
if (_cachedResponse.containsKey(target)) return _cachedResponse[target]; if (_cachedResponse.containsKey(target)) return _cachedResponse[target];
final client = ServiceFinder.configureClient('dealer'); final client = await ServiceFinder.configureClient('dealer');
final resp = await client.get('/api/links/$target'); final resp = await client.get('/api/links/$target');
if (resp.statusCode != 200) { if (resp.statusCode != 200) {
log('Unable to expand link ($url), status: ${resp.statusCode}, response: ${resp.body}'); log('Unable to expand link ($url), status: ${resp.statusCode}, response: ${resp.body}');

View File

@@ -26,21 +26,21 @@ class RelationshipProvider extends GetxController {
return _friends.any((x) => x.relatedId == account.id); return _friends.any((x) => x.relatedId == account.id);
} }
Future<Response> listRelation() { Future<Response> listRelation() async {
final AuthProvider auth = Get.find(); final AuthProvider auth = Get.find();
final client = auth.configureClient('auth'); final client = await auth.configureClient('auth');
return client.get('/users/me/relations'); return client.get('/users/me/relations');
} }
Future<Response> listRelationWithStatus(int status) { Future<Response> listRelationWithStatus(int status) async {
final AuthProvider auth = Get.find(); final AuthProvider auth = Get.find();
final client = auth.configureClient('auth'); final client = await auth.configureClient('auth');
return client.get('/users/me/relations?status=$status'); return client.get('/users/me/relations?status=$status');
} }
Future<Response> makeFriend(String username) async { Future<Response> makeFriend(String username) async {
final AuthProvider auth = Get.find(); final AuthProvider auth = Get.find();
final client = auth.configureClient('auth'); final client = await auth.configureClient('auth');
final resp = await client.post('/users/me/relations?related=$username', {}); final resp = await client.post('/users/me/relations?related=$username', {});
if (resp.statusCode != 200) { if (resp.statusCode != 200) {
throw RequestException(resp); throw RequestException(resp);
@@ -52,7 +52,7 @@ class RelationshipProvider extends GetxController {
Future<Response> handleRelation( Future<Response> handleRelation(
Relationship relationship, bool doAccept) async { Relationship relationship, bool doAccept) async {
final AuthProvider auth = Get.find(); final AuthProvider auth = Get.find();
final client = auth.configureClient('auth'); final client = await auth.configureClient('auth');
final resp = await client.post( final resp = await client.post(
'/users/me/relations/${relationship.relatedId}/${doAccept ? 'accept' : 'decline'}', '/users/me/relations/${relationship.relatedId}/${doAccept ? 'accept' : 'decline'}',
{}, {},
@@ -66,7 +66,7 @@ class RelationshipProvider extends GetxController {
Future<Response> editRelation(Relationship relationship, int status) async { Future<Response> editRelation(Relationship relationship, int status) async {
final AuthProvider auth = Get.find(); final AuthProvider auth = Get.find();
final client = auth.configureClient('auth'); final client = await auth.configureClient('auth');
final resp = await client.patch( final resp = await client.patch(
'/users/me/relations/${relationship.relatedId}', '/users/me/relations/${relationship.relatedId}',
{'status': status}, {'status': status},

View File

@@ -1,34 +1,48 @@
import 'dart:async';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:solian/models/pagination.dart'; import 'package:solian/exceptions/request.dart';
import 'package:solian/models/stickers.dart'; import 'package:solian/models/stickers.dart';
import 'package:solian/services.dart'; import 'package:solian/services.dart';
class StickerProvider extends GetxController { class StickerProvider extends GetxController {
final RxMap<String, String> aliasImageMapping = RxMap(); final RxMap<String, FutureOr<Sticker?>> stickerCache = RxMap();
final RxList<Sticker> availableStickers = RxList.empty(growable: true);
Future<void> refreshAvailableStickers() async { Future<Sticker?> getStickerByAlias(String alias) {
availableStickers.clear(); if (stickerCache.containsKey(alias)) {
aliasImageMapping.clear(); return Future.value(stickerCache[alias]);
final client = ServiceFinder.configureClient('files');
final resp = await client.get(
'/stickers/manifest?take=100',
);
if (resp.statusCode == 200) {
final result = PaginationResult.fromJson(resp.body);
final out = result.data?.map((e) => StickerPack.fromJson(e)).toList();
if (out == null) return;
for (final pack in out) {
for (final sticker in (pack.stickers ?? List<Sticker>.empty())) {
sticker.pack = pack;
aliasImageMapping[sticker.textPlaceholder.toUpperCase()] =
sticker.imageUrl;
availableStickers.add(sticker);
}
}
} }
availableStickers.refresh();
stickerCache[alias] = Future(() async {
final client = await ServiceFinder.configureClient('files');
final resp = await client.get(
'/stickers/lookup/$alias',
);
if (resp.statusCode != 200) {
if (resp.statusCode == 404) {
stickerCache[alias] = null;
}
throw RequestException(resp);
}
return Sticker.fromJson(resp.body);
}).then((result) {
stickerCache[alias] = result;
return result;
});
return Future.value(stickerCache[alias]);
}
Future<List<Sticker>> searchStickerByAlias(String alias) async {
final client = await ServiceFinder.configureClient('files');
final resp = await client.get(
'/stickers/lookup?probe=$alias',
);
if (resp.statusCode != 200) {
throw RequestException(resp);
}
return List<Sticker>.from(resp.body.map((x) => Sticker.fromJson(x)));
} }
} }

View File

@@ -0,0 +1,46 @@
import 'package:get/get.dart';
import 'package:solian/exceptions/request.dart';
import 'package:solian/exceptions/unauthorized.dart';
import 'package:solian/models/subscription.dart';
import 'package:solian/providers/auth.dart';
class SubscriptionProvider extends GetxController {
Future<Subscription?> getSubscriptionOnUser(int userId) async {
final auth = Get.find<AuthProvider>();
if (!auth.isAuthorized.value) throw const UnauthorizedException();
final client = await auth.configureClient('co');
final resp = await client.get('/subscriptions/users/$userId');
if (resp.statusCode == 404) {
return null;
} else if (resp.statusCode != 200) {
throw RequestException(resp);
}
return Subscription.fromJson(resp.body);
}
Future<Subscription> subscribeToUser(int userId) async {
final auth = Get.find<AuthProvider>();
if (!auth.isAuthorized.value) throw const UnauthorizedException();
final client = await auth.configureClient('co');
final resp = await client.post('/subscriptions/users/$userId', {});
if (resp.statusCode != 200) {
throw RequestException(resp);
}
return Subscription.fromJson(resp.body);
}
Future<void> unsubscribeFromUser(int userId) async {
final auth = Get.find<AuthProvider>();
if (!auth.isAuthorized.value) throw const UnauthorizedException();
final client = await auth.configureClient('co');
final resp = await client.delete('/subscriptions/users/$userId');
if (resp.statusCode != 200) {
throw RequestException(resp);
}
}
}

View File

@@ -138,7 +138,7 @@ class WebSocketProvider extends GetxController {
final AuthProvider auth = Get.find(); final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) return; if (auth.isAuthorized.isFalse) return;
final client = auth.configureClient('auth'); final client = await auth.configureClient('auth');
final resp = await client.get('/notifications?skip=0&take=100'); final resp = await client.get('/notifications?skip=0&take=100');
if (resp.statusCode == 200) { if (resp.statusCode == 200) {
@@ -152,6 +152,8 @@ class WebSocketProvider extends GetxController {
} }
Future<void> registerPushNotifications() async { Future<void> registerPushNotifications() async {
if (PlatformInfo.isWeb) return;
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
if (prefs.getBool('service_background_notification') == true) { if (prefs.getBool('service_background_notification') == true) {
log('Background notification service has been enabled, skip register push notifications'); log('Background notification service has been enabled, skip register push notifications');
@@ -180,7 +182,7 @@ class WebSocketProvider extends GetxController {
} }
log('Device Push Token is $token'); log('Device Push Token is $token');
final client = auth.configureClient('auth'); final client = await auth.configureClient('auth');
final resp = await client.post('/notifications/subscribe', { final resp = await client.post('/notifications/subscribe', {
'provider': provider, 'provider': provider,

View File

@@ -7,7 +7,8 @@ import 'package:solian/screens/account.dart';
import 'package:solian/screens/account/friend.dart'; import 'package:solian/screens/account/friend.dart';
import 'package:solian/screens/account/personalize.dart'; import 'package:solian/screens/account/personalize.dart';
import 'package:solian/screens/account/profile_page.dart'; import 'package:solian/screens/account/profile_page.dart';
import 'package:solian/screens/account/stickers.dart'; import 'package:solian/screens/auth/signin.dart';
import 'package:solian/screens/auth/signup.dart';
import 'package:solian/screens/channel/channel_chat.dart'; import 'package:solian/screens/channel/channel_chat.dart';
import 'package:solian/screens/channel/channel_detail.dart'; import 'package:solian/screens/channel/channel_detail.dart';
import 'package:solian/screens/channel/channel_organize.dart'; import 'package:solian/screens/channel/channel_organize.dart';
@@ -236,14 +237,6 @@ abstract class AppRouter {
name: 'accountFriend', name: 'accountFriend',
builder: (context, state) => const FriendScreen(), builder: (context, state) => const FriendScreen(),
), ),
GoRoute(
path: '/account/stickers',
name: 'accountStickers',
builder: (context, state) => TitleShell(
state: state,
child: const StickerScreen(),
),
),
GoRoute( GoRoute(
path: '/account/personalize', path: '/account/personalize',
name: 'accountProfile', name: 'accountProfile',
@@ -259,6 +252,24 @@ abstract class AppRouter {
name: state.pathParameters['name']!, name: state.pathParameters['name']!,
), ),
), ),
GoRoute(
path: '/auth/sign-in',
name: 'signin',
builder: (context, state) => TitleShell(
state: state,
isCenteredTitle: true,
child: const SignInScreen(),
),
),
GoRoute(
path: '/auth/sign-up',
name: 'signup',
builder: (context, state) => TitleShell(
state: state,
isCenteredTitle: true,
child: const SignUpScreen(),
),
),
], ],
); );
} }

View File

@@ -6,8 +6,6 @@ import 'package:solian/providers/auth.dart';
import 'package:solian/providers/account_status.dart'; import 'package:solian/providers/account_status.dart';
import 'package:solian/providers/relation.dart'; import 'package:solian/providers/relation.dart';
import 'package:solian/router.dart'; import 'package:solian/router.dart';
import 'package:solian/screens/auth/signin.dart';
import 'package:solian/screens/auth/signup.dart';
import 'package:solian/widgets/account/account_heading.dart'; import 'package:solian/widgets/account/account_heading.dart';
import 'package:solian/widgets/sized_container.dart'; import 'package:solian/widgets/sized_container.dart';
import 'package:badges/badges.dart' as badges; import 'package:badges/badges.dart' as badges;
@@ -47,11 +45,6 @@ class _AccountScreenState extends State<AccountScreen> {
'accountFriend'.tr, 'accountFriend'.tr,
'accountFriend', 'accountFriend',
), ),
(
const Icon(Icons.emoji_symbols),
'accountStickers'.tr,
'accountStickers',
),
]; ];
final AuthProvider auth = Get.find(); final AuthProvider auth = Get.find();
@@ -73,13 +66,7 @@ class _AccountScreenState extends State<AccountScreen> {
title: 'signin'.tr, title: 'signin'.tr,
caption: 'signinCaption'.tr, caption: 'signinCaption'.tr,
onTap: () { onTap: () {
showModalBottomSheet( AppRouter.instance.pushNamed('signin').then((val) async {
useRootNavigator: true,
isDismissible: false,
isScrollControlled: true,
context: context,
builder: (context) => const SignInPopup(),
).then((val) async {
if (val == true) { if (val == true) {
await auth.refreshUserProfile(); await auth.refreshUserProfile();
} }
@@ -94,13 +81,7 @@ class _AccountScreenState extends State<AccountScreen> {
title: 'signup'.tr, title: 'signup'.tr,
caption: 'signupCaption'.tr, caption: 'signupCaption'.tr,
onTap: () { onTap: () {
showModalBottomSheet( AppRouter.instance.pushNamed('signup').then((_) {
useRootNavigator: true,
isDismissible: false,
isScrollControlled: true,
context: context,
builder: (context) => const SignUpPopup(),
).then((_) {
setState(() {}); setState(() {});
}); });
}, },
@@ -219,7 +200,6 @@ class _ActionCard extends StatelessWidget {
final Function onTap; final Function onTap;
const _ActionCard({ const _ActionCard({
super.key,
required this.onTap, required this.onTap,
required this.title, required this.title,
required this.caption, required this.caption,

View File

@@ -31,7 +31,7 @@ class _NotificationScreenState extends State<NotificationScreen> {
} }
if (markList.isNotEmpty) { if (markList.isNotEmpty) {
final client = auth.configureClient('auth'); final client = await auth.configureClient('auth');
await client.put('/notifications/read', {'messages': markList}); await client.put('/notifications/read', {'messages': markList});
} }
@@ -53,7 +53,7 @@ class _NotificationScreenState extends State<NotificationScreen> {
setState(() => _isBusy = true); setState(() => _isBusy = true);
final client = auth.configureClient('auth'); final client = await auth.configureClient('auth');
await client.put('/notifications/read/${element.id}', {}); await client.put('/notifications/read/${element.id}', {});

View File

@@ -126,7 +126,7 @@ class _PersonalizeScreenState extends State<PersonalizeScreen> {
return; return;
} }
final client = auth.configureClient('auth'); final client = await auth.configureClient('auth');
final resp = await client.put( final resp = await client.put(
'/users/me/$position', '/users/me/$position',
@@ -148,7 +148,7 @@ class _PersonalizeScreenState extends State<PersonalizeScreen> {
setState(() => _isBusy = true); setState(() => _isBusy = true);
final client = auth.configureClient('auth'); final client = await auth.configureClient('auth');
_birthday?.toIso8601String(); _birthday?.toIso8601String();
final resp = await client.put( final resp = await client.put(

View File

@@ -8,8 +8,10 @@ import 'package:solian/models/account.dart';
import 'package:solian/models/attachment.dart'; import 'package:solian/models/attachment.dart';
import 'package:solian/models/pagination.dart'; import 'package:solian/models/pagination.dart';
import 'package:solian/models/post.dart'; import 'package:solian/models/post.dart';
import 'package:solian/models/subscription.dart';
import 'package:solian/providers/account_status.dart'; import 'package:solian/providers/account_status.dart';
import 'package:solian/providers/relation.dart'; import 'package:solian/providers/relation.dart';
import 'package:solian/providers/subscription.dart';
import 'package:solian/services.dart'; import 'package:solian/services.dart';
import 'package:solian/theme.dart'; import 'package:solian/theme.dart';
import 'package:solian/widgets/account/account_avatar.dart'; import 'package:solian/widgets/account/account_avatar.dart';
@@ -37,16 +39,25 @@ class _AccountProfilePageState extends State<AccountProfilePage> {
bool _isBusy = true; bool _isBusy = true;
bool _isMakingFriend = false; bool _isMakingFriend = false;
bool _isSubscribing = false;
bool _showMature = false; bool _showMature = false;
Account? _userinfo; Account? _userinfo;
Subscription? _subscription;
List<Post> _pinnedPosts = List.empty(); List<Post> _pinnedPosts = List.empty();
int _totalUpvote = 0, _totalDownvote = 0; int _totalUpvote = 0, _totalDownvote = 0;
Future<void> _getSubscription() async {
setState(() => _isSubscribing = true);
_subscription = await Get.find<SubscriptionProvider>()
.getSubscriptionOnUser(_userinfo!.id);
setState(() => _isSubscribing = false);
}
Future<void> _getUserinfo() async { Future<void> _getUserinfo() async {
setState(() => _isBusy = true); setState(() => _isBusy = true);
var client = ServiceFinder.configureClient('auth'); var client = await ServiceFinder.configureClient('auth');
var resp = await client.get('/users/${widget.name}'); var resp = await client.get('/users/${widget.name}');
if (resp.statusCode != 200) { if (resp.statusCode != 200) {
context.showErrorDialog(resp.bodyString).then((_) { context.showErrorDialog(resp.bodyString).then((_) {
@@ -56,7 +67,7 @@ class _AccountProfilePageState extends State<AccountProfilePage> {
_userinfo = Account.fromJson(resp.body); _userinfo = Account.fromJson(resp.body);
} }
client = ServiceFinder.configureClient('interactive'); client = await ServiceFinder.configureClient('interactive');
resp = await client.get('/users/${widget.name}'); resp = await client.get('/users/${widget.name}');
if (resp.statusCode != 200) { if (resp.statusCode != 200) {
context.showErrorDialog(resp.bodyString).then((_) { context.showErrorDialog(resp.bodyString).then((_) {
@@ -70,8 +81,8 @@ class _AccountProfilePageState extends State<AccountProfilePage> {
setState(() => _isBusy = false); setState(() => _isBusy = false);
} }
Future<void> getPinnedPosts() async { Future<void> _getPinnedPosts() async {
final client = ServiceFinder.configureClient('interactive'); final client = await ServiceFinder.configureClient('interactive');
final resp = await client.get('/users/${widget.name}/pin'); final resp = await client.get('/users/${widget.name}/pin');
if (resp.statusCode != 200) { if (resp.statusCode != 200) {
context.showErrorDialog(resp.bodyString).then((_) { context.showErrorDialog(resp.bodyString).then((_) {
@@ -95,7 +106,7 @@ class _AccountProfilePageState extends State<AccountProfilePage> {
_relationshipProvider = Get.find(); _relationshipProvider = Get.find();
_postController = PostListController(author: widget.name); _postController = PostListController(author: widget.name);
_albumPagingController.addPageRequestListener((pageKey) async { _albumPagingController.addPageRequestListener((pageKey) async {
final client = ServiceFinder.configureClient('files'); final client = await ServiceFinder.configureClient('files');
final resp = await client.get( final resp = await client.get(
'/attachments?take=10&offset=$pageKey&author=${widget.name}&original=true', '/attachments?take=10&offset=$pageKey&author=${widget.name}&original=true',
); );
@@ -115,8 +126,10 @@ class _AccountProfilePageState extends State<AccountProfilePage> {
} }
}); });
_getUserinfo(); _getUserinfo().then((_) {
getPinnedPosts(); _getSubscription();
_getPinnedPosts();
});
} }
Widget _buildStatisticsEntry(String label, String content) { Widget _buildStatisticsEntry(String label, String content) {
@@ -180,6 +193,40 @@ class _AccountProfilePageState extends State<AccountProfilePage> {
], ],
), ),
), ),
if (_userinfo != null && _subscription == null)
OutlinedButton(
style: const ButtonStyle(
visualDensity:
VisualDensity(horizontal: -4, vertical: -2),
),
onPressed: _isSubscribing
? null
: () async {
setState(() => _isSubscribing = true);
_subscription =
await Get.find<SubscriptionProvider>()
.subscribeToUser(_userinfo!.id);
setState(() => _isSubscribing = false);
},
child: Text('subscribe'.tr),
)
else if (_userinfo != null)
OutlinedButton(
style: const ButtonStyle(
visualDensity:
VisualDensity(horizontal: -4, vertical: -2),
),
onPressed: _isSubscribing
? null
: () async {
setState(() => _isSubscribing = true);
await Get.find<SubscriptionProvider>()
.unsubscribeFromUser(_userinfo!.id);
_subscription = null;
setState(() => _isSubscribing = false);
},
child: Text('unsubscribe'.tr),
),
if (_userinfo != null && if (_userinfo != null &&
!_relationshipProvider.hasFriend(_userinfo!)) !_relationshipProvider.hasFriend(_userinfo!))
IconButton( IconButton(
@@ -245,7 +292,7 @@ class _AccountProfilePageState extends State<AccountProfilePage> {
RefreshIndicator( RefreshIndicator(
onRefresh: () => Future.wait([ onRefresh: () => Future.wait([
_postController.reloadAllOver(), _postController.reloadAllOver(),
getPinnedPosts(), _getPinnedPosts(),
]), ]),
child: CustomScrollView(slivers: [ child: CustomScrollView(slivers: [
SliverToBoxAdapter( SliverToBoxAdapter(
@@ -302,6 +349,7 @@ class _AccountProfilePageState extends State<AccountProfilePage> {
isClickable: true, isClickable: true,
isNestedClickable: true, isNestedClickable: true,
isShowEmbed: true, isShowEmbed: true,
showFeaturedReply: true,
onUpdate: () { onUpdate: () {
_postController.reloadAllOver(); _postController.reloadAllOver();
}, },

View File

@@ -1,186 +0,0 @@
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:get/get.dart';
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
import 'package:solian/models/pagination.dart';
import 'package:solian/models/stickers.dart';
import 'package:solian/providers/auth.dart';
import 'package:solian/providers/stickers.dart';
import 'package:solian/services.dart';
import 'package:solian/widgets/auto_cache_image.dart';
import 'package:solian/widgets/stickers/sticker_uploader.dart';
class StickerScreen extends StatefulWidget {
const StickerScreen({super.key});
@override
State<StickerScreen> createState() => _StickerScreenState();
}
class _StickerScreenState extends State<StickerScreen> {
final PagingController<int, StickerPack> _pagingController =
PagingController(firstPageKey: 0);
Future<bool> _promptDelete(Sticker item, String prefix) async {
final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) return false;
final confirm = await showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('stickerDeletionConfirm'.tr),
content: Text(
'stickerDeletionConfirmCaption'.trParams({
'name': ':${'$prefix${item.alias}'.camelCase}:',
}),
),
actions: <Widget>[
TextButton(
onPressed: () => Navigator.pop(context),
child: Text('cancel'.tr),
),
TextButton(
onPressed: () => Navigator.pop(context, true),
child: Text('confirm'.tr),
),
],
),
);
if (confirm != true) return false;
final client = auth.configureClient('files');
final resp = await client.delete('/stickers/${item.id}');
return resp.statusCode == 200;
}
Future<bool?> _promptUploadSticker({Sticker? edit}) {
return showDialog(
context: context,
builder: (context) => StickerUploadDialog(
edit: edit,
),
);
}
Widget _buildEmoteEntry(Sticker item, String prefix) {
final imageUrl = ServiceFinder.buildUrl(
'files',
'/attachments/${item.attachment.rid}',
);
return ListTile(
title: Text(item.name),
subtitle: Text(item.textWarpedPlaceholder),
contentPadding: const EdgeInsets.only(left: 16, right: 14),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Icons.edit_square),
onPressed: () {
_promptUploadSticker(edit: item).then((value) {
if (value == true) _pagingController.refresh();
});
},
),
IconButton(
icon: const Icon(Icons.delete),
onPressed: () {
_promptDelete(item, prefix).then((value) {
if (value == true) _pagingController.refresh();
});
},
),
],
),
leading: AutoCacheImage(
imageUrl,
width: 28,
height: 28,
noErrorWidget: true,
),
);
}
@override
void initState() {
final AuthProvider auth = Get.find();
final name = auth.userProfile.value!['name'];
_pagingController.addPageRequestListener((pageKey) async {
final client = ServiceFinder.configureClient('files');
final resp = await client.get(
'/stickers/manifest?take=10&offset=$pageKey&author=$name',
);
if (resp.statusCode == 200) {
final result = PaginationResult.fromJson(resp.body);
final out = result.data?.map((e) => StickerPack.fromJson(e)).toList();
if (out != null && result.data!.length >= 10) {
_pagingController.appendPage(out, pageKey + out.length);
} else if (out != null) {
_pagingController.appendLastPage(out);
}
} else {
_pagingController.error = resp.bodyString;
}
});
super.initState();
}
@override
void dispose() {
final StickerProvider sticker = Get.find();
sticker.refreshAvailableStickers();
_pagingController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () {
_promptUploadSticker().then((value) {
if (value == true) _pagingController.refresh();
});
},
),
body: RefreshIndicator(
onRefresh: () => Future.sync(() => _pagingController.refresh()),
child: CustomScrollView(
slivers: [
PagedSliverList<int, StickerPack>(
pagingController: _pagingController,
builderDelegate: PagedChildBuilderDelegate(
itemBuilder: (BuildContext context, item, int index) {
return ExpansionTile(
title: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(item.name),
const Gap(6),
Badge(
label: Text('#${item.id}'),
)
],
),
subtitle: Text(
item.description,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
children: item.stickers?.map((x) {
x.pack = item;
return _buildEmoteEntry(x, item.prefix);
}).toList() ??
List.empty(),
);
},
),
),
],
),
),
);
}
}

View File

@@ -1,28 +1,45 @@
import 'package:animations/animations.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:gap/gap.dart'; import 'package:gap/gap.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:protocol_handler/protocol_handler.dart';
import 'package:solian/background.dart'; import 'package:solian/background.dart';
import 'package:solian/exceptions/request.dart';
import 'package:solian/exts.dart'; import 'package:solian/exts.dart';
import 'package:solian/providers/websocket.dart'; import 'package:solian/models/auth.dart';
import 'package:solian/providers/auth.dart'; import 'package:solian/providers/auth.dart';
import 'package:solian/providers/websocket.dart';
import 'package:solian/services.dart'; import 'package:solian/services.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:solian/widgets/sized_container.dart';
import 'package:url_launcher/url_launcher_string.dart';
class SignInPopup extends StatefulWidget { class SignInScreen extends StatefulWidget {
const SignInPopup({super.key}); const SignInScreen({super.key});
@override @override
State<SignInPopup> createState() => _SignInPopupState(); State<SignInScreen> createState() => _SignInScreenState();
} }
class _SignInPopupState extends State<SignInPopup> with ProtocolListener { class _SignInScreenState extends State<SignInScreen> {
bool _isBusy = false; bool _isBusy = false;
AuthTicket? _currentTicket;
List<AuthFactor>? _factors;
int? _factorPicked;
int? _factorPickedType;
int _period = 0;
final _usernameController = TextEditingController(); final _usernameController = TextEditingController();
final _passwordController = TextEditingController(); final _passwordController = TextEditingController();
final Map<int, (String label, IconData icon, bool isOtp)> _factorLabelMap = {
0: ('authFactorPassword'.tr, Icons.password, false),
1: ('authFactorEmail'.tr, Icons.email, true),
};
Color get _unFocusColor =>
Theme.of(context).colorScheme.onSurface.withOpacity(0.75);
void _requestResetPassword() async { void _requestResetPassword() async {
final username = _usernameController.value.text; final username = _usernameController.value.text;
if (username.isEmpty) { if (username.isEmpty) {
@@ -32,7 +49,7 @@ class _SignInPopupState extends State<SignInPopup> with ProtocolListener {
setState(() => _isBusy = true); setState(() => _isBusy = true);
final client = ServiceFinder.configureClient('auth'); final client = await ServiceFinder.configureClient('auth');
final lookupResp = await client.get('/users/lookup?probe=$username'); final lookupResp = await client.get('/users/lookup?probe=$username');
if (lookupResp.statusCode != 200) { if (lookupResp.statusCode != 200) {
context.showErrorDialog(lookupResp.bodyString); context.showErrorDialog(lookupResp.bodyString);
@@ -53,156 +70,383 @@ class _SignInPopupState extends State<SignInPopup> with ProtocolListener {
context.showModalDialog('done'.tr, 'signinResetPasswordSent'.tr); context.showModalDialog('done'.tr, 'signinResetPasswordSent'.tr);
} }
void _performAction() async { void _performNewTicket() async {
final AuthProvider auth = Get.find();
final username = _usernameController.value.text; final username = _usernameController.value.text;
final password = _passwordController.value.text; if (username.isEmpty) return;
if (username.isEmpty || password.isEmpty) return;
final client = await ServiceFinder.configureClient('auth');
setState(() => _isBusy = true); setState(() => _isBusy = true);
try { try {
await auth.signin(context, username, password); // Create ticket
await Future.delayed(const Duration(milliseconds: 250), () async { final resp = await client.post('/auth', {
await auth.refreshAuthorizeStatus(); 'username': username,
await auth.refreshUserProfile();
}); });
} on RiskyAuthenticateException catch (e) { if (resp.statusCode != 200) {
showDialog( throw RequestException(resp);
context: context, } else {
builder: (context) { final result = AuthResult.fromJson(resp.body);
return AlertDialog( _currentTicket = result.ticket;
title: Text('riskDetection'.tr), }
content: Text('signinRiskDetected'.tr),
actions: [ // Pull factors
TextButton( final factorResp = await client.get('/auth/factors',
child: Text('next'.tr), query: {'ticketId': _currentTicket!.id.toString()});
onPressed: () { if (factorResp.statusCode != 200) {
const redirect = 'solink://auth?status=done'; throw RequestException(factorResp);
launchUrlString( } else {
ServiceFinder.buildUrl('capital', final result = List<AuthFactor>.from(
'/auth/mfa?redirect_uri=$redirect&ticketId=${e.ticketId}'), factorResp.body.map((x) => AuthFactor.fromJson(x)),
mode: LaunchMode.inAppWebView, );
); _factors = result;
Navigator.pop(context); }
},
) setState(() => _period++);
],
);
},
);
return;
} catch (e) { } catch (e) {
context.showErrorDialog(e); context.showErrorDialog(e);
return; return;
} finally { } finally {
setState(() => _isBusy = false); setState(() => _isBusy = false);
} }
Get.find<WebSocketProvider>().registerPushNotifications();
autoConfigureBackgroundNotificationService();
autoStartBackgroundNotificationService();
Navigator.pop(context, true);
} }
@override void _performGetFactorCode() async {
void initState() { if (_factorPicked == null) return;
protocolHandler.addListener(this);
super.initState(); final client = await ServiceFinder.configureClient('auth');
setState(() => _isBusy = true);
try {
// Request one-time-password code
final resp = await client.post('/auth/factors/$_factorPicked', {});
if (resp.statusCode != 200 && resp.statusCode != 204) {
throw RequestException(resp);
} else {
_factorPickedType = _factors!
.where(
(x) => x.id == _factorPicked,
)
.first
.type;
}
setState(() => _period++);
} catch (e) {
context.showErrorDialog(e);
return;
} finally {
setState(() => _isBusy = false);
}
} }
@override void _performCheckTicket() async {
void dispose() { final AuthProvider auth = Get.find();
protocolHandler.removeListener(this);
super.dispose(); final password = _passwordController.value.text;
if (password.isEmpty) return;
final client = await ServiceFinder.configureClient('auth');
setState(() => _isBusy = true);
try {
// Check ticket
final resp = await client.request('/auth', 'PATCH', body: {
'ticket_id': _currentTicket!.id,
'factor_id': _factorPicked!,
'code': password,
});
if (resp.statusCode != 200) {
throw RequestException(resp);
}
final result = AuthResult.fromJson(resp.body);
_currentTicket = result.ticket;
// Finish sign in if possible
if (result.isFinished) {
await auth.signin(context, _currentTicket!);
await Future.delayed(const Duration(milliseconds: 250), () async {
await auth.refreshAuthorizeStatus();
await auth.refreshUserProfile();
Get.find<WebSocketProvider>().registerPushNotifications();
autoConfigureBackgroundNotificationService();
autoStartBackgroundNotificationService();
Navigator.pop(context, true);
});
} else {
// Skip the first step
_factorPicked = null;
_factorPickedType = null;
setState(() => _period += 2);
}
} catch (e) {
context.showErrorDialog(e);
return;
} finally {
setState(() => _isBusy = false);
}
} }
@override void _previousStep() {
void onProtocolUrlReceived(String url) { assert(_period > 0);
final uri = url.replaceFirst('solink://', ''); switch (_period % 3) {
if (uri == 'auth?status=done') { case 1:
closeInAppWebView(); _currentTicket = null;
_performAction(); _factors = null;
_factorPicked = null;
case 2:
_passwordController.clear();
_factorPickedType = null;
default:
setState(() => _period--);
} }
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SizedBox( return Material(
height: MediaQuery.of(context).size.height * 0.9, color: Theme.of(context).colorScheme.surface,
child: Center( child: CenteredContainer(
child: Container( maxWidth: 360,
width: MediaQuery.of(context).size.width * 0.6, child: PageTransitionSwitcher(
constraints: const BoxConstraints(maxWidth: 360), transitionBuilder: (
child: Column( Widget child,
mainAxisSize: MainAxisSize.min, Animation<double> primaryAnimation,
crossAxisAlignment: CrossAxisAlignment.start, Animation<double> secondaryAnimation,
children: [ ) {
ClipRRect( return SharedAxisTransition(
borderRadius: const BorderRadius.all(Radius.circular(8)), animation: primaryAnimation,
child: Image.asset('assets/logo.png', width: 64, height: 64), secondaryAnimation: secondaryAnimation,
).paddingOnly(bottom: 4), transitionType: SharedAxisTransitionType.horizontal,
Text( child: child,
'signinGreeting'.tr, );
style: const TextStyle( },
fontSize: 28, child: switch (_period % 3) {
fontWeight: FontWeight.w900, 1 => Column(
), key: const ValueKey<int>(1),
).paddingOnly(left: 4, bottom: 16), mainAxisAlignment: MainAxisAlignment.center,
TextField( crossAxisAlignment: CrossAxisAlignment.start,
autocorrect: false,
enableSuggestions: false,
controller: _usernameController,
autofillHints: const [AutofillHints.username],
decoration: InputDecoration(
isDense: true,
border: const OutlineInputBorder(),
labelText: 'username'.tr,
),
onTapOutside: (_) =>
FocusManager.instance.primaryFocus?.unfocus(),
),
const Gap(12),
TextField(
obscureText: true,
autocorrect: false,
enableSuggestions: false,
autofillHints: const [AutofillHints.password],
controller: _passwordController,
decoration: InputDecoration(
isDense: true,
border: const OutlineInputBorder(),
labelText: 'password'.tr,
),
onTapOutside: (_) =>
FocusManager.instance.primaryFocus?.unfocus(),
onSubmitted: (_) => _performAction(),
),
const Gap(12),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
TextButton( ClipRRect(
onPressed: _isBusy ? null : () => _requestResetPassword(), borderRadius: const BorderRadius.all(Radius.circular(8)),
style: TextButton.styleFrom(foregroundColor: Colors.grey), child:
child: Text('forgotPassword'.tr), Image.asset('assets/logo.png', width: 64, height: 64),
), ).paddingOnly(bottom: 8, left: 4),
TextButton( Text(
onPressed: _isBusy ? null : () => _performAction(), 'signinPickFactor'.tr,
child: Row( style: const TextStyle(
mainAxisSize: MainAxisSize.min, fontSize: 28,
children: [ fontWeight: FontWeight.w900,
Text('next'.tr),
const Icon(Icons.chevron_right),
],
), ),
).paddingOnly(left: 4, bottom: 16),
Card(
margin: const EdgeInsets.symmetric(vertical: 4),
child: Column(
children: _factors
?.map(
(x) => CheckboxListTile(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(8),
),
),
secondary: Icon(
_factorLabelMap[x.type]?.$2 ??
Icons.question_mark,
),
title: Text(
_factorLabelMap[x.type]?.$1 ?? 'unknown'.tr,
),
enabled: !_currentTicket!.factorTrail
.contains(x.id),
value: _factorPicked == x.id,
onChanged: (value) {
if (value == true) {
setState(() => _factorPicked = x.id);
}
},
),
)
.toList() ??
List.empty(),
),
),
Text(
'signinMultiFactor'.trParams(
{'n': _currentTicket!.stepRemain.toString()},
),
style: TextStyle(color: _unFocusColor, fontSize: 12),
).paddingOnly(left: 16, right: 16),
const Gap(12),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
TextButton(
onPressed: (_isBusy || _period > 1)
? null
: () => _previousStep(),
style:
TextButton.styleFrom(foregroundColor: Colors.grey),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.chevron_left),
Text('prev'.tr),
],
),
),
TextButton(
onPressed:
_isBusy ? null : () => _performGetFactorCode(),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text('next'.tr),
const Icon(Icons.chevron_right),
],
),
),
],
), ),
], ],
), ),
], 2 => Column(
), key: const ValueKey<int>(2),
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(8)),
child:
Image.asset('assets/logo.png', width: 64, height: 64),
).paddingOnly(bottom: 8, left: 4),
Text(
'signinEnterPassword'.tr,
style: const TextStyle(
fontSize: 28,
fontWeight: FontWeight.w900,
),
).paddingOnly(left: 4, bottom: 16),
TextField(
autocorrect: false,
enableSuggestions: false,
controller: _passwordController,
obscureText: true,
autofillHints: [
(_factorLabelMap[_factorPickedType]?.$3 ?? true)
? AutofillHints.password
: AutofillHints.oneTimeCode
],
decoration: InputDecoration(
isDense: true,
border: const OutlineInputBorder(),
labelText:
(_factorLabelMap[_factorPickedType]?.$3 ?? true)
? 'passwordOneTime'.tr
: 'password'.tr,
helperText:
(_factorLabelMap[_factorPickedType]?.$3 ?? true)
? 'passwordOneTimeInputHint'.tr
: 'passwordInputHint'.tr,
),
onTapOutside: (_) =>
FocusManager.instance.primaryFocus?.unfocus(),
onSubmitted: _isBusy ? null : (_) => _performCheckTicket(),
),
const Gap(12),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
TextButton(
onPressed: _isBusy ? null : () => _previousStep(),
style:
TextButton.styleFrom(foregroundColor: Colors.grey),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.chevron_left),
Text('prev'.tr),
],
),
),
TextButton(
onPressed: _isBusy ? null : () => _performCheckTicket(),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text('next'.tr),
const Icon(Icons.chevron_right),
],
),
),
],
),
],
),
_ => Column(
key: const ValueKey<int>(0),
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(8)),
child:
Image.asset('assets/logo.png', width: 64, height: 64),
).paddingOnly(bottom: 8, left: 4),
Text(
'signinGreeting'.tr,
style: const TextStyle(
fontSize: 28,
fontWeight: FontWeight.w900,
),
).paddingOnly(left: 4, bottom: 16),
TextField(
autocorrect: false,
enableSuggestions: false,
controller: _usernameController,
autofillHints: const [AutofillHints.username],
decoration: InputDecoration(
isDense: true,
border: const OutlineInputBorder(),
labelText: 'username'.tr,
helperText: 'usernameInputHint'.tr,
),
onTapOutside: (_) =>
FocusManager.instance.primaryFocus?.unfocus(),
onSubmitted: _isBusy ? null : (_) => _performNewTicket(),
),
const Gap(12),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
TextButton(
onPressed:
_isBusy ? null : () => _requestResetPassword(),
style:
TextButton.styleFrom(foregroundColor: Colors.grey),
child: Text('forgotPassword'.tr),
),
TextButton(
onPressed: _isBusy ? null : () => _performNewTicket(),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text('next'.tr),
const Icon(Icons.chevron_right),
],
),
),
],
),
],
),
},
), ),
), ),
); );

View File

@@ -3,15 +3,16 @@ import 'package:gap/gap.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:solian/exts.dart'; import 'package:solian/exts.dart';
import 'package:solian/services.dart'; import 'package:solian/services.dart';
import 'package:solian/widgets/sized_container.dart';
class SignUpPopup extends StatefulWidget { class SignUpScreen extends StatefulWidget {
const SignUpPopup({super.key}); const SignUpScreen({super.key});
@override @override
State<SignUpPopup> createState() => _SignUpPopupState(); State<SignUpScreen> createState() => _SignUpScreenState();
} }
class _SignUpPopupState extends State<SignUpPopup> { class _SignUpScreenState extends State<SignUpScreen> {
final _emailController = TextEditingController(); final _emailController = TextEditingController();
final _usernameController = TextEditingController(); final _usernameController = TextEditingController();
final _nicknameController = TextEditingController(); final _nicknameController = TextEditingController();
@@ -27,7 +28,7 @@ class _SignUpPopupState extends State<SignUpPopup> {
nickname.isEmpty || nickname.isEmpty ||
password.isEmpty) return; password.isEmpty) return;
final client = ServiceFinder.configureClient('auth'); final client = await ServiceFinder.configureClient('auth');
final resp = await client.post('/users', { final resp = await client.post('/users', {
'name': username, 'name': username,
@@ -61,100 +62,97 @@ class _SignUpPopupState extends State<SignUpPopup> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SizedBox( return Material(
height: MediaQuery.of(context).size.height * 0.9, color: Theme.of(context).colorScheme.surface,
child: Center( child: CenteredContainer(
child: Container( maxWidth: 360,
width: MediaQuery.of(context).size.width * 0.6, child: Column(
constraints: const BoxConstraints(maxWidth: 360), mainAxisSize: MainAxisSize.min,
child: Column( crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min, children: [
crossAxisAlignment: CrossAxisAlignment.start, ClipRRect(
children: [ borderRadius: const BorderRadius.all(Radius.circular(8)),
ClipRRect( child: Image.asset('assets/logo.png', width: 64, height: 64),
borderRadius: const BorderRadius.all(Radius.circular(8)), ).paddingOnly(bottom: 8, left: 4),
child: Image.asset('assets/logo.png', width: 64, height: 64), Text(
).paddingOnly(bottom: 4), 'signupGreeting'.tr,
Text( style: const TextStyle(
'signupGreeting'.tr, fontSize: 28,
style: const TextStyle( fontWeight: FontWeight.w900,
fontSize: 28,
fontWeight: FontWeight.w900,
),
).paddingOnly(left: 4, bottom: 16),
TextField(
autocorrect: false,
enableSuggestions: false,
controller: _usernameController,
autofillHints: const [AutofillHints.username],
decoration: InputDecoration(
isDense: true,
border: const OutlineInputBorder(),
labelText: 'username'.tr,
),
onTapOutside: (_) =>
FocusManager.instance.primaryFocus?.unfocus(),
), ),
const Gap(12), ).paddingOnly(left: 4, bottom: 16),
TextField( TextField(
autocorrect: false, autocorrect: false,
enableSuggestions: false, enableSuggestions: false,
controller: _nicknameController, controller: _usernameController,
autofillHints: const [AutofillHints.nickname], autofillHints: const [AutofillHints.username],
decoration: InputDecoration( decoration: InputDecoration(
isDense: true, isDense: true,
border: const OutlineInputBorder(), border: const OutlineInputBorder(),
labelText: 'nickname'.tr, labelText: 'username'.tr,
),
onTapOutside: (_) =>
FocusManager.instance.primaryFocus?.unfocus(),
), ),
const Gap(12), onTapOutside: (_) =>
TextField( FocusManager.instance.primaryFocus?.unfocus(),
autocorrect: false, ),
enableSuggestions: false, const Gap(12),
controller: _emailController, TextField(
autofillHints: const [AutofillHints.email], autocorrect: false,
decoration: InputDecoration( enableSuggestions: false,
isDense: true, controller: _nicknameController,
border: const OutlineInputBorder(), autofillHints: const [AutofillHints.nickname],
labelText: 'email'.tr, decoration: InputDecoration(
), isDense: true,
onTapOutside: (_) => border: const OutlineInputBorder(),
FocusManager.instance.primaryFocus?.unfocus(), labelText: 'nickname'.tr,
), ),
const Gap(12), onTapOutside: (_) =>
TextField( FocusManager.instance.primaryFocus?.unfocus(),
obscureText: true, ),
autocorrect: false, const Gap(12),
enableSuggestions: false, TextField(
autofillHints: const [AutofillHints.password], autocorrect: false,
controller: _passwordController, enableSuggestions: false,
decoration: InputDecoration( controller: _emailController,
isDense: true, autofillHints: const [AutofillHints.email],
border: const OutlineInputBorder(), decoration: InputDecoration(
labelText: 'password'.tr, isDense: true,
), border: const OutlineInputBorder(),
onTapOutside: (_) => labelText: 'email'.tr,
FocusManager.instance.primaryFocus?.unfocus(),
onSubmitted: (_) => performAction(context),
), ),
const Gap(16), onTapOutside: (_) =>
Align( FocusManager.instance.primaryFocus?.unfocus(),
alignment: Alignment.centerRight, ),
child: TextButton( const Gap(12),
child: Row( TextField(
mainAxisSize: MainAxisSize.min, obscureText: true,
children: [ autocorrect: false,
Text('next'.tr), enableSuggestions: false,
const Icon(Icons.chevron_right), autofillHints: const [AutofillHints.password],
], controller: _passwordController,
), decoration: InputDecoration(
onPressed: () => performAction(context), isDense: true,
border: const OutlineInputBorder(),
labelText: 'password'.tr,
),
onTapOutside: (_) =>
FocusManager.instance.primaryFocus?.unfocus(),
onSubmitted: (_) => performAction(context),
),
const Gap(16),
Align(
alignment: Alignment.centerRight,
child: TextButton(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text('next'.tr),
const Icon(Icons.chevron_right),
],
), ),
) onPressed: () => performAction(context),
], ),
), )
],
), ),
), ),
); );

View File

@@ -97,7 +97,6 @@ class _ChannelChatScreenState extends State<ChannelChatScreen>
setState(() => _ongoingCall = Call.fromJson(resp.body)); setState(() => _ongoingCall = Call.fromJson(resp.body));
} }
} catch (e) { } catch (e) {
print((e as dynamic).stackTrace);
context.showErrorDialog(e); context.showErrorDialog(e);
} }

View File

@@ -79,7 +79,7 @@ class _ChannelDetailScreenState extends State<ChannelDetailScreen> {
setState(() => _isBusy = true); setState(() => _isBusy = true);
final client = auth.configureClient('messaging'); final client = await auth.configureClient('messaging');
final resp = await client final resp = await client
.put('/channels/${widget.realm}/${widget.channel.alias}/members/me', { .put('/channels/${widget.realm}/${widget.channel.alias}/members/me', {

View File

@@ -102,7 +102,7 @@ class _ChatScreenState extends State<ChatScreen> {
body: Obx(() { body: Obx(() {
if (auth.isAuthorized.isFalse) { if (auth.isAuthorized.isFalse) {
return SigninRequiredOverlay( return SigninRequiredOverlay(
onSignedIn: () => _channels.refreshAvailableChannel(), onDone: () => _channels.refreshAvailableChannel(),
); );
} }
@@ -125,7 +125,11 @@ class _ChatScreenState extends State<ChatScreen> {
child: Obx( child: Obx(
() => ChannelListWidget( () => ChannelListWidget(
noCategory: true, noCategory: true,
channels: _channels.directChannels, channels: List.from([
..._channels.groupChannels
.where((x) => x.realmId == null),
..._channels.directChannels
]),
selfId: selfId, selfId: selfId,
useReplace: true, useReplace: true,
), ),

View File

@@ -379,6 +379,7 @@ class _DashboardScreenState extends State<DashboardScreen> {
isClickable: true, isClickable: true,
isShowEmbed: true, isShowEmbed: true,
isNestedClickable: true, isNestedClickable: true,
showFeaturedReply: true,
onUpdate: (_) { onUpdate: (_) {
_pullPosts(); _pullPosts();
}, },

View File

@@ -151,7 +151,7 @@ class _FeedScreenState extends State<FeedScreen>
); );
} else { } else {
return SigninRequiredOverlay( return SigninRequiredOverlay(
onSignedIn: () => _postController.reloadAllOver(), onDone: () => _postController.reloadAllOver(),
); );
} }
}), }),

View File

@@ -3,6 +3,7 @@ import 'package:get/get.dart';
import 'package:solian/exts.dart'; import 'package:solian/exts.dart';
import 'package:solian/models/post.dart'; import 'package:solian/models/post.dart';
import 'package:solian/providers/content/posts.dart'; import 'package:solian/providers/content/posts.dart';
import 'package:solian/providers/last_read.dart';
import 'package:solian/widgets/posts/post_item.dart'; import 'package:solian/widgets/posts/post_item.dart';
import 'package:solian/widgets/posts/post_replies.dart'; import 'package:solian/widgets/posts/post_replies.dart';
@@ -26,6 +27,7 @@ class _PostDetailScreenState extends State<PostDetailScreen> {
Future<Post?> getDetail() async { Future<Post?> getDetail() async {
if (widget.post != null) { if (widget.post != null) {
item = widget.post; item = widget.post;
Get.find<LastReadProvider>().feedLastReadAt = item?.id;
return widget.post; return widget.post;
} }
@@ -38,6 +40,8 @@ class _PostDetailScreenState extends State<PostDetailScreen> {
context.showErrorDialog(e).then((_) => Navigator.pop(context)); context.showErrorDialog(e).then((_) => Navigator.pop(context));
} }
Get.find<LastReadProvider>().feedLastReadAt = item?.id;
return item; return item;
} }

View File

@@ -75,7 +75,7 @@ class _PostPublishScreenState extends State<PostPublishScreen> {
setState(() => _isBusy = true); setState(() => _isBusy = true);
final client = auth.configureClient('interactive'); final client = await auth.configureClient('interactive');
Response resp; Response resp;
if (widget.edit != null) { if (widget.edit != null) {

View File

@@ -84,7 +84,7 @@ class _RealmListScreenState extends State<RealmListScreen> {
body: Obx(() { body: Obx(() {
if (auth.isAuthorized.isFalse) { if (auth.isAuthorized.isFalse) {
return SigninRequiredOverlay( return SigninRequiredOverlay(
onSignedIn: () => _getRealms(), onDone: () => _getRealms(),
); );
} }

View File

@@ -43,7 +43,7 @@ class _RealmOrganizeScreenState extends State<RealmOrganizeScreen> {
setState(() => _isBusy = true); setState(() => _isBusy = true);
final client = auth.configureClient('auth'); final client = await auth.configureClient('auth');
final payload = { final payload = {
'alias': _aliasController.value.text.toLowerCase(), 'alias': _aliasController.value.text.toLowerCase(),

View File

@@ -1,28 +1,58 @@
import 'package:device_info_plus/device_info_plus.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:solian/platform.dart';
abstract class ServiceFinder { abstract class ServiceFinder {
static const bool devFlag = false; static const bool devFlag = false;
static const String dealerUrl = static const String dealerUrl =
devFlag ? 'http://localhost:8442' : 'https://api.sn.solsynth.dev'; devFlag ? 'http://localhost:8442' : 'https://api.sn.solsynth.dev';
static const String capitalUrl =
devFlag ? 'http://localhost:8444' : 'https://solsynth.dev';
static String buildUrl(String serviceName, String? append) { static String buildUrl(String serviceName, String? append) {
append ??= ''; append ??= '';
if (serviceName == 'dealer') { if (serviceName == 'dealer') {
return '$dealerUrl$append'; return '$dealerUrl$append';
} else if (serviceName == 'capital') {
return '$capitalUrl$append';
} }
return '$dealerUrl/cgi/$serviceName$append'; return '$dealerUrl/cgi/$serviceName$append';
} }
static GetConnect configureClient(String serviceName, static Future<String> getUserAgent() async {
{timeout = const Duration(seconds: 5)}) { final String platformInfo;
if (PlatformInfo.isAndroid) {
final deviceInfo = await DeviceInfoPlugin().androidInfo;
platformInfo =
'Android; ${deviceInfo.brand} ${deviceInfo.model}; ${deviceInfo.id}';
} else if (PlatformInfo.isIOS) {
final deviceInfo = await DeviceInfoPlugin().iosInfo;
platformInfo = 'iOS; ${deviceInfo.model}; ${deviceInfo.name}';
} else if (PlatformInfo.isMacOS) {
final deviceInfo = await DeviceInfoPlugin().macOsInfo;
platformInfo = 'MacOS; ${deviceInfo.model}; ${deviceInfo.hostName}';
} else if (PlatformInfo.isWindows) {
final deviceInfo = await DeviceInfoPlugin().windowsInfo;
platformInfo =
'Windows NT; ${deviceInfo.productName}; ${deviceInfo.computerName}';
} else if (PlatformInfo.isLinux) {
final deviceInfo = await DeviceInfoPlugin().linuxInfo;
platformInfo = 'Linux; ${deviceInfo.prettyName}';
} else if (PlatformInfo.isWeb) {
final deviceInfo = await DeviceInfoPlugin().webBrowserInfo;
platformInfo = 'Web; ${deviceInfo.vendor}';
} else {
platformInfo = 'Unknown';
}
final packageInfo = await PackageInfo.fromPlatform();
return 'Solian/${packageInfo.version}+${packageInfo.buildNumber} ($platformInfo)';
}
static Future<GetConnect> configureClient(String serviceName,
{timeout = const Duration(seconds: 5)}) async {
final client = GetConnect( final client = GetConnect(
timeout: timeout, timeout: timeout,
userAgent: 'Solian/1.1', userAgent: await getUserAgent(),
sendUserAgent: true, sendUserAgent: true,
); );
client.httpClient.baseUrl = buildUrl(serviceName, null); client.httpClient.baseUrl = buildUrl(serviceName, null);

View File

@@ -24,6 +24,8 @@ class TitleShell extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
assert(state != null || title != null);
return Scaffold( return Scaffold(
appBar: showAppBar appBar: showAppBar
? AppBar( ? AppBar(

View File

@@ -26,7 +26,7 @@ class _AccountProfilePopupState extends State<AccountProfilePopup> {
setState(() => _isBusy = true); setState(() => _isBusy = true);
try { try {
final client = ServiceFinder.configureClient('auth'); final client = await ServiceFinder.configureClient('auth');
final resp = await client.get('/users/${widget.name}'); final resp = await client.get('/users/${widget.name}');
if (resp.statusCode == 200) { if (resp.statusCode == 200) {
setState(() { setState(() {

View File

@@ -36,16 +36,13 @@ class _AccountSelectorState extends State<AccountSelector> {
_revertSelectedUsers() async { _revertSelectedUsers() async {
if (widget.initialSelection?.isEmpty ?? true) return; if (widget.initialSelection?.isEmpty ?? true) return;
final client = ServiceFinder.configureClient('auth'); final client = await ServiceFinder.configureClient('auth');
final idQuery = widget.initialSelection!.join(','); final idQuery = widget.initialSelection!.join(',');
final resp = await client.get('/users?id=$idQuery'); final resp = await client.get('/users?id=$idQuery');
setState(() { setState(() {
_selectedUsers.addAll( _selectedUsers.addAll(
resp.body resp.body.map((e) => Account.fromJson(e)).toList().cast<Account>(),
.map((e) => Account.fromJson(e))
.toList()
.cast<Account>(),
); );
}); });
} }
@@ -73,7 +70,7 @@ class _AccountSelectorState extends State<AccountSelector> {
if (_probeController.text.isEmpty) return; if (_probeController.text.isEmpty) return;
final client = auth.configureClient('auth'); final client = await auth.configureClient('auth');
final resp = await client.get( final resp = await client.get(
'/users/search?probe=${_probeController.text}', '/users/search?probe=${_probeController.text}',
); );
@@ -156,7 +153,8 @@ class _AccountSelectorState extends State<AccountSelector> {
} }
setState(() { setState(() {
final idx = _selectedUsers.indexWhere((x) => x.id == element.id); final idx = _selectedUsers
.indexWhere((x) => x.id == element.id);
if (idx != -1) { if (idx != -1) {
_selectedUsers.removeAt(idx); _selectedUsers.removeAt(idx);
} else { } else {

View File

@@ -1,49 +1,43 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:gap/gap.dart'; import 'package:gap/gap.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:solian/screens/auth/signin.dart'; import 'package:solian/router.dart';
import 'package:solian/widgets/sized_container.dart';
class SigninRequiredOverlay extends StatelessWidget { class SigninRequiredOverlay extends StatelessWidget {
final Function onSignedIn; final Function onDone;
const SigninRequiredOverlay({super.key, required this.onSignedIn}); const SigninRequiredOverlay({super.key, required this.onDone});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GestureDetector( return GestureDetector(
child: Center( child: CenteredContainer(
child: Container( maxWidth: 280,
constraints: const BoxConstraints(maxWidth: 280), child: Column(
child: Column( mainAxisSize: MainAxisSize.min,
mainAxisSize: MainAxisSize.min, children: [
children: [ const Icon(
const Icon( Icons.login,
Icons.login, size: 48,
size: 48, ),
), const Gap(8),
const Gap(8), Text(
Text( 'signinRequired'.tr,
'signinRequired'.tr, style: Theme.of(context).textTheme.titleLarge,
style: Theme.of(context).textTheme.titleLarge, textAlign: TextAlign.center,
textAlign: TextAlign.center, ),
), Text(
Text( 'signinRequiredHint'.tr,
'signinRequiredHint'.tr, style: Theme.of(context).textTheme.bodyMedium,
style: Theme.of(context).textTheme.bodyMedium, textAlign: TextAlign.center,
textAlign: TextAlign.center, ),
), ],
],
),
), ),
), ),
onTap: () { onTap: () {
showModalBottomSheet( AppRouter.instance.pushNamed('signin').then((value) {
useRootNavigator: true, if (value != null) onDone();
isScrollControlled: true,
context: context,
builder: (context) => const SignInPopup(),
).then((value) {
if (value != null) onSignedIn();
}); });
}, },
); );

View File

@@ -312,7 +312,9 @@ class _AttachmentEditorPopupState extends State<AttachmentEditorPopup> {
} }
Widget _buildQueueEntry(AttachmentUploadTask element, int index) { Widget _buildQueueEntry(AttachmentUploadTask element, int index) {
final extName = extension(element.file.path).substring(1); final extName = element.file.name.contains('.')
? extension(element.file.name).substring(1)
: '';
final canBeCrop = ['png', 'jpg', 'jpeg', 'gif'].contains(extName); final canBeCrop = ['png', 'jpg', 'jpeg', 'gif'].contains(extName);
return Container( return Container(

View File

@@ -93,14 +93,14 @@ class _AttachmentEditorThumbnailDialogState
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AlertDialog( return AlertDialog(
title: Text('postThumbnail'.tr), title: Text('attachmentThumbnail'.tr),
content: Column( content: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Card( Card(
margin: EdgeInsets.zero, margin: EdgeInsets.zero,
child: ListTile( child: ListTile(
title: Text('postThumbnailAttachmentNew'.tr), title: Text('attachmentThumbnailAttachmentNew'.tr),
contentPadding: const EdgeInsets.only(left: 12, right: 9), contentPadding: const EdgeInsets.only(left: 12, right: 9),
trailing: const Icon(Icons.chevron_right), trailing: const Icon(Icons.chevron_right),
shape: const RoundedRectangleBorder( shape: const RoundedRectangleBorder(
@@ -122,7 +122,7 @@ class _AttachmentEditorThumbnailDialogState
isDense: true, isDense: true,
border: const OutlineInputBorder(), border: const OutlineInputBorder(),
prefixText: '#', prefixText: '#',
labelText: 'postThumbnailAttachment'.tr, labelText: 'attachmentThumbnailAttachment'.tr,
), ),
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
), ),

View File

@@ -282,6 +282,8 @@ class _AttachmentItemVideoState extends State<_AttachmentItemVideo> {
children: [ children: [
Text( Text(
widget.item.alt, widget.item.alt,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle( style: const TextStyle(
shadows: labelShadows, shadows: labelShadows,
color: Colors.white, color: Colors.white,
@@ -447,6 +449,8 @@ class _AttachmentItemAudioState extends State<_AttachmentItemAudio> {
children: [ children: [
Text( Text(
widget.item.alt, widget.item.alt,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle( style: const TextStyle(
shadows: labelShadows, shadows: labelShadows,
color: Colors.white, color: Colors.white,

View File

@@ -29,10 +29,10 @@ class _ChannelDeletionDialogState extends State<ChannelDeletionDialog> {
setState(() => _isBusy = true); setState(() => _isBusy = true);
final client = auth.configureClient('messaging'); final client = await auth.configureClient('messaging');
final resp = await client final resp =
.delete('/channels/${widget.realm}/${widget.channel.id}'); await client.delete('/channels/${widget.realm}/${widget.channel.id}');
if (resp.statusCode != 200) { if (resp.statusCode != 200) {
context.showErrorDialog(resp.bodyString); context.showErrorDialog(resp.bodyString);
} else if (Navigator.canPop(context)) { } else if (Navigator.canPop(context)) {
@@ -48,7 +48,7 @@ class _ChannelDeletionDialogState extends State<ChannelDeletionDialog> {
setState(() => _isBusy = true); setState(() => _isBusy = true);
final client = auth.configureClient('messaging'); final client = await auth.configureClient('messaging');
final resp = await client.delete( final resp = await client.delete(
'/channels/${widget.realm}/${widget.channel.alias}/members/me', '/channels/${widget.realm}/${widget.channel.alias}/members/me',
@@ -69,11 +69,11 @@ class _ChannelDeletionDialogState extends State<ChannelDeletionDialog> {
? 'channelDeletionConfirm'.tr ? 'channelDeletionConfirm'.tr
: 'channelLeaveConfirm'.tr), : 'channelLeaveConfirm'.tr),
content: Text( content: Text(
widget.isOwned ? widget.isOwned
'channelDeletionConfirmCaption' ? 'channelDeletionConfirmCaption'
.trParams({'channel': '#${widget.channel.alias}'}) : .trParams({'channel': '#${widget.channel.alias}'})
'channelLeaveConfirmCaption' : 'channelLeaveConfirmCaption'
.trParams({'channel': '#${widget.channel.alias}'}), .trParams({'channel': '#${widget.channel.alias}'}),
), ),
actions: <Widget>[ actions: <Widget>[
TextButton( TextButton(

View File

@@ -1,5 +1,8 @@
import 'dart:math';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:gap/gap.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:solian/controllers/chat_events_controller.dart'; import 'package:solian/controllers/chat_events_controller.dart';
import 'package:solian/models/channel.dart'; import 'package:solian/models/channel.dart';
@@ -213,6 +216,7 @@ class _ChannelListWidgetState extends State<ChannelListWidget> {
return _buildEntry(element); return _buildEntry(element);
}, },
), ),
SliverGap(max(16, MediaQuery.of(context).padding.bottom)),
], ],
); );
} }

View File

@@ -39,7 +39,7 @@ class _ChannelMemberListPopupState extends State<ChannelMemberListPopup> {
void getMembers() async { void getMembers() async {
setState(() => _isBusy = true); setState(() => _isBusy = true);
final client = ServiceFinder.configureClient('messaging'); final client = await ServiceFinder.configureClient('messaging');
final resp = await client final resp = await client
.get('/channels/${widget.realm}/${widget.channel.alias}/members'); .get('/channels/${widget.realm}/${widget.channel.alias}/members');
@@ -75,7 +75,7 @@ class _ChannelMemberListPopupState extends State<ChannelMemberListPopup> {
setState(() => _isBusy = true); setState(() => _isBusy = true);
final client = auth.configureClient('messaging'); final client = await auth.configureClient('messaging');
final resp = await client.post( final resp = await client.post(
'/channels/${widget.realm}/${widget.channel.alias}/members', '/channels/${widget.realm}/${widget.channel.alias}/members',
@@ -96,7 +96,7 @@ class _ChannelMemberListPopupState extends State<ChannelMemberListPopup> {
setState(() => _isBusy = true); setState(() => _isBusy = true);
final client = auth.configureClient('messaging'); final client = await auth.configureClient('messaging');
final resp = await client.request( final resp = await client.request(
'/channels/${widget.realm}/${widget.channel.alias}/members', '/channels/${widget.realm}/${widget.channel.alias}/members',

View File

@@ -33,7 +33,7 @@ class _ChatCallButtonState extends State<ChatCallButton> {
final AuthProvider auth = Get.find(); final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) return; if (auth.isAuthorized.isFalse) return;
final client = auth.configureClient('messaging'); final client = await auth.configureClient('messaging');
setState(() => _isBusy = true); setState(() => _isBusy = true);
@@ -57,7 +57,7 @@ class _ChatCallButtonState extends State<ChatCallButton> {
final AuthProvider auth = Get.find(); final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) return; if (auth.isAuthorized.isFalse) return;
final client = auth.configureClient('messaging'); final client = await auth.configureClient('messaging');
setState(() => _isBusy = true); setState(() => _isBusy = true);

View File

@@ -30,7 +30,7 @@ class _ChatEventDeletionDialogState extends State<ChatEventDeletionDialog> {
final AuthProvider auth = Get.find(); final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) return; if (auth.isAuthorized.isFalse) return;
final client = auth.configureClient('messaging'); final client = await auth.configureClient('messaging');
setState(() => _isBusy = true); setState(() => _isBusy = true);

View File

@@ -92,10 +92,13 @@ class ChatEventList extends StatelessWidget {
); );
}, },
onFetchData: () { onFetchData: () {
chatController.loadEvents( if (chatController.currentEvents.length <
chatController.channel!, chatController.totalEvents.value) {
chatController.scope!, chatController.loadEvents(
); chatController.channel!,
chatController.scope!,
);
}
}, },
); );
}), }),

View File

@@ -118,7 +118,7 @@ class _ChatMessageInputState extends State<ChatMessageInput> {
final mentionedUserNames = _findMentionedUsers(_textController.text); final mentionedUserNames = _findMentionedUsers(_textController.text);
final mentionedUserIds = List<int>.empty(growable: true); final mentionedUserIds = List<int>.empty(growable: true);
var client = auth.configureClient('auth'); var client = await auth.configureClient('auth');
if (mentionedUserNames.isNotEmpty) { if (mentionedUserNames.isNotEmpty) {
resp = await client.get('/users?name=${mentionedUserNames.join(',')}'); resp = await client.get('/users?name=${mentionedUserNames.join(',')}');
if (resp.statusCode != 200) { if (resp.statusCode != 200) {
@@ -131,7 +131,7 @@ class _ChatMessageInputState extends State<ChatMessageInput> {
} }
} }
client = auth.configureClient('messaging'); client = await auth.configureClient('messaging');
if (_textController.text.trim().isEmpty && _attachments.isEmpty) return; if (_textController.text.trim().isEmpty && _attachments.isEmpty) return;
@@ -405,12 +405,9 @@ class _ChatMessageInputState extends State<ChatMessageInput> {
if (emojiMatch != null) { if (emojiMatch != null) {
final StickerProvider stickers = Get.find(); final StickerProvider stickers = Get.find();
final emoteSearch = emojiMatch[2]!; final emoteSearch = emojiMatch[2]!;
return stickers.availableStickers final result = await stickers
.where( .searchStickerByAlias(emoteSearch.substring(1));
(x) => x.textWarpedPlaceholder return result
.toUpperCase()
.contains(emoteSearch.toUpperCase()),
)
.map( .map(
(x) => ChatMessageSuggestion( (x) => ChatMessageSuggestion(
type: 'emotes', type: 'emotes',
@@ -432,7 +429,7 @@ class _ChatMessageInputState extends State<ChatMessageInput> {
final userSearch = userMatch[1]!.toLowerCase(); final userSearch = userMatch[1]!.toLowerCase();
final AuthProvider auth = Get.find(); final AuthProvider auth = Get.find();
final client = auth.configureClient('auth'); final client = await auth.configureClient('auth');
final resp = await client.get( final resp = await client.get(
'/users/search?probe=$userSearch', '/users/search?probe=$userSearch',
); );

View File

@@ -89,7 +89,6 @@ class MarkdownTextContent extends StatelessWidget {
case 'stickers': case 'stickers':
double radius = 8; double radius = 8;
final StickerProvider sticker = Get.find(); final StickerProvider sticker = Get.find();
url = sticker.aliasImageMapping[segments[1].toUpperCase()]!;
if (emojiMatch.length <= 1 && isOnlyEmoji) { if (emojiMatch.length <= 1 && isOnlyEmoji) {
width = 128; width = 128;
height = 128; height = 128;
@@ -106,11 +105,20 @@ class MarkdownTextContent extends StatelessWidget {
borderRadius: BorderRadius.all(Radius.circular(radius)), borderRadius: BorderRadius.all(Radius.circular(radius)),
child: Container( child: Container(
color: Theme.of(context).colorScheme.surfaceContainer, color: Theme.of(context).colorScheme.surfaceContainer,
child: AutoCacheImage( child: FutureBuilder(
url, future: sticker.getStickerByAlias(segments[1]),
width: width, builder: (context, snapshot) {
height: height, if (!snapshot.hasData) {
fit: fit, return const Center(child: CircularProgressIndicator());
}
return AutoCacheImage(
snapshot.data!.imageUrl,
width: width,
height: height,
fit: fit,
noErrorWidget: true,
);
},
), ),
), ),
).paddingSymmetric(vertical: 4); ).paddingSymmetric(vertical: 4);
@@ -172,7 +180,8 @@ class _CustomEmoteInlineSyntax extends InlineSyntax {
bool onMatch(markdown.InlineParser parser, Match match) { bool onMatch(markdown.InlineParser parser, Match match) {
final StickerProvider sticker = Get.find(); final StickerProvider sticker = Get.find();
final alias = match[1]!.toUpperCase(); final alias = match[1]!.toUpperCase();
if (sticker.aliasImageMapping[alias] == null) { if (sticker.stickerCache.containsKey(alias) &&
sticker.stickerCache[alias] == null) {
parser.advanceBy(1); parser.advanceBy(1);
return false; return false;
} }

View File

@@ -138,7 +138,14 @@ class _AppNavigationDrawerState extends State<AppNavigationDrawer>
Builder( Builder(
builder: (context) { builder: (context) {
if (_accountStatus == null) { if (_accountStatus == null) {
return Text('loading'.tr).paddingOnly(left: 16); return Text(
'loading'.tr,
maxLines: 1,
overflow: TextOverflow.fade,
style: TextStyle(
color: _unFocusColor,
),
).paddingOnly(left: 16);
} }
final info = StatusProvider.determineStatus( final info = StatusProvider.determineStatus(
_accountStatus!, _accountStatus!,
@@ -274,6 +281,9 @@ class _AppNavigationDrawerState extends State<AppNavigationDrawer>
color: Theme.of(context).colorScheme.surface, color: Theme.of(context).colorScheme.surface,
child: AppNavigationRegion( child: AppNavigationRegion(
isCollapsed: _isCollapsed, isCollapsed: _isCollapsed,
onSelected: () {
_closeDrawer();
},
), ),
), ),
), ),

View File

@@ -1,3 +1,4 @@
import 'package:animations/animations.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:solian/models/realm.dart'; import 'package:solian/models/realm.dart';
@@ -10,10 +11,12 @@ import 'package:solian/widgets/channel/channel_list.dart';
class AppNavigationRegion extends StatefulWidget { class AppNavigationRegion extends StatefulWidget {
final bool isCollapsed; final bool isCollapsed;
final Function onSelected;
const AppNavigationRegion({ const AppNavigationRegion({
super.key, super.key,
this.isCollapsed = false, this.isCollapsed = false,
required this.onSelected,
}); });
@override @override
@@ -124,16 +127,12 @@ class _AppNavigationRegionState extends State<AppNavigationRegion> {
final NavigationStateProvider navState = Get.find(); final NavigationStateProvider navState = Get.find();
return Obx( return Obx(
() => AnimatedSwitcher( () => PageTransitionSwitcher(
switchInCurve: Curves.fastOutSlowIn, transitionBuilder: (child, animation, secondaryAnimation) {
switchOutCurve: Curves.fastOutSlowIn, return SharedAxisTransition(
duration: const Duration(milliseconds: 300), animation: animation,
transitionBuilder: (child, animation) { secondaryAnimation: secondaryAnimation,
return SlideTransition( transitionType: SharedAxisTransitionType.horizontal,
position: Tween<Offset>(
begin: const Offset(1.0, 0.0),
end: Offset.zero,
).animate(animation),
child: Material( child: Material(
color: Theme.of(context).colorScheme.surface, color: Theme.of(context).colorScheme.surface,
child: child, child: child,
@@ -204,6 +203,7 @@ class _AppNavigationRegionState extends State<AppNavigationRegion> {
isCollapsed: widget.isCollapsed, isCollapsed: widget.isCollapsed,
selfId: auth.userProfile.value!['id'], selfId: auth.userProfile.value!['id'],
noCategory: true, noCategory: true,
onSelected: (_) => widget.onSelected(),
), ),
), ),
), ),

View File

@@ -1,5 +1,4 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:solian/controllers/post_editor_controller.dart'; import 'package:solian/controllers/post_editor_controller.dart';
import 'package:solian/widgets/attachments/attachment_editor.dart'; import 'package:solian/widgets/attachments/attachment_editor.dart';
@@ -58,18 +57,25 @@ class _PostEditorThumbnailDialogState extends State<PostEditorThumbnailDialog> {
content: Column( content: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
ListTile( Card(
title: Text('postThumbnailAttachmentNew'.tr), margin: EdgeInsets.zero,
contentPadding: const EdgeInsets.only(left: 16, right: 13), child: ListTile(
trailing: const Icon(Icons.chevron_right), title: Text('postThumbnailAttachmentNew'.tr),
shape: const RoundedRectangleBorder( contentPadding: const EdgeInsets.only(left: 12, right: 9),
borderRadius: BorderRadius.all(Radius.circular(8)), trailing: const Icon(Icons.chevron_right),
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(8)),
),
onTap: () {
_promptUploadNewAttachment();
},
), ),
onTap: () {
_promptUploadNewAttachment();
},
), ),
const Gap(8), const Row(children: <Widget>[
Expanded(child: Divider()),
Text('OR'),
Expanded(child: Divider()),
]).paddingOnly(top: 12, bottom: 16, left: 16, right: 16),
TextField( TextField(
controller: _attachmentController, controller: _attachmentController,
decoration: InputDecoration( decoration: InputDecoration(

View File

@@ -35,7 +35,7 @@ class _TagsFieldState extends State<TagsField> {
Future<List<String>?> _searchTags(String probe) async { Future<List<String>?> _searchTags(String probe) async {
_currentSearchProbe = probe; _currentSearchProbe = probe;
final client = ServiceFinder.configureClient('interactive'); final client = await ServiceFinder.configureClient('interactive');
final resp = await client.get( final resp = await client.get(
'/tags?take=10&probe=$_currentSearchProbe', '/tags?take=10&probe=$_currentSearchProbe',
); );

View File

@@ -192,7 +192,7 @@ class _PostActionState extends State<PostAction> {
: 'unpinPost'.tr, : 'unpinPost'.tr,
), ),
onTap: () async { onTap: () async {
final client = Get.find<AuthProvider>() final client = await Get.find<AuthProvider>()
.configureClient('interactive'); .configureClient('interactive');
await client.post('/posts/${widget.item.id}/pin', {}); await client.post('/posts/${widget.item.id}/pin', {});
Navigator.pop(context, true); Navigator.pop(context, true);
@@ -254,7 +254,7 @@ class _PostDeletionDialogState extends State<PostDeletionDialog> {
final AuthProvider auth = Get.find(); final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) return; if (auth.isAuthorized.isFalse) return;
final client = auth.configureClient('interactive'); final client = await auth.configureClient('interactive');
setState(() => _isBusy = true); setState(() => _isBusy = true);
final resp = await client.delete('/posts/${widget.item.id}'); final resp = await client.delete('/posts/${widget.item.id}');

View File

@@ -1,11 +1,13 @@
import 'package:animations/animations.dart'; import 'package:animations/animations.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:gap/gap.dart'; import 'package:gap/gap.dart';
import 'package:get/get_utils/get_utils.dart'; import 'package:get/get.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:solian/models/post.dart'; import 'package:solian/models/post.dart';
import 'package:solian/providers/content/posts.dart';
import 'package:solian/screens/posts/post_detail.dart'; import 'package:solian/screens/posts/post_detail.dart';
import 'package:solian/shells/title_shell.dart'; import 'package:solian/shells/title_shell.dart';
import 'package:solian/theme.dart'; import 'package:solian/theme.dart';
@@ -30,6 +32,7 @@ class PostItem extends StatefulWidget {
final bool isFullDate; final bool isFullDate;
final bool isFullContent; final bool isFullContent;
final bool isContentSelectable; final bool isContentSelectable;
final bool showFeaturedReply;
final String? attachmentParent; final String? attachmentParent;
final Color? backgroundColor; final Color? backgroundColor;
@@ -45,6 +48,7 @@ class PostItem extends StatefulWidget {
this.isFullDate = false, this.isFullDate = false,
this.isFullContent = false, this.isFullContent = false,
this.isContentSelectable = false, this.isContentSelectable = false,
this.showFeaturedReply = false,
this.attachmentParent, this.attachmentParent,
this.backgroundColor, this.backgroundColor,
}); });
@@ -103,7 +107,7 @@ class _PostItemState extends State<PostItem> {
children: [ children: [
if (widget.isCompact) if (widget.isCompact)
AccountAvatar( AccountAvatar(
content: item.author.avatar.toString(), content: item.author.avatar,
radius: 10, radius: 10,
).paddingOnly(left: 2, top: 1), ).paddingOnly(left: 2, top: 1),
Expanded( Expanded(
@@ -320,6 +324,102 @@ class _PostItemState extends State<PostItem> {
} }
} }
Widget _buildFeaturedReply() {
if ((widget.item.metric?.replyCount ?? 0) == 0) {
return const SizedBox.shrink();
}
final List<String> attachments = item.body['attachments'] is List
? List.from(item.body['attachments']?.whereType<String>())
: List.empty();
final unFocusColor =
Theme.of(context).colorScheme.onSurface.withOpacity(0.75);
return FutureBuilder(
future: Get.find<PostProvider>().listPostFeaturedReply(
widget.item.id.toString(),
),
builder: (context, snapshot) {
if (!snapshot.hasData || snapshot.data!.isEmpty) {
return const SizedBox.shrink();
}
return Card(
margin: EdgeInsets.zero,
child: Column(
children: snapshot.data!
.map(
(x) => Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AccountAvatar(content: x.author.avatar, radius: 10),
const Gap(6),
Text(
x.author.nick,
style: const TextStyle(fontWeight: FontWeight.bold),
),
const Gap(6),
Text(
format(
x.publishedAt?.toLocal() ?? DateTime.now(),
locale: 'en_short',
),
).paddingOnly(top: 0.5),
const Gap(8),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
MarkdownTextContent(
content: x.body['content'],
parentId: 'p${item.id}-featured-reply${x.id}',
),
if (x.body['attachments'] is List &&
x.body['attachments'].length > 0)
Row(
children: [
Icon(
Icons.file_copy,
size: 15,
color: unFocusColor,
).paddingOnly(right: 5),
Text(
'attachmentHint'.trParams(
{
'count': x.body['attachments'].length
.toString()
},
),
style: TextStyle(color: unFocusColor),
)
],
),
],
),
),
],
).paddingSymmetric(horizontal: 12, vertical: 8),
)
.toList(),
),
)
.animate()
.fadeIn(
duration: 300.ms,
curve: Curves.easeIn,
)
.paddingOnly(
top: (attachments.length == 1 && !AppTheme.isLargeScreen(context))
? 10
: 6,
left:
(attachments.length == 1 && !AppTheme.isLargeScreen(context))
? 24
: 60,
right: 16,
);
},
);
}
double _contentHeight = 0; double _contentHeight = 0;
@override @override
@@ -417,7 +517,7 @@ class _PostItemState extends State<PostItem> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
GestureDetector( GestureDetector(
child: AccountAvatar(content: item.author.avatar.toString()), child: AccountAvatar(content: item.author.avatar),
onTap: () { onTap: () {
showModalBottomSheet( showModalBottomSheet(
useRootNavigator: true, useRootNavigator: true,
@@ -506,6 +606,7 @@ class _PostItemState extends State<PostItem> {
left: 16, left: 16,
), ),
_buildAttachments(), _buildAttachments(),
if (widget.showFeaturedReply) _buildFeaturedReply(),
if (widget.isShowReply || widget.isReactable) if (widget.isShowReply || widget.isReactable)
PostQuickAction( PostQuickAction(
isShowReply: widget.isShowReply, isShowReply: widget.isShowReply,

View File

@@ -33,6 +33,7 @@ class PostListWidget extends StatelessWidget {
isShowEmbed: isShowEmbed, isShowEmbed: isShowEmbed,
isNestedClickable: isNestedClickable, isNestedClickable: isNestedClickable,
isClickable: isClickable, isClickable: isClickable,
showFeaturedReply: true,
item: item, item: item,
backgroundColor: backgroundColor, backgroundColor: backgroundColor,
onUpdate: () { onUpdate: () {
@@ -51,6 +52,7 @@ class PostListEntryWidget extends StatelessWidget {
final bool isShowEmbed; final bool isShowEmbed;
final bool isNestedClickable; final bool isNestedClickable;
final bool isClickable; final bool isClickable;
final bool showFeaturedReply;
final Post item; final Post item;
final Function onUpdate; final Function onUpdate;
final Color? backgroundColor; final Color? backgroundColor;
@@ -61,6 +63,7 @@ class PostListEntryWidget extends StatelessWidget {
required this.isShowEmbed, required this.isShowEmbed,
required this.isNestedClickable, required this.isNestedClickable,
required this.isClickable, required this.isClickable,
required this.showFeaturedReply,
required this.item, required this.item,
required this.onUpdate, required this.onUpdate,
this.backgroundColor, this.backgroundColor,
@@ -74,6 +77,7 @@ class PostListEntryWidget extends StatelessWidget {
item: item, item: item,
isShowEmbed: isShowEmbed, isShowEmbed: isShowEmbed,
isClickable: isNestedClickable, isClickable: isNestedClickable,
showFeaturedReply: showFeaturedReply,
backgroundColor: backgroundColor, backgroundColor: backgroundColor,
).paddingSymmetric(vertical: 8), ).paddingSymmetric(vertical: 8),
onLongPress: () { onLongPress: () {

View File

@@ -49,7 +49,7 @@ class _PostQuickActionState extends State<PostQuickAction> {
if (_isSubmitting) return; if (_isSubmitting) return;
if (auth.isAuthorized.isFalse) return; if (auth.isAuthorized.isFalse) return;
final client = auth.configureClient('interactive'); final client = await auth.configureClient('interactive');
setState(() => _isSubmitting = true); setState(() => _isSubmitting = true);

View File

@@ -23,6 +23,7 @@ class PostSingleDisplay extends StatelessWidget {
isClickable: true, isClickable: true,
isShowEmbed: true, isShowEmbed: true,
isNestedClickable: true, isNestedClickable: true,
showFeaturedReply: true,
onUpdate: onUpdate, onUpdate: onUpdate,
backgroundColor: Theme.of(context).colorScheme.surfaceContainerLow, backgroundColor: Theme.of(context).colorScheme.surfaceContainerLow,
), ),

View File

@@ -36,6 +36,7 @@ class PostWarpedListWidget extends StatelessWidget {
isShowEmbed: isShowEmbed, isShowEmbed: isShowEmbed,
isNestedClickable: isNestedClickable, isNestedClickable: isNestedClickable,
isClickable: isClickable, isClickable: isClickable,
showFeaturedReply: true,
item: item, item: item,
onUpdate: onUpdate ?? () {}, onUpdate: onUpdate ?? () {},
); );

View File

@@ -27,7 +27,7 @@ class _RealmDeletionDialogState extends State<RealmDeletionDialog> {
setState(() => _isBusy = true); setState(() => _isBusy = true);
final client = auth.configureClient('auth'); final client = await auth.configureClient('auth');
final resp = await client.delete('/realms/${widget.realm.id}'); final resp = await client.delete('/realms/${widget.realm.id}');
if (resp.statusCode != 200) { if (resp.statusCode != 200) {
@@ -45,10 +45,9 @@ class _RealmDeletionDialogState extends State<RealmDeletionDialog> {
setState(() => _isBusy = true); setState(() => _isBusy = true);
final client = auth.configureClient('auth'); final client = await auth.configureClient('auth');
final resp = final resp = await client.delete('/realms/${widget.realm.id}/members/me');
await client.delete('/realms/${widget.realm.id}/members/me');
if (resp.statusCode != 200) { if (resp.statusCode != 200) {
context.showErrorDialog(resp.bodyString); context.showErrorDialog(resp.bodyString);
} else if (Navigator.canPop(context)) { } else if (Navigator.canPop(context)) {

View File

@@ -37,7 +37,7 @@ class _RealmMemberListPopupState extends State<RealmMemberListPopup> {
void getMembers() async { void getMembers() async {
setState(() => _isBusy = true); setState(() => _isBusy = true);
final client = ServiceFinder.configureClient('auth'); final client = await ServiceFinder.configureClient('auth');
final resp = await client.get('/realms/${widget.realm.alias}/members'); final resp = await client.get('/realms/${widget.realm.alias}/members');
if (resp.statusCode == 200) { if (resp.statusCode == 200) {
@@ -72,7 +72,7 @@ class _RealmMemberListPopupState extends State<RealmMemberListPopup> {
setState(() => _isBusy = true); setState(() => _isBusy = true);
final client = auth.configureClient('auth'); final client = await auth.configureClient('auth');
final resp = await client.post( final resp = await client.post(
'/realms/${widget.realm.alias}/members', '/realms/${widget.realm.alias}/members',
@@ -93,7 +93,7 @@ class _RealmMemberListPopupState extends State<RealmMemberListPopup> {
setState(() => _isBusy = true); setState(() => _isBusy = true);
final client = auth.configureClient('auth'); final client = await auth.configureClient('auth');
final resp = await client.request( final resp = await client.request(
'/realms/${widget.realm.alias}/members', '/realms/${widget.realm.alias}/members',

View File

@@ -1,212 +0,0 @@
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:get/get.dart';
import 'package:solian/exts.dart';
import 'package:solian/models/stickers.dart';
import 'package:solian/providers/auth.dart';
import 'package:solian/widgets/attachments/attachment_editor.dart';
class StickerUploadDialog extends StatefulWidget {
final Sticker? edit;
const StickerUploadDialog({super.key, this.edit});
@override
State<StickerUploadDialog> createState() => _StickerUploadDialogState();
}
class _StickerUploadDialogState extends State<StickerUploadDialog> {
final TextEditingController _attachmentController = TextEditingController();
final TextEditingController _packController = TextEditingController();
final TextEditingController _aliasController = TextEditingController();
final TextEditingController _nameController = TextEditingController();
Color get _unFocusColor =>
Theme.of(context).colorScheme.onSurface.withOpacity(0.75);
bool _isBusy = false;
void _promptUploadNewAttachment() {
showModalBottomSheet(
context: context,
builder: (context) => AttachmentEditorPopup(
pool: 'sticker',
singleMode: true,
imageOnly: true,
autoUpload: true,
imageMaxHeight: 28,
imageMaxWidth: 28,
onAdd: (value) {
setState(() {
_attachmentController.text = value.toString();
});
},
initialAttachments: const [],
onRemove: (_) {},
),
);
}
Future<void> _applySticker() async {
final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) return;
if ([
_nameController.text.isEmpty,
_aliasController.text.isEmpty,
_packController.text.isEmpty,
_attachmentController.text.isEmpty,
].any((x) => x)) {
return;
}
setState(() => _isBusy = true);
Response resp;
final client = auth.configureClient('files');
if (widget.edit == null) {
resp = await client.post('/stickers', {
'name': _nameController.text,
'alias': _aliasController.text,
'pack_id': int.tryParse(_packController.text),
'attachment_id': int.tryParse(_attachmentController.text),
});
} else {
resp = await client.put('/stickers/${widget.edit!.id}', {
'name': _nameController.text,
'alias': _aliasController.text,
'pack_id': int.tryParse(_packController.text),
'attachment_id': int.tryParse(_attachmentController.text),
});
}
setState(() => _isBusy = false);
if (resp.statusCode != 200) {
context.showErrorDialog(resp.bodyString);
} else {
Navigator.pop(context, true);
}
}
@override
void initState() {
super.initState();
if (widget.edit != null) {
_attachmentController.text = widget.edit!.attachmentId.toString();
_packController.text = widget.edit!.packId.toString();
_aliasController.text = widget.edit!.alias;
_nameController.text = widget.edit!.name;
}
}
@override
void dispose() {
_attachmentController.dispose();
_packController.dispose();
_aliasController.dispose();
_nameController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Text('stickerUploader'.tr),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ListTile(
title: Text('stickerUploaderAttachmentNew'.tr),
contentPadding: const EdgeInsets.only(left: 16, right: 13),
trailing: const Icon(Icons.chevron_right),
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(8)),
),
onTap: () {
_promptUploadNewAttachment();
},
),
const Gap(8),
TextField(
controller: _attachmentController,
decoration: InputDecoration(
isDense: true,
border: const OutlineInputBorder(),
prefixText: '#',
labelText: 'stickerUploaderAttachment'.tr,
),
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
),
const Gap(8),
TextField(
controller: _packController,
decoration: InputDecoration(
isDense: true,
border: const OutlineInputBorder(),
prefixText: '#',
labelText: 'stickerUploaderPack'.tr,
),
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
),
Container(
padding:
const EdgeInsets.only(left: 8, right: 8, top: 4, bottom: 6),
child: Text(
'stickerUploaderPackHint'.tr,
style: TextStyle(color: _unFocusColor),
),
),
TextField(
controller: _aliasController,
decoration: InputDecoration(
isDense: true,
border: const OutlineInputBorder(),
labelText: 'stickerUploaderAlias'.tr,
),
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
),
Container(
padding:
const EdgeInsets.only(left: 8, right: 8, top: 4, bottom: 6),
child: Text(
'stickerUploaderAliasHint'.tr,
style: TextStyle(color: _unFocusColor),
),
),
TextField(
controller: _nameController,
decoration: InputDecoration(
isDense: true,
border: const OutlineInputBorder(),
labelText: 'stickerUploaderName'.tr,
),
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
),
Container(
padding: const EdgeInsets.only(left: 8, right: 8, top: 4),
child: Text(
'stickerUploaderNameHint'.tr,
style: TextStyle(color: _unFocusColor),
),
),
],
),
actions: [
TextButton(
style: TextButton.styleFrom(
foregroundColor:
Theme.of(context).colorScheme.onSurface.withOpacity(0.8),
),
onPressed: _isBusy ? null : () => Navigator.pop(context),
child: Text('cancel'.tr),
),
TextButton(
onPressed: _isBusy ? null : () => _applySticker(),
child: Text('apply'.tr),
),
],
);
}
}

View File

@@ -909,14 +909,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "10.7.0" version: "10.7.0"
freezed_annotation:
dependency: "direct main"
description:
name: freezed_annotation
sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2
url: "https://pub.dev"
source: hosted
version: "2.4.4"
frontend_server_client: frontend_server_client:
dependency: transitive dependency: transitive
description: description:
@@ -1894,10 +1886,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: sqflite_common name: sqflite_common
sha256: "7b41b6c3507854a159e24ae90a8e3e9cc01eb26a477c118d6dca065b5f55453e" sha256: "4058172e418eb7e7f2058dcb7657d451a8fc264afa0dea4dbd0f304a57131611"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.5.4+2" version: "2.5.4+3"
sqlite3: sqlite3:
dependency: transitive dependency: transitive
description: description:
@@ -1958,10 +1950,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: synchronized name: synchronized
sha256: a824e842b8a054f91a728b783c177c1e4731f6b124f9192468457a8913371255 sha256: "51b08572b9f091f8c3eb4d9d4be253f196ff0075d5ec9b10a884026d5b55d7bc"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.2.0" version: "3.3.0+2"
term_glyph: term_glyph:
dependency: transitive dependency: transitive
description: description:

View File

@@ -2,7 +2,7 @@ name: solian
description: "The Solar Network App" description: "The Solar Network App"
publish_to: "none" publish_to: "none"
version: 1.2.1+37 version: 1.2.1+39
environment: environment:
sdk: ">=3.3.4 <4.0.0" sdk: ">=3.3.4 <4.0.0"
@@ -68,7 +68,6 @@ dependencies:
flutter_svg: ^2.0.10+1 flutter_svg: ^2.0.10+1
cross_file: ^0.3.4+2 cross_file: ^0.3.4+2
google_fonts: ^6.2.1 google_fonts: ^6.2.1
freezed_annotation: ^2.4.4
json_annotation: ^4.9.0 json_annotation: ^4.9.0
gap: ^3.0.1 gap: ^3.0.1
fl_chart: ^0.69.0 fl_chart: ^0.69.0

5
web/drift_worker.dart Normal file
View File

@@ -0,0 +1,5 @@
import 'package:drift/wasm.dart';
// When compiled with dart2js, this file defines a dedicated or shared web
// worker used by drift.
void main() => WasmDatabase.workerMainForOpen();

13325
web/drift_worker.dart.js Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,662 @@
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/async-2.11.0/lib/async.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/async-2.11.0/lib/src/async_cache.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/async-2.11.0/lib/src/async_memoizer.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/async-2.11.0/lib/src/byte_collector.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/async-2.11.0/lib/src/cancelable_operation.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/async-2.11.0/lib/src/chunked_stream_reader.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/async-2.11.0/lib/src/delegate/event_sink.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/async-2.11.0/lib/src/delegate/future.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/async-2.11.0/lib/src/delegate/sink.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/async-2.11.0/lib/src/delegate/stream.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/async-2.11.0/lib/src/delegate/stream_consumer.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/async-2.11.0/lib/src/delegate/stream_sink.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/async-2.11.0/lib/src/delegate/stream_subscription.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/async-2.11.0/lib/src/future_group.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/async-2.11.0/lib/src/lazy_stream.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/async-2.11.0/lib/src/null_stream_sink.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/async-2.11.0/lib/src/restartable_timer.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/async-2.11.0/lib/src/result/capture_sink.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/async-2.11.0/lib/src/result/capture_transformer.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/async-2.11.0/lib/src/result/error.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/async-2.11.0/lib/src/result/future.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/async-2.11.0/lib/src/result/release_sink.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/async-2.11.0/lib/src/result/release_transformer.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/async-2.11.0/lib/src/result/result.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/async-2.11.0/lib/src/result/value.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/async-2.11.0/lib/src/single_subscription_transformer.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/async-2.11.0/lib/src/sink_base.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/async-2.11.0/lib/src/stream_closer.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/async-2.11.0/lib/src/stream_completer.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/async-2.11.0/lib/src/stream_extensions.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/async-2.11.0/lib/src/stream_group.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/async-2.11.0/lib/src/stream_queue.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/async-2.11.0/lib/src/stream_sink_completer.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/async-2.11.0/lib/src/stream_sink_extensions.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/async-2.11.0/lib/src/stream_sink_transformer.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/async-2.11.0/lib/src/stream_sink_transformer/handler_transformer.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/async-2.11.0/lib/src/stream_sink_transformer/reject_errors.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/async-2.11.0/lib/src/stream_sink_transformer/stream_transformer_wrapper.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/async-2.11.0/lib/src/stream_sink_transformer/typed.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/async-2.11.0/lib/src/stream_splitter.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/async-2.11.0/lib/src/stream_subscription_transformer.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/async-2.11.0/lib/src/stream_zip.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/async-2.11.0/lib/src/subscription_stream.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/async-2.11.0/lib/src/typed/stream_subscription.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/async-2.11.0/lib/src/typed_stream_transformer.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/collection-1.18.0/lib/collection.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/collection-1.18.0/lib/src/algorithms.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/collection-1.18.0/lib/src/boollist.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/collection-1.18.0/lib/src/canonicalized_map.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/collection-1.18.0/lib/src/combined_wrappers/combined_iterable.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/collection-1.18.0/lib/src/combined_wrappers/combined_iterator.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/collection-1.18.0/lib/src/combined_wrappers/combined_list.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/collection-1.18.0/lib/src/combined_wrappers/combined_map.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/collection-1.18.0/lib/src/comparators.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/collection-1.18.0/lib/src/empty_unmodifiable_set.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/collection-1.18.0/lib/src/equality.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/collection-1.18.0/lib/src/equality_map.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/collection-1.18.0/lib/src/equality_set.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/collection-1.18.0/lib/src/functions.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/collection-1.18.0/lib/src/iterable_extensions.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/collection-1.18.0/lib/src/iterable_zip.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/collection-1.18.0/lib/src/list_extensions.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/collection-1.18.0/lib/src/priority_queue.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/collection-1.18.0/lib/src/queue_list.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/collection-1.18.0/lib/src/union_set.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/collection-1.18.0/lib/src/union_set_controller.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/collection-1.18.0/lib/src/unmodifiable_wrappers.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/collection-1.18.0/lib/src/utils.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/collection-1.18.0/lib/src/wrappers.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/convert-3.1.1/lib/convert.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/convert-3.1.1/lib/src/accumulator_sink.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/convert-3.1.1/lib/src/byte_accumulator_sink.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/convert-3.1.1/lib/src/charcodes.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/convert-3.1.1/lib/src/codepage.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/convert-3.1.1/lib/src/fixed_datetime_formatter.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/convert-3.1.1/lib/src/hex.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/convert-3.1.1/lib/src/hex/decoder.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/convert-3.1.1/lib/src/hex/encoder.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/convert-3.1.1/lib/src/identity_codec.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/convert-3.1.1/lib/src/percent.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/convert-3.1.1/lib/src/percent/decoder.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/convert-3.1.1/lib/src/percent/encoder.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/convert-3.1.1/lib/src/string_accumulator_sink.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/convert-3.1.1/lib/src/utils.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/backends.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/drift.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/internal/versioned_schema.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/remote.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/dsl/columns.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/dsl/database.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/dsl/dsl.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/dsl/table.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/remote/client_impl.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/remote/communication.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/remote/protocol.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/remote/server_impl.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/remote/web_protocol.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/api/batch.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/api/connection.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/api/connection_user.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/api/dao_base.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/api/db_base.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/api/options.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/api/runtime_api.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/api/stream_updates.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/cancellation_zone.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/custom_result_set.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/data_class.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/data_verification.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/devtools/devtools.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/devtools/service_extension.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/devtools/shared.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/exceptions.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/executor/connection_pool.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/executor/delayed_stream_queries.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/executor/executor.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/executor/helpers/delegates.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/executor/helpers/engines.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/executor/helpers/results.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/executor/interceptor.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/executor/stream_queries.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/executor/transactions.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/manager/composable.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/manager/composer.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/manager/filter.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/manager/manager.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/manager/ordering.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/manager/references.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/query_builder/components/group_by.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/query_builder/components/join.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/query_builder/components/limit.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/query_builder/components/order_by.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/query_builder/components/subquery.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/query_builder/components/table_valued_function.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/query_builder/components/where.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/query_builder/expressions/aggregate.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/query_builder/expressions/algebra.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/query_builder/expressions/bitwise.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/query_builder/expressions/bools.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/query_builder/expressions/case_when.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/query_builder/expressions/comparable.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/query_builder/expressions/custom.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/query_builder/expressions/datetimes.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/query_builder/expressions/exists.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/query_builder/expressions/expression.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/query_builder/expressions/in.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/query_builder/expressions/internal.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/query_builder/expressions/null_check.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/query_builder/expressions/text.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/query_builder/expressions/variables.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/query_builder/generation_context.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/query_builder/helpers.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/query_builder/migration.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/query_builder/on_table.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/query_builder/query_builder.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/query_builder/schema/column_impl.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/query_builder/schema/entities.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/query_builder/schema/table_info.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/query_builder/schema/view_info.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/query_builder/statements/delete.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/query_builder/statements/insert.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/query_builder/statements/query.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/query_builder/statements/select/custom_select.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/query_builder/statements/select/select.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/query_builder/statements/select/select_with_join.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/query_builder/statements/update.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/types/converters.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/types/mapping.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/runtime/utils.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/sqlite3/database.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/sqlite3/native_functions.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/utils/async.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/utils/async_map.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/utils/lazy_database.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/utils/single_transformer.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/utils/synchronized.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/web/broadcast_stream_queries.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/web/channel_new.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/web/wasm_setup.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/web/wasm_setup/dedicated_worker.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/web/wasm_setup/protocol.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/web/wasm_setup/shared.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/web/wasm_setup/shared_worker.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/src/web/wasm_setup/types.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/drift-2.20.2/lib/wasm.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/meta-1.15.0/lib/meta.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/meta-1.15.0/lib/meta_meta.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/path-1.9.0/lib/path.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/path-1.9.0/lib/src/characters.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/path-1.9.0/lib/src/context.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/path-1.9.0/lib/src/internal_style.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/path-1.9.0/lib/src/parsed_path.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/path-1.9.0/lib/src/path_exception.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/path-1.9.0/lib/src/path_map.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/path-1.9.0/lib/src/path_set.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/path-1.9.0/lib/src/style.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/path-1.9.0/lib/src/style/posix.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/path-1.9.0/lib/src/style/url.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/path-1.9.0/lib/src/style/windows.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/path-1.9.0/lib/src/utils.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/sqlite3-2.4.6/lib/common.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/sqlite3-2.4.6/lib/src/constants.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/sqlite3-2.4.6/lib/src/database.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/sqlite3-2.4.6/lib/src/exception.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/sqlite3-2.4.6/lib/src/functions.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/sqlite3-2.4.6/lib/src/implementation/bindings.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/sqlite3-2.4.6/lib/src/implementation/database.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/sqlite3-2.4.6/lib/src/implementation/exception.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/sqlite3-2.4.6/lib/src/implementation/finalizer.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/sqlite3-2.4.6/lib/src/implementation/sqlite3.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/sqlite3-2.4.6/lib/src/implementation/statement.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/sqlite3-2.4.6/lib/src/implementation/utils.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/sqlite3-2.4.6/lib/src/result_set.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/sqlite3-2.4.6/lib/src/sqlite3.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/sqlite3-2.4.6/lib/src/statement.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/sqlite3-2.4.6/lib/src/vfs.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/sqlite3-2.4.6/lib/src/wasm/bindings.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/sqlite3-2.4.6/lib/src/wasm/js_interop.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/sqlite3-2.4.6/lib/src/wasm/js_interop/atomics.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/sqlite3-2.4.6/lib/src/wasm/js_interop/core.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/sqlite3-2.4.6/lib/src/wasm/js_interop/fetch.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/sqlite3-2.4.6/lib/src/wasm/js_interop/indexed_db.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/sqlite3-2.4.6/lib/src/wasm/js_interop/new_file_system_access.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/sqlite3-2.4.6/lib/src/wasm/js_interop/typed_data.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/sqlite3-2.4.6/lib/src/wasm/js_interop/wasm.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/sqlite3-2.4.6/lib/src/wasm/sqlite3.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/sqlite3-2.4.6/lib/src/wasm/vfs/async_opfs/client.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/sqlite3-2.4.6/lib/src/wasm/vfs/async_opfs/sync_channel.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/sqlite3-2.4.6/lib/src/wasm/vfs/async_opfs/worker.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/sqlite3-2.4.6/lib/src/wasm/vfs/indexed_db.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/sqlite3-2.4.6/lib/src/wasm/vfs/memory.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/sqlite3-2.4.6/lib/src/wasm/vfs/simple_opfs.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/sqlite3-2.4.6/lib/src/wasm/vfs/utils.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/sqlite3-2.4.6/lib/src/wasm/wasm_interop.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/sqlite3-2.4.6/lib/wasm.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/stack_trace-1.11.1/lib/src/chain.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/stack_trace-1.11.1/lib/src/frame.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/stack_trace-1.11.1/lib/src/lazy_chain.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/stack_trace-1.11.1/lib/src/lazy_trace.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/stack_trace-1.11.1/lib/src/stack_zone_specification.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/stack_trace-1.11.1/lib/src/trace.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/stack_trace-1.11.1/lib/src/unparsed_frame.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/stack_trace-1.11.1/lib/src/utils.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/stack_trace-1.11.1/lib/src/vm_trace.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/stack_trace-1.11.1/lib/stack_trace.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/stream_channel-2.1.2/lib/src/close_guarantee_channel.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/stream_channel-2.1.2/lib/src/delegating_stream_channel.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/stream_channel-2.1.2/lib/src/disconnector.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/stream_channel-2.1.2/lib/src/guarantee_channel.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/stream_channel-2.1.2/lib/src/json_document_transformer.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/stream_channel-2.1.2/lib/src/multi_channel.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/stream_channel-2.1.2/lib/src/stream_channel_completer.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/stream_channel-2.1.2/lib/src/stream_channel_controller.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/stream_channel-2.1.2/lib/src/stream_channel_transformer.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/stream_channel-2.1.2/lib/stream_channel.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/typed_data-1.3.2/lib/src/typed_buffer.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/typed_data-1.3.2/lib/src/typed_queue.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/typed_data-1.3.2/lib/typed_buffers.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/typed_data-1.3.2/lib/typed_data.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/accelerometer.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/angle_instanced_arrays.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/attribution_reporting_api.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/background_sync.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/battery_status.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/clipboard_apis.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/compression.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/console.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/cookie_store.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/credential_management.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/csp.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/css_animations.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/css_animations_2.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/css_cascade.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/css_cascade_6.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/css_conditional.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/css_conditional_5.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/css_contain.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/css_counter_styles.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/css_font_loading.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/css_fonts.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/css_highlight_api.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/css_masking.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/css_paint_api.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/css_properties_values_api.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/css_transitions.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/css_transitions_2.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/css_typed_om.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/css_view_transitions.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/css_view_transitions_2.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/cssom.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/cssom_view.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/digital_identities.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/dom.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/dom_parsing.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/encoding.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/encrypted_media.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/entries_api.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/event_timing.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/ext_blend_minmax.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/ext_color_buffer_float.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/ext_color_buffer_half_float.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/ext_disjoint_timer_query.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/ext_disjoint_timer_query_webgl2.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/ext_float_blend.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/ext_frag_depth.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/ext_shader_texture_lod.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/ext_srgb.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/ext_texture_compression_bptc.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/ext_texture_compression_rgtc.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/ext_texture_filter_anisotropic.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/ext_texture_norm16.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/fedcm.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/fetch.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/fido.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/fileapi.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/filter_effects.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/fs.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/fullscreen.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/gamepad.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/generic_sensor.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/geolocation.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/geometry.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/gyroscope.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/hr_time.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/html.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/image_capture.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/indexeddb.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/intersection_observer.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/khr_parallel_shader_compile.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/largest_contentful_paint.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/mathml_core.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/media_capabilities.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/media_playback_quality.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/media_source.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/mediacapture_fromelement.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/mediacapture_streams.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/mediacapture_transform.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/mediasession.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/mediastream_recording.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/mst_content_hint.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/navigation_timing.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/netinfo.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/notifications.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/oes_draw_buffers_indexed.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/oes_element_index_uint.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/oes_fbo_render_mipmap.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/oes_standard_derivatives.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/oes_texture_float.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/oes_texture_float_linear.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/oes_texture_half_float.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/oes_texture_half_float_linear.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/oes_vertex_array_object.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/orientation_event.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/orientation_sensor.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/ovr_multiview2.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/paint_timing.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/payment_request.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/performance_timeline.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/permissions.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/picture_in_picture.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/pointerevents.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/pointerlock.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/private_network_access.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/push_api.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/referrer_policy.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/remote_playback.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/reporting.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/requestidlecallback.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/resize_observer.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/resource_timing.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/saa_non_cookie_storage.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/sanitizer_api.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/scheduling_apis.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/screen_capture.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/screen_orientation.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/screen_wake_lock.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/secure_payment_confirmation.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/selection_api.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/server_timing.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/service_workers.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/speech_api.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/storage.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/streams.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/svg.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/svg_animations.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/touch_events.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/trust_token_api.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/trusted_types.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/uievents.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/url.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/user_timing.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/vibration.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/video_rvfc.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/wasm_js_api.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/web_animations.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/web_animations_2.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/web_bluetooth.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/web_locks.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/web_otp.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/web_share.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/webaudio.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/webauthn.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/webcodecs.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/webcodecs_av1_codec_registration.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/webcodecs_avc_codec_registration.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/webcodecs_hevc_codec_registration.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/webcodecs_vp9_codec_registration.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/webcryptoapi.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/webgl1.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/webgl2.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/webgl_color_buffer_float.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/webgl_compressed_texture_astc.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/webgl_compressed_texture_etc.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/webgl_compressed_texture_etc1.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/webgl_compressed_texture_pvrtc.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/webgl_compressed_texture_s3tc.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/webgl_compressed_texture_s3tc_srgb.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/webgl_debug_renderer_info.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/webgl_debug_shaders.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/webgl_depth_texture.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/webgl_draw_buffers.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/webgl_lose_context.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/webgl_multi_draw.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/webgpu.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/webidl.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/webmidi.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/webrtc.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/webrtc_encoded_transform.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/webrtc_identity.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/webrtc_priority.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/websockets.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/webtransport.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/webvtt.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/webxr.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/webxr_hand_input.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/dom/xhr.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/helpers.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/helpers/enums.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/helpers/events/events.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/helpers/events/providers.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/helpers/events/streams.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/helpers/extensions.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/helpers/http.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/helpers/lists.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/src/helpers/renames.dart
file:///Users/littlesheep/.pub-cache/hosted/pub.dev/web-1.0.0/lib/web.dart
file:///Users/littlesheep/Documents/Projects/Hydrogen/Solian/.dart_tool/package_config.json
file:///Users/littlesheep/Documents/Projects/Hydrogen/Solian/web/drift_worker.dart
file:///opt/homebrew/Caskroom/flutter/3.24.3/flutter/bin/cache/dart-sdk/lib/_internal/dart2js_platform.dill
file:///opt/homebrew/Caskroom/flutter/3.24.3/flutter/bin/cache/dart-sdk/lib/libraries.json
org-dartlang-sdk:///lib/_http/crypto.dart
org-dartlang-sdk:///lib/_http/embedder_config.dart
org-dartlang-sdk:///lib/_http/http.dart
org-dartlang-sdk:///lib/_http/http_date.dart
org-dartlang-sdk:///lib/_http/http_headers.dart
org-dartlang-sdk:///lib/_http/http_impl.dart
org-dartlang-sdk:///lib/_http/http_parser.dart
org-dartlang-sdk:///lib/_http/http_session.dart
org-dartlang-sdk:///lib/_http/http_testing.dart
org-dartlang-sdk:///lib/_http/overrides.dart
org-dartlang-sdk:///lib/_http/websocket.dart
org-dartlang-sdk:///lib/_http/websocket_impl.dart
org-dartlang-sdk:///lib/_internal/js_runtime/lib/annotations.dart
org-dartlang-sdk:///lib/_internal/js_runtime/lib/async_patch.dart
org-dartlang-sdk:///lib/_internal/js_runtime/lib/bigint_patch.dart
org-dartlang-sdk:///lib/_internal/js_runtime/lib/collection_patch.dart
org-dartlang-sdk:///lib/_internal/js_runtime/lib/constant_map.dart
org-dartlang-sdk:///lib/_internal/js_runtime/lib/convert_patch.dart
org-dartlang-sdk:///lib/_internal/js_runtime/lib/core_patch.dart
org-dartlang-sdk:///lib/_internal/js_runtime/lib/dart2js_only.dart
org-dartlang-sdk:///lib/_internal/js_runtime/lib/dart2js_runtime_metrics.dart
org-dartlang-sdk:///lib/_internal/js_runtime/lib/developer_patch.dart
org-dartlang-sdk:///lib/_internal/js_runtime/lib/foreign_helper.dart
org-dartlang-sdk:///lib/_internal/js_runtime/lib/instantiation.dart
org-dartlang-sdk:///lib/_internal/js_runtime/lib/interceptors.dart
org-dartlang-sdk:///lib/_internal/js_runtime/lib/internal_patch.dart
org-dartlang-sdk:///lib/_internal/js_runtime/lib/io_patch.dart
org-dartlang-sdk:///lib/_internal/js_runtime/lib/isolate_patch.dart
org-dartlang-sdk:///lib/_internal/js_runtime/lib/js_allow_interop_patch.dart
org-dartlang-sdk:///lib/_internal/js_runtime/lib/js_array.dart
org-dartlang-sdk:///lib/_internal/js_runtime/lib/js_helper.dart
org-dartlang-sdk:///lib/_internal/js_runtime/lib/js_names.dart
org-dartlang-sdk:///lib/_internal/js_runtime/lib/js_number.dart
org-dartlang-sdk:///lib/_internal/js_runtime/lib/js_patch.dart
org-dartlang-sdk:///lib/_internal/js_runtime/lib/js_primitives.dart
org-dartlang-sdk:///lib/_internal/js_runtime/lib/js_string.dart
org-dartlang-sdk:///lib/_internal/js_runtime/lib/late_helper.dart
org-dartlang-sdk:///lib/_internal/js_runtime/lib/linked_hash_map.dart
org-dartlang-sdk:///lib/_internal/js_runtime/lib/math_patch.dart
org-dartlang-sdk:///lib/_internal/js_runtime/lib/native_helper.dart
org-dartlang-sdk:///lib/_internal/js_runtime/lib/native_typed_data.dart
org-dartlang-sdk:///lib/_internal/js_runtime/lib/records.dart
org-dartlang-sdk:///lib/_internal/js_runtime/lib/regexp_helper.dart
org-dartlang-sdk:///lib/_internal/js_runtime/lib/string_helper.dart
org-dartlang-sdk:///lib/_internal/js_runtime/lib/synced/async_status_codes.dart
org-dartlang-sdk:///lib/_internal/js_runtime/lib/synced/embedded_names.dart
org-dartlang-sdk:///lib/_internal/js_runtime/lib/synced/invocation_mirror_constants.dart
org-dartlang-sdk:///lib/_internal/js_runtime/lib/synced/load_library_priority.dart
org-dartlang-sdk:///lib/_internal/js_runtime/lib/typed_data_patch.dart
org-dartlang-sdk:///lib/_internal/js_shared/lib/convert_utf_patch.dart
org-dartlang-sdk:///lib/_internal/js_shared/lib/date_time_patch.dart
org-dartlang-sdk:///lib/_internal/js_shared/lib/js_interop_patch.dart
org-dartlang-sdk:///lib/_internal/js_shared/lib/js_interop_unsafe_patch.dart
org-dartlang-sdk:///lib/_internal/js_shared/lib/js_types.dart
org-dartlang-sdk:///lib/_internal/js_shared/lib/js_util_patch.dart
org-dartlang-sdk:///lib/_internal/js_shared/lib/rti.dart
org-dartlang-sdk:///lib/_internal/js_shared/lib/synced/embedded_names.dart
org-dartlang-sdk:///lib/_internal/js_shared/lib/synced/recipe_syntax.dart
org-dartlang-sdk:///lib/async/async.dart
org-dartlang-sdk:///lib/async/async_error.dart
org-dartlang-sdk:///lib/async/broadcast_stream_controller.dart
org-dartlang-sdk:///lib/async/deferred_load.dart
org-dartlang-sdk:///lib/async/future.dart
org-dartlang-sdk:///lib/async/future_extensions.dart
org-dartlang-sdk:///lib/async/future_impl.dart
org-dartlang-sdk:///lib/async/schedule_microtask.dart
org-dartlang-sdk:///lib/async/stream.dart
org-dartlang-sdk:///lib/async/stream_controller.dart
org-dartlang-sdk:///lib/async/stream_impl.dart
org-dartlang-sdk:///lib/async/stream_pipe.dart
org-dartlang-sdk:///lib/async/stream_transformers.dart
org-dartlang-sdk:///lib/async/timer.dart
org-dartlang-sdk:///lib/async/zone.dart
org-dartlang-sdk:///lib/collection/collection.dart
org-dartlang-sdk:///lib/collection/collections.dart
org-dartlang-sdk:///lib/collection/hash_map.dart
org-dartlang-sdk:///lib/collection/hash_set.dart
org-dartlang-sdk:///lib/collection/iterable.dart
org-dartlang-sdk:///lib/collection/iterator.dart
org-dartlang-sdk:///lib/collection/linked_hash_map.dart
org-dartlang-sdk:///lib/collection/linked_hash_set.dart
org-dartlang-sdk:///lib/collection/linked_list.dart
org-dartlang-sdk:///lib/collection/list.dart
org-dartlang-sdk:///lib/collection/maps.dart
org-dartlang-sdk:///lib/collection/queue.dart
org-dartlang-sdk:///lib/collection/set.dart
org-dartlang-sdk:///lib/collection/splay_tree.dart
org-dartlang-sdk:///lib/convert/ascii.dart
org-dartlang-sdk:///lib/convert/base64.dart
org-dartlang-sdk:///lib/convert/byte_conversion.dart
org-dartlang-sdk:///lib/convert/chunked_conversion.dart
org-dartlang-sdk:///lib/convert/codec.dart
org-dartlang-sdk:///lib/convert/convert.dart
org-dartlang-sdk:///lib/convert/converter.dart
org-dartlang-sdk:///lib/convert/encoding.dart
org-dartlang-sdk:///lib/convert/html_escape.dart
org-dartlang-sdk:///lib/convert/json.dart
org-dartlang-sdk:///lib/convert/latin1.dart
org-dartlang-sdk:///lib/convert/line_splitter.dart
org-dartlang-sdk:///lib/convert/string_conversion.dart
org-dartlang-sdk:///lib/convert/utf.dart
org-dartlang-sdk:///lib/core/annotations.dart
org-dartlang-sdk:///lib/core/bigint.dart
org-dartlang-sdk:///lib/core/bool.dart
org-dartlang-sdk:///lib/core/comparable.dart
org-dartlang-sdk:///lib/core/core.dart
org-dartlang-sdk:///lib/core/date_time.dart
org-dartlang-sdk:///lib/core/double.dart
org-dartlang-sdk:///lib/core/duration.dart
org-dartlang-sdk:///lib/core/enum.dart
org-dartlang-sdk:///lib/core/errors.dart
org-dartlang-sdk:///lib/core/exceptions.dart
org-dartlang-sdk:///lib/core/function.dart
org-dartlang-sdk:///lib/core/identical.dart
org-dartlang-sdk:///lib/core/int.dart
org-dartlang-sdk:///lib/core/invocation.dart
org-dartlang-sdk:///lib/core/iterable.dart
org-dartlang-sdk:///lib/core/iterator.dart
org-dartlang-sdk:///lib/core/list.dart
org-dartlang-sdk:///lib/core/map.dart
org-dartlang-sdk:///lib/core/null.dart
org-dartlang-sdk:///lib/core/num.dart
org-dartlang-sdk:///lib/core/object.dart
org-dartlang-sdk:///lib/core/pattern.dart
org-dartlang-sdk:///lib/core/print.dart
org-dartlang-sdk:///lib/core/record.dart
org-dartlang-sdk:///lib/core/regexp.dart
org-dartlang-sdk:///lib/core/set.dart
org-dartlang-sdk:///lib/core/sink.dart
org-dartlang-sdk:///lib/core/stacktrace.dart
org-dartlang-sdk:///lib/core/stopwatch.dart
org-dartlang-sdk:///lib/core/string.dart
org-dartlang-sdk:///lib/core/string_buffer.dart
org-dartlang-sdk:///lib/core/string_sink.dart
org-dartlang-sdk:///lib/core/symbol.dart
org-dartlang-sdk:///lib/core/type.dart
org-dartlang-sdk:///lib/core/uri.dart
org-dartlang-sdk:///lib/core/weak.dart
org-dartlang-sdk:///lib/developer/developer.dart
org-dartlang-sdk:///lib/developer/extension.dart
org-dartlang-sdk:///lib/developer/http_profiling.dart
org-dartlang-sdk:///lib/developer/profiler.dart
org-dartlang-sdk:///lib/developer/service.dart
org-dartlang-sdk:///lib/developer/timeline.dart
org-dartlang-sdk:///lib/html/dart2js/html_dart2js.dart
org-dartlang-sdk:///lib/html/html_common/conversions.dart
org-dartlang-sdk:///lib/html/html_common/conversions_dart2js.dart
org-dartlang-sdk:///lib/html/html_common/css_class_set.dart
org-dartlang-sdk:///lib/html/html_common/device.dart
org-dartlang-sdk:///lib/html/html_common/filtered_element_list.dart
org-dartlang-sdk:///lib/html/html_common/html_common_dart2js.dart
org-dartlang-sdk:///lib/html/html_common/lists.dart
org-dartlang-sdk:///lib/html/html_common/metadata.dart
org-dartlang-sdk:///lib/indexed_db/dart2js/indexed_db_dart2js.dart
org-dartlang-sdk:///lib/internal/async_cast.dart
org-dartlang-sdk:///lib/internal/bytes_builder.dart
org-dartlang-sdk:///lib/internal/cast.dart
org-dartlang-sdk:///lib/internal/errors.dart
org-dartlang-sdk:///lib/internal/internal.dart
org-dartlang-sdk:///lib/internal/iterable.dart
org-dartlang-sdk:///lib/internal/linked_list.dart
org-dartlang-sdk:///lib/internal/list.dart
org-dartlang-sdk:///lib/internal/patch.dart
org-dartlang-sdk:///lib/internal/print.dart
org-dartlang-sdk:///lib/internal/sort.dart
org-dartlang-sdk:///lib/internal/symbol.dart
org-dartlang-sdk:///lib/io/common.dart
org-dartlang-sdk:///lib/io/data_transformer.dart
org-dartlang-sdk:///lib/io/directory.dart
org-dartlang-sdk:///lib/io/directory_impl.dart
org-dartlang-sdk:///lib/io/embedder_config.dart
org-dartlang-sdk:///lib/io/eventhandler.dart
org-dartlang-sdk:///lib/io/file.dart
org-dartlang-sdk:///lib/io/file_impl.dart
org-dartlang-sdk:///lib/io/file_system_entity.dart
org-dartlang-sdk:///lib/io/io.dart
org-dartlang-sdk:///lib/io/io_resource_info.dart
org-dartlang-sdk:///lib/io/io_service.dart
org-dartlang-sdk:///lib/io/io_sink.dart
org-dartlang-sdk:///lib/io/link.dart
org-dartlang-sdk:///lib/io/namespace_impl.dart
org-dartlang-sdk:///lib/io/network_profiling.dart
org-dartlang-sdk:///lib/io/overrides.dart
org-dartlang-sdk:///lib/io/platform.dart
org-dartlang-sdk:///lib/io/platform_impl.dart
org-dartlang-sdk:///lib/io/process.dart
org-dartlang-sdk:///lib/io/secure_server_socket.dart
org-dartlang-sdk:///lib/io/secure_socket.dart
org-dartlang-sdk:///lib/io/security_context.dart
org-dartlang-sdk:///lib/io/service_object.dart
org-dartlang-sdk:///lib/io/socket.dart
org-dartlang-sdk:///lib/io/stdio.dart
org-dartlang-sdk:///lib/io/string_transformer.dart
org-dartlang-sdk:///lib/io/sync_socket.dart
org-dartlang-sdk:///lib/isolate/capability.dart
org-dartlang-sdk:///lib/isolate/isolate.dart
org-dartlang-sdk:///lib/js/_js.dart
org-dartlang-sdk:///lib/js/_js_annotations.dart
org-dartlang-sdk:///lib/js/_js_client.dart
org-dartlang-sdk:///lib/js/js.dart
org-dartlang-sdk:///lib/js_interop/js_interop.dart
org-dartlang-sdk:///lib/js_interop_unsafe/js_interop_unsafe.dart
org-dartlang-sdk:///lib/js_util/js_util.dart
org-dartlang-sdk:///lib/math/math.dart
org-dartlang-sdk:///lib/math/point.dart
org-dartlang-sdk:///lib/math/random.dart
org-dartlang-sdk:///lib/math/rectangle.dart
org-dartlang-sdk:///lib/svg/dart2js/svg_dart2js.dart
org-dartlang-sdk:///lib/typed_data/typed_data.dart
org-dartlang-sdk:///lib/web_audio/dart2js/web_audio_dart2js.dart
org-dartlang-sdk:///lib/web_gl/dart2js/web_gl_dart2js.dart

File diff suppressed because one or more lines are too long

BIN
web/sqlite3.wasm Normal file

Binary file not shown.