🔀 Merge pull request '♻️ 使用 SQLITE 来存储本地消息记录' (#1) from features/local-message-history into master
Reviewed-on: #1
This commit is contained in:
		| @@ -21,8 +21,8 @@ linter: | ||||
|   # `// ignore_for_file: name_of_lint` syntax on the line or in the file | ||||
|   # producing the lint. | ||||
|   rules: | ||||
|     # avoid_print: false  # Uncomment to disable the `avoid_print` rule | ||||
|     # prefer_single_quotes: true  # Uncomment to enable the `prefer_single_quotes` rule | ||||
|     avoid_print: true  # Uncomment to disable the `avoid_print` rule | ||||
|     prefer_single_quotes: true  # Uncomment to enable the `prefer_single_quotes` rule | ||||
|  | ||||
| # Additional information about this file can be found at | ||||
| # 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( | ||||
|         options: DefaultFirebaseOptions.currentPlatform, | ||||
|       ); | ||||
|  | ||||
|       if (PlatformInfo.isDesktop) { | ||||
|         await Window.initialize(); | ||||
|         await Window.setEffect( | ||||
|   | ||||
| @@ -81,24 +81,24 @@ class AccountBadge { | ||||
|   }); | ||||
|  | ||||
|   factory AccountBadge.fromJson(Map<String, dynamic> json) => AccountBadge( | ||||
|         id: json["id"], | ||||
|         accountId: json["account_id"], | ||||
|         updatedAt: DateTime.parse(json["updated_at"]), | ||||
|         createdAt: DateTime.parse(json["created_at"]), | ||||
|         deletedAt: json["deleted_at"] != null | ||||
|             ? DateTime.parse(json["deleted_at"]) | ||||
|         id: json['id'], | ||||
|         accountId: json['account_id'], | ||||
|         updatedAt: DateTime.parse(json['updated_at']), | ||||
|         createdAt: DateTime.parse(json['created_at']), | ||||
|         deletedAt: json['deleted_at'] != null | ||||
|             ? DateTime.parse(json['deleted_at']) | ||||
|             : null, | ||||
|         metadata: json["metadata"], | ||||
|         type: json["type"], | ||||
|         metadata: json['metadata'], | ||||
|         type: json['type'], | ||||
|       ); | ||||
|  | ||||
|   Map<String, dynamic> toJson() => { | ||||
|         "id": id, | ||||
|         "account_id": accountId, | ||||
|         "created_at": createdAt.toIso8601String(), | ||||
|         "updated_at": updatedAt.toIso8601String(), | ||||
|         "deleted_at": deletedAt?.toIso8601String(), | ||||
|         "metadata": metadata, | ||||
|         "type": type, | ||||
|         'id': id, | ||||
|         'account_id': accountId, | ||||
|         'created_at': createdAt.toIso8601String(), | ||||
|         'updated_at': updatedAt.toIso8601String(), | ||||
|         'deleted_at': deletedAt?.toIso8601String(), | ||||
|         'metadata': metadata, | ||||
|         'type': type, | ||||
|       }; | ||||
| } | ||||
|   | ||||
| @@ -38,40 +38,40 @@ class Attachment { | ||||
|   }); | ||||
|  | ||||
|   factory Attachment.fromJson(Map<String, dynamic> json) => Attachment( | ||||
|     id: json["id"], | ||||
|     createdAt: DateTime.parse(json["created_at"]), | ||||
|     updatedAt: DateTime.parse(json["updated_at"]), | ||||
|     deletedAt: json["deleted_at"], | ||||
|     uuid: json["uuid"], | ||||
|     size: json["size"], | ||||
|     name: json["name"], | ||||
|     alt: json["alt"], | ||||
|     usage: json["usage"], | ||||
|     mimetype: json["mimetype"], | ||||
|     hash: json["hash"], | ||||
|     destination: json["destination"], | ||||
|     metadata: json["metadata"], | ||||
|     isMature: json["is_mature"], | ||||
|     account: Account.fromJson(json["account"]), | ||||
|     accountId: json["account_id"], | ||||
|     id: json['id'], | ||||
|     createdAt: DateTime.parse(json['created_at']), | ||||
|     updatedAt: DateTime.parse(json['updated_at']), | ||||
|     deletedAt: json['deleted_at'], | ||||
|     uuid: json['uuid'], | ||||
|     size: json['size'], | ||||
|     name: json['name'], | ||||
|     alt: json['alt'], | ||||
|     usage: json['usage'], | ||||
|     mimetype: json['mimetype'], | ||||
|     hash: json['hash'], | ||||
|     destination: json['destination'], | ||||
|     metadata: json['metadata'], | ||||
|     isMature: json['is_mature'], | ||||
|     account: Account.fromJson(json['account']), | ||||
|     accountId: json['account_id'], | ||||
|   ); | ||||
|  | ||||
|   Map<String, dynamic> toJson() => { | ||||
|     "id": id, | ||||
|     "created_at": createdAt.toIso8601String(), | ||||
|     "updated_at": updatedAt.toIso8601String(), | ||||
|     "deleted_at": deletedAt, | ||||
|     "uuid": uuid, | ||||
|     "size": size, | ||||
|     "name": name, | ||||
|     "alt": alt, | ||||
|     "usage": usage, | ||||
|     "mimetype": mimetype, | ||||
|     "hash": hash, | ||||
|     "destination": destination, | ||||
|     "metadata": metadata, | ||||
|     "is_mature": isMature, | ||||
|     "account": account.toJson(), | ||||
|     "account_id": accountId, | ||||
|     'id': id, | ||||
|     'created_at': createdAt.toIso8601String(), | ||||
|     'updated_at': updatedAt.toIso8601String(), | ||||
|     'deleted_at': deletedAt, | ||||
|     'uuid': uuid, | ||||
|     'size': size, | ||||
|     'name': name, | ||||
|     'alt': alt, | ||||
|     'usage': usage, | ||||
|     'mimetype': mimetype, | ||||
|     'hash': hash, | ||||
|     'destination': destination, | ||||
|     'metadata': metadata, | ||||
|     'is_mature': isMature, | ||||
|     'account': account.toJson(), | ||||
|     'account_id': accountId, | ||||
|   }; | ||||
| } | ||||
| @@ -44,10 +44,11 @@ class Message { | ||||
|         deletedAt: json['deleted_at'], | ||||
|         content: json['content'], | ||||
|         type: json['type'], | ||||
|         attachments: json["attachments"] != null | ||||
|             ? List<int>.from(json["attachments"]) | ||||
|         attachments: json['attachments'] != null | ||||
|             ? List<int>.from(json['attachments']) | ||||
|             : null, | ||||
|         channel: Channel.fromJson(json['channel']), | ||||
|         channel: | ||||
|             json['channel'] != null ? Channel.fromJson(json['channel']) : null, | ||||
|         sender: Sender.fromJson(json['sender']), | ||||
|         replyId: json['reply_id'], | ||||
|         replyTo: json['reply_to'] != null | ||||
|   | ||||
| @@ -53,36 +53,36 @@ class Post { | ||||
|   }); | ||||
|  | ||||
|   factory Post.fromJson(Map<String, dynamic> json) => Post( | ||||
|         id: json["id"], | ||||
|         createdAt: DateTime.parse(json["created_at"]), | ||||
|         updatedAt: DateTime.parse(json["updated_at"]), | ||||
|         deletedAt: json["deleted_at"] != null | ||||
|         id: json['id'], | ||||
|         createdAt: DateTime.parse(json['created_at']), | ||||
|         updatedAt: DateTime.parse(json['updated_at']), | ||||
|         deletedAt: json['deleted_at'] != null | ||||
|             ? DateTime.parse(json['deleted_at']) | ||||
|             : null, | ||||
|         alias: json["alias"], | ||||
|         content: json["content"], | ||||
|         tags: json["tags"], | ||||
|         categories: json["categories"], | ||||
|         reactions: json["reactions"], | ||||
|         replies: json["replies"], | ||||
|         attachments: json["attachments"] != null | ||||
|             ? List<int>.from(json["attachments"]) | ||||
|         alias: json['alias'], | ||||
|         content: json['content'], | ||||
|         tags: json['tags'], | ||||
|         categories: json['categories'], | ||||
|         reactions: json['reactions'], | ||||
|         replies: json['replies'], | ||||
|         attachments: json['attachments'] != null | ||||
|             ? List<int>.from(json['attachments']) | ||||
|             : null, | ||||
|         replyId: json["reply_id"], | ||||
|         repostId: json["repost_id"], | ||||
|         realmId: json["realm_id"], | ||||
|         replyId: json['reply_id'], | ||||
|         repostId: json['repost_id'], | ||||
|         realmId: json['realm_id'], | ||||
|         replyTo: | ||||
|             json["reply_to"] != null ? Post.fromJson(json["reply_to"]) : null, | ||||
|             json['reply_to'] != null ? Post.fromJson(json['reply_to']) : null, | ||||
|         repostTo: | ||||
|             json["repost_to"] != null ? Post.fromJson(json["repost_to"]) : null, | ||||
|         realm: json["realm"] != null ? Realm.fromJson(json["realm"]) : null, | ||||
|         publishedAt: json["published_at"] != null ? DateTime.parse(json["published_at"]) : null, | ||||
|         authorId: json["author_id"], | ||||
|         author: Account.fromJson(json["author"]), | ||||
|         replyCount: json["reply_count"], | ||||
|         reactionCount: json["reaction_count"], | ||||
|         reactionList: json["reaction_list"] != null | ||||
|             ? json["reaction_list"] | ||||
|             json['repost_to'] != null ? Post.fromJson(json['repost_to']) : null, | ||||
|         realm: json['realm'] != null ? Realm.fromJson(json['realm']) : null, | ||||
|         publishedAt: json['published_at'] != null ? DateTime.parse(json['published_at']) : null, | ||||
|         authorId: json['author_id'], | ||||
|         author: Account.fromJson(json['author']), | ||||
|         replyCount: json['reply_count'], | ||||
|         reactionCount: json['reaction_count'], | ||||
|         reactionList: json['reaction_list'] != null | ||||
|             ? json['reaction_list'] | ||||
|                 .map((key, value) => MapEntry( | ||||
|                     key, | ||||
|                     int.tryParse(value.toString()) ?? | ||||
| @@ -92,28 +92,28 @@ class Post { | ||||
|       ); | ||||
|  | ||||
|   Map<String, dynamic> toJson() => { | ||||
|         "id": id, | ||||
|         "created_at": createdAt.toIso8601String(), | ||||
|         "updated_at": updatedAt.toIso8601String(), | ||||
|         "deleted_at": deletedAt, | ||||
|         "alias": alias, | ||||
|         "content": content, | ||||
|         "tags": tags, | ||||
|         "categories": categories, | ||||
|         "reactions": reactions, | ||||
|         "replies": replies, | ||||
|         "attachments": attachments, | ||||
|         "reply_id": replyId, | ||||
|         "repost_id": repostId, | ||||
|         "realm_id": realmId, | ||||
|         "reply_to": replyTo?.toJson(), | ||||
|         "repost_to": repostTo?.toJson(), | ||||
|         "realm": realm?.toJson(), | ||||
|         "published_at": publishedAt?.toIso8601String(), | ||||
|         "author_id": authorId, | ||||
|         "author": author.toJson(), | ||||
|         "reply_count": replyCount, | ||||
|         "reaction_count": reactionCount, | ||||
|         "reaction_list": reactionList, | ||||
|         'id': id, | ||||
|         'created_at': createdAt.toIso8601String(), | ||||
|         'updated_at': updatedAt.toIso8601String(), | ||||
|         'deleted_at': deletedAt, | ||||
|         'alias': alias, | ||||
|         'content': content, | ||||
|         'tags': tags, | ||||
|         'categories': categories, | ||||
|         'reactions': reactions, | ||||
|         'replies': replies, | ||||
|         'attachments': attachments, | ||||
|         'reply_id': replyId, | ||||
|         'repost_id': repostId, | ||||
|         'realm_id': realmId, | ||||
|         'reply_to': replyTo?.toJson(), | ||||
|         'repost_to': repostTo?.toJson(), | ||||
|         'realm': realm?.toJson(), | ||||
|         'published_at': publishedAt?.toIso8601String(), | ||||
|         'author_id': authorId, | ||||
|         'author': author.toJson(), | ||||
|         'reply_count': replyCount, | ||||
|         'reaction_count': reactionCount, | ||||
|         'reaction_list': reactionList, | ||||
|       }; | ||||
| } | ||||
|   | ||||
| @@ -190,10 +190,10 @@ class AccountProvider extends GetxController { | ||||
|     } | ||||
|  | ||||
|     if (PlatformInfo.isIOS || PlatformInfo.isMacOS) { | ||||
|       provider = "apple"; | ||||
|       provider = 'apple'; | ||||
|       token = await FirebaseMessaging.instance.getAPNSToken(); | ||||
|     } else { | ||||
|       provider = "firebase"; | ||||
|       provider = 'firebase'; | ||||
|       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:get/get.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/chat.dart'; | ||||
| import 'package:solian/services.dart'; | ||||
| @@ -79,7 +80,7 @@ class AuthProvider extends GetConnect { | ||||
|  | ||||
|     if (credentials!.isExpired) { | ||||
|       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>().notificationUnread.value = 0; | ||||
|  | ||||
|     final chatHistory = ChatHistoryController(); | ||||
|     chatHistory.initialize().then((_) async { | ||||
|       await chatHistory.database.localMessages.wipeLocalMessages(); | ||||
|     }); | ||||
|  | ||||
|     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); | ||||
|  | ||||
|     String twoDigits(int n) => n.toString().padLeft(2, '0'); | ||||
|     String formattedTime = "${twoDigits(duration.inHours)}:" | ||||
|         "${twoDigits(duration.inMinutes.remainder(60))}:" | ||||
|         "${twoDigits(duration.inSeconds.remainder(60))}"; | ||||
|     String formattedTime = '${twoDigits(duration.inHours)}:' | ||||
|         '${twoDigits(duration.inMinutes.remainder(60))}:' | ||||
|         '${twoDigits(duration.inSeconds.remainder(60))}'; | ||||
|  | ||||
|     return formattedTime; | ||||
|   } | ||||
| @@ -66,7 +66,7 @@ class _CallScreenState extends State<CallScreen> { | ||||
|                 text: 'call'.tr, | ||||
|                 style: Theme.of(context).textTheme.titleLarge, | ||||
|               ), | ||||
|               const TextSpan(text: "\n"), | ||||
|               const TextSpan(text: '\n'), | ||||
|               TextSpan( | ||||
|                 text: currentDuration, | ||||
|                 style: Theme.of(context).textTheme.bodySmall, | ||||
|   | ||||
| @@ -2,14 +2,14 @@ import 'dart:async'; | ||||
| import 'dart:ui'; | ||||
|  | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_animate/flutter_animate.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/models/call.dart'; | ||||
| import 'package:solian/models/channel.dart'; | ||||
| import 'package:solian/models/message.dart'; | ||||
| import 'package:solian/models/packet.dart'; | ||||
| import 'package:solian/models/pagination.dart'; | ||||
| import 'package:solian/providers/auth.dart'; | ||||
| import 'package:solian/providers/chat.dart'; | ||||
| import 'package:solian/providers/content/call.dart'; | ||||
| @@ -46,12 +46,11 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> { | ||||
|   String? _overrideAlias; | ||||
|  | ||||
|   Channel? _channel; | ||||
|   ChannelMember? _channelProfile; | ||||
|   Call? _ongoingCall; | ||||
|   ChannelMember? _channelProfile; | ||||
|   StreamSubscription<NetworkPackage>? _subscription; | ||||
|  | ||||
|   final PagingController<int, Message> _pagingController = | ||||
|       PagingController(firstPageKey: 0); | ||||
|   late final ChatHistoryController _chatController; | ||||
|  | ||||
|   getProfile() async { | ||||
|     final AuthProvider auth = Get.find(); | ||||
| @@ -106,31 +105,6 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> { | ||||
|     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() { | ||||
|     final ChatProvider provider = Get.find(); | ||||
|     _subscription = provider.stream.stream.listen((event) { | ||||
| @@ -138,33 +112,19 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> { | ||||
|         case 'messages.new': | ||||
|           final payload = Message.fromJson(event.payload!); | ||||
|           if (payload.channelId == _channel?.id) { | ||||
|             final idx = _pagingController.itemList | ||||
|                 ?.indexWhere((e) => e.uuid == payload.uuid); | ||||
|             if ((idx ?? -1) >= 0) { | ||||
|               _pagingController.itemList?[idx!] = payload; | ||||
|             } else { | ||||
|               _pagingController.itemList?.insert(0, payload); | ||||
|             } | ||||
|             _chatController.receiveMessage(payload); | ||||
|           } | ||||
|           break; | ||||
|         case 'messages.update': | ||||
|           final payload = Message.fromJson(event.payload!); | ||||
|           if (payload.channelId == _channel?.id) { | ||||
|             final idx = _pagingController.itemList | ||||
|                 ?.indexWhere((x) => x.uuid == payload.uuid); | ||||
|             if (idx != null) { | ||||
|               _pagingController.itemList?[idx] = payload; | ||||
|             } | ||||
|             _chatController.replaceMessage(payload); | ||||
|           } | ||||
|           break; | ||||
|         case 'messages.burnt': | ||||
|           final payload = Message.fromJson(event.payload!); | ||||
|           if (payload.channelId == _channel?.id) { | ||||
|             final idx = _pagingController.itemList | ||||
|                 ?.indexWhere((x) => x.uuid != payload.uuid); | ||||
|             if (idx != null) { | ||||
|               _pagingController.itemList?.removeAt(idx - 1); | ||||
|             } | ||||
|             _chatController.burnMessage(payload.id); | ||||
|           } | ||||
|           break; | ||||
|         case 'calls.new': | ||||
| @@ -175,7 +135,6 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> { | ||||
|           _ongoingCall = null; | ||||
|           break; | ||||
|       } | ||||
|       setState(() {}); | ||||
|     }); | ||||
|   } | ||||
|  | ||||
| @@ -200,24 +159,9 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> { | ||||
|   Message? _messageToReplying; | ||||
|   Message? _messageToEditing; | ||||
|  | ||||
|   Widget buildHistory(context, Message item, index) { | ||||
|     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; | ||||
|   Widget buildHistoryBody(Message item, {bool isMerged = false}) { | ||||
|     if (item.replyTo != null) { | ||||
|       content = Column( | ||||
|       return Column( | ||||
|         children: [ | ||||
|           ChatMessage( | ||||
|             key: Key('m${item.replyTo!.uuid}'), | ||||
| @@ -231,17 +175,35 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> { | ||||
|           ), | ||||
|         ], | ||||
|       ); | ||||
|     } else { | ||||
|       content = ChatMessage( | ||||
|         key: Key('m${item.uuid}'), | ||||
|         item: item, | ||||
|         isMerged: isMerged, | ||||
|     } | ||||
|  | ||||
|     return ChatMessage( | ||||
|       key: Key('m${item.uuid}'), | ||||
|       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( | ||||
|       child: Container( | ||||
|         child: content.paddingOnly( | ||||
|         child: buildHistoryBody(item, isMerged: isMerged).paddingOnly( | ||||
|           top: !isMerged ? 8 : 0, | ||||
|           bottom: !hasMerged ? 8 : 0, | ||||
|         ), | ||||
| @@ -268,14 +230,19 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> { | ||||
|  | ||||
|   @override | ||||
|   void initState() { | ||||
|     super.initState(); | ||||
|     _chatController = ChatHistoryController(); | ||||
|     _chatController.initialize(); | ||||
|  | ||||
|     getChannel().then((_) { | ||||
|       _chatController.getMessages(_channel!, widget.realm); | ||||
|     }); | ||||
|  | ||||
|     getProfile(); | ||||
|     getChannel().then((_) { | ||||
|       listenMessages(); | ||||
|       _pagingController.addPageRequestListener(getMessages); | ||||
|     }); | ||||
|     getOngoingCall(); | ||||
|  | ||||
|     listenMessages(); | ||||
|  | ||||
|     super.initState(); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
| @@ -352,47 +319,84 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> { | ||||
|           Column( | ||||
|             children: [ | ||||
|               Expanded( | ||||
|                 child: PagedListView<int, Message>( | ||||
|                   clipBehavior: Clip.none, | ||||
|                 child: CustomScrollView( | ||||
|                   reverse: true, | ||||
|                   pagingController: _pagingController, | ||||
|                   builderDelegate: PagedChildBuilderDelegate<Message>( | ||||
|                     itemBuilder: buildHistory, | ||||
|                     noItemsFoundIndicatorBuilder: (_) => Container(), | ||||
|                   ), | ||||
|                 ).paddingOnly(bottom: 56), | ||||
|                   slivers: [ | ||||
|                     Obx(() { | ||||
|                       return SliverList.builder( | ||||
|                         key: Key('chat-history#${_channel!.id}'), | ||||
|                         itemCount: _chatController.currentHistory.length, | ||||
|                         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), | ||||
|                       ); | ||||
|                     }), | ||||
|                   ], | ||||
|                 ), | ||||
|               ), | ||||
|             ], | ||||
|           ), | ||||
|           Positioned( | ||||
|             bottom: 0, | ||||
|             left: 0, | ||||
|             right: 0, | ||||
|             child: ClipRect( | ||||
|               child: BackdropFilter( | ||||
|                 filter: ImageFilter.blur(sigmaX: 50, sigmaY: 50), | ||||
|                 child: SafeArea( | ||||
|                   child: ChatMessageInput( | ||||
|                     edit: _messageToEditing, | ||||
|                     reply: _messageToReplying, | ||||
|                     realm: widget.realm, | ||||
|                     placeholder: placeholder, | ||||
|                     channel: _channel!, | ||||
|                     onSent: (Message item) { | ||||
|                       setState(() { | ||||
|                         _pagingController.itemList?.insert(0, item); | ||||
|                       }); | ||||
|                     }, | ||||
|                     onReset: () { | ||||
|                       setState(() { | ||||
|                         _messageToReplying = null; | ||||
|                         _messageToEditing = null; | ||||
|                       }); | ||||
|                     }, | ||||
|               ClipRect( | ||||
|                 child: BackdropFilter( | ||||
|                   filter: ImageFilter.blur(sigmaX: 50, sigmaY: 50), | ||||
|                   child: SafeArea( | ||||
|                     child: ChatMessageInput( | ||||
|                       edit: _messageToEditing, | ||||
|                       reply: _messageToReplying, | ||||
|                       realm: widget.realm, | ||||
|                       placeholder: placeholder, | ||||
|                       channel: _channel!, | ||||
|                       onSent: (Message item) { | ||||
|                         setState(() { | ||||
|                           _chatController.addTemporaryMessage(item); | ||||
|                         }); | ||||
|                       }, | ||||
|                       onReset: () { | ||||
|                         setState(() { | ||||
|                           _messageToReplying = null; | ||||
|                           _messageToEditing = null; | ||||
|                         }); | ||||
|                       }, | ||||
|                     ), | ||||
|                   ), | ||||
|                 ), | ||||
|               ), | ||||
|             ), | ||||
|             ], | ||||
|           ), | ||||
|           if (_ongoingCall != null) | ||||
|             Positioned( | ||||
|   | ||||
| @@ -101,7 +101,7 @@ class _ChatScreenState extends State<ChatScreen> { | ||||
|                         context), | ||||
|                     sliver: SliverAppBar( | ||||
|                       title: AppBarTitle('chat'.tr), | ||||
|                       centerTitle: true, | ||||
|                       centerTitle: false, | ||||
|                       floating: true, | ||||
|                       titleSpacing: SolianTheme.titleSpacing(context), | ||||
|                       toolbarHeight: SolianTheme.toolbarHeight(context), | ||||
|   | ||||
| @@ -164,6 +164,8 @@ class SolianMessages extends Translations { | ||||
|           'channelNotifyLevelMentioned': 'Only mentioned', | ||||
|           'channelNotifyLevelNone': 'Ignore all', | ||||
|           'channelNotifyLevelApplied': 'Your notification settings has been applied.', | ||||
|           'messageUnsync': 'Messages Un-synced', | ||||
|           'messageUnsyncCaption': '@count message(s) still in un-synced.', | ||||
|           'messageDecoding': 'Decoding...', | ||||
|           'messageDecodeFailed': 'Unable to decode: @message', | ||||
|           'messageInputPlaceholder': 'Message @channel', | ||||
| @@ -364,6 +366,8 @@ class SolianMessages extends Translations { | ||||
|           'channelNotifyLevelMentioned': '仅提及', | ||||
|           'channelNotifyLevelNone': '忽略一切', | ||||
|           'channelNotifyLevelApplied': '你的通知设置已经应用。', | ||||
|           'messageUnsync': '消息未同步', | ||||
|           'messageUnsyncCaption': '还有 @count 条消息未同步', | ||||
|           'messageDecoding': '解码信息中…', | ||||
|           'messageDecodeFailed': '解码信息失败:@message', | ||||
|           'messageInputPlaceholder': '在 @channel 发信息', | ||||
|   | ||||
| @@ -67,7 +67,7 @@ class SliverFriendList extends StatelessWidget { | ||||
|   Widget build(BuildContext context) { | ||||
|     return SliverList.builder( | ||||
|       itemCount: items.length, | ||||
|       itemBuilder: (_, __) => buildItem(_, __), | ||||
|       itemBuilder: (context, idx) => buildItem(context, idx), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -65,7 +65,7 @@ class _ChatCallButtonState extends State<ChatCallButton> { | ||||
|         ? widget.realm?.alias | ||||
|         : 'global'; | ||||
|     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 (widget.onEnded != null) widget.onEnded!(); | ||||
|     } else { | ||||
|   | ||||
| @@ -98,7 +98,7 @@ class _PostItemState extends State<PostItem> { | ||||
|  | ||||
|     if (labels.isNotEmpty) { | ||||
|       return Text( | ||||
|         labels.join(" · "), | ||||
|         labels.join(' · '), | ||||
|         textAlign: TextAlign.left, | ||||
|         style: TextStyle( | ||||
|           fontSize: 12, | ||||
|   | ||||
							
								
								
									
										330
									
								
								pubspec.lock
									
									
									
									
									
								
							
							
						
						
									
										330
									
								
								pubspec.lock
									
									
									
									
									
								
							| @@ -1,6 +1,14 @@ | ||||
| # Generated by pub | ||||
| # See https://dart.dev/tools/pub/glossary#lockfile | ||||
| 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: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -9,6 +17,14 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "1.3.37" | ||||
|   analyzer: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: analyzer | ||||
|       sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "6.4.1" | ||||
|   archive: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -41,6 +57,70 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     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: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
| @@ -81,6 +161,14 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "1.3.0" | ||||
|   charcode: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: charcode | ||||
|       sha256: fb98c0f6d12c920a02ee2d998da788bca066ca5f148492b7085ee23372b12306 | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "1.3.1" | ||||
|   checked_yaml: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -113,6 +201,14 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     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: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -137,6 +233,14 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "2.0.0" | ||||
|   convert: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: convert | ||||
|       sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "3.1.1" | ||||
|   cross_file: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -169,6 +273,14 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     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: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -185,6 +297,14 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     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: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
| @@ -329,6 +449,38 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     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: | ||||
|     dependency: "direct main" | ||||
|     description: flutter | ||||
| @@ -504,6 +656,14 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     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: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
| @@ -512,6 +672,14 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "4.6.6" | ||||
|   glob: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: glob | ||||
|       sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "2.1.2" | ||||
|   go_router: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
| @@ -520,6 +688,14 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "14.2.0" | ||||
|   graphs: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: graphs | ||||
|       sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19 | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "2.3.1" | ||||
|   html: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -536,6 +712,14 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     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: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -632,6 +816,14 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "0.19.0" | ||||
|   io: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: io | ||||
|       sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "1.0.4" | ||||
|   js: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -680,6 +872,14 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "4.0.0" | ||||
|   lists: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: lists | ||||
|       sha256: "4ca5c19ae4350de036a7e996cdd1ee39c93ac0a2b840f4915459b7d0a7d4ab27" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "1.0.1" | ||||
|   livekit_client: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
| @@ -776,6 +976,14 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     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: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
| @@ -928,6 +1136,22 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     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: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -952,6 +1176,14 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     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: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -1040,6 +1272,22 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     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: | ||||
|     dependency: transitive | ||||
|     description: flutter | ||||
| @@ -1053,6 +1301,14 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     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: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -1070,7 +1326,7 @@ packages: | ||||
|     source: hosted | ||||
|     version: "7.0.0" | ||||
|   sqflite: | ||||
|     dependency: transitive | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: sqflite | ||||
|       sha256: a43e5a27235518c03ca238e7b4732cf35eabe863a369ceba6cbefa537a66f16d | ||||
| @@ -1085,6 +1341,38 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     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: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -1101,6 +1389,14 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     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: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -1109,6 +1405,14 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "1.2.0" | ||||
|   strings: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: strings | ||||
|       sha256: b33f40c4dd3e597bf6d9e7f4f4dc282dad0f19b07d9f320cb5c2183859cbccf5 | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "3.1.1" | ||||
|   synchronized: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -1149,6 +1453,14 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "0.9.3" | ||||
|   timing: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: timing | ||||
|       sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "1.0.1" | ||||
|   typed_data: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -1157,6 +1469,14 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "1.3.2" | ||||
|   unicode: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: unicode | ||||
|       sha256: "0f69e46593d65245774d4f17125c6084d2c20b4e473a983f6e21b7d7762218f1" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "0.3.1" | ||||
|   url_launcher: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
| @@ -1309,6 +1629,14 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "1.2.1" | ||||
|   watcher: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: watcher | ||||
|       sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "1.1.0" | ||||
|   web: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|   | ||||
							
								
								
									
										68
									
								
								pubspec.yaml
									
									
									
									
									
								
							
							
						
						
									
										68
									
								
								pubspec.yaml
									
									
									
									
									
								
							| @@ -1,38 +1,16 @@ | ||||
| name: solian | ||||
| description: "The Solar Network App" | ||||
| # The following line prevents the package from being accidentally published to | ||||
| # 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 | ||||
| publish_to: "none" | ||||
|  | ||||
| # 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 | ||||
|  | ||||
| environment: | ||||
|   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: | ||||
|   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 | ||||
|   go_router: ^14.1.1 | ||||
|   get: ^4.6.6 | ||||
| @@ -70,59 +48,27 @@ dependencies: | ||||
|   device_info_plus: ^10.1.0 | ||||
|   shared_preferences: ^2.2.3 | ||||
|   flutter_acrylic: ^1.1.4 | ||||
|   floor: ^1.5.0 | ||||
|   sqflite: ^2.3.3+1 | ||||
|  | ||||
| dev_dependencies: | ||||
|   flutter_test: | ||||
|     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_launcher_icons: ^0.13.1 | ||||
|  | ||||
| # For information on the generic Dart part of this file, see the | ||||
| # following page: https://dart.dev/tools/pub/pubspec | ||||
|   floor_generator: ^1.4.0 | ||||
|   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: | ||||
|   # 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 | ||||
|  | ||||
|   # To add assets to your application, add an assets section, like this: | ||||
|   assets: | ||||
|     - 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: | ||||
|   android: "launcher_icon" | ||||
|   ios: true | ||||
|   | ||||
		Reference in New Issue
	
	Block a user