data loading wip

This commit is contained in:
franzap
2024-05-02 00:10:27 -03:00
parent 91624ff0ab
commit 7efb22a91f
16 changed files with 338 additions and 248 deletions
+6 -4
View File
@@ -3,8 +3,10 @@ import 'package:flutter_data/flutter_data.dart';
import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:path_provider/path_provider.dart';
import 'package:purplebase/purplebase.dart';
import 'package:zapstore/main.data.dart';
import 'package:zapstore/models/app.dart';
import 'package:zapstore/models/user.dart';
import 'package:zapstore/screens/app_detail_screen.dart';
import 'package:zapstore/screens/settings_screen.dart';
import 'package:zapstore/screens/updates_screen.dart';
@@ -23,7 +25,7 @@ void main() {
print('initializing local storage at $path');
return path;
},
clear: LocalStorageClearStrategy.whenError,
clear: LocalStorageClearStrategy.always, // TODO whenError
),
)
],
@@ -127,9 +129,9 @@ final goRouter = GoRouter(
final newInitializer = FutureProvider<void>((ref) async {
await ref.read(initializeFlutterData(adapterProvidersMap).future);
// ref
// .read(p.relayMessageNotifierProvider.notifier)
// .initialize(['wss://relay.zap.store', 'wss://relay.nostr.band']);
ref
.read(relayMessageNotifierProvider.notifier)
.initialize(['wss://relay.zap.store', 'wss://relay.nostr.band']);
});
class ScaffoldWithNestedNavigation extends HookConsumerWidget {
+5 -27
View File
@@ -1,38 +1,16 @@
import 'package:equatable/equatable.dart';
import 'package:flutter_data/flutter_data.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:purplebase/purplebase.dart';
import 'package:zapstore/models/nostr_adapter.dart';
import 'package:zapstore/models/release.dart';
import 'package:zapstore/models/user.dart';
part 'app.g.dart';
@JsonSerializable()
@DataAdapter([NostrAdapter, CoolAdapter])
class App extends ZapstoreEvent<App> with BaseApp, EquatableMixin {
@DataAdapter([NostrAdapter])
class App extends ZapstoreEvent<App> with BaseApp {
late final HasMany<Release> releases;
App();
factory App.fromJson(Map<String, dynamic> json) => _$AppFromJson(json);
@override
List<Object?> get props => [id];
}
mixin CoolAdapter on Adapter<App> {
@override
Future<void> onInitialized() async {
// db.execute('''
// CREATE TABLE IF NOT EXISTS $internalType (
// key INTEGER PRIMARY KEY AUTOINCREMENT,
// data TEXT
// );
// ''');
super.onInitialized();
}
Future<List<App>> query(int kind) async {
final result = db.select(
"SELECT data FROM $internalType WHERE json_extract(data, '\$.kind') = ?",
[kind]);
return deserializeFromResult(result);
}
late final BelongsTo<User> signer;
late final BelongsTo<User> developer;
}
+35 -6
View File
@@ -16,6 +16,18 @@ mixin _$AppAdapter on Adapter<App> {
type: 'releases',
kind: 'HasMany',
instance: (_) => (_ as App).releases,
),
'signer': RelationshipMeta<User>(
name: 'signer',
type: 'users',
kind: 'BelongsTo',
instance: (_) => (_ as App).signer,
),
'developer': RelationshipMeta<User>(
name: 'developer',
type: 'users',
kind: 'BelongsTo',
instance: (_) => (_ as App).developer,
)
};
@@ -25,7 +37,7 @@ mixin _$AppAdapter on Adapter<App> {
@override
App deserializeLocal(map, {String? key}) {
map = transformDeserialize(map);
return internalWrapStopInit(() => App.fromJson(map), key: key);
return internalWrapStopInit(() => _$AppFromJson(map), key: key);
}
@override
@@ -37,15 +49,13 @@ mixin _$AppAdapter on Adapter<App> {
final _appsFinders = <String, dynamic>{};
class $AppAdapter = Adapter<App>
with _$AppAdapter, NostrAdapter<App>, CoolAdapter;
class $AppAdapter = Adapter<App> with _$AppAdapter, NostrAdapter<App>;
final appsAdapterProvider = Provider<Adapter<App>>(
(ref) => $AppAdapter(ref, InternalHolder(_appsFinders)));
extension AppAdapterX on Adapter<App> {
NostrAdapter<App> get nostrAdapter => this as NostrAdapter<App>;
CoolAdapter get coolAdapter => this as CoolAdapter;
}
extension AppRelationshipGraphNodeX on RelationshipGraphNode<App> {
@@ -55,6 +65,20 @@ extension AppRelationshipGraphNodeX on RelationshipGraphNode<App> {
return meta.clone(
parent: this is RelationshipMeta ? this as RelationshipMeta : null);
}
RelationshipGraphNode<User> get signer {
final meta =
_$AppAdapter._kAppRelationshipMetas['signer'] as RelationshipMeta<User>;
return meta.clone(
parent: this is RelationshipMeta ? this as RelationshipMeta : null);
}
RelationshipGraphNode<User> get developer {
final meta = _$AppAdapter._kAppRelationshipMetas['developer']
as RelationshipMeta<User>;
return meta.clone(
parent: this is RelationshipMeta ? this as RelationshipMeta : null);
}
}
// **************************************************************************
@@ -66,13 +90,16 @@ App _$AppFromJson(Map<String, dynamic> json) => App()
..pubkey = json['pubkey'] as String
..createdAt = DateTime.parse(json['createdAt'] as String)
..content = json['content'] as String
..kind = json['kind'] as int
..kind = (json['kind'] as num).toInt()
..tags = (json['tags'] as List<dynamic>)
.map((e) => (e as List<dynamic>).map((e) => e as String).toList())
.toList()
..signature = json['signature'] as String?
..releases =
HasMany<Release>.fromJson(json['releases'] as Map<String, dynamic>);
HasMany<Release>.fromJson(json['releases'] as Map<String, dynamic>)
..signer = BelongsTo<User>.fromJson(json['signer'] as Map<String, dynamic>)
..developer =
BelongsTo<User>.fromJson(json['developer'] as Map<String, dynamic>);
Map<String, dynamic> _$AppToJson(App instance) => <String, dynamic>{
'id': instance.id,
@@ -83,4 +110,6 @@ Map<String, dynamic> _$AppToJson(App instance) => <String, dynamic>{
'tags': instance.tags,
'signature': instance.signature,
'releases': instance.releases,
'signer': instance.signer,
'developer': instance.developer,
};
+2 -3
View File
@@ -1,13 +1,13 @@
import 'dart:io';
import 'package:android_package_installer/android_package_installer.dart';
import 'package:android_package_manager/android_package_manager.dart';
import 'package:crypto/crypto.dart';
import 'package:flutter_data/flutter_data.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:purplebase/purplebase.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:zapstore/models/nostr_adapter.dart';
import 'package:zapstore/models/release.dart';
import 'package:zapstore/models/user.dart';
import 'package:http/http.dart' as http;
@@ -15,13 +15,12 @@ import 'package:path/path.dart' as path;
part 'file_metadata.g.dart';
final packageManager = AndroidPackageManager();
@JsonSerializable()
@DataAdapter([NostrAdapter, FileMetadataAdapter])
class FileMetadata extends ZapstoreEvent<FileMetadata> with BaseFileMetadata {
late final BelongsTo<User> author;
late final BelongsTo<Release> release = BelongsTo();
late final BelongsTo<User> signer;
Future<void> install() async {
// throw Exception('something nasty');
+17 -2
View File
@@ -22,6 +22,12 @@ mixin _$FileMetadataAdapter on Adapter<FileMetadata> {
type: 'releases',
kind: 'BelongsTo',
instance: (_) => (_ as FileMetadata).release,
),
'signer': RelationshipMeta<User>(
name: 'signer',
type: 'users',
kind: 'BelongsTo',
instance: (_) => (_ as FileMetadata).signer,
)
};
@@ -72,6 +78,13 @@ extension FileMetadataRelationshipGraphNodeX
return meta.clone(
parent: this is RelationshipMeta ? this as RelationshipMeta : null);
}
RelationshipGraphNode<User> get signer {
final meta = _$FileMetadataAdapter._kFileMetadataRelationshipMetas['signer']
as RelationshipMeta<User>;
return meta.clone(
parent: this is RelationshipMeta ? this as RelationshipMeta : null);
}
}
// **************************************************************************
@@ -83,12 +96,13 @@ FileMetadata _$FileMetadataFromJson(Map<String, dynamic> json) => FileMetadata()
..pubkey = json['pubkey'] as String
..createdAt = DateTime.parse(json['createdAt'] as String)
..content = json['content'] as String
..kind = json['kind'] as int
..kind = (json['kind'] as num).toInt()
..tags = (json['tags'] as List<dynamic>)
.map((e) => (e as List<dynamic>).map((e) => e as String).toList())
.toList()
..signature = json['signature'] as String?
..author = BelongsTo<User>.fromJson(json['author'] as Map<String, dynamic>);
..author = BelongsTo<User>.fromJson(json['author'] as Map<String, dynamic>)
..signer = BelongsTo<User>.fromJson(json['signer'] as Map<String, dynamic>);
Map<String, dynamic> _$FileMetadataToJson(FileMetadata instance) =>
<String, dynamic>{
@@ -100,4 +114,5 @@ Map<String, dynamic> _$FileMetadataToJson(FileMetadata instance) =>
'tags': instance.tags,
'signature': instance.signature,
'author': instance.author,
'signer': instance.signer,
};
+85
View File
@@ -0,0 +1,85 @@
import 'package:flutter_data/flutter_data.dart';
import 'package:purplebase/purplebase.dart';
import 'package:zapstore/models/user.dart';
abstract class ZapstoreEvent<T extends ZapstoreEvent<T>> = BaseEvent
with DataModelMixin<T>;
mixin NostrAdapter<T extends ZapstoreEvent<T>> on Adapter<T> {
late final RelayMessageNotifier notifier =
ref.read(relayMessageNotifierProvider.notifier);
Map<int, String> kindType = {
0: 'users',
3: 'users',
1063: 'fileMetadata',
30063: 'releases',
32267: 'apps'
};
@override
DeserializedData<T> deserialize(Object? data, {String? key}) {
final list = data is Iterable ? data : [data as Map];
final models = <T>[];
final included = <DataModelMixin>[];
for (final e in list) {
final map = e as Map<String, dynamic>;
map['createdAt'] =
DateTime.fromMillisecondsSinceEpoch(map['created_at'] * 1000)
.toIso8601String();
final kind = map['kind'] as int;
final dTags = (map['tags'] as Iterable).where((t) => t[0] == 'd');
if (dTags.length == 1) {
map['id'] = (dTags.first as List)[1];
}
map['signer'] = map['pubkey'];
final zapTags = (map['tags'] as Iterable).where((t) => t[0] == 'zap');
if (zapTags.length == 1) {
map['developer'] = (zapTags.first as List)[1];
}
final xType = kindType[kind];
if (xType != null) {
if (xType == internalType) {
final newData = super.deserialize(map);
models.addAll(newData.models as Iterable<T>);
} else {
final newData = adapters[xType]!.deserialize(map);
included.addAll(newData.models as Iterable<DataModelMixin>);
}
}
}
return DeserializedData<T>(models, included: included);
}
int get kind =>
kindType.entries.firstWhere((e) => e.value == internalType).key;
@override
Future<List<T>> findAll(
{bool? remote,
bool? background,
Map<String, dynamic>? params,
Map<String, String>? headers,
bool? syncLocal,
OnSuccessAll<T>? onSuccess,
OnErrorAll<T>? onError,
DataRequestLabel? label}) async {
if (remote == false) {
return findAllLocal();
}
final req = RelayRequest(
kinds: {kind, ...?params?.remove('kinds')},
tags: params ?? {},
);
print(req);
final result =
await notifier.query(req, relayUrls: ['wss://relay.zap.store']);
final deserialized = await deserializeAsync(result, save: true);
return deserialized.models;
}
}
+3 -6
View File
@@ -3,6 +3,7 @@ import 'package:json_annotation/json_annotation.dart';
import 'package:purplebase/purplebase.dart';
import 'package:zapstore/models/app.dart';
import 'package:zapstore/models/file_metadata.dart';
import 'package:zapstore/models/nostr_adapter.dart';
import 'package:zapstore/models/user.dart';
part 'release.g.dart';
@@ -12,6 +13,7 @@ part 'release.g.dart';
class Release extends ZapstoreEvent<Release> with BaseRelease {
late final HasMany<FileMetadata> artifacts;
late final BelongsTo<App> app;
late final BelongsTo<User> signer;
}
mixin ReleaseAdapter on Adapter<Release> {
@@ -26,12 +28,7 @@ mixin ReleaseAdapter on Adapter<Release> {
map['artifacts'] = eTags.toList();
final appIdentifier =
map['tags'].firstWhere((t) => t.first == 'd')[1].split('@').first;
map['app'] = (adapters['apps'] as Adapter<App>)
.findAllLocal()
.where((a) => a.identifier == appIdentifier)
// ignore: invalid_use_of_visible_for_testing_member
.safeFirst
?.id;
map['app'] = appIdentifier;
}
return super.deserialize(data);
}
+17 -2
View File
@@ -23,6 +23,12 @@ mixin _$ReleaseAdapter on Adapter<Release> {
type: 'apps',
kind: 'BelongsTo',
instance: (_) => (_ as Release).app,
),
'signer': RelationshipMeta<User>(
name: 'signer',
type: 'users',
kind: 'BelongsTo',
instance: (_) => (_ as Release).signer,
)
};
@@ -70,6 +76,13 @@ extension ReleaseRelationshipGraphNodeX on RelationshipGraphNode<Release> {
return meta.clone(
parent: this is RelationshipMeta ? this as RelationshipMeta : null);
}
RelationshipGraphNode<User> get signer {
final meta = _$ReleaseAdapter._kReleaseRelationshipMetas['signer']
as RelationshipMeta<User>;
return meta.clone(
parent: this is RelationshipMeta ? this as RelationshipMeta : null);
}
}
// **************************************************************************
@@ -81,14 +94,15 @@ Release _$ReleaseFromJson(Map<String, dynamic> json) => Release()
..pubkey = json['pubkey'] as String
..createdAt = DateTime.parse(json['createdAt'] as String)
..content = json['content'] as String
..kind = json['kind'] as int
..kind = (json['kind'] as num).toInt()
..tags = (json['tags'] as List<dynamic>)
.map((e) => (e as List<dynamic>).map((e) => e as String).toList())
.toList()
..signature = json['signature'] as String?
..artifacts =
HasMany<FileMetadata>.fromJson(json['artifacts'] as Map<String, dynamic>)
..app = BelongsTo<App>.fromJson(json['app'] as Map<String, dynamic>);
..app = BelongsTo<App>.fromJson(json['app'] as Map<String, dynamic>)
..signer = BelongsTo<User>.fromJson(json['signer'] as Map<String, dynamic>);
Map<String, dynamic> _$ReleaseToJson(Release instance) => <String, dynamic>{
'id': instance.id,
@@ -100,4 +114,5 @@ Map<String, dynamic> _$ReleaseToJson(Release instance) => <String, dynamic>{
'signature': instance.signature,
'artifacts': instance.artifacts,
'app': instance.app,
'signer': instance.signer,
};
+30 -95
View File
@@ -1,26 +1,17 @@
import 'package:equatable/equatable.dart';
import 'package:flutter_data/flutter_data.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:purplebase/purplebase.dart';
import 'package:zapstore/models/nostr_adapter.dart';
part 'user.g.dart';
abstract class ZapstoreEvent<T extends ZapstoreEvent<T>> = BaseEvent
with DataModelMixin<T>;
@JsonSerializable()
@DataAdapter([NostrAdapter, UserAdapter])
class User extends ZapstoreEvent<User> with BaseUser, EquatableMixin {
User();
User.fromMap(super.map) : super.fromMap();
class User extends ZapstoreEvent<User> with BaseUser {
@DataRelationship(inverse: 'followers')
late final HasMany<User> following;
@DataRelationship(inverse: 'following')
late final HasMany<User> followers = HasMany();
@override
List<Object?> get props => [id];
}
mixin UserAdapter on NostrAdapter<User> {
@@ -32,15 +23,14 @@ mixin UserAdapter on NostrAdapter<User> {
final map = e as Map<String, dynamic>;
if (map['kind'] == 3) {
for (final [_, id, ..._] in map['tags'] as Iterable) {
users.add(User.fromMap({
super.deserialize({
'id': id,
'content': '',
'pubkey': id,
'created_at': DateTime.now().millisecondsSinceEpoch ~/ 1000,
'kind': 0,
'tags': [],
'following': {'_': null},
}));
});
}
}
}
@@ -64,6 +54,26 @@ mixin UserAdapter on NostrAdapter<User> {
return DeserializedData(users);
}
@override
Future<List<User>> findAll(
{bool? remote,
bool? background,
Map<String, dynamic>? params,
Map<String, String>? headers,
bool? syncLocal,
OnSuccessAll<User>? onSuccess,
OnErrorAll<User>? onError,
DataRequestLabel? label}) async {
final req =
RelayRequest(authors: Set<String>.from(params!['ids']), kinds: {kind});
print('in user findall $req');
final result =
await notifier.query(req, relayUrls: ['wss://relay.nostr.band']);
final data = await deserializeAsync(result, save: true);
return data.models;
}
@override
Future<User?> findOne(Object id,
{bool remote = true,
@@ -73,17 +83,12 @@ mixin UserAdapter on NostrAdapter<User> {
OnSuccessOne<User>? onSuccess,
OnErrorOne<User>? onError,
DataRequestLabel? label}) async {
String? publicKey;
try {
publicKey = '$id'.hexKey;
} catch (_) {
String username;
String domain;
try {
[username, domain] = id.toString().split('@');
} catch (_) {
return null;
}
var publicKey = id.toString();
if (publicKey.startsWith('npub')) {
publicKey = publicKey.hexKey;
} else if (publicKey.contains('@')) {
final [username, domain] = id.toString().split('@');
publicKey = await sendRequest<dynamic>(
Uri.parse('https://$domain/.well-known/nostr.json?name=$username'),
onSuccess: (response, label) {
@@ -92,10 +97,6 @@ mixin UserAdapter on NostrAdapter<User> {
);
}
if (publicKey == null) {
return null;
}
final req = RelayRequest(
authors: {publicKey},
kinds: {kind, ...?params?.remove('kinds')},
@@ -108,69 +109,3 @@ mixin UserAdapter on NostrAdapter<User> {
return data.model?..saveLocal();
}
}
mixin NostrAdapter<T extends ZapstoreEvent<T>> on Adapter<T> {
late final RelayMessageNotifier notifier =
ref.read(relayMessageNotifierProvider.notifier);
Map<int, String> kindType = {
0: 'users',
3: 'users',
1063: 'fileMetadata',
30063: 'releases',
32267: 'apps'
};
@override
DeserializedData<T> deserialize(Object? data, {String? key}) {
final list = data is Iterable ? data : [data as Map];
final models = <T>[];
final included = <DataModelMixin>[];
for (final e in list) {
final map = e as Map<String, dynamic>;
map['createdAt'] =
DateTime.fromMillisecondsSinceEpoch(map['created_at'] * 1000)
.toIso8601String();
final kind = map['kind'] as int;
final xType = kindType[kind];
if (xType != null) {
if (xType == internalType) {
final newData = super.deserialize(map);
models.addAll(newData.models as Iterable<T>);
} else {
final newData = adapters[xType]!.deserialize(map);
included.addAll(newData.models as Iterable<DataModelMixin>);
}
}
}
return DeserializedData<T>(models, included: included);
}
int get kind =>
kindType.entries.firstWhere((e) => e.value == internalType).key;
@override
Future<List<T>> findAll(
{bool? remote,
bool? background,
Map<String, dynamic>? params,
Map<String, String>? headers,
bool? syncLocal,
OnSuccessAll<T>? onSuccess,
OnErrorAll<T>? onError,
DataRequestLabel? label}) async {
if (remote == false) {
return findAllLocal();
}
final req = RelayRequest(
kinds: {kind, ...?params?.remove('kinds')},
tags: params ?? {},
);
final result = await notifier.query(req);
final deserialized = await deserializeAsync(result, save: true);
return deserialized.models;
}
}
+1 -1
View File
@@ -81,7 +81,7 @@ User _$UserFromJson(Map<String, dynamic> json) => User()
..pubkey = json['pubkey'] as String
..createdAt = DateTime.parse(json['createdAt'] as String)
..content = json['content'] as String
..kind = json['kind'] as int
..kind = (json['kind'] as num).toInt()
..tags = (json['tags'] as List<dynamic>)
.map((e) => (e as List<dynamic>).map((e) => e as String).toList())
.toList()
+28 -31
View File
@@ -5,6 +5,7 @@ import 'dart:convert';
import 'package:auto_size_text/auto_size_text.dart';
import 'package:carousel_slider/carousel_slider.dart';
import 'package:flutter/material.dart';
import 'package:flutter_data/flutter_data.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
@@ -13,15 +14,6 @@ import 'package:zapstore/models/user.dart';
import 'package:zapstore/widgets/card.dart';
import 'package:zapstore/widgets/pill_widget.dart';
final List<String> imgList = [
'https://images.unsplash.com/photo-1520342868574-5fa3804e551c?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=6ff92caffcdd63681a35134a6770ed3b&auto=format&fit=crop&w=1951&q=80',
'https://images.unsplash.com/photo-1522205408450-add114ad53fe?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=368f45b0888aeb0b7b08e3a1084d3ede&auto=format&fit=crop&w=1950&q=80',
'https://images.unsplash.com/photo-1519125323398-675f0ddb6308?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=94a1e718d89ca60a6337a6008341ca50&auto=format&fit=crop&w=1950&q=80',
'https://images.unsplash.com/photo-1523205771623-e0faa4d2813d?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=89719a0d55dd05e2deae4120227e6efc&auto=format&fit=crop&w=1953&q=80',
'https://images.unsplash.com/photo-1508704019882-f9cf40e475b4?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=8c6e5e3aba713b17aa1fe71ab4f0ae5b&auto=format&fit=crop&w=1352&q=80',
'https://images.unsplash.com/photo-1519985176271-adb1088fa94c?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=a0c8d632e977f94e5d312d9893258f59&auto=format&fit=crop&w=1355&q=80'
];
class AppDetailScreen extends HookConsumerWidget {
final App app;
const AppDetailScreen({
@@ -31,16 +23,6 @@ class AppDetailScreen extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final user = User.fromMap({
'kind': 0,
'created_at': 112899202,
'pubkey':
'a9e95a4eb32b55441b222ae5674f063949bfd0759b82deb03d7cd262e82d5626',
'content': jsonEncode(
{'name': 'test', 'picture': 'https://picsum.photos/200/300'}),
'tags': [],
});
return Column(
children: [
Expanded(
@@ -62,7 +44,7 @@ class AppDetailScreen extends HookConsumerWidget {
mainAxisAlignment: MainAxisAlignment.start,
children: [
CircularImage(
url: 'https://picsum.photos/200/200',
url: app.icon,
size: 80,
radius: 25,
),
@@ -73,7 +55,7 @@ class AppDetailScreen extends HookConsumerWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AutoSizeText(
'Mutiny Wallet',
app.name,
minFontSize: 16,
style: TextStyle(
fontSize: 28, fontWeight: FontWeight.bold),
@@ -81,7 +63,7 @@ class AppDetailScreen extends HookConsumerWidget {
overflow: TextOverflow.ellipsis,
),
Gap(8),
PillWidget(text: '0.5.6'),
PillWidget(text: '0.5.9', color: Colors.grey[800]),
],
),
),
@@ -92,10 +74,10 @@ class AppDetailScreen extends HookConsumerWidget {
options: CarouselOptions(
enableInfiniteScroll: false,
),
items: imgList
.map((item) => Padding(
items: (app.tagMap['image'] ?? [])
.map((i) => Padding(
padding: const EdgeInsets.only(right: 10),
child: Image.network(item,
child: Image.network(i,
fit: BoxFit.cover, width: 1000),
))
.toList(),
@@ -108,11 +90,14 @@ class AppDetailScreen extends HookConsumerWidget {
p: TextStyle(fontSize: 16, fontWeight: FontWeight.w300),
),
selectable: false,
data:
'Lorem ipsum dolor sit amet\n - consectetur adipisicing elit\n - sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, ',
data: app.content,
),
Gap(10),
AuthorContainer(user: user, text: 'Built by', oneLine: false),
if (app.developer.isPresent)
AuthorContainer(
user: app.developer.value!,
text: 'Built by',
oneLine: false),
Gap(10),
Divider(height: 24),
Container(
@@ -127,21 +112,33 @@ class AppDetailScreen extends HookConsumerWidget {
padding: const EdgeInsets.all(8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [Text('Source'), Text('github link')],
children: [
Text('Source'),
Text(
app.repository,
style: TextStyle(fontSize: 11),
)
],
),
),
Padding(
padding: const EdgeInsets.all(8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [Text('Github stars'), Text('456')],
children: [
Text('Github stars'),
Text(app.githubStars)
],
),
),
Padding(
padding: const EdgeInsets.all(8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [Text('Github forks'), Text('3')],
children: [
Text('Github forks'),
Text(app.githubForks)
],
),
)
],
+61 -23
View File
@@ -1,4 +1,5 @@
// ignore_for_file: prefer_const_literals_to_create_immutables
import 'dart:developer';
import 'dart:ffi';
import 'package:flutter/material.dart';
import 'package:flutter_data/flutter_data.dart';
@@ -6,7 +7,7 @@ import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:zapstore/main.data.dart';
import 'package:zapstore/models/app.dart';
import 'package:zapstore/screens/app_detail_screen.dart';
import 'package:zapstore/models/release.dart';
import 'package:zapstore/widgets/card.dart';
import 'package:zapstore/widgets/user_avatar.dart';
@@ -25,12 +26,6 @@ class SearchScreen extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final state = ref.watch(searchStateProvider);
if (state.isLoading) {
return Center(child: CircularProgressIndicator());
}
final apps = state.model;
return Column(
children: [
Row(
@@ -61,17 +56,58 @@ class SearchScreen extends HookConsumerWidget {
autoFocus: true,
elevation: MaterialStatePropertyAll(2.2),
onSubmitted: (query) async {
ref.read(screenInfoProvider.notifier).state = 'No items';
final apps = [App(), App(), App(), App()];
// await ref.apps.findAll(params: {'search': query});
// debugger;
// final releases = await ref.releases.findAll(
// params: {'#i': apps.map((a) => a.identifier).toSet()});
// final app = App()
// ..id = '1'
// ..kind = 32267
// ..createdAt = DateTime.now()
// ..pubkey =
// 'a9e95a4eb32b55441b222ae5674f063949bfd0759b82deb03d7cd262e82d5626'
// ..content = 'testing'
// ..tags = [
// ['d', 'com.test']
// ]
// ..releases = HasMany()
// ..init()
// ..saveLocal();
// final release = ref.releases.deserialize({
// 'kind': 30063,
// 'created_at': 112899202,
// 'pubkey':
// 'a9e95a4eb32b55441b222ae5674f063949bfd0759b82deb03d7cd262e82d5626',
// 'content': 'mutiny',
// 'tags': [
// ['d', 'com.test@v0.6.4']
// ],
// });
// debugger;
// ref.read(searchStateProvider.notifier).state =
// DataState([app]);
// print(release.model!.app.value?.id);
ref.read(screenInfoProvider.notifier).state =
'No apps found for "$query"';
ref.read(searchStateProvider.notifier).state =
DataState([], isLoading: true);
final apps =
await ref.apps.findAll(params: {'search': query});
// load all signers and developers
final userIds = {
for (final app in apps) app.signer.id,
for (final app in apps) app.developer.id
};
await ref.users.findAll(params: {'ids': userIds});
await ref.releases.findAll(
params: {'#i': apps.map((a) => a.identifier).toSet()});
// await ref.fileMetadata.findAll(
// params: {
// 'ids':
// releases.map((r) => r.tagMap['e']!.first).toSet(),
// 'ids': releases.map((r) => r.tagMap['e']!.first).toSet(),
// '#m': [kAndroidMimeType],
// },
// );
@@ -83,22 +119,24 @@ class SearchScreen extends HookConsumerWidget {
],
),
Gap(10),
if (apps.isEmpty)
if (state.model.isEmpty || state.isLoading)
Expanded(
child: Center(
child: Text(
ref.read(screenInfoProvider),
textAlign: TextAlign.center,
),
child: state.isLoading
? CircularProgressIndicator()
: Text(
ref.read(screenInfoProvider),
textAlign: TextAlign.center,
),
),
),
if (apps.isNotEmpty)
if (state.hasModel)
Expanded(
child: ListView.builder(
shrinkWrap: true,
itemCount: apps.length,
itemCount: state.model.length,
itemBuilder: (context, index) {
final app = apps[index];
final app = state.model[index];
return CardWidget(app: app);
},
),
+13 -13
View File
@@ -32,19 +32,19 @@ class AppDrawer extends HookConsumerWidget {
},
child: Text('Log in'),
),
ElevatedButton(
onPressed: () async {
ref.read(loggedInUser.notifier).state = null;
controller.clear();
// final as = await packageManager.getInstalledApplications();
// for (final a in as!) {
// if (!a.packageName!.contains('android')) {
// print(a.packageName);
// }
// }
},
child: Text('check'),
),
// ElevatedButton(
// onPressed: () async {
// ref.read(loggedInUser.notifier).state = null;
// controller.clear();
// // final as = await packageManager.getInstalledApplications();
// // for (final a in as!) {
// // if (!a.packageName!.contains('android')) {
// // print(a.packageName);
// // }
// // }
// },
// child: Text('check'),
// ),
if (u2 != null)
Expanded(
child: Column(
+31 -31
View File
@@ -1,5 +1,6 @@
import 'dart:convert';
import 'package:auto_size_text/auto_size_text.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
@@ -16,18 +17,9 @@ class CardWidget extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final user = User.fromMap({
'kind': 0,
'created_at': 112899202,
'pubkey':
'a9e95a4eb32b55441b222ae5674f063949bfd0759b82deb03d7cd262e82d5626',
'content': jsonEncode(
{'name': 'test', 'picture': 'https://picsum.photos/200/300'}),
'tags': [],
});
return Card(
margin: EdgeInsets.only(top: 20),
elevation: 0,
child: GestureDetector(
onTap: () => context.go('/details', extra: app),
child: Padding(
@@ -37,7 +29,7 @@ class CardWidget extends HookConsumerWidget {
mainAxisAlignment: MainAxisAlignment.start,
children: [
CircularImage(
url: 'https://picsum.photos/200/200',
url: app.icon,
size: 80,
radius: 25,
),
@@ -46,19 +38,19 @@ class CardWidget extends HookConsumerWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Mutiny Wallet - Mutiny Wallet - Muti',
// '${app.name} - ${app.name}',
AutoSizeText(
app.releases.safeFirst?.identifier.split('@').last ??
'none',
minFontSize: 16,
style:
TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
TextStyle(fontSize: 23, fontWeight: FontWeight.bold),
overflow: TextOverflow.ellipsis,
maxLines: 1,
softWrap: true,
),
Padding(
padding: const EdgeInsets.only(top: 4),
child: Text(
'Best web-based Lightning and Ecash wallet web-based Lightning and Ecash wallet',
app.content,
style: TextStyle(
fontSize: 14, fontWeight: FontWeight.w300),
overflow: TextOverflow.ellipsis,
@@ -67,10 +59,17 @@ class CardWidget extends HookConsumerWidget {
),
),
Gap(10),
AuthorContainer(user: user),
AuthorContainer(user: user),
Gap(16),
TagsContainer(),
if (app.developer.isPresent)
AuthorContainer(
user: app.developer.value!,
text: 'Built by',
),
if (app.signer.isPresent)
AuthorContainer(user: app.signer.value!),
// Gap(16),
// TagsContainer(
// tags: app.tagMap['t'] ?? [],
// ),
],
),
),
@@ -83,17 +82,21 @@ class CardWidget extends HookConsumerWidget {
}
class TagsContainer extends StatelessWidget {
final List<String> tags;
const TagsContainer({
super.key,
required this.tags,
});
@override
Widget build(BuildContext context) {
return Row(
children: const [
PillWidget(text: 'nostr'),
Gap(6),
PillWidget(text: 'wallet'),
children: [
for (final tag in tags)
Padding(
padding: const EdgeInsets.only(right: 6),
child: PillWidget(text: tag),
),
],
);
}
@@ -156,14 +159,11 @@ class CircularImage extends StatelessWidget {
Widget build(BuildContext context) {
return ClipRRect(
borderRadius: BorderRadius.circular(radius.toDouble()),
child: Container(
child: CachedNetworkImage(
imageUrl: url ?? '',
fit: BoxFit.cover,
width: size.toDouble(),
height: size.toDouble(),
color: Colors.grey[850],
child: CachedNetworkImage(
imageUrl: url ?? '',
fit: BoxFit.cover,
),
),
);
}
+4 -3
View File
@@ -2,21 +2,22 @@ import 'package:flutter/material.dart';
class PillWidget extends StatelessWidget {
final String text;
const PillWidget({super.key, required this.text});
final Color? color;
const PillWidget({super.key, required this.text, this.color = Colors.blue});
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Colors.blue,
color: color,
borderRadius: BorderRadius.circular(12),
),
child: Text(
text,
style: TextStyle(
color: Colors.white,
fontSize: 11,
fontSize: 12,
fontWeight: FontWeight.bold,
),
),
-1
View File
@@ -43,7 +43,6 @@ dependencies:
async_button_builder: ^3.0.0+1
android_package_manager: ^0.7.1
flutter_styled_toast: ^2.2.1
equatable: ^2.0.5
url_launcher: ^6.2.5
device_apps: ^2.2.0
purplebase: