byebye Firebase, hello indexer
This commit is contained in:
parent
6f8d14a12f
commit
da4b539618
|
@ -1,82 +0,0 @@
|
||||||
// File generated by FlutterFire CLI.
|
|
||||||
// ignore_for_file: lines_longer_than_80_chars, avoid_classes_with_only_static_members
|
|
||||||
import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
|
|
||||||
import 'package:flutter/foundation.dart'
|
|
||||||
show defaultTargetPlatform, kIsWeb, TargetPlatform;
|
|
||||||
|
|
||||||
/// Default [FirebaseOptions] for use with your Firebase apps.
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
/// ```dart
|
|
||||||
/// import 'firebase_options.dart';
|
|
||||||
/// // ...
|
|
||||||
/// await Firebase.initializeApp(
|
|
||||||
/// options: DefaultFirebaseOptions.currentPlatform,
|
|
||||||
/// );
|
|
||||||
/// ```
|
|
||||||
class DefaultFirebaseOptions {
|
|
||||||
static FirebaseOptions get currentPlatform {
|
|
||||||
if (kIsWeb) {
|
|
||||||
return web;
|
|
||||||
}
|
|
||||||
switch (defaultTargetPlatform) {
|
|
||||||
case TargetPlatform.android:
|
|
||||||
return android;
|
|
||||||
case TargetPlatform.iOS:
|
|
||||||
return ios;
|
|
||||||
case TargetPlatform.macOS:
|
|
||||||
return macos;
|
|
||||||
case TargetPlatform.windows:
|
|
||||||
throw UnsupportedError(
|
|
||||||
'DefaultFirebaseOptions have not been configured for windows - '
|
|
||||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
|
||||||
);
|
|
||||||
case TargetPlatform.linux:
|
|
||||||
throw UnsupportedError(
|
|
||||||
'DefaultFirebaseOptions have not been configured for linux - '
|
|
||||||
'you can reconfigure this by running the FlutterFire CLI again.',
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
throw UnsupportedError(
|
|
||||||
'DefaultFirebaseOptions are not supported for this platform.',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static const FirebaseOptions web = FirebaseOptions(
|
|
||||||
apiKey: 'AIzaSyB6eFpKM-FX31cZPRPbNwlj6VCoc1G3SeU',
|
|
||||||
appId: '1:655141330055:web:b262dc3d240a32a81a2c22',
|
|
||||||
messagingSenderId: '655141330055',
|
|
||||||
projectId: 'gdev-annuaire',
|
|
||||||
authDomain: 'gdev-annuaire.firebaseapp.com',
|
|
||||||
storageBucket: 'gdev-annuaire.appspot.com',
|
|
||||||
);
|
|
||||||
|
|
||||||
static const FirebaseOptions android = FirebaseOptions(
|
|
||||||
apiKey: 'AIzaSyAxFULgOFom_Dsx6AVmcZdHGetDvtjGzbk',
|
|
||||||
appId: '1:655141330055:android:db10532c8a73cc9d1a2c22',
|
|
||||||
messagingSenderId: '655141330055',
|
|
||||||
projectId: 'gdev-annuaire',
|
|
||||||
storageBucket: 'gdev-annuaire.appspot.com',
|
|
||||||
);
|
|
||||||
|
|
||||||
static const FirebaseOptions ios = FirebaseOptions(
|
|
||||||
apiKey: 'AIzaSyB6u7EpW4pvrR9gJGq4B7LrzesBV-sIq1I',
|
|
||||||
appId: '1:655141330055:ios:82c8fec7ae27b2951a2c22',
|
|
||||||
messagingSenderId: '655141330055',
|
|
||||||
projectId: 'gdev-annuaire',
|
|
||||||
storageBucket: 'gdev-annuaire.appspot.com',
|
|
||||||
iosClientId: '655141330055-e3uim74419qgvjlt7svqk763q758qfak.apps.googleusercontent.com',
|
|
||||||
iosBundleId: 'com.example.gdevAnnuaire',
|
|
||||||
);
|
|
||||||
|
|
||||||
static const FirebaseOptions macos = FirebaseOptions(
|
|
||||||
apiKey: 'AIzaSyB6u7EpW4pvrR9gJGq4B7LrzesBV-sIq1I',
|
|
||||||
appId: '1:655141330055:ios:82c8fec7ae27b2951a2c22',
|
|
||||||
messagingSenderId: '655141330055',
|
|
||||||
projectId: 'gdev-annuaire',
|
|
||||||
storageBucket: 'gdev-annuaire.appspot.com',
|
|
||||||
iosClientId: '655141330055-e3uim74419qgvjlt7svqk763q758qfak.apps.googleusercontent.com',
|
|
||||||
iosBundleId: 'com.example.gdevAnnuaire',
|
|
||||||
);
|
|
||||||
}
|
|
313
lib/main.dart
313
lib/main.dart
|
@ -1,55 +1,57 @@
|
||||||
// ignore_for_file: avoid_print
|
// ignore_for_file: avoid_print
|
||||||
|
|
||||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:gdev_annuaire/queries.dart';
|
||||||
|
import 'package:graphql_flutter/graphql_flutter.dart';
|
||||||
// import 'package:hive_flutter/hive_flutter.dart';
|
// import 'package:hive_flutter/hive_flutter.dart';
|
||||||
import 'package:qr_flutter/qr_flutter.dart';
|
import 'package:qr_flutter/qr_flutter.dart';
|
||||||
import 'package:truncate/truncate.dart';
|
import 'package:truncate/truncate.dart';
|
||||||
import 'package:firebase_core/firebase_core.dart';
|
|
||||||
import 'firebase_options.dart';
|
|
||||||
import 'package:firebase_auth/firebase_auth.dart';
|
|
||||||
|
|
||||||
// late Box addressBox;
|
// late Box addressBox;
|
||||||
late FirebaseFirestore db;
|
|
||||||
bool canAdd = false;
|
bool canAdd = false;
|
||||||
List profiles = [];
|
List profiles = [];
|
||||||
|
bool showAll = false;
|
||||||
|
|
||||||
Future<void> main() async {
|
Future<void> main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
const indexerEndpoint = "https://duniter-indexer.coinduf.eu/v1/graphql";
|
||||||
|
|
||||||
// await Hive.initFlutter();
|
// await Hive.initFlutter();
|
||||||
await Firebase.initializeApp(
|
|
||||||
options: DefaultFirebaseOptions.currentPlatform,
|
|
||||||
);
|
|
||||||
db = FirebaseFirestore.instance;
|
|
||||||
// await eraseDB();
|
|
||||||
await FirebaseAuth.instance.signInAnonymously();
|
|
||||||
FirebaseAuth.instance.authStateChanges().listen((User? user) {
|
|
||||||
if (user == null) {
|
|
||||||
print('User is currently signed out!');
|
|
||||||
} else {
|
|
||||||
print('User is signed in!');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// addressBox = await Hive.openBox("addressBox");
|
// addressBox = await Hive.openBox("addressBox");
|
||||||
// await addressBox.clear();
|
// await addressBox.clear();
|
||||||
|
|
||||||
runApp(const MyApp());
|
runApp(const MyApp(indexerEndpoint));
|
||||||
}
|
}
|
||||||
|
|
||||||
class MyApp extends StatelessWidget {
|
class MyApp extends StatelessWidget {
|
||||||
const MyApp({Key? key}) : super(key: key);
|
const MyApp(this.indexerEndpoint, {Key? key}) : super(key: key);
|
||||||
|
final String? indexerEndpoint;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MaterialApp(
|
final httpLink = HttpLink(
|
||||||
title: 'ĞDev annuaire',
|
indexerEndpoint!,
|
||||||
theme: ThemeData(
|
);
|
||||||
primarySwatch: Colors.blue,
|
|
||||||
|
final client = ValueNotifier(
|
||||||
|
GraphQLClient(
|
||||||
|
cache: GraphQLCache(),
|
||||||
|
link: httpLink,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return GraphQLProvider(
|
||||||
|
client: client,
|
||||||
|
child: MaterialApp(
|
||||||
|
title: 'ĞDev annuaire',
|
||||||
|
theme: ThemeData(
|
||||||
|
primarySwatch: Colors.blue,
|
||||||
|
),
|
||||||
|
home: const MyHomePage(title: 'ĞDev annuaire'),
|
||||||
),
|
),
|
||||||
home: const MyHomePage(title: 'ĞDev annuaire'),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -69,6 +71,12 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
void initState() {
|
||||||
|
// showAll = false;
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
// profiles.clear();
|
// profiles.clear();
|
||||||
// addressBox.toMap().forEach((key, value) {
|
// addressBox.toMap().forEach((key, value) {
|
||||||
|
@ -76,7 +84,7 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||||
// });
|
// });
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: Colors.orange[200],
|
backgroundColor: yellowC,
|
||||||
body: profileTiles(context, refresh),
|
body: profileTiles(context, refresh),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -85,9 +93,6 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||||
Widget profileTiles(BuildContext context, refresh) {
|
Widget profileTiles(BuildContext context, refresh) {
|
||||||
final double screenWidth = MediaQuery.of(context).size.width;
|
final double screenWidth = MediaQuery.of(context).size.width;
|
||||||
int nbrColumn = 9;
|
int nbrColumn = 9;
|
||||||
TextEditingController nameController = TextEditingController();
|
|
||||||
TextEditingController addressController = TextEditingController();
|
|
||||||
|
|
||||||
if (screenWidth <= 600) {
|
if (screenWidth <= 600) {
|
||||||
nbrColumn = 1;
|
nbrColumn = 1;
|
||||||
} else if (screenWidth <= 900) {
|
} else if (screenWidth <= 900) {
|
||||||
|
@ -106,120 +111,111 @@ Widget profileTiles(BuildContext context, refresh) {
|
||||||
nbrColumn = 8;
|
nbrColumn = 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
return CustomScrollView(slivers: <Widget>[
|
return Query(
|
||||||
const SliverToBoxAdapter(child: SizedBox(height: 20)),
|
options: QueryOptions(
|
||||||
SliverToBoxAdapter(
|
document: gql(showAll ? getAllAccountsQ : getAllIdentitiesQ),
|
||||||
child: Row(
|
// pollInterval: const Duration(seconds: 10),
|
||||||
children: [
|
),
|
||||||
const SizedBox(width: 20),
|
builder: (QueryResult resultQ,
|
||||||
SizedBox(
|
{VoidCallback? refetch, FetchMore? fetchMore}) {
|
||||||
width: 150,
|
if (resultQ.hasException) {
|
||||||
child: TextField(
|
return Text(resultQ.exception.toString());
|
||||||
controller: nameController,
|
}
|
||||||
onChanged: (a) {
|
|
||||||
canAddProfile(nameController.text, addressController.text);
|
if (resultQ.isLoading) {
|
||||||
// refresh();
|
return const Text('Loading');
|
||||||
},
|
}
|
||||||
decoration: const InputDecoration(
|
|
||||||
hintText: "Nom",
|
final List listProfiles = resultQ.data?['account'] ?? [];
|
||||||
|
|
||||||
|
return CustomScrollView(
|
||||||
|
slivers: <Widget>[
|
||||||
|
const SliverToBoxAdapter(child: SizedBox(height: 15)),
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
showAll = !showAll;
|
||||||
|
refresh();
|
||||||
|
},
|
||||||
|
child: Row(children: [
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Icon(
|
||||||
|
showAll ? Icons.check_box : Icons.check_box_outline_blank,
|
||||||
|
color: orangeC,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text(
|
||||||
|
'Afficher tous les comptes existants',
|
||||||
|
style: TextStyle(fontSize: 16, color: Colors.grey[700]),
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
const SliverToBoxAdapter(child: SizedBox(height: 15)),
|
||||||
),
|
SliverGrid.count(
|
||||||
const SizedBox(width: 20),
|
key: const Key('listWallets'),
|
||||||
SizedBox(
|
crossAxisCount: nbrColumn,
|
||||||
width: 470,
|
childAspectRatio: 1,
|
||||||
child: TextField(
|
crossAxisSpacing: 0,
|
||||||
controller: addressController,
|
mainAxisSpacing: 0,
|
||||||
onChanged: (a) {
|
children: <Widget>[
|
||||||
canAddProfile(nameController.text, addressController.text);
|
for (Map profile in listProfiles)
|
||||||
// refresh();
|
Container(
|
||||||
},
|
color: yellowC,
|
||||||
decoration: const InputDecoration(
|
child: Padding(
|
||||||
hintText: "Adresse",
|
padding: const EdgeInsets.all(0),
|
||||||
),
|
child: Container(
|
||||||
),
|
// color: Colors.orange[100],
|
||||||
),
|
decoration: BoxDecoration(
|
||||||
const SizedBox(width: 20),
|
// shape: BoxShape.circle,
|
||||||
IconButton(
|
gradient: RadialGradient(
|
||||||
icon: const Icon(Icons.add_reaction_outlined),
|
radius: 1.3,
|
||||||
onPressed: (() async {
|
colors: [
|
||||||
if (canAdd) {
|
// yellowC,
|
||||||
await addProfile(nameController.text, addressController.text);
|
Colors.white,
|
||||||
refresh();
|
Colors.orange[300]!,
|
||||||
canAdd = false;
|
],
|
||||||
}
|
),
|
||||||
}),
|
),
|
||||||
),
|
child: Padding(
|
||||||
const Spacer(),
|
padding: const EdgeInsets.all(10),
|
||||||
const Text(
|
child: tile(
|
||||||
'Annuaire ĞDev',
|
context,
|
||||||
style: TextStyle(fontSize: 20),
|
profile['id'],
|
||||||
),
|
profile['identity']?['name'] ?? '',
|
||||||
const SizedBox(width: 80),
|
profile['identity']?['validated_at'] == null
|
||||||
],
|
? false
|
||||||
)),
|
: true),
|
||||||
const SliverToBoxAdapter(child: SizedBox(height: 30)),
|
),
|
||||||
FutureBuilder(
|
|
||||||
future: getDbData(),
|
|
||||||
builder: (context, AsyncSnapshot<List>? snapshot) {
|
|
||||||
List result = snapshot?.data ?? [];
|
|
||||||
result.sort();
|
|
||||||
return SliverGrid.count(
|
|
||||||
key: const Key('listWallets'),
|
|
||||||
crossAxisCount: nbrColumn,
|
|
||||||
childAspectRatio: 1,
|
|
||||||
crossAxisSpacing: 0,
|
|
||||||
mainAxisSpacing: 0,
|
|
||||||
children: <Widget>[
|
|
||||||
for (String profile in result)
|
|
||||||
Container(
|
|
||||||
color: Colors.orange[200],
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(10),
|
|
||||||
child: Container(
|
|
||||||
color: Colors.orange[100],
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(10),
|
|
||||||
child: tile(context, profile.split(':')[0],
|
|
||||||
profile.split(':')[1], refresh),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
]);
|
),
|
||||||
},
|
],
|
||||||
),
|
);
|
||||||
]);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget tile(BuildContext context, String name, String address, refresh) {
|
Widget tile(BuildContext context, String address, String name, bool isMember) {
|
||||||
int lastTap = DateTime.now().millisecondsSinceEpoch;
|
|
||||||
int consecutiveTaps = 0;
|
|
||||||
return Container(
|
return Container(
|
||||||
color: Colors.orange[50],
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.orange[50],
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
|
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () {
|
onTap: () {},
|
||||||
int now = DateTime.now().millisecondsSinceEpoch;
|
|
||||||
if (now - lastTap < 400) {
|
|
||||||
consecutiveTaps++;
|
|
||||||
if (consecutiveTaps > 8) {
|
|
||||||
print("DELETE PROFILE");
|
|
||||||
deleteAddress(address, refresh);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
consecutiveTaps = 0;
|
|
||||||
}
|
|
||||||
lastTap = now;
|
|
||||||
},
|
|
||||||
child: Text(
|
child: Text(
|
||||||
name,
|
name,
|
||||||
style:
|
style: TextStyle(
|
||||||
const TextStyle(fontWeight: FontWeight.w600, fontSize: 17),
|
fontWeight: FontWeight.w600,
|
||||||
|
fontSize: 17,
|
||||||
|
color: isMember ? orangeC : Colors.black),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 3),
|
const SizedBox(height: 3),
|
||||||
|
@ -262,52 +258,10 @@ snackCopyKey(BuildContext context, String address) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future addProfile(String name, String address) async {
|
|
||||||
final newProfile = <String, dynamic>{"name": name, "address": address};
|
|
||||||
|
|
||||||
db.collection("profiles").add(newProfile).then((DocumentReference doc) =>
|
|
||||||
print('DocumentSnapshot added with ID: ${doc.id}'));
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List> getDbData() async {
|
Future<List> getDbData() async {
|
||||||
await db.collection("profiles").get().then((event) {
|
|
||||||
profiles.clear();
|
|
||||||
for (var doc in event.docs) {
|
|
||||||
final name = doc.data()['name'];
|
|
||||||
final address = doc.data()['address'];
|
|
||||||
profiles.add('$name:$address');
|
|
||||||
print("${doc.id} => ${doc.data()}");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return profiles;
|
return profiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future eraseDB() async {
|
|
||||||
var collection = FirebaseFirestore.instance.collection('profiles');
|
|
||||||
var snapshots = await collection.get();
|
|
||||||
for (var doc in snapshots.docs) {
|
|
||||||
await doc.reference.delete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future deleteAddress(String address, refresh) async {
|
|
||||||
await db.collection("profiles").get().then((event) async {
|
|
||||||
profiles.clear();
|
|
||||||
for (var doc in event.docs) {
|
|
||||||
if (address == doc.data()['address']) {
|
|
||||||
await FirebaseFirestore.instance
|
|
||||||
.runTransaction((Transaction myTransaction) async {
|
|
||||||
myTransaction.delete(doc.reference);
|
|
||||||
});
|
|
||||||
refresh();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// await doc.reference.delete();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isAddress(address) {
|
bool isAddress(address) {
|
||||||
final RegExp regExp = RegExp(
|
final RegExp regExp = RegExp(
|
||||||
r'^[a-zA-Z0-9]+$',
|
r'^[a-zA-Z0-9]+$',
|
||||||
|
@ -327,17 +281,8 @@ bool isAddress(address) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool canAddProfile(String name, String address) {
|
// Colors
|
||||||
if (name == '' || !isAddress(address)) {
|
Color orangeC = const Color(0xffd07316);
|
||||||
return false;
|
Color yellowC = const Color(0xffFFD68E);
|
||||||
}
|
Color floattingYellow = const Color(0xffEFEFBF);
|
||||||
|
Color backgroundColor = const Color(0xFFF5F5F5);
|
||||||
for (var profile in profiles) {
|
|
||||||
final nameStored = profile.split(':')[0];
|
|
||||||
final addressStored = profile.split(':')[1];
|
|
||||||
if (address == addressStored || name == nameStored) return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
canAdd = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,105 @@
|
||||||
|
const String getAllAccountsQ = r'''
|
||||||
|
query {
|
||||||
|
account(order_by: {identity: {name: asc}}) {
|
||||||
|
id
|
||||||
|
identity {
|
||||||
|
name
|
||||||
|
validated_at
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
const String getAllIdentitiesQ = r'''
|
||||||
|
query {
|
||||||
|
account(where: {identity: {name: {_is_null: false}}}, order_by: {identity: {name: asc}}) {
|
||||||
|
id
|
||||||
|
identity {
|
||||||
|
name
|
||||||
|
validated_at
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
const String getNameByAddressQ = r'''
|
||||||
|
query ($address: String!) {
|
||||||
|
account_by_pk(id: $address) {
|
||||||
|
identity {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
const String searchAddressByNameQ = r'''
|
||||||
|
query ($name: String!) {
|
||||||
|
search_identity(args: {name: $name}) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
const String getHistoryByAddressQ = r'''
|
||||||
|
query ($address: String!) {
|
||||||
|
account_by_pk(id: "5CQ8T4qpbYJq7uVsxGPQ5q2df7x3Wa4aRY6HUWMBYjfLZhnn") {
|
||||||
|
transactions_issued {
|
||||||
|
receiver_id
|
||||||
|
amount
|
||||||
|
created_at
|
||||||
|
created_on
|
||||||
|
}
|
||||||
|
transactions_received {
|
||||||
|
issuer_id
|
||||||
|
amount
|
||||||
|
created_at
|
||||||
|
created_on
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
const String getHistoryByAddressQ2 = r'''
|
||||||
|
query ($address: String!) {
|
||||||
|
{
|
||||||
|
transaction(where: {_or: [{issuer_id: {_eq: $address}},
|
||||||
|
{receiver_id: {_eq: $address}}]}, order_by: {created_at: desc})
|
||||||
|
{
|
||||||
|
amount
|
||||||
|
created_at
|
||||||
|
issuer_id
|
||||||
|
receiver_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
const String getHistoryByAddressQ3 = r'''
|
||||||
|
query ($address: String!) {
|
||||||
|
transaction_connection(where:
|
||||||
|
{_or: [
|
||||||
|
{issuer_id: {_eq: $address}},
|
||||||
|
{receiver_id: {_eq: $address}}
|
||||||
|
]},
|
||||||
|
order_by: {created_at: desc}) {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
amount
|
||||||
|
created_at
|
||||||
|
issuer_id
|
||||||
|
receiver_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pageInfo {
|
||||||
|
endCursor
|
||||||
|
hasNextPage
|
||||||
|
hasPreviousPage
|
||||||
|
startCursor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
// To parse indexer date format
|
||||||
|
// log.d(DateTime.parse("2022-06-13T16:51:24.001+00:00").toString());
|
|
@ -5,14 +5,10 @@
|
||||||
import FlutterMacOS
|
import FlutterMacOS
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
import cloud_firestore
|
import connectivity_plus_macos
|
||||||
import firebase_auth
|
|
||||||
import firebase_core
|
|
||||||
import path_provider_macos
|
import path_provider_macos
|
||||||
|
|
||||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||||
FLTFirebaseFirestorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseFirestorePlugin"))
|
ConnectivityPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlugin"))
|
||||||
FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin"))
|
|
||||||
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
|
|
||||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||||
}
|
}
|
||||||
|
|
252
pubspec.lock
252
pubspec.lock
|
@ -1,6 +1,13 @@
|
||||||
# Generated by pub
|
# Generated by pub
|
||||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
packages:
|
packages:
|
||||||
|
args:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: args
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.1"
|
||||||
async:
|
async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -36,27 +43,6 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0"
|
version: "1.1.0"
|
||||||
cloud_firestore:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: cloud_firestore
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "3.1.17"
|
|
||||||
cloud_firestore_platform_interface:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: cloud_firestore_platform_interface
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "5.5.7"
|
|
||||||
cloud_firestore_web:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: cloud_firestore_web
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "2.6.16"
|
|
||||||
collection:
|
collection:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -64,6 +50,48 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.16.0"
|
version: "1.16.0"
|
||||||
|
connectivity_plus:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: connectivity_plus
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.4"
|
||||||
|
connectivity_plus_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: connectivity_plus_linux
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.1"
|
||||||
|
connectivity_plus_macos:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: connectivity_plus_macos
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.3"
|
||||||
|
connectivity_plus_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: connectivity_plus_platform_interface
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.1"
|
||||||
|
connectivity_plus_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: connectivity_plus_web
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.2"
|
||||||
|
connectivity_plus_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: connectivity_plus_windows
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.2"
|
||||||
crypto:
|
crypto:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -78,6 +106,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.5"
|
version: "1.0.5"
|
||||||
|
dbus:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: dbus
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.7.5"
|
||||||
fake_async:
|
fake_async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -99,53 +134,18 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.1.2"
|
version: "6.1.2"
|
||||||
firebase_auth:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: firebase_auth
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "3.3.19"
|
|
||||||
firebase_auth_platform_interface:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: firebase_auth_platform_interface
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "6.2.7"
|
|
||||||
firebase_auth_web:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: firebase_auth_web
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "3.3.16"
|
|
||||||
firebase_core:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: firebase_core
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "1.17.1"
|
|
||||||
firebase_core_platform_interface:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: firebase_core_platform_interface
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "4.4.0"
|
|
||||||
firebase_core_web:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: firebase_core_web
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "1.6.4"
|
|
||||||
flutter:
|
flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
flutter_hooks:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_hooks
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.18.4"
|
||||||
flutter_lints:
|
flutter_lints:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
|
@ -163,6 +163,69 @@ packages:
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
gql:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: gql
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.13.1"
|
||||||
|
gql_dedupe_link:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: gql_dedupe_link
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.2"
|
||||||
|
gql_error_link:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: gql_error_link
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.2"
|
||||||
|
gql_exec:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: gql_exec
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.4.0"
|
||||||
|
gql_http_link:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: gql_http_link
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.4.2"
|
||||||
|
gql_link:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: gql_link
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.4.2"
|
||||||
|
gql_transform_link:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: gql_transform_link
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.2"
|
||||||
|
graphql:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: graphql
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "5.1.2-beta.2"
|
||||||
|
graphql_flutter:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: graphql_flutter
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "5.1.1-beta.3"
|
||||||
hive:
|
hive:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -177,6 +240,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0"
|
version: "1.1.0"
|
||||||
|
http:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.13.4"
|
||||||
http_parser:
|
http_parser:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -184,13 +254,6 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.1"
|
version: "4.0.1"
|
||||||
intl:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: intl
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "0.17.0"
|
|
||||||
js:
|
js:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -226,6 +289,20 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.7.0"
|
version: "1.7.0"
|
||||||
|
nm:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: nm
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.5.0"
|
||||||
|
normalize:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: normalize
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.6.0+1"
|
||||||
path:
|
path:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -282,6 +359,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.0"
|
version: "2.1.0"
|
||||||
|
petitparser:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: petitparser
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "5.0.0"
|
||||||
platform:
|
platform:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -319,6 +403,13 @@ packages:
|
||||||
url: "https://github.com/insinfo/qr.flutter.git"
|
url: "https://github.com/insinfo/qr.flutter.git"
|
||||||
source: git
|
source: git
|
||||||
version: "4.0.0"
|
version: "4.0.0"
|
||||||
|
rxdart:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: rxdart
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.27.4"
|
||||||
sky_engine:
|
sky_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
|
@ -380,6 +471,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.1"
|
version: "1.3.1"
|
||||||
|
uuid:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: uuid
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.6"
|
||||||
vector_math:
|
vector_math:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -387,6 +485,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.2"
|
version: "2.1.2"
|
||||||
|
web_socket_channel:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web_socket_channel
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.0"
|
||||||
win32:
|
win32:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -401,6 +506,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.0+1"
|
version: "0.2.0+1"
|
||||||
|
xml:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: xml
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "6.1.0"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=2.17.1 <3.0.0"
|
dart: ">=2.17.1 <3.0.0"
|
||||||
flutter: ">=3.0.0"
|
flutter: ">=3.0.0"
|
||||||
|
|
|
@ -2,7 +2,7 @@ name: gdev_annuaire
|
||||||
description: gdev_annuaire
|
description: gdev_annuaire
|
||||||
|
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
version: 0.0.0+1
|
version: 0.0.0+2
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.17.1 <3.0.0"
|
sdk: ">=2.17.1 <3.0.0"
|
||||||
|
@ -10,6 +10,7 @@ environment:
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
cupertino_icons: ^1.0.2
|
||||||
qr_flutter: #^4.0.0
|
qr_flutter: #^4.0.0
|
||||||
git:
|
git:
|
||||||
url: https://github.com/insinfo/qr.flutter.git
|
url: https://github.com/insinfo/qr.flutter.git
|
||||||
|
@ -17,11 +18,7 @@ dependencies:
|
||||||
truncate: ^3.0.1
|
truncate: ^3.0.1
|
||||||
hive: ^2.0.4
|
hive: ^2.0.4
|
||||||
hive_flutter: ^1.1.0
|
hive_flutter: ^1.1.0
|
||||||
|
graphql_flutter: ^5.1.1-beta.3
|
||||||
cupertino_icons: ^1.0.2
|
|
||||||
firebase_core: ^1.17.1
|
|
||||||
cloud_firestore: ^3.1.17
|
|
||||||
firebase_auth: ^3.3.19
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
// This is a basic Flutter widget test.
|
|
||||||
//
|
|
||||||
// To perform an interaction with a widget in your test, use the WidgetTester
|
|
||||||
// utility in the flutter_test package. For example, you can send tap and scroll
|
|
||||||
// gestures. You can also use WidgetTester to find child widgets in the widget
|
|
||||||
// tree, read text, and verify that the values of widget properties are correct.
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
|
|
||||||
import 'package:gdev_annuaire/main.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
|
||||||
// Build our app and trigger a frame.
|
|
||||||
await tester.pumpWidget(const MyApp());
|
|
||||||
|
|
||||||
// Verify that our counter starts at 0.
|
|
||||||
expect(find.text('0'), findsOneWidget);
|
|
||||||
expect(find.text('1'), findsNothing);
|
|
||||||
|
|
||||||
// Tap the '+' icon and trigger a frame.
|
|
||||||
await tester.tap(find.byIcon(Icons.add));
|
|
||||||
await tester.pump();
|
|
||||||
|
|
||||||
// Verify that our counter has incremented.
|
|
||||||
expect(find.text('0'), findsNothing);
|
|
||||||
expect(find.text('1'), findsOneWidget);
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -6,6 +6,9 @@
|
||||||
|
|
||||||
#include "generated_plugin_registrant.h"
|
#include "generated_plugin_registrant.h"
|
||||||
|
|
||||||
|
#include <connectivity_plus_windows/connectivity_plus_windows_plugin.h>
|
||||||
|
|
||||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||||
|
ConnectivityPlusWindowsPluginRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin"));
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
|
connectivity_plus_windows
|
||||||
)
|
)
|
||||||
|
|
||||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||||
|
|
Loading…
Reference in New Issue