kopia lustrzana https://gitlab.com/mysocialportal/relatica
Initial implementation of connections persistence
rodzic
33d11f78df
commit
dfef64d748
|
@ -1,5 +1,3 @@
|
|||
import 'dart:collection';
|
||||
|
||||
import 'package:result_monad/result_monad.dart';
|
||||
|
||||
import '../../models/connection.dart';
|
||||
|
@ -26,15 +24,11 @@ class IConnectionsRepo {
|
|||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
Result<Connection, ExecError> getByProfileUrl(String url) {
|
||||
List<Connection> getMyContacts() {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
UnmodifiableListView<Connection> getMyContacts() {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
UnmodifiableListView<Connection> getKnownUsersByName(String name) {
|
||||
List<Connection> getKnownUsersByName(String name) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import 'dart:collection';
|
||||
|
||||
import 'package:result_monad/result_monad.dart';
|
||||
|
||||
import '../../models/connection.dart';
|
||||
|
@ -9,7 +7,6 @@ import '../interfaces/connections_repo_intf.dart';
|
|||
class MemoryConnectionsRepo implements IConnectionsRepo {
|
||||
final _connectionsById = <String, Connection>{};
|
||||
final _connectionsByName = <String, Connection>{};
|
||||
final _connectionsByProfileUrl = <String, Connection>{};
|
||||
final _myContacts = <Connection>[];
|
||||
|
||||
@override
|
||||
|
@ -32,21 +29,20 @@ class MemoryConnectionsRepo implements IConnectionsRepo {
|
|||
}
|
||||
|
||||
@override
|
||||
UnmodifiableListView<Connection> getKnownUsersByName(String name) {
|
||||
return UnmodifiableListView(_connectionsByName.values.where((it) {
|
||||
List<Connection> getKnownUsersByName(String name) {
|
||||
return _connectionsByName.values.where((it) {
|
||||
final normalizedHandle = it.handle.toLowerCase();
|
||||
final normalizedName = it.name.toLowerCase();
|
||||
final normalizedQuery = name.toLowerCase();
|
||||
return normalizedHandle.contains(normalizedQuery) ||
|
||||
normalizedName.contains(normalizedQuery);
|
||||
}));
|
||||
}).toList();
|
||||
}
|
||||
|
||||
@override
|
||||
bool updateConnection(Connection connection) {
|
||||
_connectionsById[connection.id] = connection;
|
||||
_connectionsByName[connection.name] = connection;
|
||||
_connectionsByProfileUrl[connection.profileUrl.toString()] = connection;
|
||||
int index = _myContacts.indexWhere((c) => c.id == connection.id);
|
||||
if (index >= 0) {
|
||||
_myContacts.removeAt(index);
|
||||
|
@ -69,8 +65,8 @@ class MemoryConnectionsRepo implements IConnectionsRepo {
|
|||
}
|
||||
|
||||
@override
|
||||
UnmodifiableListView<Connection> getMyContacts() {
|
||||
return UnmodifiableListView(_myContacts);
|
||||
List<Connection> getMyContacts() {
|
||||
return _myContacts;
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -98,16 +94,4 @@ class MemoryConnectionsRepo implements IConnectionsRepo {
|
|||
|
||||
return Result.ok(result);
|
||||
}
|
||||
|
||||
@override
|
||||
Result<Connection, ExecError> getByProfileUrl(String url) {
|
||||
final result = _connectionsByProfileUrl[url];
|
||||
if (result == null) {
|
||||
return Result.error(ExecError(
|
||||
type: ErrorType.notFound,
|
||||
message: '$url not found',
|
||||
));
|
||||
}
|
||||
return Result.ok(result);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
import 'package:objectbox/objectbox.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
import '../../objectbox.g.dart';
|
||||
|
||||
class ObjectBoxCache {
|
||||
late final Store store;
|
||||
|
||||
ObjectBoxCache._create(this.store);
|
||||
|
||||
static Future<ObjectBoxCache> create() async {
|
||||
final docsDir = await getApplicationSupportDirectory();
|
||||
|
||||
final path = p.join(docsDir.path, 'objectboxcache');
|
||||
final store = await openStore(directory: path);
|
||||
return ObjectBoxCache._create(store);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
import 'package:result_monad/result_monad.dart';
|
||||
|
||||
import '../../globals.dart';
|
||||
import '../../models/connection.dart';
|
||||
import '../../models/exec_error.dart';
|
||||
import '../../objectbox.g.dart';
|
||||
import '../interfaces/connections_repo_intf.dart';
|
||||
import 'objectbox_cache.dart';
|
||||
|
||||
class ObjectBoxConnectionsRepo implements IConnectionsRepo {
|
||||
late final Box<Connection> box;
|
||||
|
||||
ObjectBoxConnectionsRepo() {
|
||||
box = getIt<ObjectBoxCache>().store.box<Connection>();
|
||||
}
|
||||
|
||||
@override
|
||||
bool addAllConnections(Iterable<Connection> newConnections) {
|
||||
final result = box.putMany(newConnections.toList());
|
||||
return result.length == newConnections.length;
|
||||
}
|
||||
|
||||
@override
|
||||
bool addConnection(Connection connection) {
|
||||
box.putAsync(connection);
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
Result<Connection, ExecError> getById(String id) {
|
||||
return _getConnection(
|
||||
Connection_.id.equals(id),
|
||||
).mapError(
|
||||
(error) => error.copy(
|
||||
message: "Can't find Connection for ID: $id",
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Result<Connection, ExecError> getByName(String name) {
|
||||
return _getConnection(
|
||||
Connection_.name.equals(name),
|
||||
).mapError(
|
||||
(error) => error.copy(
|
||||
message: "Can't find Connection for ID: $name",
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Connection> getKnownUsersByName(String name) {
|
||||
return box
|
||||
.query(
|
||||
Connection_.name.contains(name, caseSensitive: false) |
|
||||
Connection_.handle.contains(name, caseSensitive: false),
|
||||
)
|
||||
.build()
|
||||
.find();
|
||||
}
|
||||
|
||||
@override
|
||||
List<Connection> getMyContacts() {
|
||||
final connectedStatuses = [
|
||||
ConnectionStatus.youFollowThem.code,
|
||||
ConnectionStatus.theyFollowYou.code,
|
||||
ConnectionStatus.mutual.code,
|
||||
];
|
||||
return box
|
||||
.query(Connection_.dbStatus.oneOf(connectedStatuses))
|
||||
.build()
|
||||
.find();
|
||||
}
|
||||
|
||||
@override
|
||||
bool updateConnection(Connection connection) {
|
||||
box.put(connection);
|
||||
return true;
|
||||
}
|
||||
|
||||
Result<Connection, ExecError> _getConnection(Condition<Connection> query) {
|
||||
final result = box.query(query).build().findFirst();
|
||||
if (result == null) {
|
||||
return buildErrorResult(type: ErrorType.notFound);
|
||||
}
|
||||
|
||||
return Result.ok(result);
|
||||
}
|
||||
}
|
|
@ -1,14 +1,15 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import 'package:friendica_portal/data/interfaces/groups_repo.intf.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:multi_trigger_autocomplete/multi_trigger_autocomplete.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:result_monad/result_monad.dart';
|
||||
|
||||
import 'data/interfaces/connections_repo_intf.dart';
|
||||
import 'data/memory/memory_connections_repo.dart';
|
||||
import 'data/interfaces/groups_repo.intf.dart';
|
||||
import 'data/memory/memory_groups_repo.dart';
|
||||
import 'data/objectbox/objectbox_cache.dart';
|
||||
import 'data/objectbox/objectbox_connections_repo.dart';
|
||||
import 'globals.dart';
|
||||
import 'models/TimelineIdentifiers.dart';
|
||||
import 'routes.dart';
|
||||
|
@ -49,7 +50,10 @@ void main() async {
|
|||
await service.initialize();
|
||||
return service;
|
||||
});
|
||||
getIt.registerSingleton<IConnectionsRepo>(MemoryConnectionsRepo());
|
||||
|
||||
final objectBoxCache = await ObjectBoxCache.create();
|
||||
getIt.registerSingleton<ObjectBoxCache>(objectBoxCache);
|
||||
getIt.registerSingleton<IConnectionsRepo>(ObjectBoxConnectionsRepo());
|
||||
getIt.registerSingleton<IGroupsRepo>(MemoryGroupsRepo());
|
||||
getIt.registerLazySingleton<ConnectionsManager>(() => ConnectionsManager());
|
||||
getIt.registerLazySingleton<HashtagService>(() => HashtagService());
|
||||
|
|
|
@ -1,44 +1,67 @@
|
|||
import 'package:objectbox/objectbox.dart';
|
||||
|
||||
@Entity()
|
||||
class Connection {
|
||||
final ConnectionStatus status;
|
||||
@Id()
|
||||
int obId;
|
||||
|
||||
ConnectionStatus status;
|
||||
|
||||
int get dbStatus => status.code;
|
||||
|
||||
set dbStatus(int value) => status = ConnectionStatus.fromValue(value);
|
||||
|
||||
final String name;
|
||||
|
||||
final String handle;
|
||||
|
||||
@Unique(onConflict: ConflictStrategy.replace)
|
||||
final String id;
|
||||
|
||||
final Uri profileUrl;
|
||||
final String profileUrl;
|
||||
|
||||
final String network;
|
||||
|
||||
final Uri avatarUrl;
|
||||
final String avatarUrl;
|
||||
|
||||
Connection({this.status = ConnectionStatus.unknown,
|
||||
this.name = '',
|
||||
this.handle = '',
|
||||
this.id = '',
|
||||
Uri? profileUrl,
|
||||
this.network = '',
|
||||
Uri? avatarUrl})
|
||||
: profileUrl = profileUrl ?? Uri(),
|
||||
avatarUrl = avatarUrl ?? Uri();
|
||||
@Property(type: PropertyType.date)
|
||||
final DateTime lastUpdateTime;
|
||||
|
||||
Connection(
|
||||
{this.obId = 0,
|
||||
this.status = ConnectionStatus.unknown,
|
||||
this.name = '',
|
||||
this.handle = '',
|
||||
this.id = '',
|
||||
String? profileUrl,
|
||||
this.network = '',
|
||||
String? avatarUrl,
|
||||
DateTime? lastUpdateTime})
|
||||
: profileUrl = profileUrl ?? '',
|
||||
avatarUrl = avatarUrl ?? '',
|
||||
lastUpdateTime = lastUpdateTime ?? DateTime.now();
|
||||
|
||||
bool get isEmpty =>
|
||||
name.isEmpty &&
|
||||
id.isEmpty &&
|
||||
network.isEmpty &&
|
||||
status == ConnectionStatus.unknown;
|
||||
id.isEmpty &&
|
||||
network.isEmpty &&
|
||||
status == ConnectionStatus.unknown;
|
||||
|
||||
bool get isNotEmpty => !isEmpty;
|
||||
|
||||
Connection copy({ConnectionStatus? status,
|
||||
Connection copy({
|
||||
int? obId,
|
||||
ConnectionStatus? status,
|
||||
String? name,
|
||||
String? handle,
|
||||
String? id,
|
||||
Uri? profileUrl,
|
||||
String? profileUrl,
|
||||
String? network,
|
||||
Uri? avatarUrl}) =>
|
||||
String? avatarUrl,
|
||||
DateTime? lastUpdateTime,
|
||||
}) =>
|
||||
Connection(
|
||||
obId: obId ?? this.obId,
|
||||
status: status ?? this.status,
|
||||
name: name ?? this.name,
|
||||
handle: handle ?? this.handle,
|
||||
|
@ -46,6 +69,7 @@ class Connection {
|
|||
profileUrl: profileUrl ?? this.profileUrl,
|
||||
network: network ?? this.network,
|
||||
avatarUrl: avatarUrl ?? this.avatarUrl,
|
||||
lastUpdateTime: lastUpdateTime ?? this.lastUpdateTime,
|
||||
);
|
||||
|
||||
@override
|
||||
|
@ -56,20 +80,28 @@ class Connection {
|
|||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is Connection && runtimeType == other.runtimeType &&
|
||||
id == other.id;
|
||||
other is Connection && runtimeType == other.runtimeType && id == other.id;
|
||||
|
||||
@override
|
||||
int get hashCode => id.hashCode;
|
||||
}
|
||||
|
||||
enum ConnectionStatus {
|
||||
youFollowThem,
|
||||
theyFollowYou,
|
||||
mutual,
|
||||
you,
|
||||
none,
|
||||
unknown,
|
||||
youFollowThem(1),
|
||||
theyFollowYou(2),
|
||||
mutual(3),
|
||||
you(4),
|
||||
none(5),
|
||||
unknown(6),
|
||||
;
|
||||
|
||||
final int code;
|
||||
|
||||
const ConnectionStatus(this.code);
|
||||
|
||||
factory ConnectionStatus.fromValue(int value) {
|
||||
return ConnectionStatus.values.where((e) => e.code == value).first;
|
||||
}
|
||||
}
|
||||
|
||||
extension FriendStatusWriter on ConnectionStatus {
|
||||
|
|
|
@ -1,11 +1,31 @@
|
|||
import 'package:result_monad/result_monad.dart';
|
||||
|
||||
Result<T, ExecError> buildErrorResult<T>({
|
||||
required ErrorType type,
|
||||
String message = '',
|
||||
}) =>
|
||||
Result.error(
|
||||
ExecError(
|
||||
type: type,
|
||||
message: message,
|
||||
),
|
||||
);
|
||||
|
||||
class ExecError {
|
||||
final ErrorType type;
|
||||
final String message;
|
||||
|
||||
ExecError({required this.type, this.message = ''});
|
||||
|
||||
ExecError copy({
|
||||
ErrorType? type,
|
||||
String? message,
|
||||
}) =>
|
||||
ExecError(
|
||||
type: type ?? this.type,
|
||||
message: message ?? this.message,
|
||||
);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ExecError{type: $type, message: $message}';
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
{
|
||||
"_note1": "KEEP THIS FILE! Check it into a version control system (VCS) like git.",
|
||||
"_note2": "ObjectBox manages crucial IDs for your object model. See docs for details.",
|
||||
"_note3": "If you have VCS merge conflicts, you must resolve them according to ObjectBox docs.",
|
||||
"entities": [
|
||||
{
|
||||
"id": "1:1213035855270739890",
|
||||
"lastPropertyId": "9:7727190023732579468",
|
||||
"name": "Connection",
|
||||
"properties": [
|
||||
{
|
||||
"id": "1:4133343279264917280",
|
||||
"name": "obId",
|
||||
"type": 6,
|
||||
"flags": 1
|
||||
},
|
||||
{
|
||||
"id": "2:3393770296096844708",
|
||||
"name": "name",
|
||||
"type": 9
|
||||
},
|
||||
{
|
||||
"id": "3:5864801995210079539",
|
||||
"name": "handle",
|
||||
"type": 9
|
||||
},
|
||||
{
|
||||
"id": "4:2926904168461994523",
|
||||
"name": "id",
|
||||
"type": 9,
|
||||
"flags": 34848,
|
||||
"indexId": "1:8342366639839511243"
|
||||
},
|
||||
{
|
||||
"id": "5:3621370552742492695",
|
||||
"name": "network",
|
||||
"type": 9
|
||||
},
|
||||
{
|
||||
"id": "6:3054748457893853359",
|
||||
"name": "profileUrl",
|
||||
"type": 9
|
||||
},
|
||||
{
|
||||
"id": "7:3716471511430220806",
|
||||
"name": "avatarUrl",
|
||||
"type": 9
|
||||
},
|
||||
{
|
||||
"id": "8:3334077197732145885",
|
||||
"name": "dbStatus",
|
||||
"type": 6
|
||||
},
|
||||
{
|
||||
"id": "9:7727190023732579468",
|
||||
"name": "lastUpdateTime",
|
||||
"type": 10
|
||||
}
|
||||
],
|
||||
"relations": []
|
||||
}
|
||||
],
|
||||
"lastEntityId": "1:1213035855270739890",
|
||||
"lastIndexId": "1:8342366639839511243",
|
||||
"lastRelationId": "0:0",
|
||||
"lastSequenceId": "0:0",
|
||||
"modelVersion": 5,
|
||||
"modelVersionParserMinimum": 5,
|
||||
"retiredEntityUids": [],
|
||||
"retiredIndexUids": [],
|
||||
"retiredPropertyUids": [],
|
||||
"retiredRelationUids": [],
|
||||
"version": 1
|
||||
}
|
|
@ -0,0 +1,206 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// This code was generated by ObjectBox. To update it run the generator again:
|
||||
// With a Flutter package, run `flutter pub run build_runner build`.
|
||||
// With a Dart package, run `dart run build_runner build`.
|
||||
// See also https://docs.objectbox.io/getting-started#generate-objectbox-code
|
||||
|
||||
// ignore_for_file: camel_case_types
|
||||
// coverage:ignore-file
|
||||
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flat_buffers/flat_buffers.dart' as fb;
|
||||
import 'package:objectbox/internal.dart'; // generated code can access "internal" functionality
|
||||
import 'package:objectbox/objectbox.dart';
|
||||
import 'package:objectbox_flutter_libs/objectbox_flutter_libs.dart';
|
||||
|
||||
import 'models/connection.dart';
|
||||
|
||||
export 'package:objectbox/objectbox.dart'; // so that callers only have to import this file
|
||||
|
||||
final _entities = <ModelEntity>[
|
||||
ModelEntity(
|
||||
id: const IdUid(1, 1213035855270739890),
|
||||
name: 'Connection',
|
||||
lastPropertyId: const IdUid(9, 7727190023732579468),
|
||||
flags: 0,
|
||||
properties: <ModelProperty>[
|
||||
ModelProperty(
|
||||
id: const IdUid(1, 4133343279264917280),
|
||||
name: 'obId',
|
||||
type: 6,
|
||||
flags: 1),
|
||||
ModelProperty(
|
||||
id: const IdUid(2, 3393770296096844708),
|
||||
name: 'name',
|
||||
type: 9,
|
||||
flags: 0),
|
||||
ModelProperty(
|
||||
id: const IdUid(3, 5864801995210079539),
|
||||
name: 'handle',
|
||||
type: 9,
|
||||
flags: 0),
|
||||
ModelProperty(
|
||||
id: const IdUid(4, 2926904168461994523),
|
||||
name: 'id',
|
||||
type: 9,
|
||||
flags: 34848,
|
||||
indexId: const IdUid(1, 8342366639839511243)),
|
||||
ModelProperty(
|
||||
id: const IdUid(5, 3621370552742492695),
|
||||
name: 'network',
|
||||
type: 9,
|
||||
flags: 0),
|
||||
ModelProperty(
|
||||
id: const IdUid(6, 3054748457893853359),
|
||||
name: 'profileUrl',
|
||||
type: 9,
|
||||
flags: 0),
|
||||
ModelProperty(
|
||||
id: const IdUid(7, 3716471511430220806),
|
||||
name: 'avatarUrl',
|
||||
type: 9,
|
||||
flags: 0),
|
||||
ModelProperty(
|
||||
id: const IdUid(8, 3334077197732145885),
|
||||
name: 'dbStatus',
|
||||
type: 6,
|
||||
flags: 0),
|
||||
ModelProperty(
|
||||
id: const IdUid(9, 7727190023732579468),
|
||||
name: 'lastUpdateTime',
|
||||
type: 10,
|
||||
flags: 0)
|
||||
],
|
||||
relations: <ModelRelation>[],
|
||||
backlinks: <ModelBacklink>[])
|
||||
];
|
||||
|
||||
/// Open an ObjectBox store with the model declared in this file.
|
||||
Future<Store> openStore(
|
||||
{String? directory,
|
||||
int? maxDBSizeInKB,
|
||||
int? fileMode,
|
||||
int? maxReaders,
|
||||
bool queriesCaseSensitiveDefault = true,
|
||||
String? macosApplicationGroup}) async =>
|
||||
Store(getObjectBoxModel(),
|
||||
directory: directory ?? (await defaultStoreDirectory()).path,
|
||||
maxDBSizeInKB: maxDBSizeInKB,
|
||||
fileMode: fileMode,
|
||||
maxReaders: maxReaders,
|
||||
queriesCaseSensitiveDefault: queriesCaseSensitiveDefault,
|
||||
macosApplicationGroup: macosApplicationGroup);
|
||||
|
||||
/// ObjectBox model definition, pass it to [Store] - Store(getObjectBoxModel())
|
||||
ModelDefinition getObjectBoxModel() {
|
||||
final model = ModelInfo(
|
||||
entities: _entities,
|
||||
lastEntityId: const IdUid(1, 1213035855270739890),
|
||||
lastIndexId: const IdUid(1, 8342366639839511243),
|
||||
lastRelationId: const IdUid(0, 0),
|
||||
lastSequenceId: const IdUid(0, 0),
|
||||
retiredEntityUids: const [],
|
||||
retiredIndexUids: const [],
|
||||
retiredPropertyUids: const [],
|
||||
retiredRelationUids: const [],
|
||||
modelVersion: 5,
|
||||
modelVersionParserMinimum: 5,
|
||||
version: 1);
|
||||
|
||||
final bindings = <Type, EntityDefinition>{
|
||||
Connection: EntityDefinition<Connection>(
|
||||
model: _entities[0],
|
||||
toOneRelations: (Connection object) => [],
|
||||
toManyRelations: (Connection object) => {},
|
||||
getId: (Connection object) => object.obId,
|
||||
setId: (Connection object, int id) {
|
||||
object.obId = id;
|
||||
},
|
||||
objectToFB: (Connection object, fb.Builder fbb) {
|
||||
final nameOffset = fbb.writeString(object.name);
|
||||
final handleOffset = fbb.writeString(object.handle);
|
||||
final idOffset = fbb.writeString(object.id);
|
||||
final networkOffset = fbb.writeString(object.network);
|
||||
final profileUrlOffset = fbb.writeString(object.profileUrl);
|
||||
final avatarUrlOffset = fbb.writeString(object.avatarUrl);
|
||||
fbb.startTable(10);
|
||||
fbb.addInt64(0, object.obId);
|
||||
fbb.addOffset(1, nameOffset);
|
||||
fbb.addOffset(2, handleOffset);
|
||||
fbb.addOffset(3, idOffset);
|
||||
fbb.addOffset(4, networkOffset);
|
||||
fbb.addOffset(5, profileUrlOffset);
|
||||
fbb.addOffset(6, avatarUrlOffset);
|
||||
fbb.addInt64(7, object.dbStatus);
|
||||
fbb.addInt64(8, object.lastUpdateTime.millisecondsSinceEpoch);
|
||||
fbb.finish(fbb.endTable());
|
||||
return object.obId;
|
||||
},
|
||||
objectFromFB: (Store store, ByteData fbData) {
|
||||
final buffer = fb.BufferContext(fbData);
|
||||
final rootOffset = buffer.derefObject(0);
|
||||
|
||||
final object = Connection(
|
||||
obId: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
|
||||
name: const fb.StringReader(asciiOptimization: true)
|
||||
.vTableGet(buffer, rootOffset, 6, ''),
|
||||
handle: const fb.StringReader(asciiOptimization: true)
|
||||
.vTableGet(buffer, rootOffset, 8, ''),
|
||||
id: const fb.StringReader(asciiOptimization: true)
|
||||
.vTableGet(buffer, rootOffset, 10, ''),
|
||||
profileUrl: const fb.StringReader(asciiOptimization: true)
|
||||
.vTableGet(buffer, rootOffset, 14, ''),
|
||||
network: const fb.StringReader(asciiOptimization: true)
|
||||
.vTableGet(buffer, rootOffset, 12, ''),
|
||||
avatarUrl: const fb.StringReader(asciiOptimization: true)
|
||||
.vTableGet(buffer, rootOffset, 16, ''),
|
||||
lastUpdateTime: DateTime.fromMillisecondsSinceEpoch(
|
||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 20, 0)))
|
||||
..dbStatus =
|
||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 18, 0);
|
||||
|
||||
return object;
|
||||
})
|
||||
};
|
||||
|
||||
return ModelDefinition(model, bindings);
|
||||
}
|
||||
|
||||
/// [Connection] entity fields to define ObjectBox queries.
|
||||
class Connection_ {
|
||||
/// see [Connection.obId]
|
||||
static final obId =
|
||||
QueryIntegerProperty<Connection>(_entities[0].properties[0]);
|
||||
|
||||
/// see [Connection.name]
|
||||
static final name =
|
||||
QueryStringProperty<Connection>(_entities[0].properties[1]);
|
||||
|
||||
/// see [Connection.handle]
|
||||
static final handle =
|
||||
QueryStringProperty<Connection>(_entities[0].properties[2]);
|
||||
|
||||
/// see [Connection.id]
|
||||
static final id = QueryStringProperty<Connection>(_entities[0].properties[3]);
|
||||
|
||||
/// see [Connection.network]
|
||||
static final network =
|
||||
QueryStringProperty<Connection>(_entities[0].properties[4]);
|
||||
|
||||
/// see [Connection.profileUrl]
|
||||
static final profileUrl =
|
||||
QueryStringProperty<Connection>(_entities[0].properties[5]);
|
||||
|
||||
/// see [Connection.avatarUrl]
|
||||
static final avatarUrl =
|
||||
QueryStringProperty<Connection>(_entities[0].properties[6]);
|
||||
|
||||
/// see [Connection.dbStatus]
|
||||
static final dbStatus =
|
||||
QueryIntegerProperty<Connection>(_entities[0].properties[7]);
|
||||
|
||||
/// see [Connection.lastUpdateTime]
|
||||
static final lastUpdateTime =
|
||||
QueryIntegerProperty<Connection>(_entities[0].properties[8]);
|
||||
}
|
|
@ -14,7 +14,7 @@ extension ConnectionFriendicaExtensions on Connection {
|
|||
status: status,
|
||||
name: name,
|
||||
id: id,
|
||||
profileUrl: profileUrl,
|
||||
profileUrl: profileUrl.toString(),
|
||||
network: network);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,9 +23,9 @@ extension ConnectionMastodonExtensions on Connection {
|
|||
name: name,
|
||||
id: id,
|
||||
handle: handle,
|
||||
profileUrl: profileUrl,
|
||||
profileUrl: profileUrl.toString(),
|
||||
network: network,
|
||||
avatarUrl: avatar,
|
||||
avatarUrl: avatar.toString(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -134,7 +134,7 @@ class ConnectionsManager extends ChangeNotifier {
|
|||
);
|
||||
}
|
||||
|
||||
UnmodifiableListView<Connection> getMyContacts() {
|
||||
List<Connection> getMyContacts() {
|
||||
return conRepo.getMyContacts();
|
||||
}
|
||||
|
||||
|
@ -265,15 +265,6 @@ class ConnectionsManager extends ChangeNotifier {
|
|||
}).execErrorCast();
|
||||
}
|
||||
|
||||
Result<Connection, ExecError> getByProfileUrl(Uri url) {
|
||||
return conRepo.getByProfileUrl(url.toString()).andThenSuccess((c) {
|
||||
if (c.status == ConnectionStatus.unknown) {
|
||||
_refreshConnection(c, true);
|
||||
}
|
||||
return c;
|
||||
}).execErrorCast();
|
||||
}
|
||||
|
||||
Future<void> fullRefresh(Connection connection) async {
|
||||
await _updateMyGroups(false);
|
||||
await _refreshGroupListData(connection.id, false);
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
#include <desktop_window/desktop_window_plugin.h>
|
||||
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
|
||||
#include <objectbox_flutter_libs/objectbox_flutter_libs_plugin.h>
|
||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||
|
||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||
|
@ -17,6 +18,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
|
|||
g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin");
|
||||
flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar);
|
||||
g_autoptr(FlPluginRegistrar) objectbox_flutter_libs_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "ObjectboxFlutterLibsPlugin");
|
||||
objectbox_flutter_libs_plugin_register_with_registrar(objectbox_flutter_libs_registrar);
|
||||
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
|
||||
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
desktop_window
|
||||
flutter_secure_storage_linux
|
||||
objectbox_flutter_libs
|
||||
url_launcher_linux
|
||||
)
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ import Foundation
|
|||
|
||||
import desktop_window
|
||||
import flutter_secure_storage_macos
|
||||
import objectbox_flutter_libs
|
||||
import path_provider_macos
|
||||
import shared_preferences_foundation
|
||||
import sqflite
|
||||
|
@ -15,6 +16,7 @@ import url_launcher_macos
|
|||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
DesktopWindowPlugin.register(with: registry.registrar(forPlugin: "DesktopWindowPlugin"))
|
||||
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
||||
ObjectboxFlutterLibsPlugin.register(with: registry.registrar(forPlugin: "ObjectboxFlutterLibsPlugin"))
|
||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
||||
|
|
268
pubspec.lock
268
pubspec.lock
|
@ -1,6 +1,20 @@
|
|||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
_fe_analyzer_shared:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: _fe_analyzer_shared
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "50.0.0"
|
||||
analyzer:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: analyzer
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "5.2.0"
|
||||
archive:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -29,6 +43,62 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
build:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.3.1"
|
||||
build_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_config
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
build_daemon:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_daemon
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
build_resolvers:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_resolvers
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
build_runner:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: build_runner
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.3.3"
|
||||
build_runner_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_runner_core
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "7.2.7"
|
||||
built_collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: built_collection
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "5.1.1"
|
||||
built_value:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: built_value
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "8.4.3"
|
||||
cached_network_image:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -57,6 +127,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
checked_yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: checked_yaml
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -64,6 +141,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
code_builder:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: code_builder
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "4.4.0"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -92,6 +176,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
cryptography:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cryptography
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.5"
|
||||
csslib:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -106,6 +197,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.5"
|
||||
dart_style:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: dart_style
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.2.4"
|
||||
desktop_window:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -148,6 +246,20 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "5.2.5"
|
||||
fixnum:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fixnum
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
flat_buffers:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flat_buffers
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.5"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
|
@ -261,6 +373,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.9.1"
|
||||
frontend_server_client:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: frontend_server_client
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.2.0"
|
||||
functional_listener:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -289,6 +408,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.1.4+2"
|
||||
glob:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: glob
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
go_router:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -296,6 +422,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "5.2.4"
|
||||
graphs:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: graphs
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
html:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -310,6 +443,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.13.5"
|
||||
http_multi_server:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_multi_server
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.2.1"
|
||||
http_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -359,6 +499,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.6.2"
|
||||
io:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: io
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.3"
|
||||
js:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -366,6 +513,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.6.4"
|
||||
json_annotation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: json_annotation
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "4.7.0"
|
||||
lints:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -415,6 +569,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.4.1"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: mime
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
multi_trigger_autocomplete:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -436,6 +597,27 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "4.0.1"
|
||||
objectbox:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: objectbox
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.7.1"
|
||||
objectbox_flutter_libs:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: objectbox_flutter_libs
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.7.1"
|
||||
objectbox_generator:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: objectbox_generator
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.7.1"
|
||||
octo_image:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -443,6 +625,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
package_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_config
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
path:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -451,7 +640,7 @@ packages:
|
|||
source: hosted
|
||||
version: "1.8.2"
|
||||
path_provider:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: path_provider
|
||||
url: "https://pub.dartlang.org"
|
||||
|
@ -534,6 +723,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.6.2"
|
||||
pool:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pool
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.5.1"
|
||||
process:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -548,6 +744,20 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "6.0.5"
|
||||
pub_semver:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pub_semver
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.3"
|
||||
pubspec_parse:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pubspec_parse
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
result_monad:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -618,11 +828,32 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
shelf:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
shelf_web_socket:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf_web_socket
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.3"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.99"
|
||||
source_gen:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_gen
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.6"
|
||||
source_span:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -665,6 +896,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
stream_transform:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_transform
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
string_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -707,6 +945,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.9.17"
|
||||
timing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: timing
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -819,6 +1064,20 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.13"
|
||||
watcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: watcher
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
web_socket_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web_socket_channel
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
win32:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -840,6 +1099,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "6.1.0"
|
||||
yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: yaml
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.1.1"
|
||||
sdks:
|
||||
dart: ">=2.18.2 <3.0.0"
|
||||
flutter: ">=3.3.0"
|
||||
|
|
|
@ -39,11 +39,16 @@ dependencies:
|
|||
url_launcher: ^6.1.6
|
||||
uuid: ^3.0.6
|
||||
video_player: ^2.4.10
|
||||
objectbox: ^1.7.1
|
||||
objectbox_flutter_libs: ^1.7.1
|
||||
path_provider: ^2.0.11
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter_lints: ^2.0.0
|
||||
build_runner: ^2.3.3
|
||||
objectbox_generator: ^1.7.1
|
||||
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
#include <desktop_window/desktop_window_plugin.h>
|
||||
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
|
||||
#include <objectbox_flutter_libs/objectbox_flutter_libs_plugin.h>
|
||||
#include <url_launcher_windows/url_launcher_windows.h>
|
||||
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
|
@ -15,6 +16,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
|
|||
registry->GetRegistrarForPlugin("DesktopWindowPlugin"));
|
||||
FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
|
||||
ObjectboxFlutterLibsPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("ObjectboxFlutterLibsPlugin"));
|
||||
UrlLauncherWindowsRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
desktop_window
|
||||
flutter_secure_storage_windows
|
||||
objectbox_flutter_libs
|
||||
url_launcher_windows
|
||||
)
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue