♻️ 使用 SQLITE 来存储本地消息记录 #1
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user