mirror of
				https://gitlab.com/famedly/fluffychat.git
				synced 2025-11-04 06:17:26 +01:00 
			
		
		
		
	Revert "feat: Use sembast over sqflite"
This reverts commit 2fbf7376f6b464c60efcb3e8448c0beac7131638.
This commit is contained in:
		
							parent
							
								
									2fbf7376f6
								
							
						
					
					
						commit
						815d4d5a35
					
				@ -5,13 +5,13 @@ import 'package:flutter/foundation.dart';
 | 
			
		||||
import 'package:hive_flutter/hive_flutter.dart';
 | 
			
		||||
import 'package:matrix/encryption/utils/key_verification.dart';
 | 
			
		||||
import 'package:matrix/matrix.dart';
 | 
			
		||||
import 'package:path_provider/path_provider.dart';
 | 
			
		||||
import 'package:sembast/sembast.dart';
 | 
			
		||||
import 'package:path_provider/path_provider.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:fluffychat/utils/platform_infos.dart';
 | 
			
		||||
import 'famedlysdk_store.dart';
 | 
			
		||||
import 'matrix_sdk_extensions.dart/flutter_matrix_hive_database.dart';
 | 
			
		||||
import 'matrix_sdk_extensions.dart/flutter_matrix_sembast_database.dart';
 | 
			
		||||
import 'matrix_sdk_extensions.dart/flutter_matrix_sembast_database_old.dart';
 | 
			
		||||
 | 
			
		||||
abstract class ClientManager {
 | 
			
		||||
  static const String clientNamespace = 'im.fluffychat.store.clients';
 | 
			
		||||
@ -81,8 +81,7 @@ abstract class ClientManager {
 | 
			
		||||
        },
 | 
			
		||||
        importantStateEvents: <String>{'im.ponies.room_emotes'},
 | 
			
		||||
        databaseBuilder: FlutterMatrixSembastDatabase.databaseBuilder,
 | 
			
		||||
        legacyDatabaseBuilder: FlutterMatrixSembastDatabaseOld.databaseBuilder,
 | 
			
		||||
        //legacyDatabaseBuilder: FlutterMatrixHiveStore.hiveDatabaseBuilder,
 | 
			
		||||
        legacyDatabaseBuilder: FlutterMatrixHiveStore.hiveDatabaseBuilder,
 | 
			
		||||
        supportedLoginTypes: {
 | 
			
		||||
          AuthenticationTypes.password,
 | 
			
		||||
          if (PlatformInfos.isMobile || PlatformInfos.isWeb)
 | 
			
		||||
 | 
			
		||||
@ -1,120 +0,0 @@
 | 
			
		||||
//@dart=2.12
 | 
			
		||||
 | 
			
		||||
import 'dart:convert';
 | 
			
		||||
import 'dart:math';
 | 
			
		||||
import 'dart:typed_data';
 | 
			
		||||
 | 
			
		||||
import 'package:crypto/crypto.dart';
 | 
			
		||||
import 'package:encrypt/encrypt.dart';
 | 
			
		||||
import 'package:sembast/sembast.dart';
 | 
			
		||||
 | 
			
		||||
var _random = Random.secure();
 | 
			
		||||
 | 
			
		||||
/// Random bytes generator
 | 
			
		||||
Uint8List _randBytes(int length) {
 | 
			
		||||
  return Uint8List.fromList(
 | 
			
		||||
      List<int>.generate(length, (i) => _random.nextInt(256)));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Generate an encryption password based on a user input password
 | 
			
		||||
///
 | 
			
		||||
/// It uses MD5 which generates a 16 bytes blob, size needed for Salsa20
 | 
			
		||||
Uint8List _generateEncryptPassword(String password) {
 | 
			
		||||
  final blob = Uint8List.fromList(md5.convert(utf8.encode(password)).bytes);
 | 
			
		||||
  assert(blob.length == 16);
 | 
			
		||||
  return blob;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Salsa20 based encoder
 | 
			
		||||
class _EncryptEncoder extends Converter<dynamic, String> {
 | 
			
		||||
  final Salsa20 salsa20;
 | 
			
		||||
 | 
			
		||||
  _EncryptEncoder(this.salsa20);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  String convert(dynamic input) {
 | 
			
		||||
    // Generate random initial value
 | 
			
		||||
    final iv = _randBytes(8);
 | 
			
		||||
    final ivEncoded = base64.encode(iv);
 | 
			
		||||
    assert(ivEncoded.length == 12);
 | 
			
		||||
 | 
			
		||||
    // Encode the input value
 | 
			
		||||
    final encoded =
 | 
			
		||||
        Encrypter(salsa20).encrypt(json.encode(input), iv: IV(iv)).base64;
 | 
			
		||||
 | 
			
		||||
    // Prepend the initial value
 | 
			
		||||
    return '$ivEncoded$encoded';
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Salsa20 based decoder
 | 
			
		||||
class _EncryptDecoder extends Converter<String, dynamic> {
 | 
			
		||||
  final Salsa20 salsa20;
 | 
			
		||||
 | 
			
		||||
  _EncryptDecoder(this.salsa20);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  dynamic convert(String input) {
 | 
			
		||||
    // Read the initial value that was prepended
 | 
			
		||||
    assert(input.length >= 12);
 | 
			
		||||
    final iv = base64.decode(input.substring(0, 12));
 | 
			
		||||
 | 
			
		||||
    // Extract the real input
 | 
			
		||||
    input = input.substring(12);
 | 
			
		||||
 | 
			
		||||
    // Decode the input
 | 
			
		||||
    final decoded =
 | 
			
		||||
        json.decode(Encrypter(salsa20).decrypt64(input, iv: IV(iv)));
 | 
			
		||||
    if (decoded is Map) {
 | 
			
		||||
      return decoded.cast<String, dynamic>();
 | 
			
		||||
    }
 | 
			
		||||
    return decoded;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Salsa20 based Codec
 | 
			
		||||
class _EncryptCodec extends Codec<dynamic, String> {
 | 
			
		||||
  late _EncryptEncoder _encoder;
 | 
			
		||||
  late _EncryptDecoder _decoder;
 | 
			
		||||
 | 
			
		||||
  _EncryptCodec(Uint8List passwordBytes) {
 | 
			
		||||
    final salsa20 = Salsa20(Key(passwordBytes));
 | 
			
		||||
    _encoder = _EncryptEncoder(salsa20);
 | 
			
		||||
    _decoder = _EncryptDecoder(salsa20);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Converter<String, dynamic> get decoder => _decoder;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Converter<dynamic, String> get encoder => _encoder;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Our plain text signature
 | 
			
		||||
const _encryptCodecSignature = 'encrypt';
 | 
			
		||||
 | 
			
		||||
/// Create a codec to use to open a database with encrypted stored data.
 | 
			
		||||
///
 | 
			
		||||
/// Hash (md5) of the password is used (but never stored) as a key to encrypt
 | 
			
		||||
/// the data using the Salsa20 algorithm with a random (8 bytes) initial value
 | 
			
		||||
///
 | 
			
		||||
/// This is just used as a demonstration and should not be considered as a
 | 
			
		||||
/// reference since its implementation (and storage format) might change.
 | 
			
		||||
///
 | 
			
		||||
/// No performance metrics has been made to check whether this is a viable
 | 
			
		||||
/// solution for big databases.
 | 
			
		||||
///
 | 
			
		||||
/// The usage is then
 | 
			
		||||
///
 | 
			
		||||
/// ```dart
 | 
			
		||||
/// // Initialize the encryption codec with a user password
 | 
			
		||||
/// var codec = getEncryptSembastCodec(password: '[your_user_password]');
 | 
			
		||||
/// // Open the database with the codec
 | 
			
		||||
/// Database db = await factory.openDatabase(dbPath, codec: codec);
 | 
			
		||||
///
 | 
			
		||||
/// // ...your database is ready to use
 | 
			
		||||
/// ```
 | 
			
		||||
SembastCodec getEncryptSembastCodec({required String password}) => SembastCodec(
 | 
			
		||||
      signature: _encryptCodecSignature,
 | 
			
		||||
      codec: _EncryptCodec(_generateEncryptPassword(password)),
 | 
			
		||||
    );
 | 
			
		||||
@ -1,3 +1,4 @@
 | 
			
		||||
import 'dart:convert';
 | 
			
		||||
import 'dart:io';
 | 
			
		||||
import 'dart:typed_data';
 | 
			
		||||
 | 
			
		||||
@ -10,13 +11,9 @@ import 'package:matrix/matrix.dart';
 | 
			
		||||
import 'package:path_provider/path_provider.dart';
 | 
			
		||||
import 'package:sembast/sembast.dart';
 | 
			
		||||
import 'package:sembast/sembast_io.dart';
 | 
			
		||||
import 'package:sembast_sqflite/sembast_sqflite.dart';
 | 
			
		||||
import 'package:sembast_web/sembast_web.dart';
 | 
			
		||||
import 'package:sqflite/sqflite.dart' as sqflite;
 | 
			
		||||
import 'package:sqflite_common_ffi/sqflite_ffi.dart' as sqflite_ffi;
 | 
			
		||||
 | 
			
		||||
import '../platform_infos.dart';
 | 
			
		||||
import 'codec.dart';
 | 
			
		||||
 | 
			
		||||
class FlutterMatrixSembastDatabase extends MatrixSembastDatabase {
 | 
			
		||||
  FlutterMatrixSembastDatabase(
 | 
			
		||||
@ -32,7 +29,7 @@ class FlutterMatrixSembastDatabase extends MatrixSembastDatabase {
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
  static const String _cipherStorageKey = 'sembast_encryption_key';
 | 
			
		||||
  static const int _cipherStorageKeyLength = 1024;
 | 
			
		||||
  static const int _cipherStorageKeyLength = 512;
 | 
			
		||||
 | 
			
		||||
  static Future<FlutterMatrixSembastDatabase> databaseBuilder(
 | 
			
		||||
      Client client) async {
 | 
			
		||||
@ -56,6 +53,7 @@ class FlutterMatrixSembastDatabase extends MatrixSembastDatabase {
 | 
			
		||||
      // workaround for if we just wrote to the key and it still doesn't exist
 | 
			
		||||
      final rawEncryptionKey = await secureStorage.read(key: _cipherStorageKey);
 | 
			
		||||
      if (rawEncryptionKey == null) throw MissingPluginException();
 | 
			
		||||
 | 
			
		||||
      codec = getEncryptSembastCodec(password: rawEncryptionKey);
 | 
			
		||||
    } on MissingPluginException catch (_) {
 | 
			
		||||
      Logs().i('Sembast encryption is not supported on this platform');
 | 
			
		||||
@ -65,26 +63,13 @@ class FlutterMatrixSembastDatabase extends MatrixSembastDatabase {
 | 
			
		||||
      client.clientName,
 | 
			
		||||
      codec: codec,
 | 
			
		||||
      path: await _findDatabasePath(client),
 | 
			
		||||
      dbFactory: kIsWeb
 | 
			
		||||
          ? databaseFactoryWeb
 | 
			
		||||
          : getDatabaseFactorySqflite(sqflite.databaseFactory),
 | 
			
		||||
      dbFactory: kIsWeb ? databaseFactoryWeb : databaseFactoryIo,
 | 
			
		||||
    );
 | 
			
		||||
    await db.open();
 | 
			
		||||
    Logs().d('Sembast is ready');
 | 
			
		||||
    return db;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static DatabaseFactory get factory {
 | 
			
		||||
    if (kIsWeb) return databaseFactoryWeb;
 | 
			
		||||
    if (Platform.isAndroid || Platform.isIOS) {
 | 
			
		||||
      return getDatabaseFactorySqflite(sqflite.databaseFactory);
 | 
			
		||||
    }
 | 
			
		||||
    if (Platform.isLinux || Platform.isWindows || Platform.isMacOS) {
 | 
			
		||||
      return getDatabaseFactorySqflite(sqflite_ffi.databaseFactoryFfi);
 | 
			
		||||
    }
 | 
			
		||||
    return databaseFactoryIo;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static Future<String> _findDatabasePath(Client client) async {
 | 
			
		||||
    String path = client.clientName;
 | 
			
		||||
    if (!kIsWeb) {
 | 
			
		||||
@ -98,7 +83,7 @@ class FlutterMatrixSembastDatabase extends MatrixSembastDatabase {
 | 
			
		||||
          directory = Directory.current;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      path = '${directory.path}${client.clientName}.sqflite';
 | 
			
		||||
      path = '${directory.path}${client.clientName}.db';
 | 
			
		||||
    }
 | 
			
		||||
    return path;
 | 
			
		||||
  }
 | 
			
		||||
@ -144,3 +129,79 @@ class FlutterMatrixSembastDatabase extends MatrixSembastDatabase {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _EncryptEncoder extends Converter<Map<String, dynamic>, String> {
 | 
			
		||||
  final String key;
 | 
			
		||||
  final String signature;
 | 
			
		||||
  _EncryptEncoder(this.key, this.signature);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  String convert(Map<String, dynamic> input) {
 | 
			
		||||
    String encoded;
 | 
			
		||||
    switch (signature) {
 | 
			
		||||
      case "Salsa20":
 | 
			
		||||
        encoded = Encrypter(Salsa20(Key.fromUtf8(key)))
 | 
			
		||||
            .encrypt(json.encode(input), iv: IV.fromLength(8))
 | 
			
		||||
            .base64;
 | 
			
		||||
        break;
 | 
			
		||||
      case "AES":
 | 
			
		||||
        encoded = Encrypter(AES(Key.fromUtf8(key)))
 | 
			
		||||
            .encrypt(json.encode(input), iv: IV.fromLength(16))
 | 
			
		||||
            .base64;
 | 
			
		||||
        break;
 | 
			
		||||
      default:
 | 
			
		||||
        throw FormatException('invalid $signature');
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
    return encoded;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _EncryptDecoder extends Converter<String, Map<String, dynamic>> {
 | 
			
		||||
  final String key;
 | 
			
		||||
  final String signature;
 | 
			
		||||
  _EncryptDecoder(this.key, this.signature);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Map<String, dynamic> convert(String input) {
 | 
			
		||||
    dynamic decoded;
 | 
			
		||||
    switch (signature) {
 | 
			
		||||
      case "Salsa20":
 | 
			
		||||
        decoded = json.decode(Encrypter(Salsa20(Key.fromUtf8(key)))
 | 
			
		||||
            .decrypt64(input, iv: IV.fromLength(8)));
 | 
			
		||||
        break;
 | 
			
		||||
      case "AES":
 | 
			
		||||
        decoded = json.decode(Encrypter(AES(Key.fromUtf8(key)))
 | 
			
		||||
            .decrypt64(input, iv: IV.fromLength(16)));
 | 
			
		||||
        break;
 | 
			
		||||
      default:
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
    if (decoded is Map) {
 | 
			
		||||
      return decoded.cast<String, dynamic>();
 | 
			
		||||
    }
 | 
			
		||||
    throw FormatException('invalid input $input');
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _EncryptCodec extends Codec<Map<String, dynamic>, String> {
 | 
			
		||||
  final String signature;
 | 
			
		||||
  _EncryptEncoder _encoder;
 | 
			
		||||
  _EncryptDecoder _decoder;
 | 
			
		||||
  _EncryptCodec(String password, this.signature) {
 | 
			
		||||
    _encoder = _EncryptEncoder(password, signature);
 | 
			
		||||
    _decoder = _EncryptDecoder(password, signature);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Converter<String, Map<String, dynamic>> get decoder => _decoder;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Converter<Map<String, dynamic>, String> get encoder => _encoder;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Salsa20 (16 length key required) or AES (32 length key required)
 | 
			
		||||
SembastCodec getEncryptSembastCodec(
 | 
			
		||||
        {@required String password, String signature = "Salsa20"}) =>
 | 
			
		||||
    SembastCodec(
 | 
			
		||||
        signature: signature, codec: _EncryptCodec(password, signature));
 | 
			
		||||
 | 
			
		||||
@ -1,131 +0,0 @@
 | 
			
		||||
import 'dart:io';
 | 
			
		||||
import 'dart:typed_data';
 | 
			
		||||
 | 
			
		||||
import 'package:flutter/foundation.dart' hide Key;
 | 
			
		||||
import 'package:flutter/services.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:encrypt/encrypt.dart';
 | 
			
		||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
 | 
			
		||||
import 'package:matrix/matrix.dart';
 | 
			
		||||
import 'package:path_provider/path_provider.dart';
 | 
			
		||||
import 'package:sembast/sembast.dart';
 | 
			
		||||
import 'package:sembast/sembast_io.dart';
 | 
			
		||||
import 'package:sembast_web/sembast_web.dart';
 | 
			
		||||
 | 
			
		||||
import '../platform_infos.dart';
 | 
			
		||||
import 'codec.dart';
 | 
			
		||||
 | 
			
		||||
class FlutterMatrixSembastDatabaseOld extends MatrixSembastDatabase {
 | 
			
		||||
  FlutterMatrixSembastDatabaseOld(
 | 
			
		||||
    String name, {
 | 
			
		||||
    SembastCodec codec,
 | 
			
		||||
    String path,
 | 
			
		||||
    DatabaseFactory dbFactory,
 | 
			
		||||
  }) : super(
 | 
			
		||||
          name,
 | 
			
		||||
          codec: codec,
 | 
			
		||||
          path: path,
 | 
			
		||||
          dbFactory: dbFactory,
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
  static const String _cipherStorageKey = 'sembast_encryption_key';
 | 
			
		||||
  static const int _cipherStorageKeyLength = 512;
 | 
			
		||||
 | 
			
		||||
  static Future<FlutterMatrixSembastDatabaseOld> databaseBuilder(
 | 
			
		||||
      Client client) async {
 | 
			
		||||
    Logs().d('Open Sembast...');
 | 
			
		||||
    SembastCodec codec;
 | 
			
		||||
    try {
 | 
			
		||||
      // Workaround for secure storage is calling Platform.operatingSystem on web
 | 
			
		||||
      if (kIsWeb) throw MissingPluginException();
 | 
			
		||||
 | 
			
		||||
      const secureStorage = FlutterSecureStorage();
 | 
			
		||||
      final containsEncryptionKey =
 | 
			
		||||
          await secureStorage.containsKey(key: _cipherStorageKey);
 | 
			
		||||
      if (!containsEncryptionKey) {
 | 
			
		||||
        final key = SecureRandom(_cipherStorageKeyLength).base64;
 | 
			
		||||
        await secureStorage.write(
 | 
			
		||||
          key: _cipherStorageKey,
 | 
			
		||||
          value: key,
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // workaround for if we just wrote to the key and it still doesn't exist
 | 
			
		||||
      final rawEncryptionKey = await secureStorage.read(key: _cipherStorageKey);
 | 
			
		||||
      if (rawEncryptionKey == null) throw MissingPluginException();
 | 
			
		||||
 | 
			
		||||
      codec = getEncryptSembastCodec(password: rawEncryptionKey);
 | 
			
		||||
    } on MissingPluginException catch (_) {
 | 
			
		||||
      Logs().i('Sembast encryption is not supported on this platform');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    final db = FlutterMatrixSembastDatabaseOld(
 | 
			
		||||
      client.clientName,
 | 
			
		||||
      codec: codec,
 | 
			
		||||
      path: await _findDatabasePath(client),
 | 
			
		||||
      dbFactory: kIsWeb ? databaseFactoryWeb : databaseFactoryIo,
 | 
			
		||||
    );
 | 
			
		||||
    await db.open();
 | 
			
		||||
    Logs().d('Sembast is ready');
 | 
			
		||||
    return db;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static Future<String> _findDatabasePath(Client client) async {
 | 
			
		||||
    String path = client.clientName;
 | 
			
		||||
    if (!kIsWeb) {
 | 
			
		||||
      Directory directory;
 | 
			
		||||
      try {
 | 
			
		||||
        directory = await getApplicationSupportDirectory();
 | 
			
		||||
      } catch (_) {
 | 
			
		||||
        try {
 | 
			
		||||
          directory = await getLibraryDirectory();
 | 
			
		||||
        } catch (_) {
 | 
			
		||||
          directory = Directory.current;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      path = '${directory.path}${client.clientName}.db';
 | 
			
		||||
    }
 | 
			
		||||
    return path;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  int get maxFileSize => supportsFileStoring ? 100 * 1024 * 1024 : 0;
 | 
			
		||||
  @override
 | 
			
		||||
  bool get supportsFileStoring => (PlatformInfos.isIOS ||
 | 
			
		||||
      PlatformInfos.isAndroid ||
 | 
			
		||||
      PlatformInfos.isDesktop);
 | 
			
		||||
 | 
			
		||||
  Future<String> _getFileStoreDirectory() async {
 | 
			
		||||
    try {
 | 
			
		||||
      try {
 | 
			
		||||
        return (await getApplicationSupportDirectory()).path;
 | 
			
		||||
      } catch (_) {
 | 
			
		||||
        return (await getApplicationDocumentsDirectory()).path;
 | 
			
		||||
      }
 | 
			
		||||
    } catch (_) {
 | 
			
		||||
      return (await getDownloadsDirectory()).path;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Future<Uint8List> getFile(Uri mxcUri) async {
 | 
			
		||||
    if (!supportsFileStoring) return null;
 | 
			
		||||
    final tempDirectory = await _getFileStoreDirectory();
 | 
			
		||||
    final file =
 | 
			
		||||
        File('$tempDirectory/${Uri.encodeComponent(mxcUri.toString())}');
 | 
			
		||||
    if (await file.exists() == false) return null;
 | 
			
		||||
    final bytes = await file.readAsBytes();
 | 
			
		||||
    return bytes;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Future storeFile(Uri mxcUri, Uint8List bytes, int time) async {
 | 
			
		||||
    if (!supportsFileStoring) return null;
 | 
			
		||||
    final tempDirectory = await _getFileStoreDirectory();
 | 
			
		||||
    final file =
 | 
			
		||||
        File('$tempDirectory/${Uri.encodeComponent(mxcUri.toString())}');
 | 
			
		||||
    if (await file.exists()) return;
 | 
			
		||||
    await file.writeAsBytes(bytes);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										23
									
								
								pubspec.lock
									
									
									
									
									
								
							
							
						
						
									
										23
									
								
								pubspec.lock
									
									
									
									
									
								
							@ -1171,13 +1171,6 @@ packages:
 | 
			
		||||
      url: "https://pub.dartlang.org"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "3.1.1"
 | 
			
		||||
  sembast_sqflite:
 | 
			
		||||
    dependency: "direct main"
 | 
			
		||||
    description:
 | 
			
		||||
      name: sembast_sqflite
 | 
			
		||||
      url: "https://pub.dartlang.org"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "2.0.0+1"
 | 
			
		||||
  sembast_web:
 | 
			
		||||
    dependency: "direct main"
 | 
			
		||||
    description:
 | 
			
		||||
@ -1315,21 +1308,7 @@ packages:
 | 
			
		||||
      name: sqflite_common
 | 
			
		||||
      url: "https://pub.dartlang.org"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "2.0.1+1"
 | 
			
		||||
  sqflite_common_ffi:
 | 
			
		||||
    dependency: "direct main"
 | 
			
		||||
    description:
 | 
			
		||||
      name: sqflite_common_ffi
 | 
			
		||||
      url: "https://pub.dartlang.org"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "2.1.0"
 | 
			
		||||
  sqlite3:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: sqlite3
 | 
			
		||||
      url: "https://pub.dartlang.org"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "1.3.1"
 | 
			
		||||
    version: "2.0.0+2"
 | 
			
		||||
  stack_trace:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
 | 
			
		||||
@ -63,12 +63,10 @@ dependencies:
 | 
			
		||||
  record: ^3.0.0
 | 
			
		||||
  salomon_bottom_bar: ^3.1.0
 | 
			
		||||
  scroll_to_index: ^2.1.0
 | 
			
		||||
  sembast_sqflite: ^2.0.0+1
 | 
			
		||||
  sembast_web: ^2.0.1+1
 | 
			
		||||
  sentry: ^6.0.1
 | 
			
		||||
  share: ^2.0.4
 | 
			
		||||
  slugify: ^2.0.0
 | 
			
		||||
  sqflite_common_ffi: ^2.1.0
 | 
			
		||||
  swipe_to_action: ^0.2.0
 | 
			
		||||
  uni_links: ^0.5.1
 | 
			
		||||
  unifiedpush: ^1.0.6
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user