mirror of
https://github.com/zapstore/zapstore.git
synced 2026-10-05 20:48:24 +00:00
Demo WIP
This commit is contained in:
+1
-1
@@ -1 +1 @@
|
||||
/home/admin/fvm/versions/3.16.0
|
||||
/home/admin/fvm/versions/3.16.9
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"flutterSdkVersion": "3.16.0",
|
||||
"flutterSdkVersion": "3.16.9",
|
||||
"flavors": {}
|
||||
}
|
||||
@@ -1,16 +1,9 @@
|
||||
# zapstore
|
||||
|
||||
A new Flutter project.
|
||||
|
||||
## Getting Started
|
||||
|
||||
This project is a starting point for a Flutter application.
|
||||
|
||||
A few resources to get you started if this is your first Flutter project:
|
||||
|
||||
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
|
||||
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
|
||||
|
||||
For help getting started with Flutter development, view the
|
||||
[online documentation](https://docs.flutter.dev/), which offers tutorials,
|
||||
samples, guidance on mobile development, and a full API reference.
|
||||
- test on macos
|
||||
- drop file
|
||||
- compute sha256 and query nostr by `x`
|
||||
- install button
|
||||
- downloads file (to whwatever the downloads provider is)
|
||||
- checks sha256 with crypto and updates statenotifier bool state
|
||||
- display if matches (green) or not (red)
|
||||
@@ -1,78 +0,0 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:dart_nostr/dart_nostr.dart';
|
||||
import 'package:flutter_data/flutter_data.dart';
|
||||
import 'package:zapstore/models/base_event.dart';
|
||||
|
||||
mixin NostrAdapter<T extends BaseEvent<T>> on RemoteAdapter<T> {
|
||||
late final Nostr nostr;
|
||||
|
||||
@override
|
||||
Future<void> onInitialized() async {
|
||||
nostr = await ref.read(nostrProvider.future);
|
||||
super.onInitialized();
|
||||
}
|
||||
|
||||
void update(List<NostrFilter> filters) async {
|
||||
final req = NostrRequest(filters: filters);
|
||||
await Nostr.instance.relaysService.startEventsSubscriptionAsync(
|
||||
request: req,
|
||||
timeout: Duration(seconds: 10),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
DataStateNotifier<List<T>> watchAllNotifier(
|
||||
{bool? remote,
|
||||
Map<String, dynamic>? params,
|
||||
Map<String, String>? headers,
|
||||
bool? syncLocal,
|
||||
String? finder,
|
||||
DataRequestLabel? label}) {
|
||||
late final StreamSubscription _sub;
|
||||
_sub = Nostr.instance.relaysService.streamsController.events
|
||||
.listen((NostrEvent event) {
|
||||
final baseEvent = localAdapter.deserialize({
|
||||
'id': event.id,
|
||||
'kind': event.kind,
|
||||
'content': event.content,
|
||||
'sig': event.sig,
|
||||
'pubkey': event.pubkey,
|
||||
'created_at': event.createdAt.toIso8601String(),
|
||||
'tags': event.tags,
|
||||
});
|
||||
baseEvent.saveLocal();
|
||||
});
|
||||
|
||||
ref.onDispose(() {
|
||||
print('disposing nostr sub');
|
||||
_sub.cancel();
|
||||
});
|
||||
|
||||
return super
|
||||
.watchAllNotifier(
|
||||
remote: remote,
|
||||
params: params,
|
||||
headers: headers,
|
||||
syncLocal: syncLocal,
|
||||
finder: finder,
|
||||
label: label)
|
||||
.where((e) {
|
||||
final search = params?['search'];
|
||||
if (search != null && search.isNotEmpty) {
|
||||
return e.content.contains(search);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
final nostrProvider = FutureProvider<Nostr>((Ref ref) async {
|
||||
final relays = ['wss://relay.nostr.band'];
|
||||
print('***** initing nostr ********');
|
||||
await Nostr.instance.relaysService.init(
|
||||
relaysUrl: relays,
|
||||
);
|
||||
Nostr.instance.utilsService.utils.disableLogs();
|
||||
return Nostr.instance;
|
||||
});
|
||||
+11
-2
@@ -3,6 +3,7 @@ import 'package:bitsdojo_window/bitsdojo_window.dart';
|
||||
import 'package:flutter_data/flutter_data.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:ndk/ndk.dart';
|
||||
import 'package:zapstore/main.data.dart';
|
||||
import 'package:zapstore/screens/profile_screen.dart';
|
||||
import 'package:zapstore/screens/search_screen.dart';
|
||||
@@ -12,7 +13,8 @@ void main() {
|
||||
runApp(
|
||||
ProviderScope(
|
||||
overrides: [
|
||||
configureRepositoryLocalStorage(clear: LocalStorageClearStrategy.never),
|
||||
configureRepositoryLocalStorage(
|
||||
clear: LocalStorageClearStrategy.always),
|
||||
],
|
||||
child: const ZapStoreApp(),
|
||||
),
|
||||
@@ -31,12 +33,19 @@ void main() {
|
||||
|
||||
const borderColor = Color(0xFF805306);
|
||||
|
||||
final newInitializer = FutureProvider<void>((ref) async {
|
||||
await ref.read(repositoryInitializerProvider.future);
|
||||
await ref
|
||||
.read(frameProvider.notifier)
|
||||
.initialize({'wss://relay.nostr.band', 'wss://relay.damus.io'});
|
||||
});
|
||||
|
||||
class ZapStoreApp extends HookConsumerWidget {
|
||||
const ZapStoreApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final initializer = ref.watch(repositoryInitializerProvider);
|
||||
final initializer = ref.watch(newInitializer);
|
||||
return MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
theme: ThemeData.dark(),
|
||||
|
||||
+3
-13
@@ -8,8 +8,6 @@ import 'package:flutter/foundation.dart' show kIsWeb;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:zapstore/models/metadata.dart';
|
||||
import 'package:zapstore/models/note.dart';
|
||||
import 'package:zapstore/models/user.dart';
|
||||
|
||||
// ignore: prefer_function_declarations_over_variables
|
||||
@@ -31,18 +29,14 @@ ConfigureRepositoryLocalStorage configureRepositoryLocalStorage = ({FutureFn<Str
|
||||
};
|
||||
|
||||
final repositoryProviders = <String, Provider<Repository<DataModelMixin>>>{
|
||||
'fileMetadata': fileMetadataRepositoryProvider,
|
||||
'notes': notesRepositoryProvider,
|
||||
'users': usersRepositoryProvider
|
||||
'users': usersRepositoryProvider
|
||||
};
|
||||
|
||||
final repositoryInitializerProvider =
|
||||
FutureProvider<RepositoryInitializer>((ref) async {
|
||||
DataHelpers.setInternalType<FileMetadata>('fileMetadata');
|
||||
DataHelpers.setInternalType<Note>('notes');
|
||||
DataHelpers.setInternalType<User>('users');
|
||||
final adapters = <String, RemoteAdapter>{'fileMetadata': ref.watch(internalFileMetadataRemoteAdapterProvider), 'notes': ref.watch(internalNotesRemoteAdapterProvider), 'users': ref.watch(internalUsersRemoteAdapterProvider)};
|
||||
final remotes = <String, bool>{'fileMetadata': true, 'notes': true, 'users': true};
|
||||
final adapters = <String, RemoteAdapter>{'users': ref.watch(internalUsersRemoteAdapterProvider)};
|
||||
final remotes = <String, bool>{'users': true};
|
||||
|
||||
await ref.watch(graphNotifierProvider).initialize();
|
||||
|
||||
@@ -60,14 +54,10 @@ final repositoryInitializerProvider =
|
||||
return RepositoryInitializer();
|
||||
});
|
||||
extension RepositoryWidgetRefX on WidgetRef {
|
||||
Repository<FileMetadata> get fileMetadata => watch(fileMetadataRepositoryProvider)..remoteAdapter.internalWatch = watch;
|
||||
Repository<Note> get notes => watch(notesRepositoryProvider)..remoteAdapter.internalWatch = watch;
|
||||
Repository<User> get users => watch(usersRepositoryProvider)..remoteAdapter.internalWatch = watch;
|
||||
}
|
||||
|
||||
extension RepositoryRefX on Ref {
|
||||
|
||||
Repository<FileMetadata> get fileMetadata => watch(fileMetadataRepositoryProvider)..remoteAdapter.internalWatch = watch as Watcher;
|
||||
Repository<Note> get notes => watch(notesRepositoryProvider)..remoteAdapter.internalWatch = watch as Watcher;
|
||||
Repository<User> get users => watch(usersRepositoryProvider)..remoteAdapter.internalWatch = watch as Watcher;
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:dart_nostr/dart_nostr.dart';
|
||||
import 'package:flutter_data/flutter_data.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
abstract class BaseEvent<T extends BaseEvent<T>> extends DataModel<T> {
|
||||
@override
|
||||
final String id;
|
||||
|
||||
final NostrEvent _event;
|
||||
String get pubkey => _event.pubkey;
|
||||
int get kind => _event.kind;
|
||||
String get content => _event.content;
|
||||
String get sig => _event.sig;
|
||||
@JsonKey(name: 'created_at')
|
||||
DateTime get createdAt => _event.createdAt;
|
||||
List<List<String>> get tags => _event.tags;
|
||||
|
||||
Map<String, List<String>> get tagMap {
|
||||
final f2 = _event.tags.groupFoldBy<String, List<String>>(
|
||||
(e) => e.first, (acc, e) => [...?acc, e[1]]);
|
||||
return f2;
|
||||
}
|
||||
|
||||
BaseEvent(
|
||||
{required this.id,
|
||||
required String pubkey,
|
||||
required DateTime createdAt,
|
||||
required int kind,
|
||||
List<List<String>> tags = const [],
|
||||
required String content,
|
||||
required String sig})
|
||||
: _event = NostrEvent(
|
||||
id: id,
|
||||
pubkey: pubkey,
|
||||
createdAt: createdAt,
|
||||
kind: kind,
|
||||
tags: tags,
|
||||
content: content,
|
||||
sig: sig,
|
||||
ots: null,
|
||||
);
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
import 'package:dart_nostr/dart_nostr.dart';
|
||||
import 'package:flutter_data/flutter_data.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:zapstore/adapters/nostr_adapter.dart';
|
||||
import 'package:zapstore/models/base_event.dart';
|
||||
|
||||
part 'metadata.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
@DataRepository([NostrAdapter])
|
||||
class FileMetadata extends BaseEvent<FileMetadata> {
|
||||
String get url => tagMap['url']!.first;
|
||||
String get mimeType => tagMap['m']!.first;
|
||||
String get sha256 => tagMap['x']!.first;
|
||||
|
||||
FileMetadata(
|
||||
{required super.id,
|
||||
required super.pubkey,
|
||||
required super.createdAt,
|
||||
required super.content,
|
||||
required super.sig,
|
||||
super.tags})
|
||||
: super(kind: 1063);
|
||||
}
|
||||
|
||||
class FileMetadataFilter extends NostrFilter {
|
||||
final List<String>? m;
|
||||
FileMetadataFilter(
|
||||
{super.ids,
|
||||
super.authors,
|
||||
super.e,
|
||||
super.p,
|
||||
this.m,
|
||||
super.since,
|
||||
super.until,
|
||||
super.limit,
|
||||
super.search})
|
||||
: super(kinds: [1063]);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toMap() {
|
||||
return {'#m': m, ...super.toMap()};
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'metadata.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RepositoryGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// ignore_for_file: non_constant_identifier_names, duplicate_ignore
|
||||
|
||||
mixin $FileMetadataLocalAdapter on LocalAdapter<FileMetadata> {
|
||||
static final Map<String, RelationshipMeta> _kFileMetadataRelationshipMetas =
|
||||
{};
|
||||
|
||||
@override
|
||||
Map<String, RelationshipMeta> get relationshipMetas =>
|
||||
_kFileMetadataRelationshipMetas;
|
||||
|
||||
@override
|
||||
FileMetadata deserialize(map) {
|
||||
map = transformDeserialize(map);
|
||||
return _$FileMetadataFromJson(map);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> serialize(model, {bool withRelationships = true}) {
|
||||
final map = _$FileMetadataToJson(model);
|
||||
return transformSerialize(map, withRelationships: withRelationships);
|
||||
}
|
||||
}
|
||||
|
||||
final _fileMetadataFinders = <String, dynamic>{};
|
||||
|
||||
// ignore: must_be_immutable
|
||||
class $FileMetadataHiveLocalAdapter = HiveLocalAdapter<FileMetadata>
|
||||
with $FileMetadataLocalAdapter;
|
||||
|
||||
class $FileMetadataRemoteAdapter = RemoteAdapter<FileMetadata>
|
||||
with NostrAdapter<FileMetadata>;
|
||||
|
||||
final internalFileMetadataRemoteAdapterProvider =
|
||||
Provider<RemoteAdapter<FileMetadata>>((ref) => $FileMetadataRemoteAdapter(
|
||||
$FileMetadataHiveLocalAdapter(ref),
|
||||
InternalHolder(_fileMetadataFinders)));
|
||||
|
||||
final fileMetadataRepositoryProvider =
|
||||
Provider<Repository<FileMetadata>>((ref) => Repository<FileMetadata>(ref));
|
||||
|
||||
extension FileMetadataDataRepositoryX on Repository<FileMetadata> {
|
||||
NostrAdapter<FileMetadata> get nostrAdapter =>
|
||||
remoteAdapter as NostrAdapter<FileMetadata>;
|
||||
}
|
||||
|
||||
extension FileMetadataRelationshipGraphNodeX
|
||||
on RelationshipGraphNode<FileMetadata> {}
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
FileMetadata _$FileMetadataFromJson(Map<String, dynamic> json) => FileMetadata(
|
||||
id: json['id'] as String,
|
||||
pubkey: json['pubkey'] as String,
|
||||
createdAt: DateTime.parse(json['created_at'] as String),
|
||||
content: json['content'] as String,
|
||||
sig: json['sig'] as String,
|
||||
tags: (json['tags'] as List<dynamic>?)
|
||||
?.map(
|
||||
(e) => (e as List<dynamic>).map((e) => e as String).toList())
|
||||
.toList() ??
|
||||
const [],
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$FileMetadataToJson(FileMetadata instance) =>
|
||||
<String, dynamic>{
|
||||
'id': instance.id,
|
||||
'pubkey': instance.pubkey,
|
||||
'content': instance.content,
|
||||
'sig': instance.sig,
|
||||
'created_at': instance.createdAt.toIso8601String(),
|
||||
'tags': instance.tags,
|
||||
};
|
||||
@@ -1,19 +0,0 @@
|
||||
import 'package:flutter_data/flutter_data.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:zapstore/adapters/nostr_adapter.dart';
|
||||
import 'package:zapstore/models/base_event.dart';
|
||||
|
||||
part 'note.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
@DataRepository([NostrAdapter])
|
||||
class Note extends BaseEvent<Note> {
|
||||
Note(
|
||||
{required super.id,
|
||||
required super.pubkey,
|
||||
required super.createdAt,
|
||||
required super.content,
|
||||
required super.sig,
|
||||
super.tags})
|
||||
: super(kind: 1);
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'note.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RepositoryGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// ignore_for_file: non_constant_identifier_names, duplicate_ignore
|
||||
|
||||
mixin $NoteLocalAdapter on LocalAdapter<Note> {
|
||||
static final Map<String, RelationshipMeta> _kNoteRelationshipMetas = {};
|
||||
|
||||
@override
|
||||
Map<String, RelationshipMeta> get relationshipMetas =>
|
||||
_kNoteRelationshipMetas;
|
||||
|
||||
@override
|
||||
Note deserialize(map) {
|
||||
map = transformDeserialize(map);
|
||||
return _$NoteFromJson(map);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> serialize(model, {bool withRelationships = true}) {
|
||||
final map = _$NoteToJson(model);
|
||||
return transformSerialize(map, withRelationships: withRelationships);
|
||||
}
|
||||
}
|
||||
|
||||
final _notesFinders = <String, dynamic>{};
|
||||
|
||||
// ignore: must_be_immutable
|
||||
class $NoteHiveLocalAdapter = HiveLocalAdapter<Note> with $NoteLocalAdapter;
|
||||
|
||||
class $NoteRemoteAdapter = RemoteAdapter<Note> with NostrAdapter<Note>;
|
||||
|
||||
final internalNotesRemoteAdapterProvider = Provider<RemoteAdapter<Note>>(
|
||||
(ref) => $NoteRemoteAdapter(
|
||||
$NoteHiveLocalAdapter(ref), InternalHolder(_notesFinders)));
|
||||
|
||||
final notesRepositoryProvider =
|
||||
Provider<Repository<Note>>((ref) => Repository<Note>(ref));
|
||||
|
||||
extension NoteDataRepositoryX on Repository<Note> {
|
||||
NostrAdapter<Note> get nostrAdapter => remoteAdapter as NostrAdapter<Note>;
|
||||
}
|
||||
|
||||
extension NoteRelationshipGraphNodeX on RelationshipGraphNode<Note> {}
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
Note _$NoteFromJson(Map<String, dynamic> json) => Note(
|
||||
id: json['id'] as String,
|
||||
pubkey: json['pubkey'] as String,
|
||||
createdAt: DateTime.parse(json['created_at'] as String),
|
||||
content: json['content'] as String,
|
||||
sig: json['sig'] as String,
|
||||
tags: (json['tags'] as List<dynamic>?)
|
||||
?.map(
|
||||
(e) => (e as List<dynamic>).map((e) => e as String).toList())
|
||||
.toList() ??
|
||||
const [],
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$NoteToJson(Note instance) => <String, dynamic>{
|
||||
'id': instance.id,
|
||||
'pubkey': instance.pubkey,
|
||||
'content': instance.content,
|
||||
'sig': instance.sig,
|
||||
'created_at': instance.createdAt.toIso8601String(),
|
||||
'tags': instance.tags,
|
||||
};
|
||||
+35
-4
@@ -1,13 +1,44 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter_data/flutter_data.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'user.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
@DataRepository([])
|
||||
class User extends DataModel<User> {
|
||||
@DataRepository([UserAdapter])
|
||||
class User extends DataModel<User> with EquatableMixin {
|
||||
final String id;
|
||||
final String name;
|
||||
String? name;
|
||||
String? pictureUrl;
|
||||
String? nip05;
|
||||
@DataRelationship(inverse: 'followers')
|
||||
final HasMany<User> following;
|
||||
@DataRelationship(inverse: 'following')
|
||||
final HasMany<User> followers;
|
||||
|
||||
User({required this.id, required this.name});
|
||||
User({required this.id, HasMany<User>? following, HasMany<User>? followers})
|
||||
: this.following = following ?? HasMany(),
|
||||
this.followers = followers ?? HasMany();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [id];
|
||||
}
|
||||
|
||||
mixin UserAdapter on RemoteAdapter<User> {
|
||||
@override
|
||||
DataStateNotifier<List<User>> watchAllNotifier(
|
||||
{bool? remote,
|
||||
Map<String, dynamic>? params,
|
||||
Map<String, String>? headers,
|
||||
bool? syncLocal,
|
||||
String? finder,
|
||||
DataRequestLabel? label}) {
|
||||
return super.watchAllNotifier(
|
||||
remote: remote,
|
||||
params: params,
|
||||
headers: headers,
|
||||
syncLocal: syncLocal,
|
||||
finder: finder,
|
||||
label: label);
|
||||
}
|
||||
}
|
||||
|
||||
+49
-6
@@ -9,7 +9,22 @@ part of 'user.dart';
|
||||
// ignore_for_file: non_constant_identifier_names, duplicate_ignore
|
||||
|
||||
mixin $UserLocalAdapter on LocalAdapter<User> {
|
||||
static final Map<String, RelationshipMeta> _kUserRelationshipMetas = {};
|
||||
static final Map<String, RelationshipMeta> _kUserRelationshipMetas = {
|
||||
'following': RelationshipMeta<User>(
|
||||
name: 'following',
|
||||
inverseName: 'followers',
|
||||
type: 'users',
|
||||
kind: 'HasMany',
|
||||
instance: (_) => (_ as User).following,
|
||||
),
|
||||
'followers': RelationshipMeta<User>(
|
||||
name: 'followers',
|
||||
inverseName: 'following',
|
||||
type: 'users',
|
||||
kind: 'HasMany',
|
||||
instance: (_) => (_ as User).followers,
|
||||
)
|
||||
};
|
||||
|
||||
@override
|
||||
Map<String, RelationshipMeta> get relationshipMetas =>
|
||||
@@ -33,7 +48,7 @@ final _usersFinders = <String, dynamic>{};
|
||||
// ignore: must_be_immutable
|
||||
class $UserHiveLocalAdapter = HiveLocalAdapter<User> with $UserLocalAdapter;
|
||||
|
||||
class $UserRemoteAdapter = RemoteAdapter<User> with NothingMixin;
|
||||
class $UserRemoteAdapter = RemoteAdapter<User> with UserAdapter;
|
||||
|
||||
final internalUsersRemoteAdapterProvider = Provider<RemoteAdapter<User>>(
|
||||
(ref) => $UserRemoteAdapter(
|
||||
@@ -42,9 +57,25 @@ final internalUsersRemoteAdapterProvider = Provider<RemoteAdapter<User>>(
|
||||
final usersRepositoryProvider =
|
||||
Provider<Repository<User>>((ref) => Repository<User>(ref));
|
||||
|
||||
extension UserDataRepositoryX on Repository<User> {}
|
||||
extension UserDataRepositoryX on Repository<User> {
|
||||
UserAdapter get userAdapter => remoteAdapter as UserAdapter;
|
||||
}
|
||||
|
||||
extension UserRelationshipGraphNodeX on RelationshipGraphNode<User> {}
|
||||
extension UserRelationshipGraphNodeX on RelationshipGraphNode<User> {
|
||||
RelationshipGraphNode<User> get following {
|
||||
final meta = $UserLocalAdapter._kUserRelationshipMetas['following']
|
||||
as RelationshipMeta<User>;
|
||||
return meta.clone(
|
||||
parent: this is RelationshipMeta ? this as RelationshipMeta : null);
|
||||
}
|
||||
|
||||
RelationshipGraphNode<User> get followers {
|
||||
final meta = $UserLocalAdapter._kUserRelationshipMetas['followers']
|
||||
as RelationshipMeta<User>;
|
||||
return meta.clone(
|
||||
parent: this is RelationshipMeta ? this as RelationshipMeta : null);
|
||||
}
|
||||
}
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
@@ -52,10 +83,22 @@ extension UserRelationshipGraphNodeX on RelationshipGraphNode<User> {}
|
||||
|
||||
User _$UserFromJson(Map<String, dynamic> json) => User(
|
||||
id: json['id'] as String,
|
||||
name: json['name'] as String,
|
||||
);
|
||||
following: json['following'] == null
|
||||
? null
|
||||
: HasMany<User>.fromJson(json['following'] as Map<String, dynamic>),
|
||||
followers: json['followers'] == null
|
||||
? null
|
||||
: HasMany<User>.fromJson(json['followers'] as Map<String, dynamic>),
|
||||
)
|
||||
..name = json['name'] as String?
|
||||
..pictureUrl = json['pictureUrl'] as String?
|
||||
..nip05 = json['nip05'] as String?;
|
||||
|
||||
Map<String, dynamic> _$UserToJson(User instance) => <String, dynamic>{
|
||||
'id': instance.id,
|
||||
'name': instance.name,
|
||||
'pictureUrl': instance.pictureUrl,
|
||||
'nip05': instance.nip05,
|
||||
'following': instance.following,
|
||||
'followers': instance.followers,
|
||||
};
|
||||
|
||||
+154
-21
@@ -1,34 +1,167 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:bech32/bech32.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_data/flutter_data.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:ndk/ndk.dart';
|
||||
import 'package:zapstore/main.data.dart';
|
||||
import 'package:zapstore/models/user.dart';
|
||||
import 'package:convert/convert.dart';
|
||||
|
||||
final profileProvider =
|
||||
FutureProvider.family<User, String>((ref, pubkey) async {
|
||||
final completer = Completer<User>();
|
||||
final notifier = ref.read(frameProvider.notifier);
|
||||
notifier.send(jsonEncode([
|
||||
"REQ",
|
||||
pubkey,
|
||||
{
|
||||
'authors': [pubkey],
|
||||
'kinds': [0, 3],
|
||||
}
|
||||
]));
|
||||
|
||||
late Function _sub;
|
||||
|
||||
User? u;
|
||||
List<String>? contacts;
|
||||
_sub = ref.watch(frameProvider.notifier).addListener((frame) async {
|
||||
// print('listener: ${frame.event}');
|
||||
final event = frame.event;
|
||||
|
||||
if (event is Metadata) {
|
||||
u = (await ref.users.findOne(event.pubkey)) ?? User(id: event.pubkey);
|
||||
final map = jsonDecode(event.content);
|
||||
u!.name = map['displayName'] ?? map['display_name'] ?? map['name'];
|
||||
u!.nip05 = map['nip05'];
|
||||
}
|
||||
|
||||
if (event is ContactList) {
|
||||
contacts = [...?contacts, ...event.tagMap['p']!];
|
||||
}
|
||||
|
||||
if (u != null && contacts != null) {
|
||||
if (completer.isCompleted == false) {
|
||||
print('completing with $u');
|
||||
contacts!.forEach((c) {
|
||||
final uc = User(id: c);
|
||||
u!.following.add(uc);
|
||||
uc.saveLocal();
|
||||
});
|
||||
u!.saveLocal();
|
||||
completer.complete(u);
|
||||
}
|
||||
}
|
||||
});
|
||||
ref.onDispose(() {
|
||||
print('disposing');
|
||||
_sub();
|
||||
});
|
||||
return completer.future;
|
||||
});
|
||||
|
||||
final loggedInUser = StateProvider<User?>((ref) => null);
|
||||
final downloadsPath = StateProvider<String>((ref) => '/tmp');
|
||||
|
||||
class ProfileScreen extends HookConsumerWidget {
|
||||
const ProfileScreen({super.key});
|
||||
ProfileScreen({super.key});
|
||||
final codec = Bech32Codec();
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final watcher = ref.notes.watchAll(remote: false);
|
||||
final u2 = ref.watch(loggedInUser);
|
||||
final controller = useTextEditingController();
|
||||
final controller2 = useTextEditingController(text: ref.read(downloadsPath));
|
||||
|
||||
return Expanded(
|
||||
child: Column(
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () => {},
|
||||
child: Text('press me'),
|
||||
),
|
||||
switch (watcher) {
|
||||
DataState(isLoading: false, exception: null, model: final value) =>
|
||||
Text(value.length.toString()),
|
||||
_ => Text('loading')
|
||||
}
|
||||
],
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Column(
|
||||
children: [
|
||||
TextField(controller: controller),
|
||||
SizedBox(height: 10),
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
final pubkey = bech32Decode(controller.text);
|
||||
final uid = await ref.read(profileProvider(pubkey).future);
|
||||
ref.read(loggedInUser.notifier).state =
|
||||
await ref.users.findOne(uid, remote: false);
|
||||
},
|
||||
child: Text('Log in with npub')),
|
||||
if (u2 != null)
|
||||
Expanded(
|
||||
child: Column(
|
||||
children: [
|
||||
Text('Logged in as ${u2.name}'),
|
||||
Text('(following: ${u2.following.length})'),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
// ListView.builder(
|
||||
// shrinkWrap: true,
|
||||
// itemCount: contacts.length,
|
||||
// itemBuilder: (context, i) {
|
||||
// return Text(' - ${contacts[i].id}');
|
||||
// }),
|
||||
TextField(controller: controller2),
|
||||
SizedBox(height: 10),
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
ref.read(downloadsPath.notifier).state = controller2.text;
|
||||
},
|
||||
child: Text('Save downloads path'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
// return Column(
|
||||
// children: [
|
||||
// Text(
|
||||
// 'profile, ${Nip19.decodePubkey('npub1wf4pufsucer5va8g9p0rj5dnhvfeh6d8w0g6eayaep5dhps6rsgs43dgh9')}'),
|
||||
// Text(value.isLoading ? 'loading' : value.value!.join('\n')),
|
||||
// ],
|
||||
// );
|
||||
}
|
||||
}
|
||||
|
||||
String bech32Encode(String prefix, String hexData) {
|
||||
final data = hex.decode(hexData);
|
||||
final convertedData = convertBits(data, 8, 5, true);
|
||||
final bech32Data = Bech32(prefix, convertedData);
|
||||
return bech32.encode(bech32Data);
|
||||
}
|
||||
|
||||
String bech32Decode(String bech32Data) {
|
||||
final decodedData = bech32.decode(bech32Data);
|
||||
final convertedData = convertBits(decodedData.data, 5, 8, false);
|
||||
final hexData = hex.encode(convertedData);
|
||||
|
||||
return hexData;
|
||||
}
|
||||
|
||||
List<int> convertBits(List<int> data, int fromBits, int toBits, bool pad) {
|
||||
var acc = 0;
|
||||
var bits = 0;
|
||||
final maxv = (1 << toBits) - 1;
|
||||
final result = <int>[];
|
||||
|
||||
for (final value in data) {
|
||||
if (value < 0 || value >> fromBits != 0) {
|
||||
throw Exception('Invalid value: $value');
|
||||
}
|
||||
acc = (acc << fromBits) | value;
|
||||
bits += fromBits;
|
||||
|
||||
while (bits >= toBits) {
|
||||
bits -= toBits;
|
||||
result.add((acc >> bits) & maxv);
|
||||
}
|
||||
}
|
||||
|
||||
if (pad) {
|
||||
if (bits > 0) {
|
||||
result.add((acc << (toBits - bits)) & maxv);
|
||||
}
|
||||
} else if (bits >= fromBits || ((acc << (toBits - bits)) & maxv) != 0) {
|
||||
throw Exception('Invalid data');
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
+185
-52
@@ -1,74 +1,207 @@
|
||||
import 'package:dart_nostr/dart_nostr.dart';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:desktop_drop/desktop_drop.dart';
|
||||
import 'package:expansion_tile_card/expansion_tile_card.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_data/flutter_data.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:ndk/ndk.dart';
|
||||
import 'package:zapstore/main.data.dart';
|
||||
import 'package:zapstore/models/note.dart';
|
||||
import 'package:zapstore/models/user.dart';
|
||||
import 'package:zapstore/screens/profile_screen.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
class SearchScreen extends HookConsumerWidget {
|
||||
const SearchScreen({super.key});
|
||||
SearchScreen({super.key});
|
||||
final r = Random();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final searchState = useState<String?>(null);
|
||||
final watcher = ref.notes
|
||||
.watchAll(remote: false, params: {'search': searchState.value});
|
||||
final subscriptionIdState = useState<String?>(null);
|
||||
final value =
|
||||
ref.watch(subscriptionFrameProvider(subscriptionIdState.value));
|
||||
|
||||
return Expanded(
|
||||
child: switch (watcher) {
|
||||
DataState(isLoading: false, exception: null, model: final value) =>
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Column(
|
||||
children: [
|
||||
SearchBar(
|
||||
elevation: MaterialStatePropertyAll(2.2),
|
||||
onSubmitted: (query) {
|
||||
searchState.value = query;
|
||||
if (query.isNotEmpty) {
|
||||
ref.notes.nostrAdapter.update(
|
||||
[
|
||||
NostrFilter(
|
||||
search: query,
|
||||
since: DateTime.now().subtract(Duration(days: 1)),
|
||||
limit: 10,
|
||||
)
|
||||
],
|
||||
);
|
||||
child: DropTarget(
|
||||
onDragDone: (detail) async {
|
||||
if (detail.files.isNotEmpty) {
|
||||
final bytes = await detail.files.first.readAsBytes();
|
||||
final digest = sha256.convert(bytes).toString();
|
||||
|
||||
subscriptionIdState.value = 'sub-${digest}';
|
||||
final notifier = ref.read(frameProvider.notifier);
|
||||
notifier.send(jsonEncode([
|
||||
"REQ",
|
||||
subscriptionIdState.value!,
|
||||
{
|
||||
'kinds': [1063],
|
||||
'#x': [digest],
|
||||
}
|
||||
]));
|
||||
}
|
||||
// print(detail.files.map((e) => e.path));
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Column(
|
||||
children: [
|
||||
SearchBar(
|
||||
elevation: MaterialStatePropertyAll(2.2),
|
||||
onSubmitted: (query) {
|
||||
searchState.value = query;
|
||||
subscriptionIdState.value = 'sub-${r.nextInt(9999999)}';
|
||||
final notifier = ref.read(frameProvider.notifier);
|
||||
notifier.send(jsonEncode([
|
||||
"REQ",
|
||||
subscriptionIdState.value!,
|
||||
{
|
||||
'kinds': [1063],
|
||||
'limit': 20,
|
||||
'search': query,
|
||||
// '#m': ['video/mp4'],
|
||||
// 'since': DateTime.now()
|
||||
// .subtract(Duration(days: 1))
|
||||
// .millisecondsSinceEpoch ~/
|
||||
// 1000,
|
||||
}
|
||||
]));
|
||||
},
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(6.0),
|
||||
child: Text('Total results: ${value.length}'),
|
||||
),
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: value.length,
|
||||
itemBuilder: (context, index) {
|
||||
final event = value[index];
|
||||
return CardWidget(note: event as FileMetadata);
|
||||
},
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(6.0),
|
||||
child: Text(
|
||||
'Total results: ${value.length}, search term: ${searchState.value}'),
|
||||
),
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: value.length,
|
||||
itemBuilder: (context, index) {
|
||||
final note = value[index];
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(note.pubkey),
|
||||
Text(note.content),
|
||||
// Image.network(note.),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CardWidget extends HookConsumerWidget {
|
||||
final FileMetadata note;
|
||||
|
||||
const CardWidget({super.key, required this.note});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final authorUser = useState<User?>(null);
|
||||
final currentUser = ref.read(loggedInUser);
|
||||
|
||||
final isWebOfTrust =
|
||||
currentUser?.following.contains(authorUser.value) ?? false;
|
||||
final isSha256Ok = useState<bool?>(null);
|
||||
|
||||
return ExpansionTileCard(
|
||||
// key: cardA,
|
||||
leading: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(15.0),
|
||||
child: Image.network(
|
||||
note.tagMap['url']!.first,
|
||||
width: 80,
|
||||
height: 80,
|
||||
fit: BoxFit.cover,
|
||||
errorBuilder: (context, err, _) => Text(err.toString()),
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
note.content,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
|
||||
),
|
||||
subtitle: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Type: ${note.tagMap['m']?.first}'),
|
||||
],
|
||||
),
|
||||
onExpansionChanged: (isOpen) async {
|
||||
if (isOpen && currentUser != null) {
|
||||
final existingUser =
|
||||
await ref.users.findOne(note.pubkey, remote: false);
|
||||
if (existingUser?.name != null) {
|
||||
print('fd has ${note.pubkey}! (${existingUser!.name}) ');
|
||||
authorUser.value = existingUser;
|
||||
} else {
|
||||
authorUser.value =
|
||||
await ref.read(profileProvider(note.pubkey).future);
|
||||
}
|
||||
}
|
||||
},
|
||||
children: [
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16.0,
|
||||
vertical: 8.0,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (authorUser.value != null)
|
||||
Text(
|
||||
'Author: ${authorUser.value!.name} (${authorUser.value!.nip05})'),
|
||||
if (isWebOfTrust)
|
||||
Text(
|
||||
'Author is in your web of trust',
|
||||
style: TextStyle(color: Colors.green),
|
||||
),
|
||||
if (!isWebOfTrust)
|
||||
Text('Author is not in your web of trust',
|
||||
style: TextStyle(color: Colors.red)),
|
||||
Text(
|
||||
'SHA-256: ${note.tagMap['x']?.first}',
|
||||
style: TextStyle(
|
||||
color: (isSha256Ok.value != null)
|
||||
? (isSha256Ok.value! ? Colors.green : Colors.red)
|
||||
: Colors.white),
|
||||
),
|
||||
TextButton(
|
||||
child: const Column(
|
||||
children: <Widget>[
|
||||
Text('Install'),
|
||||
Icon(Icons.arrow_downward),
|
||||
],
|
||||
),
|
||||
onPressed: () async {
|
||||
final uri = Uri.parse(note.tagMap['url']!.first);
|
||||
final response = await http.get(uri);
|
||||
final dir = ref.read(downloadsPath);
|
||||
File file = File(path.join(dir, uri.pathSegments.last));
|
||||
await file.writeAsBytes(response.bodyBytes);
|
||||
final digest = sha256.convert(response.bodyBytes);
|
||||
isSha256Ok.value =
|
||||
digest.toString() == note.tagMap['x']!.first;
|
||||
if (isSha256Ok.value == false) {
|
||||
print('actual digest $digest');
|
||||
} else {
|
||||
print('digest ok $digest');
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
DataState(isLoading: true, exception: null) =>
|
||||
CircularProgressIndicator(),
|
||||
DataState(:final exception) => Text(exception.toString()),
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <bitsdojo_window_linux/bitsdojo_window_plugin.h>
|
||||
#include <desktop_drop/desktop_drop_plugin.h>
|
||||
#include <handy_window/handy_window_plugin.h>
|
||||
#include <screen_retriever/screen_retriever_plugin.h>
|
||||
#include <window_manager/window_manager_plugin.h>
|
||||
@@ -15,6 +16,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
|
||||
g_autoptr(FlPluginRegistrar) bitsdojo_window_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "BitsdojoWindowPlugin");
|
||||
bitsdojo_window_plugin_register_with_registrar(bitsdojo_window_linux_registrar);
|
||||
g_autoptr(FlPluginRegistrar) desktop_drop_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "DesktopDropPlugin");
|
||||
desktop_drop_plugin_register_with_registrar(desktop_drop_registrar);
|
||||
g_autoptr(FlPluginRegistrar) handy_window_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "HandyWindowPlugin");
|
||||
handy_window_plugin_register_with_registrar(handy_window_registrar);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
bitsdojo_window_linux
|
||||
desktop_drop
|
||||
handy_window
|
||||
screen_retriever
|
||||
window_manager
|
||||
|
||||
+46
-74
@@ -33,46 +33,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.11.0"
|
||||
base58check:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: base58check
|
||||
sha256: "6c300dfc33e598d2fe26319e13f6243fea81eaf8204cb4c6b69ef20a625319a5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
bech32:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: bech32
|
||||
sha256: "156cbace936f7720c79a79d16a03efad343b1ef17106716e04b8b8e39f99f7f7"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.2"
|
||||
bip32_bip44:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: bip32_bip44
|
||||
sha256: "8e28e6bde00da1ed207f2c0a5361792375799196176742c0d36c71e89a5485d3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
bip340:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: bip340
|
||||
sha256: "9a2f454d3f5e2d67a1b21494203132786d95bde8784e7ada74adac00df3ddc11"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.0"
|
||||
bip39:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: bip39
|
||||
sha256: de1ee27ebe7d96b84bb3a04a4132a0a3007dcdd5ad27dd14aa87a29d97c45edc
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.6"
|
||||
bitsdojo_window:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -233,8 +201,16 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.1"
|
||||
crypto:
|
||||
cross_file:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cross_file
|
||||
sha256: fedaadfa3a6996f75211d835aaeb8fede285dae94262485698afd832371b9a5e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.3+8"
|
||||
crypto:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: crypto
|
||||
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
|
||||
@@ -249,14 +225,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.6"
|
||||
dart_nostr:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: dart_nostr
|
||||
sha256: "9d75157b4fe9ade7a186765efe3200a40f8b6673f88d617e96de2bc69671cb8c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.0.0"
|
||||
dart_style:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -265,14 +233,30 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
desktop_drop:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: desktop_drop
|
||||
sha256: d55a010fe46c8e8fcff4ea4b451a9ff84a162217bdb3b2a0aa1479776205e15d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.4.4"
|
||||
equatable:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: equatable
|
||||
sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.5"
|
||||
expansion_tile_card:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: expansion_tile_card
|
||||
sha256: "27ce4cb518f00e21d0f2309aaa6462b26b148e93cee2029a73088cecf42b1eb0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -338,15 +322,20 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_riverpod
|
||||
sha256: da9591d1f8d5881628ccd5c25c40e74fc3eef50ba45e40c3905a06e1712412d5
|
||||
sha256: "4bce556b7ecbfea26109638d5237684538d4abc509d253e6c5c4c5733b360098"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.9"
|
||||
version: "2.4.10"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_web_plugins:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
frontend_server_client:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -387,14 +376,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.1"
|
||||
hex:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: hex
|
||||
sha256: "4e7cd54e4b59ba026432a6be2dd9d96e4c5205725194997193bf871703b82c4a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
hive:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -407,10 +388,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: hooks_riverpod
|
||||
sha256: c12a456e03ef9be65b0be66963596650ad7a3220e96c7e7b0a048562ea32d6ae
|
||||
sha256: "758b07eba336e3cbacbd81dba481f2228a14102083fdde07045e8514e8054c49"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.9"
|
||||
version: "2.4.10"
|
||||
http:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -523,6 +504,13 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.5"
|
||||
ndk:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "../ndk"
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.1.0"
|
||||
package_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -603,14 +591,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.8"
|
||||
pointycastle:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pointycastle
|
||||
sha256: "43ac87de6e10afabc85c445745a7b799e04de84cebaa4fd7bf55a5e1e9604d29"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.7.4"
|
||||
pool:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -655,10 +635,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: riverpod
|
||||
sha256: "942999ee48b899f8a46a860f1e13cee36f2f77609eb54c5b7a669bb20d550b11"
|
||||
sha256: "548e2192eb7aeb826eb89387f814edb76594f3363e2c0bb99dd733d795ba3589"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.9"
|
||||
version: "2.5.0"
|
||||
screen_retriever:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -824,14 +804,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.0"
|
||||
web_socket_client:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: web_socket_client
|
||||
sha256: de52c9b3acf1f9fafb2951aa2764dd67c7d99d70be70c7c1fd791fae8f632f98
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.0"
|
||||
win32:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -865,5 +837,5 @@ packages:
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
sdks:
|
||||
dart: ">=3.2.0 <=3.9.9"
|
||||
dart: ">=3.2.0 <4.0.0"
|
||||
flutter: ">=3.10.0"
|
||||
|
||||
+10
-2
@@ -40,8 +40,16 @@ dependencies:
|
||||
build_runner: ^2.4.8
|
||||
collection: ^1.18.0
|
||||
path_provider: ^2.1.2
|
||||
web_socket_client: ^0.1.0
|
||||
dart_nostr: ^8.0.0
|
||||
ndk: ^0.0.1
|
||||
bech32: ^0.2.2
|
||||
expansion_tile_card: ^3.0.0
|
||||
desktop_drop: ^0.4.4
|
||||
equatable: ^2.0.5
|
||||
crypto: ^3.0.3
|
||||
|
||||
dependency_overrides:
|
||||
ndk:
|
||||
path: ../ndk
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
Reference in New Issue
Block a user