mirror of
https://github.com/Linloir/Simple-TCP-Client.git
synced 2025-12-17 00:38:11 +08:00
More Codes
- Local Service Repository
This commit is contained in:
parent
2bbad43739
commit
d3a5a32fdb
@ -1,6 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
||||
|
||||
void main() {
|
||||
sqfliteFfiInit();
|
||||
runApp(const MyApp());
|
||||
}
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-11 10:30:05
|
||||
* @LastEditTime : 2022-10-11 15:36:23
|
||||
* @LastEditTime : 2022-10-11 23:35:45
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
@ -32,6 +32,7 @@ class Message extends JSONEncodable {
|
||||
final String _content;
|
||||
final int _timestamp;
|
||||
late final String _contentmd5;
|
||||
late final String? _filemd5;
|
||||
final LocalFile? _payload;
|
||||
|
||||
Message({
|
||||
@ -54,6 +55,7 @@ class Message extends JSONEncodable {
|
||||
..addAll(Uint8List(4)..buffer.asInt32List()[0] = targetid)
|
||||
..addAll(Uint8List(4)..buffer.asInt32List()[0] = _timestamp)
|
||||
).toString();
|
||||
_filemd5 = _payload?.filemd5;
|
||||
}
|
||||
|
||||
Message.fromJSONObject({
|
||||
@ -66,6 +68,7 @@ class Message extends JSONEncodable {
|
||||
_content = jsonObject['content'] as String,
|
||||
_timestamp = jsonObject['timestamp'] as int,
|
||||
_contentmd5 = jsonObject['md5encoded'] as String,
|
||||
_filemd5 = jsonObject['filemd5'] as String,
|
||||
_payload = payload;
|
||||
|
||||
int get senderID => _userid;
|
||||
@ -75,6 +78,7 @@ class Message extends JSONEncodable {
|
||||
String get contentEncoded => _content;
|
||||
String get contentmd5 => _contentmd5;
|
||||
int get timeStamp => _timestamp;
|
||||
String? get filemd5 => _filemd5;
|
||||
LocalFile? get payload => _payload;
|
||||
|
||||
@override
|
||||
|
||||
@ -1,6 +1,250 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-11 10:56:02
|
||||
* @LastEditTime : 2022-10-11 10:56:03
|
||||
* @Description :
|
||||
* @LastEditTime : 2022-10-11 23:49:16
|
||||
* @Description : Local Service Repository
|
||||
*/
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:tcp_client/repositories/common_models/message.dart';
|
||||
import 'package:tcp_client/repositories/common_models/userinfo.dart';
|
||||
import 'package:tcp_client/repositories/local_service_repository/models/local_file.dart';
|
||||
import 'package:sqflite_common/sqlite_api.dart';
|
||||
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
||||
|
||||
class LocalServiceRepository {
|
||||
late final Database _database;
|
||||
|
||||
LocalServiceRepository._internal({
|
||||
required Database database
|
||||
}): _database = database;
|
||||
|
||||
static FutureOr<void> _onDatabaseCreate(Database db, int version) async {
|
||||
await db.execute(
|
||||
'''
|
||||
create table users (
|
||||
userid integer primary key,
|
||||
username text not null,
|
||||
avatar text
|
||||
);
|
||||
create table msgs (
|
||||
userid integer not null,
|
||||
targetid integer not null,
|
||||
contenttype text not null,
|
||||
content text not null,
|
||||
timestamp int not null,
|
||||
md5encoded text primary key,
|
||||
filemd5 text not null
|
||||
);
|
||||
create table files (
|
||||
filemd5 text primary key,
|
||||
dir text not null
|
||||
);
|
||||
'''
|
||||
);
|
||||
}
|
||||
|
||||
static Future<LocalServiceRepository> create({
|
||||
UserInfo? currentUser,
|
||||
required String databaseFilePath
|
||||
}) async {
|
||||
var database = await databaseFactoryFfi.openDatabase(
|
||||
databaseFilePath,
|
||||
options: OpenDatabaseOptions(
|
||||
version: 1,
|
||||
onCreate: _onDatabaseCreate
|
||||
)
|
||||
);
|
||||
return LocalServiceRepository._internal(database: database);
|
||||
}
|
||||
|
||||
//Calls on the system to open the file
|
||||
Future<LocalFile?> pickFile(FileType fileType) async {
|
||||
var filePickResult = await FilePicker.platform.pickFiles(
|
||||
type: fileType,
|
||||
allowMultiple: false,
|
||||
);
|
||||
if (filePickResult == null) return null;
|
||||
var file = File(filePickResult.files.single.path!);
|
||||
return LocalFile(
|
||||
file: file,
|
||||
filemd5: md5.convert(await file.readAsBytes()).toString(),
|
||||
ext: file.path.substring(file.path.lastIndexOf('.'))
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> storeMessages(List<Message> messages) async {
|
||||
for(var message in messages) {
|
||||
try {
|
||||
await _database.insert(
|
||||
'msgs',
|
||||
message.jsonObject,
|
||||
conflictAlgorithm: ConflictAlgorithm.replace
|
||||
);
|
||||
} catch (err) {
|
||||
//TODO: do something
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<Message>> findMessages({required String pattern}) async {
|
||||
// Obtain shared preferences.
|
||||
final pref = await SharedPreferences.getInstance();
|
||||
// Get user info from preferences
|
||||
var currentUserID = pref.getInt('userid');
|
||||
var alikeMessages = await _database.query(
|
||||
'msgs',
|
||||
where: 'userid = ? or targetid = ?',
|
||||
whereArgs: [
|
||||
currentUserID, currentUserID
|
||||
],
|
||||
orderBy: 'timestamp desc',
|
||||
limit: 100
|
||||
);
|
||||
return alikeMessages.map((e) => Message.fromJSONObject(jsonObject: e)).toList();
|
||||
}
|
||||
|
||||
//Find the most recent message of given users
|
||||
Future<List<List<Message>>> fetchMessageList({required List<int> users}) async {
|
||||
var pref = await SharedPreferences.getInstance();
|
||||
var currentUserID = pref.getInt('userid');
|
||||
var messages = <List<Message>>[];
|
||||
for(var user in users) {
|
||||
var queryResult = await _database.query(
|
||||
'msgs',
|
||||
where: '(userid = ? and targetid = ?) and (userid = ? and targetid = ?)',
|
||||
whereArgs: [
|
||||
currentUserID, user, user, currentUserID
|
||||
],
|
||||
orderBy: 'timestamp desc',
|
||||
limit: 1
|
||||
);
|
||||
if(queryResult.isEmpty) {
|
||||
messages.add([]);
|
||||
}
|
||||
else {
|
||||
messages.add([Message.fromJSONObject(jsonObject: queryResult[0])]);
|
||||
}
|
||||
}
|
||||
return messages;
|
||||
}
|
||||
|
||||
//Fetch chat history with another user, provided the user ID
|
||||
Future<List<Message>> fetchMessageHistory({required int userID, required int position, int num = 20}) async {
|
||||
//the histories with userID
|
||||
var pref = await SharedPreferences.getInstance();
|
||||
var currentUserID = pref.getInt('userid');
|
||||
if(currentUserID == null) {
|
||||
//TODO: do something
|
||||
return [];
|
||||
}
|
||||
var queryResult = await _database.query(
|
||||
'msgs',
|
||||
where: '(userid = ? and targetid = ?) or (userid = ? and targetid = ?)',
|
||||
whereArgs: [
|
||||
currentUserID, userID, userID, currentUserID
|
||||
],
|
||||
orderBy: 'timestamp desc',
|
||||
limit: num,
|
||||
offset: position
|
||||
);
|
||||
return queryResult.map((e) => Message.fromJSONObject(jsonObject: e)).toList();
|
||||
}
|
||||
|
||||
Future<File?> findFile({required String filemd5}) async {
|
||||
var directory = await _database.query(
|
||||
'files',
|
||||
where: 'filemd5 = ?',
|
||||
whereArgs: [
|
||||
filemd5
|
||||
]
|
||||
);
|
||||
if(directory.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
var filePath = directory[0]['dir'] as String;
|
||||
//Try if the file exists
|
||||
var file = File(filePath);
|
||||
if(await file.exists()) {
|
||||
return file;
|
||||
}
|
||||
else {
|
||||
//Delete all linked files
|
||||
await _database.delete(
|
||||
'files',
|
||||
where: 'filemd5 = ?',
|
||||
whereArgs: [
|
||||
filemd5
|
||||
]
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> storeFile({
|
||||
required LocalFile tempFile
|
||||
}) async {
|
||||
//Write to file library
|
||||
var documentPath = (await getApplicationDocumentsDirectory()).path;
|
||||
var permanentFilePath = '$documentPath/files/${tempFile.filemd5}${tempFile.ext}';
|
||||
await tempFile.file.copy(permanentFilePath);
|
||||
await _database.insert(
|
||||
'files',
|
||||
{
|
||||
'filemd5': tempFile.filemd5,
|
||||
'dir': permanentFilePath
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
final StreamController<UserInfo> _userInfoChangeStreamController = StreamController();
|
||||
Stream<UserInfo> get userInfoChangedStream => _userInfoChangeStreamController.stream;
|
||||
|
||||
Future<void> storeUserInfo({
|
||||
required UserInfo userInfo
|
||||
}) async {
|
||||
//check if exist
|
||||
var queryResult = await _database.query(
|
||||
'users',
|
||||
where: 'userid = ?',
|
||||
whereArgs: [userInfo.userID]
|
||||
);
|
||||
if(queryResult.isEmpty) {
|
||||
_database.insert(
|
||||
'users',
|
||||
userInfo.jsonObject
|
||||
);
|
||||
}
|
||||
else {
|
||||
_database.update(
|
||||
'users',
|
||||
userInfo.jsonObject,
|
||||
where: 'userid = ?',
|
||||
whereArgs: [userInfo.userID]
|
||||
);
|
||||
}
|
||||
_userInfoChangeStreamController.add(userInfo);
|
||||
}
|
||||
|
||||
Future<UserInfo?> fetchUserInfo({required userid}) async {
|
||||
var targetUser = await _database.query(
|
||||
'users',
|
||||
where: 'userid = ?',
|
||||
whereArgs: [userid]
|
||||
);
|
||||
if(targetUser.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
return UserInfo.fromJSONObject(jsonObject: targetUser[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-11 10:55:36
|
||||
* @LastEditTime : 2022-10-11 17:44:06
|
||||
* @LastEditTime : 2022-10-11 22:54:59
|
||||
* @Description : Local File Model
|
||||
*/
|
||||
|
||||
@ -12,8 +12,9 @@ import 'package:equatable/equatable.dart';
|
||||
class LocalFile extends Equatable {
|
||||
final File file;
|
||||
final String filemd5;
|
||||
final String ext;
|
||||
|
||||
const LocalFile({required this.file, required this.filemd5});
|
||||
const LocalFile({required this.file, required this.filemd5, required this.ext});
|
||||
|
||||
@override
|
||||
List<Object> get props => [filemd5];
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-11 11:02:19
|
||||
* @LastEditTime : 2022-10-11 16:00:53
|
||||
* @LastEditTime : 2022-10-11 22:55:48
|
||||
* @Description :
|
||||
*/
|
||||
|
||||
@ -195,12 +195,18 @@ class FindFileResponse extends TCPResponse {
|
||||
}
|
||||
|
||||
class FetchFileResponse extends TCPResponse {
|
||||
final LocalFile _payload;
|
||||
late final LocalFile _payload;
|
||||
|
||||
FetchFileResponse({
|
||||
required Map<String, Object?> jsonObject,
|
||||
required LocalFile payload
|
||||
}): _payload = payload, super(jsonObject: jsonObject);
|
||||
}): super(jsonObject: jsonObject) {
|
||||
_payload = LocalFile(
|
||||
file: payload.file,
|
||||
filemd5: payload.filemd5,
|
||||
ext: (jsonObject['body'] as Map<String, Object?>)['ext'] as String
|
||||
);
|
||||
}
|
||||
|
||||
LocalFile get payload => _payload;
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/*
|
||||
* @Author : Linloir
|
||||
* @Date : 2022-10-11 09:42:05
|
||||
* @LastEditTime : 2022-10-11 17:41:11
|
||||
* @LastEditTime : 2022-10-11 22:55:28
|
||||
* @Description : TCP repository
|
||||
*/
|
||||
|
||||
@ -11,12 +11,17 @@ import 'dart:io';
|
||||
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:tcp_client/repositories/local_service_repository/models/local_file.dart';
|
||||
import 'package:tcp_client/repositories/tcp_repository/models/tcp_request.dart';
|
||||
import 'package:tcp_client/repositories/tcp_repository/models/tcp_response.dart';
|
||||
|
||||
class TCPRepository {
|
||||
TCPRepository(this._socket) {
|
||||
TCPRepository({
|
||||
required Socket socket,
|
||||
required String remoteAddress,
|
||||
required int remotePort
|
||||
}): _socket = socket, _remoteAddress = remoteAddress, _remotePort = remotePort {
|
||||
_socket.listen(_pullResponse);
|
||||
//This future never ends, would that be bothersome?
|
||||
Future(() async {
|
||||
@ -33,6 +38,8 @@ class TCPRepository {
|
||||
}
|
||||
|
||||
final Socket _socket;
|
||||
final String _remoteAddress;
|
||||
final int _remotePort;
|
||||
|
||||
//Stores the incoming bytes of the TCP connection temporarily
|
||||
final List<int> buffer = [];
|
||||
@ -50,7 +57,12 @@ class TCPRepository {
|
||||
|
||||
//Provide a response stream for blocs to listen on
|
||||
final StreamController<TCPResponse> _responseStreamController = StreamController();
|
||||
Stream<TCPResponse>? _responseStreamBroadcast;
|
||||
Stream<TCPResponse> get responseStream => _responseStreamController.stream;
|
||||
Stream<TCPResponse> get responseStreamBroadcast {
|
||||
_responseStreamBroadcast ??= _responseStreamController.stream.asBroadcastStream();
|
||||
return _responseStreamBroadcast!;
|
||||
}
|
||||
|
||||
//Provide a request stream for widgets to push to
|
||||
final StreamController<TCPRequest> _requestStreamController = StreamController();
|
||||
@ -213,7 +225,8 @@ class TCPRepository {
|
||||
jsonObject: responseObject,
|
||||
payload: LocalFile(
|
||||
file: tempFile,
|
||||
filemd5: md5.convert(await tempFile.readAsBytes()).toString()
|
||||
filemd5: md5.convert(await tempFile.readAsBytes()).toString(),
|
||||
ext: ""
|
||||
)
|
||||
));
|
||||
break;
|
||||
@ -239,4 +252,37 @@ class TCPRepository {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> checkFileExistence({
|
||||
required LocalFile file
|
||||
}) async {
|
||||
//Duplicate current socket
|
||||
Socket socket = await Socket.connect(_remoteAddress, _remotePort);
|
||||
TCPRepository duplicatedRepository = TCPRepository(
|
||||
socket: socket,
|
||||
remoteAddress: _remoteAddress,
|
||||
remotePort: _remotePort
|
||||
);
|
||||
var pref = await SharedPreferences.getInstance();
|
||||
var request = FindFileRequest(file: file, token: pref.getInt('token')!);
|
||||
duplicatedRepository.pushRequest(request);
|
||||
var hasFile = false;
|
||||
await for(var response in duplicatedRepository.responseStream) {
|
||||
if(response.type == TCPResponseType.findFile) {
|
||||
hasFile = response.status == TCPResponseStatus.ok;
|
||||
break;
|
||||
}
|
||||
}
|
||||
duplicatedRepository.dispose();
|
||||
return hasFile;
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
_responseRawStreamController.close();
|
||||
_payloadPullStreamController.close();
|
||||
_payloadRawStreamController.close();
|
||||
_responseStreamController.close();
|
||||
_requestStreamController.close();
|
||||
_socket.close();
|
||||
}
|
||||
}
|
||||
@ -5,6 +5,10 @@
|
||||
import FlutterMacOS
|
||||
import Foundation
|
||||
|
||||
import path_provider_macos
|
||||
import shared_preferences_macos
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||
}
|
||||
|
||||
177
pubspec.lock
177
pubspec.lock
@ -85,6 +85,20 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
file:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "6.1.4"
|
||||
file_picker:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: file_picker
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "5.2.1"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
@ -104,11 +118,23 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
flutter_plugin_android_lifecycle:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_plugin_android_lifecycle
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.0.7"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_web_plugins:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
formz:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -158,6 +184,15 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
open_file:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: HEAD
|
||||
resolved-ref: fbf68e4bb5cb3e262d8f8ebe10b2f8449ff8e030
|
||||
url: "https://github.com/crazecoder/open_file"
|
||||
source: git
|
||||
version: "3.2.2"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -165,6 +200,76 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "1.8.2"
|
||||
path_provider:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: path_provider
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.0.11"
|
||||
path_provider_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_android
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.0.20"
|
||||
path_provider_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_ios
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.0.11"
|
||||
path_provider_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_linux
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.1.7"
|
||||
path_provider_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_macos
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.0.6"
|
||||
path_provider_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_platform_interface
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.0.5"
|
||||
path_provider_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_windows
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.1.3"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: platform
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
plugin_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: plugin_platform_interface
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.1.3"
|
||||
process:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: process
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "4.2.4"
|
||||
provider:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -172,6 +277,62 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "6.0.3"
|
||||
shared_preferences:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: shared_preferences
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.0.15"
|
||||
shared_preferences_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_android
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.0.13"
|
||||
shared_preferences_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_ios
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
shared_preferences_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_linux
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
shared_preferences_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_macos
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.0.4"
|
||||
shared_preferences_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_platform_interface
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
shared_preferences_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_web
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.0.4"
|
||||
shared_preferences_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_windows
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
@ -261,6 +422,20 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
win32:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: win32
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xdg_directories
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
version: "0.2.0+2"
|
||||
sdks:
|
||||
dart: ">=2.18.2 <3.0.0"
|
||||
flutter: ">=1.16.0"
|
||||
flutter: ">=3.0.0"
|
||||
|
||||
@ -32,6 +32,7 @@ dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
|
||||
path_provider: ^2.0.11
|
||||
sqflite_common_ffi: ^2.1.1+1
|
||||
sqflite_common: ^2.3.0
|
||||
crypto: ^3.0.2
|
||||
@ -40,6 +41,11 @@ dependencies:
|
||||
formz: ^0.4.1
|
||||
bloc: ^8.1.0
|
||||
flutter_bloc: ^8.1.1
|
||||
file_picker: ^5.2.1
|
||||
open_file:
|
||||
git:
|
||||
url: https://github.com/crazecoder/open_file
|
||||
shared_preferences: ^2.0.15
|
||||
|
||||
# The following adds the Cupertino Icons font to your application.
|
||||
# Use with the CupertinoIcons class for iOS style icons.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user