♻️ 使用 SQLITE 来存储本地消息记录 #1
@ -21,8 +21,8 @@ linter:
|
|||||||
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
||||||
# producing the lint.
|
# producing the lint.
|
||||||
rules:
|
rules:
|
||||||
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
avoid_print: true # Uncomment to disable the `avoid_print` rule
|
||||||
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||||
|
|
||||||
# Additional information about this file can be found at
|
# Additional information about this file can be found at
|
||||||
# https://dart.dev/guides/language/analysis-options
|
# https://dart.dev/guides/language/analysis-options
|
||||||
|
79
lib/controllers/chat_history_controller.dart
Normal file
79
lib/controllers/chat_history_controller.dart
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:solian/models/channel.dart';
|
||||||
|
import 'package:solian/models/message.dart';
|
||||||
|
import 'package:solian/providers/message/helper.dart';
|
||||||
|
import 'package:solian/providers/message/history.dart';
|
||||||
|
|
||||||
|
class ChatHistoryController {
|
||||||
|
late final MessageHistoryDb database;
|
||||||
|
|
||||||
|
final RxList<LocalMessage> currentHistory = RxList.empty(growable: true);
|
||||||
|
final RxInt totalHistoryCount = 0.obs;
|
||||||
|
|
||||||
|
final RxBool isLoading = false.obs;
|
||||||
|
|
||||||
|
initialize() async {
|
||||||
|
database = await createHistoryDb();
|
||||||
|
currentHistory.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> getMessages(Channel channel, String scope) async {
|
||||||
|
isLoading.value = true;
|
||||||
|
totalHistoryCount.value =
|
||||||
|
await database.syncMessages(channel, scope: scope);
|
||||||
|
await syncHistory(channel);
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> getMoreMessages(Channel channel, String scope) async {
|
||||||
|
isLoading.value = true;
|
||||||
|
totalHistoryCount.value = await database.syncMessages(
|
||||||
|
channel,
|
||||||
|
breath: 3,
|
||||||
|
scope: scope,
|
||||||
|
offset: currentHistory.length,
|
||||||
|
);
|
||||||
|
await syncHistory(channel);
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> syncHistory(Channel channel) async {
|
||||||
|
currentHistory.replaceRange(0, currentHistory.length,
|
||||||
|
await database.localMessages.findAllByChannel(channel.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
receiveMessage(Message remote) async {
|
||||||
|
final entry = await database.receiveMessage(remote);
|
||||||
|
final idx = currentHistory.indexWhere((x) => x.data.uuid == remote.uuid);
|
||||||
|
if (idx != -1) {
|
||||||
|
currentHistory[idx] = entry;
|
||||||
|
} else {
|
||||||
|
currentHistory.insert(0, entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addTemporaryMessage(Message info) async {
|
||||||
|
currentHistory.insert(
|
||||||
|
0,
|
||||||
|
LocalMessage(
|
||||||
|
info.id,
|
||||||
|
info,
|
||||||
|
info.channelId,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void replaceMessage(Message remote) async {
|
||||||
|
final entry = await database.replaceMessage(remote);
|
||||||
|
currentHistory.replaceRange(
|
||||||
|
0,
|
||||||
|
currentHistory.length,
|
||||||
|
currentHistory.map((x) => x.id == entry.id ? entry : x),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void burnMessage(int id) async {
|
||||||
|
await database.burnMessage(id);
|
||||||
|
currentHistory.removeWhere((x) => x.id == id);
|
||||||
|
}
|
||||||
|
}
|
@ -33,6 +33,7 @@ void main() async {
|
|||||||
await Firebase.initializeApp(
|
await Firebase.initializeApp(
|
||||||
options: DefaultFirebaseOptions.currentPlatform,
|
options: DefaultFirebaseOptions.currentPlatform,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (PlatformInfo.isDesktop) {
|
if (PlatformInfo.isDesktop) {
|
||||||
await Window.initialize();
|
await Window.initialize();
|
||||||
await Window.setEffect(
|
await Window.setEffect(
|
||||||
|
@ -81,24 +81,24 @@ class AccountBadge {
|
|||||||
});
|
});
|
||||||
|
|
||||||
factory AccountBadge.fromJson(Map<String, dynamic> json) => AccountBadge(
|
factory AccountBadge.fromJson(Map<String, dynamic> json) => AccountBadge(
|
||||||
id: json["id"],
|
id: json['id'],
|
||||||
accountId: json["account_id"],
|
accountId: json['account_id'],
|
||||||
updatedAt: DateTime.parse(json["updated_at"]),
|
updatedAt: DateTime.parse(json['updated_at']),
|
||||||
createdAt: DateTime.parse(json["created_at"]),
|
createdAt: DateTime.parse(json['created_at']),
|
||||||
deletedAt: json["deleted_at"] != null
|
deletedAt: json['deleted_at'] != null
|
||||||
? DateTime.parse(json["deleted_at"])
|
? DateTime.parse(json['deleted_at'])
|
||||||
: null,
|
: null,
|
||||||
metadata: json["metadata"],
|
metadata: json['metadata'],
|
||||||
type: json["type"],
|
type: json['type'],
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => {
|
Map<String, dynamic> toJson() => {
|
||||||
"id": id,
|
'id': id,
|
||||||
"account_id": accountId,
|
'account_id': accountId,
|
||||||
"created_at": createdAt.toIso8601String(),
|
'created_at': createdAt.toIso8601String(),
|
||||||
"updated_at": updatedAt.toIso8601String(),
|
'updated_at': updatedAt.toIso8601String(),
|
||||||
"deleted_at": deletedAt?.toIso8601String(),
|
'deleted_at': deletedAt?.toIso8601String(),
|
||||||
"metadata": metadata,
|
'metadata': metadata,
|
||||||
"type": type,
|
'type': type,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -38,40 +38,40 @@ class Attachment {
|
|||||||
});
|
});
|
||||||
|
|
||||||
factory Attachment.fromJson(Map<String, dynamic> json) => Attachment(
|
factory Attachment.fromJson(Map<String, dynamic> json) => Attachment(
|
||||||
id: json["id"],
|
id: json['id'],
|
||||||
createdAt: DateTime.parse(json["created_at"]),
|
createdAt: DateTime.parse(json['created_at']),
|
||||||
updatedAt: DateTime.parse(json["updated_at"]),
|
updatedAt: DateTime.parse(json['updated_at']),
|
||||||
deletedAt: json["deleted_at"],
|
deletedAt: json['deleted_at'],
|
||||||
uuid: json["uuid"],
|
uuid: json['uuid'],
|
||||||
size: json["size"],
|
size: json['size'],
|
||||||
name: json["name"],
|
name: json['name'],
|
||||||
alt: json["alt"],
|
alt: json['alt'],
|
||||||
usage: json["usage"],
|
usage: json['usage'],
|
||||||
mimetype: json["mimetype"],
|
mimetype: json['mimetype'],
|
||||||
hash: json["hash"],
|
hash: json['hash'],
|
||||||
destination: json["destination"],
|
destination: json['destination'],
|
||||||
metadata: json["metadata"],
|
metadata: json['metadata'],
|
||||||
isMature: json["is_mature"],
|
isMature: json['is_mature'],
|
||||||
account: Account.fromJson(json["account"]),
|
account: Account.fromJson(json['account']),
|
||||||
accountId: json["account_id"],
|
accountId: json['account_id'],
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => {
|
Map<String, dynamic> toJson() => {
|
||||||
"id": id,
|
'id': id,
|
||||||
"created_at": createdAt.toIso8601String(),
|
'created_at': createdAt.toIso8601String(),
|
||||||
"updated_at": updatedAt.toIso8601String(),
|
'updated_at': updatedAt.toIso8601String(),
|
||||||
"deleted_at": deletedAt,
|
'deleted_at': deletedAt,
|
||||||
"uuid": uuid,
|
'uuid': uuid,
|
||||||
"size": size,
|
'size': size,
|
||||||
"name": name,
|
'name': name,
|
||||||
"alt": alt,
|
'alt': alt,
|
||||||
"usage": usage,
|
'usage': usage,
|
||||||
"mimetype": mimetype,
|
'mimetype': mimetype,
|
||||||
"hash": hash,
|
'hash': hash,
|
||||||
"destination": destination,
|
'destination': destination,
|
||||||
"metadata": metadata,
|
'metadata': metadata,
|
||||||
"is_mature": isMature,
|
'is_mature': isMature,
|
||||||
"account": account.toJson(),
|
'account': account.toJson(),
|
||||||
"account_id": accountId,
|
'account_id': accountId,
|
||||||
};
|
};
|
||||||
}
|
}
|
@ -44,10 +44,11 @@ class Message {
|
|||||||
deletedAt: json['deleted_at'],
|
deletedAt: json['deleted_at'],
|
||||||
content: json['content'],
|
content: json['content'],
|
||||||
type: json['type'],
|
type: json['type'],
|
||||||
attachments: json["attachments"] != null
|
attachments: json['attachments'] != null
|
||||||
? List<int>.from(json["attachments"])
|
? List<int>.from(json['attachments'])
|
||||||
: null,
|
: null,
|
||||||
channel: Channel.fromJson(json['channel']),
|
channel:
|
||||||
|
json['channel'] != null ? Channel.fromJson(json['channel']) : null,
|
||||||
sender: Sender.fromJson(json['sender']),
|
sender: Sender.fromJson(json['sender']),
|
||||||
replyId: json['reply_id'],
|
replyId: json['reply_id'],
|
||||||
replyTo: json['reply_to'] != null
|
replyTo: json['reply_to'] != null
|
||||||
|
@ -53,36 +53,36 @@ class Post {
|
|||||||
});
|
});
|
||||||
|
|
||||||
factory Post.fromJson(Map<String, dynamic> json) => Post(
|
factory Post.fromJson(Map<String, dynamic> json) => Post(
|
||||||
id: json["id"],
|
id: json['id'],
|
||||||
createdAt: DateTime.parse(json["created_at"]),
|
createdAt: DateTime.parse(json['created_at']),
|
||||||
updatedAt: DateTime.parse(json["updated_at"]),
|
updatedAt: DateTime.parse(json['updated_at']),
|
||||||
deletedAt: json["deleted_at"] != null
|
deletedAt: json['deleted_at'] != null
|
||||||
? DateTime.parse(json['deleted_at'])
|
? DateTime.parse(json['deleted_at'])
|
||||||
: null,
|
: null,
|
||||||
alias: json["alias"],
|
alias: json['alias'],
|
||||||
content: json["content"],
|
content: json['content'],
|
||||||
tags: json["tags"],
|
tags: json['tags'],
|
||||||
categories: json["categories"],
|
categories: json['categories'],
|
||||||
reactions: json["reactions"],
|
reactions: json['reactions'],
|
||||||
replies: json["replies"],
|
replies: json['replies'],
|
||||||
attachments: json["attachments"] != null
|
attachments: json['attachments'] != null
|
||||||
? List<int>.from(json["attachments"])
|
? List<int>.from(json['attachments'])
|
||||||
: null,
|
: null,
|
||||||
replyId: json["reply_id"],
|
replyId: json['reply_id'],
|
||||||
repostId: json["repost_id"],
|
repostId: json['repost_id'],
|
||||||
realmId: json["realm_id"],
|
realmId: json['realm_id'],
|
||||||
replyTo:
|
replyTo:
|
||||||
json["reply_to"] != null ? Post.fromJson(json["reply_to"]) : null,
|
json['reply_to'] != null ? Post.fromJson(json['reply_to']) : null,
|
||||||
repostTo:
|
repostTo:
|
||||||
json["repost_to"] != null ? Post.fromJson(json["repost_to"]) : null,
|
json['repost_to'] != null ? Post.fromJson(json['repost_to']) : null,
|
||||||
realm: json["realm"] != null ? Realm.fromJson(json["realm"]) : null,
|
realm: json['realm'] != null ? Realm.fromJson(json['realm']) : null,
|
||||||
publishedAt: json["published_at"] != null ? DateTime.parse(json["published_at"]) : null,
|
publishedAt: json['published_at'] != null ? DateTime.parse(json['published_at']) : null,
|
||||||
authorId: json["author_id"],
|
authorId: json['author_id'],
|
||||||
author: Account.fromJson(json["author"]),
|
author: Account.fromJson(json['author']),
|
||||||
replyCount: json["reply_count"],
|
replyCount: json['reply_count'],
|
||||||
reactionCount: json["reaction_count"],
|
reactionCount: json['reaction_count'],
|
||||||
reactionList: json["reaction_list"] != null
|
reactionList: json['reaction_list'] != null
|
||||||
? json["reaction_list"]
|
? json['reaction_list']
|
||||||
.map((key, value) => MapEntry(
|
.map((key, value) => MapEntry(
|
||||||
key,
|
key,
|
||||||
int.tryParse(value.toString()) ??
|
int.tryParse(value.toString()) ??
|
||||||
@ -92,28 +92,28 @@ class Post {
|
|||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => {
|
Map<String, dynamic> toJson() => {
|
||||||
"id": id,
|
'id': id,
|
||||||
"created_at": createdAt.toIso8601String(),
|
'created_at': createdAt.toIso8601String(),
|
||||||
"updated_at": updatedAt.toIso8601String(),
|
'updated_at': updatedAt.toIso8601String(),
|
||||||
"deleted_at": deletedAt,
|
'deleted_at': deletedAt,
|
||||||
"alias": alias,
|
'alias': alias,
|
||||||
"content": content,
|
'content': content,
|
||||||
"tags": tags,
|
'tags': tags,
|
||||||
"categories": categories,
|
'categories': categories,
|
||||||
"reactions": reactions,
|
'reactions': reactions,
|
||||||
"replies": replies,
|
'replies': replies,
|
||||||
"attachments": attachments,
|
'attachments': attachments,
|
||||||
"reply_id": replyId,
|
'reply_id': replyId,
|
||||||
"repost_id": repostId,
|
'repost_id': repostId,
|
||||||
"realm_id": realmId,
|
'realm_id': realmId,
|
||||||
"reply_to": replyTo?.toJson(),
|
'reply_to': replyTo?.toJson(),
|
||||||
"repost_to": repostTo?.toJson(),
|
'repost_to': repostTo?.toJson(),
|
||||||
"realm": realm?.toJson(),
|
'realm': realm?.toJson(),
|
||||||
"published_at": publishedAt?.toIso8601String(),
|
'published_at': publishedAt?.toIso8601String(),
|
||||||
"author_id": authorId,
|
'author_id': authorId,
|
||||||
"author": author.toJson(),
|
'author': author.toJson(),
|
||||||
"reply_count": replyCount,
|
'reply_count': replyCount,
|
||||||
"reaction_count": reactionCount,
|
'reaction_count': reactionCount,
|
||||||
"reaction_list": reactionList,
|
'reaction_list': reactionList,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -190,10 +190,10 @@ class AccountProvider extends GetxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (PlatformInfo.isIOS || PlatformInfo.isMacOS) {
|
if (PlatformInfo.isIOS || PlatformInfo.isMacOS) {
|
||||||
provider = "apple";
|
provider = 'apple';
|
||||||
token = await FirebaseMessaging.instance.getAPNSToken();
|
token = await FirebaseMessaging.instance.getAPNSToken();
|
||||||
} else {
|
} else {
|
||||||
provider = "firebase";
|
provider = 'firebase';
|
||||||
token = await FirebaseMessaging.instance.getToken();
|
token = await FirebaseMessaging.instance.getToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:get/get_connect/http/src/request/request.dart';
|
import 'package:get/get_connect/http/src/request/request.dart';
|
||||||
|
import 'package:solian/controllers/chat_history_controller.dart';
|
||||||
import 'package:solian/providers/account.dart';
|
import 'package:solian/providers/account.dart';
|
||||||
import 'package:solian/providers/chat.dart';
|
import 'package:solian/providers/chat.dart';
|
||||||
import 'package:solian/services.dart';
|
import 'package:solian/services.dart';
|
||||||
@ -79,7 +80,7 @@ class AuthProvider extends GetConnect {
|
|||||||
|
|
||||||
if (credentials!.isExpired) {
|
if (credentials!.isExpired) {
|
||||||
await refreshCredentials();
|
await refreshCredentials();
|
||||||
log("Refreshed credentials at ${DateTime.now()}");
|
log('Refreshed credentials at ${DateTime.now()}');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,6 +136,11 @@ class AuthProvider extends GetConnect {
|
|||||||
Get.find<AccountProvider>().notifications.clear();
|
Get.find<AccountProvider>().notifications.clear();
|
||||||
Get.find<AccountProvider>().notificationUnread.value = 0;
|
Get.find<AccountProvider>().notificationUnread.value = 0;
|
||||||
|
|
||||||
|
final chatHistory = ChatHistoryController();
|
||||||
|
chatHistory.initialize().then((_) async {
|
||||||
|
await chatHistory.database.localMessages.wipeLocalMessages();
|
||||||
|
});
|
||||||
|
|
||||||
storage.deleteAll();
|
storage.deleteAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
108
lib/providers/message/helper.dart
Normal file
108
lib/providers/message/helper.dart
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:solian/models/channel.dart';
|
||||||
|
import 'package:solian/models/message.dart';
|
||||||
|
import 'package:solian/models/pagination.dart';
|
||||||
|
import 'package:solian/providers/auth.dart';
|
||||||
|
import 'package:solian/providers/message/history.dart';
|
||||||
|
|
||||||
|
Future<MessageHistoryDb> createHistoryDb() async {
|
||||||
|
return await $FloorMessageHistoryDb
|
||||||
|
.databaseBuilder('messaging_data.dart')
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
extension MessageHistoryHelper on MessageHistoryDb {
|
||||||
|
receiveMessage(Message remote) async {
|
||||||
|
final entry = LocalMessage(
|
||||||
|
remote.id,
|
||||||
|
remote,
|
||||||
|
remote.channelId,
|
||||||
|
);
|
||||||
|
await localMessages.insert(entry);
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
replaceMessage(Message remote) async {
|
||||||
|
final entry = LocalMessage(
|
||||||
|
remote.id,
|
||||||
|
remote,
|
||||||
|
remote.channelId,
|
||||||
|
);
|
||||||
|
await localMessages.update(entry);
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
burnMessage(int id) async {
|
||||||
|
await localMessages.delete(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
syncMessages(Channel channel, {String scope = 'global', breath = 10, offset = 0}) async {
|
||||||
|
final lastOne = await localMessages.findLastByChannel(channel.id);
|
||||||
|
|
||||||
|
final data = await _getRemoteMessages(
|
||||||
|
channel,
|
||||||
|
scope,
|
||||||
|
remainBreath: breath,
|
||||||
|
offset: offset,
|
||||||
|
onBrake: (items) {
|
||||||
|
return items.any((x) => x.id == lastOne?.id);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (data != null) {
|
||||||
|
await localMessages.insertBulk(
|
||||||
|
data.$1.map((x) => LocalMessage(x.id, x, x.channelId)).toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return data?.$2 ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<(List<Message>, int)?> _getRemoteMessages(
|
||||||
|
Channel channel,
|
||||||
|
String scope, {
|
||||||
|
required int remainBreath,
|
||||||
|
bool Function(List<Message> items)? onBrake,
|
||||||
|
take = 10,
|
||||||
|
offset = 0,
|
||||||
|
}) async {
|
||||||
|
if (remainBreath <= 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final AuthProvider auth = Get.find();
|
||||||
|
if (!await auth.isAuthorized) return null;
|
||||||
|
|
||||||
|
final client = auth.configureClient('messaging');
|
||||||
|
|
||||||
|
final resp = await client.get(
|
||||||
|
'/api/channels/$scope/${channel.alias}/messages?take=$take&offset=$offset');
|
||||||
|
|
||||||
|
if (resp.statusCode != 200) {
|
||||||
|
throw Exception(resp.bodyString);
|
||||||
|
}
|
||||||
|
|
||||||
|
final PaginationResult response = PaginationResult.fromJson(resp.body);
|
||||||
|
final result =
|
||||||
|
response.data?.map((e) => Message.fromJson(e)).toList() ?? List.empty();
|
||||||
|
|
||||||
|
if (onBrake != null && onBrake(result)) {
|
||||||
|
return (result, response.count);
|
||||||
|
}
|
||||||
|
|
||||||
|
final expandResult = (await _getRemoteMessages(
|
||||||
|
channel,
|
||||||
|
scope,
|
||||||
|
remainBreath: remainBreath - 1,
|
||||||
|
take: take,
|
||||||
|
offset: offset + result.length,
|
||||||
|
))
|
||||||
|
?.$1 ??
|
||||||
|
List.empty();
|
||||||
|
|
||||||
|
return ([...result, ...expandResult], response.count);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<LocalMessage>> listMessages(Channel channel) async {
|
||||||
|
return await localMessages.findAllByChannel(channel.id);
|
||||||
|
}
|
||||||
|
}
|
66
lib/providers/message/history.dart
Normal file
66
lib/providers/message/history.dart
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'package:floor/floor.dart';
|
||||||
|
import 'package:solian/models/message.dart';
|
||||||
|
import 'package:sqflite/sqflite.dart' as sqflite;
|
||||||
|
|
||||||
|
part 'history.g.dart';
|
||||||
|
|
||||||
|
@entity
|
||||||
|
class LocalMessage {
|
||||||
|
@primaryKey
|
||||||
|
final int id;
|
||||||
|
|
||||||
|
final Message data;
|
||||||
|
final int channelId;
|
||||||
|
|
||||||
|
LocalMessage(this.id, this.data, this.channelId);
|
||||||
|
}
|
||||||
|
|
||||||
|
class RemoteMessageConverter extends TypeConverter<Message, String> {
|
||||||
|
@override
|
||||||
|
Message decode(String databaseValue) {
|
||||||
|
return Message.fromJson(jsonDecode(databaseValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String encode(Message value) {
|
||||||
|
return jsonEncode(value.toJson());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@dao
|
||||||
|
abstract class LocalMessageDao {
|
||||||
|
@Query('SELECT COUNT(id) FROM LocalMessage WHERE channelId = :channelId')
|
||||||
|
Future<int?> countByChannel(int channelId);
|
||||||
|
|
||||||
|
@Query('SELECT * FROM LocalMessage WHERE channelId = :channelId ORDER BY id DESC')
|
||||||
|
Future<List<LocalMessage>> findAllByChannel(int channelId);
|
||||||
|
|
||||||
|
@Query('SELECT * FROM LocalMessage WHERE channelId = :channelId ORDER BY id DESC LIMIT 1')
|
||||||
|
Future<LocalMessage?> findLastByChannel(int channelId);
|
||||||
|
|
||||||
|
@Insert(onConflict: OnConflictStrategy.replace)
|
||||||
|
Future<void> insert(LocalMessage m);
|
||||||
|
|
||||||
|
@Insert(onConflict: OnConflictStrategy.replace)
|
||||||
|
Future<void> insertBulk(List<LocalMessage> m);
|
||||||
|
|
||||||
|
@Update(onConflict: OnConflictStrategy.replace)
|
||||||
|
Future<void> update(LocalMessage person);
|
||||||
|
|
||||||
|
@Query('DELETE FROM LocalMessage WHERE id = :id')
|
||||||
|
Future<void> delete(int id);
|
||||||
|
|
||||||
|
@Query('DELETE FROM LocalMessage WHERE channelId = :channelId')
|
||||||
|
Future<List<LocalMessage>> deleteByChannel(int channelId);
|
||||||
|
|
||||||
|
@Query('DELETE FROM LocalMessage')
|
||||||
|
Future<void> wipeLocalMessages();
|
||||||
|
}
|
||||||
|
|
||||||
|
@TypeConverters([RemoteMessageConverter])
|
||||||
|
@Database(version: 1, entities: [LocalMessage])
|
||||||
|
abstract class MessageHistoryDb extends FloorDatabase {
|
||||||
|
LocalMessageDao get localMessages;
|
||||||
|
}
|
214
lib/providers/message/history.g.dart
Normal file
214
lib/providers/message/history.g.dart
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'history.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// FloorGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
abstract class $MessageHistoryDbBuilderContract {
|
||||||
|
/// Adds migrations to the builder.
|
||||||
|
$MessageHistoryDbBuilderContract addMigrations(List<Migration> migrations);
|
||||||
|
|
||||||
|
/// Adds a database [Callback] to the builder.
|
||||||
|
$MessageHistoryDbBuilderContract addCallback(Callback callback);
|
||||||
|
|
||||||
|
/// Creates the database and initializes it.
|
||||||
|
Future<MessageHistoryDb> build();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore: avoid_classes_with_only_static_members
|
||||||
|
class $FloorMessageHistoryDb {
|
||||||
|
/// Creates a database builder for a persistent database.
|
||||||
|
/// Once a database is built, you should keep a reference to it and re-use it.
|
||||||
|
static $MessageHistoryDbBuilderContract databaseBuilder(String name) =>
|
||||||
|
_$MessageHistoryDbBuilder(name);
|
||||||
|
|
||||||
|
/// Creates a database builder for an in memory database.
|
||||||
|
/// Information stored in an in memory database disappears when the process is killed.
|
||||||
|
/// Once a database is built, you should keep a reference to it and re-use it.
|
||||||
|
static $MessageHistoryDbBuilderContract inMemoryDatabaseBuilder() =>
|
||||||
|
_$MessageHistoryDbBuilder(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _$MessageHistoryDbBuilder implements $MessageHistoryDbBuilderContract {
|
||||||
|
_$MessageHistoryDbBuilder(this.name);
|
||||||
|
|
||||||
|
final String? name;
|
||||||
|
|
||||||
|
final List<Migration> _migrations = [];
|
||||||
|
|
||||||
|
Callback? _callback;
|
||||||
|
|
||||||
|
@override
|
||||||
|
$MessageHistoryDbBuilderContract addMigrations(List<Migration> migrations) {
|
||||||
|
_migrations.addAll(migrations);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
$MessageHistoryDbBuilderContract addCallback(Callback callback) {
|
||||||
|
_callback = callback;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<MessageHistoryDb> build() async {
|
||||||
|
final path = name != null
|
||||||
|
? await sqfliteDatabaseFactory.getDatabasePath(name!)
|
||||||
|
: ':memory:';
|
||||||
|
final database = _$MessageHistoryDb();
|
||||||
|
database.database = await database.open(
|
||||||
|
path,
|
||||||
|
_migrations,
|
||||||
|
_callback,
|
||||||
|
);
|
||||||
|
return database;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _$MessageHistoryDb extends MessageHistoryDb {
|
||||||
|
_$MessageHistoryDb([StreamController<String>? listener]) {
|
||||||
|
changeListener = listener ?? StreamController<String>.broadcast();
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalMessageDao? _localMessagesInstance;
|
||||||
|
|
||||||
|
Future<sqflite.Database> open(
|
||||||
|
String path,
|
||||||
|
List<Migration> migrations, [
|
||||||
|
Callback? callback,
|
||||||
|
]) async {
|
||||||
|
final databaseOptions = sqflite.OpenDatabaseOptions(
|
||||||
|
version: 1,
|
||||||
|
onConfigure: (database) async {
|
||||||
|
await database.execute('PRAGMA foreign_keys = ON');
|
||||||
|
await callback?.onConfigure?.call(database);
|
||||||
|
},
|
||||||
|
onOpen: (database) async {
|
||||||
|
await callback?.onOpen?.call(database);
|
||||||
|
},
|
||||||
|
onUpgrade: (database, startVersion, endVersion) async {
|
||||||
|
await MigrationAdapter.runMigrations(
|
||||||
|
database, startVersion, endVersion, migrations);
|
||||||
|
|
||||||
|
await callback?.onUpgrade?.call(database, startVersion, endVersion);
|
||||||
|
},
|
||||||
|
onCreate: (database, version) async {
|
||||||
|
await database.execute(
|
||||||
|
'CREATE TABLE IF NOT EXISTS `LocalMessage` (`id` INTEGER NOT NULL, `data` TEXT NOT NULL, `channelId` INTEGER NOT NULL, PRIMARY KEY (`id`))');
|
||||||
|
|
||||||
|
await callback?.onCreate?.call(database, version);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return sqfliteDatabaseFactory.openDatabase(path, options: databaseOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
LocalMessageDao get localMessages {
|
||||||
|
return _localMessagesInstance ??=
|
||||||
|
_$LocalMessageDao(database, changeListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _$LocalMessageDao extends LocalMessageDao {
|
||||||
|
_$LocalMessageDao(
|
||||||
|
this.database,
|
||||||
|
this.changeListener,
|
||||||
|
) : _queryAdapter = QueryAdapter(database),
|
||||||
|
_localMessageInsertionAdapter = InsertionAdapter(
|
||||||
|
database,
|
||||||
|
'LocalMessage',
|
||||||
|
(LocalMessage item) => <String, Object?>{
|
||||||
|
'id': item.id,
|
||||||
|
'data': _remoteMessageConverter.encode(item.data),
|
||||||
|
'channelId': item.channelId
|
||||||
|
}),
|
||||||
|
_localMessageUpdateAdapter = UpdateAdapter(
|
||||||
|
database,
|
||||||
|
'LocalMessage',
|
||||||
|
['id'],
|
||||||
|
(LocalMessage item) => <String, Object?>{
|
||||||
|
'id': item.id,
|
||||||
|
'data': _remoteMessageConverter.encode(item.data),
|
||||||
|
'channelId': item.channelId
|
||||||
|
});
|
||||||
|
|
||||||
|
final sqflite.DatabaseExecutor database;
|
||||||
|
|
||||||
|
final StreamController<String> changeListener;
|
||||||
|
|
||||||
|
final QueryAdapter _queryAdapter;
|
||||||
|
|
||||||
|
final InsertionAdapter<LocalMessage> _localMessageInsertionAdapter;
|
||||||
|
|
||||||
|
final UpdateAdapter<LocalMessage> _localMessageUpdateAdapter;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<int?> countByChannel(int channelId) async {
|
||||||
|
return _queryAdapter.query(
|
||||||
|
'SELECT COUNT(id) FROM LocalMessage WHERE channelId = ?1',
|
||||||
|
mapper: (Map<String, Object?> row) => row.values.first as int,
|
||||||
|
arguments: [channelId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<LocalMessage>> findAllByChannel(int channelId) async {
|
||||||
|
return _queryAdapter.queryList(
|
||||||
|
'SELECT * FROM LocalMessage WHERE channelId = ?1 ORDER BY id DESC',
|
||||||
|
mapper: (Map<String, Object?> row) => LocalMessage(
|
||||||
|
row['id'] as int,
|
||||||
|
_remoteMessageConverter.decode(row['data'] as String),
|
||||||
|
row['channelId'] as int),
|
||||||
|
arguments: [channelId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<LocalMessage?> findLastByChannel(int channelId) async {
|
||||||
|
return _queryAdapter.query(
|
||||||
|
'SELECT * FROM LocalMessage WHERE channelId = ?1 ORDER BY id DESC LIMIT 1',
|
||||||
|
mapper: (Map<String, Object?> row) => LocalMessage(row['id'] as int, _remoteMessageConverter.decode(row['data'] as String), row['channelId'] as int),
|
||||||
|
arguments: [channelId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> delete(int id) async {
|
||||||
|
await _queryAdapter.queryNoReturn('DELETE FROM LocalMessage WHERE id = ?1',
|
||||||
|
arguments: [id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<LocalMessage>> deleteByChannel(int channelId) async {
|
||||||
|
return _queryAdapter.queryList(
|
||||||
|
'DELETE FROM LocalMessage WHERE channelId = ?1',
|
||||||
|
mapper: (Map<String, Object?> row) => LocalMessage(
|
||||||
|
row['id'] as int,
|
||||||
|
_remoteMessageConverter.decode(row['data'] as String),
|
||||||
|
row['channelId'] as int),
|
||||||
|
arguments: [channelId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> wipeLocalMessages() async {
|
||||||
|
await _queryAdapter.queryNoReturn('DELETE FROM LocalMessage');
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> insert(LocalMessage m) async {
|
||||||
|
await _localMessageInsertionAdapter.insert(m, OnConflictStrategy.replace);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> insertBulk(List<LocalMessage> m) async {
|
||||||
|
await _localMessageInsertionAdapter.insertList(
|
||||||
|
m, OnConflictStrategy.replace);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> update(LocalMessage person) async {
|
||||||
|
await _localMessageUpdateAdapter.update(person, OnConflictStrategy.replace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore_for_file: unused_element
|
||||||
|
final _remoteMessageConverter = RemoteMessageConverter();
|
@ -27,9 +27,9 @@ class _CallScreenState extends State<CallScreen> {
|
|||||||
DateTime.now().difference(provider.current.value!.createdAt);
|
DateTime.now().difference(provider.current.value!.createdAt);
|
||||||
|
|
||||||
String twoDigits(int n) => n.toString().padLeft(2, '0');
|
String twoDigits(int n) => n.toString().padLeft(2, '0');
|
||||||
String formattedTime = "${twoDigits(duration.inHours)}:"
|
String formattedTime = '${twoDigits(duration.inHours)}:'
|
||||||
"${twoDigits(duration.inMinutes.remainder(60))}:"
|
'${twoDigits(duration.inMinutes.remainder(60))}:'
|
||||||
"${twoDigits(duration.inSeconds.remainder(60))}";
|
'${twoDigits(duration.inSeconds.remainder(60))}';
|
||||||
|
|
||||||
return formattedTime;
|
return formattedTime;
|
||||||
}
|
}
|
||||||
@ -66,7 +66,7 @@ class _CallScreenState extends State<CallScreen> {
|
|||||||
text: 'call'.tr,
|
text: 'call'.tr,
|
||||||
style: Theme.of(context).textTheme.titleLarge,
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
),
|
),
|
||||||
const TextSpan(text: "\n"),
|
const TextSpan(text: '\n'),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: currentDuration,
|
text: currentDuration,
|
||||||
style: Theme.of(context).textTheme.bodySmall,
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
|
@ -2,14 +2,14 @@ import 'dart:async';
|
|||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_animate/flutter_animate.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
import 'package:solian/controllers/chat_history_controller.dart';
|
||||||
import 'package:solian/exts.dart';
|
import 'package:solian/exts.dart';
|
||||||
import 'package:solian/models/call.dart';
|
import 'package:solian/models/call.dart';
|
||||||
import 'package:solian/models/channel.dart';
|
import 'package:solian/models/channel.dart';
|
||||||
import 'package:solian/models/message.dart';
|
import 'package:solian/models/message.dart';
|
||||||
import 'package:solian/models/packet.dart';
|
import 'package:solian/models/packet.dart';
|
||||||
import 'package:solian/models/pagination.dart';
|
|
||||||
import 'package:solian/providers/auth.dart';
|
import 'package:solian/providers/auth.dart';
|
||||||
import 'package:solian/providers/chat.dart';
|
import 'package:solian/providers/chat.dart';
|
||||||
import 'package:solian/providers/content/call.dart';
|
import 'package:solian/providers/content/call.dart';
|
||||||
@ -46,12 +46,11 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||||||
String? _overrideAlias;
|
String? _overrideAlias;
|
||||||
|
|
||||||
Channel? _channel;
|
Channel? _channel;
|
||||||
ChannelMember? _channelProfile;
|
|
||||||
Call? _ongoingCall;
|
Call? _ongoingCall;
|
||||||
|
ChannelMember? _channelProfile;
|
||||||
StreamSubscription<NetworkPackage>? _subscription;
|
StreamSubscription<NetworkPackage>? _subscription;
|
||||||
|
|
||||||
final PagingController<int, Message> _pagingController =
|
late final ChatHistoryController _chatController;
|
||||||
PagingController(firstPageKey: 0);
|
|
||||||
|
|
||||||
getProfile() async {
|
getProfile() async {
|
||||||
final AuthProvider auth = Get.find();
|
final AuthProvider auth = Get.find();
|
||||||
@ -106,31 +105,6 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||||||
setState(() => _isBusy = false);
|
setState(() => _isBusy = false);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> getMessages(int pageKey) async {
|
|
||||||
final AuthProvider auth = Get.find();
|
|
||||||
if (!await auth.isAuthorized) return;
|
|
||||||
|
|
||||||
final client = auth.configureClient('messaging');
|
|
||||||
|
|
||||||
final resp = await client.get(
|
|
||||||
'/api/channels/${widget.realm}/${widget.alias}/messages?take=10&offset=$pageKey');
|
|
||||||
|
|
||||||
if (resp.statusCode == 200) {
|
|
||||||
final PaginationResult result = PaginationResult.fromJson(resp.body);
|
|
||||||
final parsed = result.data?.map((e) => Message.fromJson(e)).toList();
|
|
||||||
|
|
||||||
if (parsed != null && parsed.length >= 10) {
|
|
||||||
_pagingController.appendPage(parsed, pageKey + parsed.length);
|
|
||||||
} else if (parsed != null) {
|
|
||||||
_pagingController.appendLastPage(parsed);
|
|
||||||
}
|
|
||||||
} else if (resp.statusCode == 403) {
|
|
||||||
_pagingController.appendLastPage([]);
|
|
||||||
} else {
|
|
||||||
_pagingController.error = resp.bodyString;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void listenMessages() {
|
void listenMessages() {
|
||||||
final ChatProvider provider = Get.find();
|
final ChatProvider provider = Get.find();
|
||||||
_subscription = provider.stream.stream.listen((event) {
|
_subscription = provider.stream.stream.listen((event) {
|
||||||
@ -138,33 +112,19 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||||||
case 'messages.new':
|
case 'messages.new':
|
||||||
final payload = Message.fromJson(event.payload!);
|
final payload = Message.fromJson(event.payload!);
|
||||||
if (payload.channelId == _channel?.id) {
|
if (payload.channelId == _channel?.id) {
|
||||||
final idx = _pagingController.itemList
|
_chatController.receiveMessage(payload);
|
||||||
?.indexWhere((e) => e.uuid == payload.uuid);
|
|
||||||
if ((idx ?? -1) >= 0) {
|
|
||||||
_pagingController.itemList?[idx!] = payload;
|
|
||||||
} else {
|
|
||||||
_pagingController.itemList?.insert(0, payload);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'messages.update':
|
case 'messages.update':
|
||||||
final payload = Message.fromJson(event.payload!);
|
final payload = Message.fromJson(event.payload!);
|
||||||
if (payload.channelId == _channel?.id) {
|
if (payload.channelId == _channel?.id) {
|
||||||
final idx = _pagingController.itemList
|
_chatController.replaceMessage(payload);
|
||||||
?.indexWhere((x) => x.uuid == payload.uuid);
|
|
||||||
if (idx != null) {
|
|
||||||
_pagingController.itemList?[idx] = payload;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'messages.burnt':
|
case 'messages.burnt':
|
||||||
final payload = Message.fromJson(event.payload!);
|
final payload = Message.fromJson(event.payload!);
|
||||||
if (payload.channelId == _channel?.id) {
|
if (payload.channelId == _channel?.id) {
|
||||||
final idx = _pagingController.itemList
|
_chatController.burnMessage(payload.id);
|
||||||
?.indexWhere((x) => x.uuid != payload.uuid);
|
|
||||||
if (idx != null) {
|
|
||||||
_pagingController.itemList?.removeAt(idx - 1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'calls.new':
|
case 'calls.new':
|
||||||
@ -175,7 +135,6 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||||||
_ongoingCall = null;
|
_ongoingCall = null;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
setState(() {});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -200,24 +159,9 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||||||
Message? _messageToReplying;
|
Message? _messageToReplying;
|
||||||
Message? _messageToEditing;
|
Message? _messageToEditing;
|
||||||
|
|
||||||
Widget buildHistory(context, Message item, index) {
|
Widget buildHistoryBody(Message item, {bool isMerged = false}) {
|
||||||
bool isMerged = false, hasMerged = false;
|
|
||||||
if (index > 0) {
|
|
||||||
hasMerged = checkMessageMergeable(
|
|
||||||
_pagingController.itemList?[index - 1],
|
|
||||||
item,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (index + 1 < (_pagingController.itemList?.length ?? 0)) {
|
|
||||||
isMerged = checkMessageMergeable(
|
|
||||||
item,
|
|
||||||
_pagingController.itemList?[index + 1],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget content;
|
|
||||||
if (item.replyTo != null) {
|
if (item.replyTo != null) {
|
||||||
content = Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
ChatMessage(
|
ChatMessage(
|
||||||
key: Key('m${item.replyTo!.uuid}'),
|
key: Key('m${item.replyTo!.uuid}'),
|
||||||
@ -231,17 +175,35 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
} else {
|
}
|
||||||
content = ChatMessage(
|
|
||||||
key: Key('m${item.uuid}'),
|
return ChatMessage(
|
||||||
item: item,
|
key: Key('m${item.uuid}'),
|
||||||
isMerged: isMerged,
|
item: item,
|
||||||
|
isMerged: isMerged,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildHistory(context, index) {
|
||||||
|
bool isMerged = false, hasMerged = false;
|
||||||
|
if (index > 0) {
|
||||||
|
hasMerged = checkMessageMergeable(
|
||||||
|
_chatController.currentHistory[index - 1].data,
|
||||||
|
_chatController.currentHistory[index].data,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (index + 1 < _chatController.currentHistory.length) {
|
||||||
|
isMerged = checkMessageMergeable(
|
||||||
|
_chatController.currentHistory[index].data,
|
||||||
|
_chatController.currentHistory[index + 1].data,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final item = _chatController.currentHistory[index].data;
|
||||||
|
|
||||||
return InkWell(
|
return InkWell(
|
||||||
child: Container(
|
child: Container(
|
||||||
child: content.paddingOnly(
|
child: buildHistoryBody(item, isMerged: isMerged).paddingOnly(
|
||||||
top: !isMerged ? 8 : 0,
|
top: !isMerged ? 8 : 0,
|
||||||
bottom: !hasMerged ? 8 : 0,
|
bottom: !hasMerged ? 8 : 0,
|
||||||
),
|
),
|
||||||
@ -268,14 +230,19 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
_chatController = ChatHistoryController();
|
||||||
|
_chatController.initialize();
|
||||||
|
|
||||||
|
getChannel().then((_) {
|
||||||
|
_chatController.getMessages(_channel!, widget.realm);
|
||||||
|
});
|
||||||
|
|
||||||
getProfile();
|
getProfile();
|
||||||
getChannel().then((_) {
|
|
||||||
listenMessages();
|
|
||||||
_pagingController.addPageRequestListener(getMessages);
|
|
||||||
});
|
|
||||||
getOngoingCall();
|
getOngoingCall();
|
||||||
|
|
||||||
|
listenMessages();
|
||||||
|
|
||||||
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -352,47 +319,84 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
|||||||
Column(
|
Column(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: PagedListView<int, Message>(
|
child: CustomScrollView(
|
||||||
clipBehavior: Clip.none,
|
|
||||||
reverse: true,
|
reverse: true,
|
||||||
pagingController: _pagingController,
|
slivers: [
|
||||||
builderDelegate: PagedChildBuilderDelegate<Message>(
|
Obx(() {
|
||||||
itemBuilder: buildHistory,
|
return SliverList.builder(
|
||||||
noItemsFoundIndicatorBuilder: (_) => Container(),
|
key: Key('chat-history#${_channel!.id}'),
|
||||||
),
|
itemCount: _chatController.currentHistory.length,
|
||||||
).paddingOnly(bottom: 56),
|
itemBuilder: buildHistory,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
Obx(() {
|
||||||
|
final amount = _chatController.totalHistoryCount -
|
||||||
|
_chatController.currentHistory.length;
|
||||||
|
|
||||||
|
if (amount.value <= 0 ||
|
||||||
|
_chatController.isLoading.isTrue) {
|
||||||
|
return const SliverToBoxAdapter(child: SizedBox());
|
||||||
|
}
|
||||||
|
|
||||||
|
return SliverToBoxAdapter(
|
||||||
|
child: ListTile(
|
||||||
|
tileColor:
|
||||||
|
Theme.of(context).colorScheme.surfaceContainerLow,
|
||||||
|
leading: const Icon(Icons.sync_disabled),
|
||||||
|
title: Text('messageUnsync'.tr),
|
||||||
|
subtitle: Text('messageUnsyncCaption'.trParams({
|
||||||
|
'count': amount.string,
|
||||||
|
})),
|
||||||
|
onTap: () {
|
||||||
|
_chatController.getMoreMessages(
|
||||||
|
_channel!,
|
||||||
|
widget.realm,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
Obx(() {
|
||||||
|
if (_chatController.isLoading.isFalse) {
|
||||||
|
return const SliverToBoxAdapter(child: SizedBox());
|
||||||
|
}
|
||||||
|
|
||||||
|
return SliverToBoxAdapter(
|
||||||
|
child: const LinearProgressIndicator()
|
||||||
|
.animate()
|
||||||
|
.slideY()
|
||||||
|
.paddingOnly(bottom: 4),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
ClipRect(
|
||||||
),
|
child: BackdropFilter(
|
||||||
Positioned(
|
filter: ImageFilter.blur(sigmaX: 50, sigmaY: 50),
|
||||||
bottom: 0,
|
child: SafeArea(
|
||||||
left: 0,
|
child: ChatMessageInput(
|
||||||
right: 0,
|
edit: _messageToEditing,
|
||||||
child: ClipRect(
|
reply: _messageToReplying,
|
||||||
child: BackdropFilter(
|
realm: widget.realm,
|
||||||
filter: ImageFilter.blur(sigmaX: 50, sigmaY: 50),
|
placeholder: placeholder,
|
||||||
child: SafeArea(
|
channel: _channel!,
|
||||||
child: ChatMessageInput(
|
onSent: (Message item) {
|
||||||
edit: _messageToEditing,
|
setState(() {
|
||||||
reply: _messageToReplying,
|
_chatController.addTemporaryMessage(item);
|
||||||
realm: widget.realm,
|
});
|
||||||
placeholder: placeholder,
|
},
|
||||||
channel: _channel!,
|
onReset: () {
|
||||||
onSent: (Message item) {
|
setState(() {
|
||||||
setState(() {
|
_messageToReplying = null;
|
||||||
_pagingController.itemList?.insert(0, item);
|
_messageToEditing = null;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onReset: () {
|
),
|
||||||
setState(() {
|
|
||||||
_messageToReplying = null;
|
|
||||||
_messageToEditing = null;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
if (_ongoingCall != null)
|
if (_ongoingCall != null)
|
||||||
Positioned(
|
Positioned(
|
||||||
|
@ -101,7 +101,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
context),
|
context),
|
||||||
sliver: SliverAppBar(
|
sliver: SliverAppBar(
|
||||||
title: AppBarTitle('chat'.tr),
|
title: AppBarTitle('chat'.tr),
|
||||||
centerTitle: true,
|
centerTitle: false,
|
||||||
floating: true,
|
floating: true,
|
||||||
titleSpacing: SolianTheme.titleSpacing(context),
|
titleSpacing: SolianTheme.titleSpacing(context),
|
||||||
toolbarHeight: SolianTheme.toolbarHeight(context),
|
toolbarHeight: SolianTheme.toolbarHeight(context),
|
||||||
|
@ -164,6 +164,8 @@ class SolianMessages extends Translations {
|
|||||||
'channelNotifyLevelMentioned': 'Only mentioned',
|
'channelNotifyLevelMentioned': 'Only mentioned',
|
||||||
'channelNotifyLevelNone': 'Ignore all',
|
'channelNotifyLevelNone': 'Ignore all',
|
||||||
'channelNotifyLevelApplied': 'Your notification settings has been applied.',
|
'channelNotifyLevelApplied': 'Your notification settings has been applied.',
|
||||||
|
'messageUnsync': 'Messages Un-synced',
|
||||||
|
'messageUnsyncCaption': '@count message(s) still in un-synced.',
|
||||||
'messageDecoding': 'Decoding...',
|
'messageDecoding': 'Decoding...',
|
||||||
'messageDecodeFailed': 'Unable to decode: @message',
|
'messageDecodeFailed': 'Unable to decode: @message',
|
||||||
'messageInputPlaceholder': 'Message @channel',
|
'messageInputPlaceholder': 'Message @channel',
|
||||||
@ -364,6 +366,8 @@ class SolianMessages extends Translations {
|
|||||||
'channelNotifyLevelMentioned': '仅提及',
|
'channelNotifyLevelMentioned': '仅提及',
|
||||||
'channelNotifyLevelNone': '忽略一切',
|
'channelNotifyLevelNone': '忽略一切',
|
||||||
'channelNotifyLevelApplied': '你的通知设置已经应用。',
|
'channelNotifyLevelApplied': '你的通知设置已经应用。',
|
||||||
|
'messageUnsync': '消息未同步',
|
||||||
|
'messageUnsyncCaption': '还有 @count 条消息未同步',
|
||||||
'messageDecoding': '解码信息中…',
|
'messageDecoding': '解码信息中…',
|
||||||
'messageDecodeFailed': '解码信息失败:@message',
|
'messageDecodeFailed': '解码信息失败:@message',
|
||||||
'messageInputPlaceholder': '在 @channel 发信息',
|
'messageInputPlaceholder': '在 @channel 发信息',
|
||||||
|
@ -67,7 +67,7 @@ class SliverFriendList extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SliverList.builder(
|
return SliverList.builder(
|
||||||
itemCount: items.length,
|
itemCount: items.length,
|
||||||
itemBuilder: (_, __) => buildItem(_, __),
|
itemBuilder: (context, idx) => buildItem(context, idx),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,7 @@ class _ChatCallButtonState extends State<ChatCallButton> {
|
|||||||
? widget.realm?.alias
|
? widget.realm?.alias
|
||||||
: 'global';
|
: 'global';
|
||||||
final resp = await client
|
final resp = await client
|
||||||
.delete('/api/channels/${scope}/${widget.channel.alias}/calls/ongoing');
|
.delete('/api/channels/$scope/${widget.channel.alias}/calls/ongoing');
|
||||||
if (resp.statusCode == 200) {
|
if (resp.statusCode == 200) {
|
||||||
if (widget.onEnded != null) widget.onEnded!();
|
if (widget.onEnded != null) widget.onEnded!();
|
||||||
} else {
|
} else {
|
||||||
|
@ -98,7 +98,7 @@ class _PostItemState extends State<PostItem> {
|
|||||||
|
|
||||||
if (labels.isNotEmpty) {
|
if (labels.isNotEmpty) {
|
||||||
return Text(
|
return Text(
|
||||||
labels.join(" · "),
|
labels.join(' · '),
|
||||||
textAlign: TextAlign.left,
|
textAlign: TextAlign.left,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
|
330
pubspec.lock
330
pubspec.lock
@ -1,6 +1,14 @@
|
|||||||
# Generated by pub
|
# Generated by pub
|
||||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
packages:
|
packages:
|
||||||
|
_fe_analyzer_shared:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: _fe_analyzer_shared
|
||||||
|
sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "67.0.0"
|
||||||
_flutterfire_internals:
|
_flutterfire_internals:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -9,6 +17,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.37"
|
version: "1.3.37"
|
||||||
|
analyzer:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: analyzer
|
||||||
|
sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.4.1"
|
||||||
archive:
|
archive:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -41,6 +57,70 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.1"
|
version: "2.1.1"
|
||||||
|
build:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: build
|
||||||
|
sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.1"
|
||||||
|
build_config:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: build_config
|
||||||
|
sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.1"
|
||||||
|
build_daemon:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: build_daemon
|
||||||
|
sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.0.2"
|
||||||
|
build_resolvers:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: build_resolvers
|
||||||
|
sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.2"
|
||||||
|
build_runner:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: build_runner
|
||||||
|
sha256: "644dc98a0f179b872f612d3eb627924b578897c629788e858157fa5e704ca0c7"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.11"
|
||||||
|
build_runner_core:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: build_runner_core
|
||||||
|
sha256: e3c79f69a64bdfcd8a776a3c28db4eb6e3fb5356d013ae5eb2e52007706d5dbe
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "7.3.1"
|
||||||
|
built_collection:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: built_collection
|
||||||
|
sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "5.1.1"
|
||||||
|
built_value:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: built_value
|
||||||
|
sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "8.9.2"
|
||||||
cached_network_image:
|
cached_network_image:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -81,6 +161,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.0"
|
version: "1.3.0"
|
||||||
|
charcode:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: charcode
|
||||||
|
sha256: fb98c0f6d12c920a02ee2d998da788bca066ca5f148492b7085ee23372b12306
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.1"
|
||||||
checked_yaml:
|
checked_yaml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -113,6 +201,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.1"
|
version: "1.1.1"
|
||||||
|
code_builder:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: code_builder
|
||||||
|
sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.10.0"
|
||||||
collection:
|
collection:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -137,6 +233,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.0"
|
version: "2.0.0"
|
||||||
|
convert:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: convert
|
||||||
|
sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.1"
|
||||||
cross_file:
|
cross_file:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -169,6 +273,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.8"
|
version: "1.0.8"
|
||||||
|
dart_style:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: dart_style
|
||||||
|
sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.6"
|
||||||
dart_webrtc:
|
dart_webrtc:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -185,6 +297,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.10"
|
version: "0.7.10"
|
||||||
|
dev_build:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: dev_build
|
||||||
|
sha256: "4a4c8e3aaaaa9a7de4039dc711ee573de5612a35a4469d01ecf30e5ae74cbcfd"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.16.7+4"
|
||||||
device_info_plus:
|
device_info_plus:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -329,6 +449,38 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0"
|
version: "1.1.0"
|
||||||
|
floor:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: floor
|
||||||
|
sha256: c1b06023912b5b8e49deb6a9d867863c535ae1a232d991c3582bba3ee8687867
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.5.0"
|
||||||
|
floor_annotation:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: floor_annotation
|
||||||
|
sha256: a40949580a7ab0eee572686e2d3b1638fd6bd6a753e661d792ab4236b365b23b
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.5.0"
|
||||||
|
floor_common:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: floor_common
|
||||||
|
sha256: "41c9914862f83a821815e1b1ffd47a1e1ae2130c35ff882ba2d000a67713ba64"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.5.0"
|
||||||
|
floor_generator:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: floor_generator
|
||||||
|
sha256: "1499b3ab878a807e6fbe6f140dc014124845cd1df3090a113aae5fa7577a1e77"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.5.0"
|
||||||
flutter:
|
flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
@ -504,6 +656,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "10.7.0"
|
version: "10.7.0"
|
||||||
|
frontend_server_client:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: frontend_server_client
|
||||||
|
sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.0.0"
|
||||||
get:
|
get:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -512,6 +672,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.6.6"
|
version: "4.6.6"
|
||||||
|
glob:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: glob
|
||||||
|
sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.2"
|
||||||
go_router:
|
go_router:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -520,6 +688,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "14.2.0"
|
version: "14.2.0"
|
||||||
|
graphs:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: graphs
|
||||||
|
sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.1"
|
||||||
html:
|
html:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -536,6 +712,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.1"
|
version: "1.2.1"
|
||||||
|
http_multi_server:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http_multi_server
|
||||||
|
sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.2.1"
|
||||||
http_parser:
|
http_parser:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -632,6 +816,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.19.0"
|
version: "0.19.0"
|
||||||
|
io:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: io
|
||||||
|
sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.4"
|
||||||
js:
|
js:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -680,6 +872,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.0"
|
version: "4.0.0"
|
||||||
|
lists:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: lists
|
||||||
|
sha256: "4ca5c19ae4350de036a7e996cdd1ee39c93ac0a2b840f4915459b7d0a7d4ab27"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.1"
|
||||||
livekit_client:
|
livekit_client:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -776,6 +976,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.0"
|
version: "2.0.0"
|
||||||
|
package_config:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: package_config
|
||||||
|
sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.0"
|
||||||
package_info_plus:
|
package_info_plus:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -928,6 +1136,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.8"
|
version: "2.1.8"
|
||||||
|
pool:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pool
|
||||||
|
sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.5.1"
|
||||||
|
process_run:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: process_run
|
||||||
|
sha256: "8d9c6198b98fbbfb511edd42e7364e24d85c163e47398919871b952dc86a423e"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.14.2"
|
||||||
protobuf:
|
protobuf:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -952,6 +1176,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.4"
|
version: "2.1.4"
|
||||||
|
pubspec_parse:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pubspec_parse
|
||||||
|
sha256: c799b721d79eb6ee6fa56f00c04b472dcd44a30d258fac2174a6ec57302678f8
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.0"
|
||||||
rxdart:
|
rxdart:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1040,6 +1272,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.2"
|
version: "2.3.2"
|
||||||
|
shelf:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf
|
||||||
|
sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.1"
|
||||||
|
shelf_web_socket:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf_web_socket
|
||||||
|
sha256: "073c147238594ecd0d193f3456a5fe91c4b0abbcc68bf5cd95b36c4e194ac611"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.0"
|
||||||
sky_engine:
|
sky_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
@ -1053,6 +1301,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.12"
|
version: "0.2.12"
|
||||||
|
source_gen:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_gen
|
||||||
|
sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.5.0"
|
||||||
source_span:
|
source_span:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1070,7 +1326,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "7.0.0"
|
version: "7.0.0"
|
||||||
sqflite:
|
sqflite:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: sqflite
|
name: sqflite
|
||||||
sha256: a43e5a27235518c03ca238e7b4732cf35eabe863a369ceba6cbefa537a66f16d
|
sha256: a43e5a27235518c03ca238e7b4732cf35eabe863a369ceba6cbefa537a66f16d
|
||||||
@ -1085,6 +1341,38 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.5.4"
|
version: "2.5.4"
|
||||||
|
sqflite_common_ffi:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: sqflite_common_ffi
|
||||||
|
sha256: "4d6137c29e930d6e4a8ff373989dd9de7bac12e3bc87bce950f6e844e8ad3bb5"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.3"
|
||||||
|
sqflite_common_ffi_web:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: sqflite_common_ffi_web
|
||||||
|
sha256: cfc9d1c61a3e06e5b2e96994a44b11125b4f451fee95b9fad8bd473b4613d592
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.4.3+1"
|
||||||
|
sqlite3:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: sqlite3
|
||||||
|
sha256: b384f598b813b347c5a7e5ffad82cbaff1bec3d1561af267041e66f6f0899295
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.3"
|
||||||
|
sqlparser:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: sqlparser
|
||||||
|
sha256: "7b20045d1ccfb7bc1df7e8f9fee5ae58673fce6ff62cefbb0e0fd7214e90e5a0"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.34.1"
|
||||||
stack_trace:
|
stack_trace:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1101,6 +1389,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.2"
|
version: "2.1.2"
|
||||||
|
stream_transform:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stream_transform
|
||||||
|
sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.0"
|
||||||
string_scanner:
|
string_scanner:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1109,6 +1405,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.0"
|
version: "1.2.0"
|
||||||
|
strings:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: strings
|
||||||
|
sha256: b33f40c4dd3e597bf6d9e7f4f4dc282dad0f19b07d9f320cb5c2183859cbccf5
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.1"
|
||||||
synchronized:
|
synchronized:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1149,6 +1453,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.9.3"
|
version: "0.9.3"
|
||||||
|
timing:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: timing
|
||||||
|
sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.1"
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1157,6 +1469,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.2"
|
version: "1.3.2"
|
||||||
|
unicode:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: unicode
|
||||||
|
sha256: "0f69e46593d65245774d4f17125c6084d2c20b4e473a983f6e21b7d7762218f1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.3.1"
|
||||||
url_launcher:
|
url_launcher:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -1309,6 +1629,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.1"
|
version: "1.2.1"
|
||||||
|
watcher:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: watcher
|
||||||
|
sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.0"
|
||||||
web:
|
web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
68
pubspec.yaml
68
pubspec.yaml
@ -1,38 +1,16 @@
|
|||||||
name: solian
|
name: solian
|
||||||
description: "The Solar Network App"
|
description: "The Solar Network App"
|
||||||
# The following line prevents the package from being accidentally published to
|
publish_to: "none"
|
||||||
# pub.dev using `flutter pub publish`. This is preferred for private packages.
|
|
||||||
publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
|
||||||
|
|
||||||
# The following defines the version and build number for your application.
|
|
||||||
# A version number is three numbers separated by dots, like 1.2.43
|
|
||||||
# followed by an optional build number separated by a +.
|
|
||||||
# Both the version and the builder number may be overridden in flutter
|
|
||||||
# build by specifying --build-name and --build-number, respectively.
|
|
||||||
# In Android, build-name is used as versionName while build-number used as versionCode.
|
|
||||||
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
|
|
||||||
# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
|
|
||||||
# Read more about iOS versioning at
|
|
||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
|
||||||
# In Windows, build-name is used as the major, minor, and patch parts
|
|
||||||
# of the product and file versions while build-number is used as the build suffix.
|
|
||||||
version: 1.1.0+1
|
version: 1.1.0+1
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=3.3.4 <4.0.0"
|
sdk: ">=3.3.4 <4.0.0"
|
||||||
|
|
||||||
# Dependencies specify other packages that your package needs in order to work.
|
|
||||||
# To automatically upgrade your package dependencies to the latest versions
|
|
||||||
# consider running `flutter pub upgrade --major-versions`. Alternatively,
|
|
||||||
# dependencies can be manually updated by changing the version numbers below to
|
|
||||||
# the latest version available on pub.dev. To see which dependencies have newer
|
|
||||||
# versions available, run `flutter pub outdated`.
|
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
||||||
# The following adds the Cupertino Icons font to your application.
|
|
||||||
# Use with the CupertinoIcons class for iOS style icons.
|
|
||||||
cupertino_icons: ^1.0.6
|
cupertino_icons: ^1.0.6
|
||||||
go_router: ^14.1.1
|
go_router: ^14.1.1
|
||||||
get: ^4.6.6
|
get: ^4.6.6
|
||||||
@ -70,59 +48,27 @@ dependencies:
|
|||||||
device_info_plus: ^10.1.0
|
device_info_plus: ^10.1.0
|
||||||
shared_preferences: ^2.2.3
|
shared_preferences: ^2.2.3
|
||||||
flutter_acrylic: ^1.1.4
|
flutter_acrylic: ^1.1.4
|
||||||
|
floor: ^1.5.0
|
||||||
|
sqflite: ^2.3.3+1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
||||||
# The "flutter_lints" package below contains a set of recommended lints to
|
|
||||||
# encourage good coding practices. The lint set provided by the package is
|
|
||||||
# activated in the `analysis_options.yaml` file located at the root of your
|
|
||||||
# package. See that file for information about deactivating specific lint
|
|
||||||
# rules and activating additional ones.
|
|
||||||
flutter_lints: ^4.0.0
|
flutter_lints: ^4.0.0
|
||||||
flutter_launcher_icons: ^0.13.1
|
flutter_launcher_icons: ^0.13.1
|
||||||
|
|
||||||
# For information on the generic Dart part of this file, see the
|
floor_generator: ^1.4.0
|
||||||
# following page: https://dart.dev/tools/pub/pubspec
|
build_runner: ^2.1.2
|
||||||
|
sqflite_common_ffi: ^2.3.3
|
||||||
|
sqflite_common_ffi_web: ^0.4.3+1
|
||||||
|
|
||||||
# The following section is specific to Flutter packages.
|
|
||||||
flutter:
|
flutter:
|
||||||
# The following line ensures that the Material Icons font is
|
|
||||||
# included with your application, so that you can use the icons in
|
|
||||||
# the material Icons class.
|
|
||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
|
|
||||||
# To add assets to your application, add an assets section, like this:
|
|
||||||
assets:
|
assets:
|
||||||
- assets/logo.png
|
- assets/logo.png
|
||||||
|
|
||||||
# An image asset can refer to one or more resolution-specific "variants", see
|
|
||||||
# https://flutter.dev/assets-and-images/#resolution-aware
|
|
||||||
|
|
||||||
# For details regarding adding assets from package dependencies, see
|
|
||||||
# https://flutter.dev/assets-and-images/#from-packages
|
|
||||||
|
|
||||||
# To add custom fonts to your application, add a fonts section here,
|
|
||||||
# in this "flutter" section. Each entry in this list should have a
|
|
||||||
# "family" key with the font family name, and a "fonts" key with a
|
|
||||||
# list giving the asset and other descriptors for the font. For
|
|
||||||
# example:
|
|
||||||
# fonts:
|
|
||||||
# - family: Schyler
|
|
||||||
# fonts:
|
|
||||||
# - asset: fonts/Schyler-Regular.ttf
|
|
||||||
# - asset: fonts/Schyler-Italic.ttf
|
|
||||||
# style: italic
|
|
||||||
# - family: Trajan Pro
|
|
||||||
# fonts:
|
|
||||||
# - asset: fonts/TrajanPro.ttf
|
|
||||||
# - asset: fonts/TrajanPro_Bold.ttf
|
|
||||||
# weight: 700
|
|
||||||
#
|
|
||||||
# For details regarding fonts from package dependencies,
|
|
||||||
# see https://flutter.dev/custom-fonts/#from-packages
|
|
||||||
|
|
||||||
flutter_launcher_icons:
|
flutter_launcher_icons:
|
||||||
android: "launcher_icon"
|
android: "launcher_icon"
|
||||||
ios: true
|
ios: true
|
||||||
|
Loading…
Reference in New Issue
Block a user