This commit is contained in:
franzap
2024-05-01 18:00:21 -03:00
parent 2be91581f0
commit 91624ff0ab
33 changed files with 1264 additions and 709 deletions
+2
View File
@@ -24,7 +24,9 @@ linter:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
prefer_const_constructors: false
prefer_const_constructors_in_immutables: false
avoid_print: false
prefer_const_literals_to_create_immutables: false
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options
+1
View File
@@ -53,6 +53,7 @@
</provider>
</application>
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION" />
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
View File
+104 -104
View File
@@ -1,28 +1,39 @@
// ignore_for_file: prefer_const_literals_to_create_immutables, prefer_const_constructors
import 'package:flutter/material.dart';
import 'package:flutter_data/flutter_data.dart';
import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:ndk/ndk.dart' as ndk;
import 'package:path_provider/path_provider.dart';
import 'package:zapstore/main.data.dart';
import 'package:zapstore/models/release.dart';
import 'package:zapstore/models/app.dart';
import 'package:zapstore/screens/app_detail_screen.dart';
import 'package:zapstore/screens/profile_screen.dart';
import 'package:zapstore/screens/settings_screen.dart';
import 'package:zapstore/screens/updates_screen.dart';
import 'package:zapstore/utils/extensions.dart';
import 'package:zapstore/widgets/app_drawer.dart';
import 'package:zapstore/screens/search_screen.dart';
void main() {
runApp(
ProviderScope(
overrides: [
configureRepositoryLocalStorage(
clear: LocalStorageClearStrategy.always),
localStorageProvider.overrideWithValue(
LocalStorage(
baseDirFn: () async {
final path = (await getApplicationSupportDirectory()).path;
print('initializing local storage at $path');
return path;
},
clear: LocalStorageClearStrategy.whenError,
),
)
],
child: const ZapstoreApp(),
),
);
}
const kBackgroundColor = Color.fromARGB(255, 6, 6, 6);
class ZapstoreApp extends StatelessWidget {
const ZapstoreApp({super.key});
@@ -31,11 +42,17 @@ class ZapstoreApp extends StatelessWidget {
return MaterialApp.router(
routerConfig: goRouter,
debugShowCheckedModeBanner: false,
darkTheme: ThemeData(
primarySwatch: Colors.purple,
theme: ThemeData(
primarySwatch: Colors.lightBlue,
brightness: Brightness.dark,
fontFamily: 'Inter',
useMaterial3: true,
scaffoldBackgroundColor: kBackgroundColor,
visualDensity: VisualDensity.adaptivePlatformDensity,
// textTheme: context.theme.textTheme.copyWith(
// bodyLarge:
// TextStyle(color: Colors.white, fontWeight: FontWeight.w300))
),
theme: ThemeData.dark(useMaterial3: true),
);
}
}
@@ -44,21 +61,16 @@ class ZapstoreApp extends StatelessWidget {
final _rootNavigatorKey = GlobalKey<NavigatorState>();
final _searchNavigatorKey = GlobalKey<NavigatorState>(debugLabel: 'search');
final _updatesNavigatorKey = GlobalKey<NavigatorState>(debugLabel: 'updates');
final _profileNavigatorKey = GlobalKey<NavigatorState>(debugLabel: 'profile');
final _notificationsNavigatorKey =
GlobalKey<NavigatorState>(debugLabel: 'notifications');
final _settingsNavigatorKey = GlobalKey<NavigatorState>(debugLabel: 'settings');
final goRouter = GoRouter(
initialLocation: '/search',
initialLocation: '/',
// * Passing a navigatorKey causes an issue on hot reload:
// * https://github.com/flutter/flutter/issues/113757#issuecomment-1518421380
// * However it's still necessary otherwise the navigator pops back to
// * root on hot reload
navigatorKey: _rootNavigatorKey,
// debugLogDiagnostics: true,
routes: [
// Stateful navigation based on:
// https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/stateful_shell_route.dart
StatefulShellRoute.indexedStack(
builder: (context, state, navigationShell) {
return ScaffoldWithNestedNavigation(navigationShell: navigationShell);
@@ -68,15 +80,19 @@ final goRouter = GoRouter(
navigatorKey: _searchNavigatorKey,
routes: [
GoRoute(
path: '/search',
pageBuilder: (context, state) => NoTransitionPage(
child: SearchScreen(),
),
path: '/',
builder: (context, state) => SearchScreen(),
routes: [
GoRoute(
path: 'details',
builder: (context, state) =>
AppDetailScreen(release: state.extra as Release),
AppDetailScreen(app: state.extra as App),
// pageBuilder: (context, state) =>
// buildWithSlideTransition<void>(
// context: context,
// state: state,
// child: AppDetailScreen(app: state.extra as App),
// ),
),
],
),
@@ -87,41 +103,18 @@ final goRouter = GoRouter(
routes: [
GoRoute(
path: '/updates',
pageBuilder: (context, state) => NoTransitionPage(
child: Center(
child: ElevatedButton(
onPressed: () => {},
child: const Text('Updates coming soon!'),
),
),
),
builder: (context, state) => UpdatesScreen(),
),
],
),
StatefulShellBranch(
navigatorKey: _profileNavigatorKey,
navigatorKey: _settingsNavigatorKey,
routes: [
GoRoute(
path: '/profile',
path: '/settings',
pageBuilder: (context, state) => NoTransitionPage(
child: Center(
child: ProfileScreen(),
),
),
),
],
),
StatefulShellBranch(
navigatorKey: _notificationsNavigatorKey,
routes: [
GoRoute(
path: '/notifications',
pageBuilder: (context, state) => NoTransitionPage(
child: Center(
child: ElevatedButton(
onPressed: () => {},
child: const Text('Notifications coming soon!'),
),
child: SettingsScreen(),
),
),
),
@@ -133,12 +126,12 @@ final goRouter = GoRouter(
);
final newInitializer = FutureProvider<void>((ref) async {
await ref.read(repositoryInitializerProvider.future);
await ref.read(ndk.frameProvider.notifier).initialize('wss://relay.damus.io');
await ref.read(initializeFlutterData(adapterProvidersMap).future);
// ref
// .read(p.relayMessageNotifierProvider.notifier)
// .initialize(['wss://relay.zap.store', 'wss://relay.nostr.band']);
});
// Stateful navigation based on:
// https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/stateful_shell_route.dart
class ScaffoldWithNestedNavigation extends HookConsumerWidget {
const ScaffoldWithNestedNavigation({
Key? key,
@@ -163,7 +156,7 @@ class ScaffoldWithNestedNavigation extends HookConsumerWidget {
return SafeArea(
child: LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth < 450) {
if (constraints.maxWidth < 550) {
return ScaffoldWithNavigationBar(
body: navigationShell,
selectedIndex: navigationShell.currentIndex,
@@ -197,34 +190,48 @@ class ScaffoldWithNavigationBar extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final initializer = ref.watch(newInitializer);
return Scaffold(
body: initializer.when(
data: (_) => body,
error: (e, _) => const Text('error'),
loading: () => const Center(child: CircularProgressIndicator()),
body: Padding(
padding: const EdgeInsets.fromLTRB(16, 20, 16, 0),
child: initializer.when(
data: (_) => body,
error: (e, _) => const Text('error'),
loading: () => const Center(child: CircularProgressIndicator()),
),
),
bottomNavigationBar: NavigationBar(
height: 60,
backgroundColor: kBackgroundColor,
indicatorColor: Colors.transparent,
selectedIndex: selectedIndex,
labelBehavior: NavigationDestinationLabelBehavior.alwaysHide,
destinations: const [
NavigationDestination(
label: 'Search',
icon: Icon(Icons.search_outlined),
label: 'Home',
icon: Icon(Icons.home_outlined),
selectedIcon: Icon(Icons.home_filled),
),
NavigationDestination(
label: 'Updates',
icon: Icon(Icons.download_for_offline_outlined),
selectedIcon: Icon(Icons.download_for_offline),
),
NavigationDestination(
label: 'Profile',
icon: Icon(Icons.person_outline),
),
NavigationDestination(
label: 'Notifications',
icon: Icon(Icons.notifications_outlined),
label: 'Settings',
icon: Icon(Icons.settings_outlined),
selectedIcon: Icon(Icons.settings),
),
],
onDestinationSelected: onDestinationSelected,
),
drawer: Drawer(
child: Column(
children: [
DrawerHeader(
child: AppDrawer(),
),
],
),
),
);
}
}
@@ -262,10 +269,6 @@ class ScaffoldWithNavigationRail extends StatelessWidget {
label: Text('Profile'),
icon: Icon(Icons.person_outline),
),
const NavigationRailDestination(
label: Text('Notifications'),
icon: Icon(Icons.notifications_outlined),
),
],
),
body,
@@ -275,37 +278,34 @@ class ScaffoldWithNavigationRail extends StatelessWidget {
}
}
// const borderColor = Color(0xFF805306);
// const sidebarColor = Color(0xFFF6A00C);
// const backgroundStartColor = Color(0xFFFFD500);
// const backgroundEndColor = Color(0xFFF6A00C);
// final buttonColors = WindowButtonColors(
// iconNormal: const Color(0xFF805306),
// mouseOver: const Color(0xFFF6A00C),
// mouseDown: const Color(0xFF805306),
// iconMouseOver: const Color(0xFF805306),
// iconMouseDown: const Color(0xFFFFD500));
// final closeButtonColors = WindowButtonColors(
// mouseOver: const Color(0xFFD32F2F),
// mouseDown: const Color(0xFFB71C1C),
// iconNormal: const Color(0xFF805306),
// iconMouseOver: Colors.white);
// class WindowButtons extends StatelessWidget {
// const WindowButtons({super.key});
// @override
// Widget build(BuildContext context) {
// return Row(
// children: [
// MinimizeWindowButton(colors: buttonColors),
// MaximizeWindowButton(colors: buttonColors),
// CloseWindowButton(colors: closeButtonColors),
// ],
// );
// }
// }
CustomTransitionPage buildWithSlideTransition<T>({
required BuildContext context,
required GoRouterState state,
required Widget child,
}) {
return CustomTransitionPage<T>(
key: state.pageKey,
child: child,
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return SlideTransition(
position:
Tween<Offset>(begin: const Offset(1, 0), end: Offset.zero).animate(
CurvedAnimation(
parent: animation,
curve: Curves.fastOutSlowIn,
),
),
child: AnimatedBuilder(
animation: animation,
builder: (context, child) {
return Container(
color: context.theme.colorScheme.background,
child: child,
);
},
child: child,
),
);
},
);
}
+16 -55
View File
@@ -4,70 +4,31 @@
// ignore_for_file: directives_ordering, top_level_function_literal_block, depend_on_referenced_packages
import 'package:flutter_data/flutter_data.dart';
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/app.dart';
import 'package:zapstore/models/file_metadata.dart';
import 'package:zapstore/models/release.dart';
import 'package:zapstore/models/user.dart';
// ignore: prefer_function_declarations_over_variables
ConfigureRepositoryLocalStorage configureRepositoryLocalStorage = ({FutureFn<String>? baseDirFn, List<int>? encryptionKey, LocalStorageClearStrategy? clear}) {
if (!kIsWeb) {
baseDirFn ??= () => getApplicationDocumentsDirectory().then((dir) => dir.path);
} else {
baseDirFn ??= () => '';
}
return hiveLocalStorageProvider.overrideWith(
(ref) => HiveLocalStorage(
hive: ref.read(hiveProvider),
baseDirFn: baseDirFn,
encryptionKey: encryptionKey,
clear: clear,
),
);
final adapterProvidersMap = <String, Provider<Adapter<DataModelMixin>>>{
'apps': appsAdapterProvider,
'fileMetadata': fileMetadataAdapterProvider,
'releases': releasesAdapterProvider,
'users': usersAdapterProvider
};
final repositoryProviders = <String, Provider<Repository<DataModelMixin>>>{
'fileMetadata': fileMetadataRepositoryProvider,
'releases': releasesRepositoryProvider,
'users': usersRepositoryProvider
};
final repositoryInitializerProvider =
FutureProvider<RepositoryInitializer>((ref) async {
DataHelpers.setInternalType<FileMetadata>('fileMetadata');
DataHelpers.setInternalType<Release>('releases');
DataHelpers.setInternalType<User>('users');
final adapters = <String, RemoteAdapter>{'fileMetadata': ref.watch(internalFileMetadataRemoteAdapterProvider), 'releases': ref.watch(internalReleasesRemoteAdapterProvider), 'users': ref.watch(internalUsersRemoteAdapterProvider)};
final remotes = <String, bool>{'fileMetadata': true, 'releases': true, 'users': true};
await ref.watch(graphNotifierProvider).initialize();
// initialize and register
for (final type in repositoryProviders.keys) {
final repository = ref.read(repositoryProviders[type]!);
repository.dispose();
await repository.initialize(
remote: remotes[type],
adapters: adapters,
);
internalRepositories[type] = repository;
}
return RepositoryInitializer();
});
extension RepositoryWidgetRefX on WidgetRef {
Repository<FileMetadata> get fileMetadata => watch(fileMetadataRepositoryProvider)..remoteAdapter.internalWatch = watch;
Repository<Release> get releases => watch(releasesRepositoryProvider)..remoteAdapter.internalWatch = watch;
Repository<User> get users => watch(usersRepositoryProvider)..remoteAdapter.internalWatch = watch;
extension AdapterWidgetRefX on WidgetRef {
Adapter<App> get apps => watch(appsAdapterProvider)..internalWatch = watch;
Adapter<FileMetadata> get fileMetadata => watch(fileMetadataAdapterProvider)..internalWatch = watch;
Adapter<Release> get releases => watch(releasesAdapterProvider)..internalWatch = watch;
Adapter<User> get users => watch(usersAdapterProvider)..internalWatch = watch;
}
extension RepositoryRefX on Ref {
extension AdapterRefX on Ref {
Repository<FileMetadata> get fileMetadata => watch(fileMetadataRepositoryProvider)..remoteAdapter.internalWatch = watch as Watcher;
Repository<Release> get releases => watch(releasesRepositoryProvider)..remoteAdapter.internalWatch = watch as Watcher;
Repository<User> get users => watch(usersRepositoryProvider)..remoteAdapter.internalWatch = watch as Watcher;
Adapter<App> get apps => watch(appsAdapterProvider)..internalWatch = watch as Watcher;
Adapter<FileMetadata> get fileMetadata => watch(fileMetadataAdapterProvider)..internalWatch = watch as Watcher;
Adapter<Release> get releases => watch(releasesAdapterProvider)..internalWatch = watch as Watcher;
Adapter<User> get users => watch(usersAdapterProvider)..internalWatch = watch as Watcher;
}
+38
View File
@@ -0,0 +1,38 @@
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/release.dart';
import 'package:zapstore/models/user.dart';
part 'app.g.dart';
@JsonSerializable()
@DataAdapter([NostrAdapter, CoolAdapter])
class App extends ZapstoreEvent<App> with BaseApp, EquatableMixin {
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);
}
}
+86
View File
@@ -0,0 +1,86 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'app.dart';
// **************************************************************************
// AdapterGenerator
// **************************************************************************
// ignore_for_file: non_constant_identifier_names, duplicate_ignore
mixin _$AppAdapter on Adapter<App> {
static final Map<String, RelationshipMeta> _kAppRelationshipMetas = {
'releases': RelationshipMeta<Release>(
name: 'releases',
inverseName: 'app',
type: 'releases',
kind: 'HasMany',
instance: (_) => (_ as App).releases,
)
};
@override
Map<String, RelationshipMeta> get relationshipMetas => _kAppRelationshipMetas;
@override
App deserializeLocal(map, {String? key}) {
map = transformDeserialize(map);
return internalWrapStopInit(() => App.fromJson(map), key: key);
}
@override
Map<String, dynamic> serializeLocal(model, {bool withRelationships = true}) {
final map = _$AppToJson(model);
return transformSerialize(map, withRelationships: withRelationships);
}
}
final _appsFinders = <String, dynamic>{};
class $AppAdapter = Adapter<App>
with _$AppAdapter, NostrAdapter<App>, CoolAdapter;
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> {
RelationshipGraphNode<Release> get releases {
final meta = _$AppAdapter._kAppRelationshipMetas['releases']
as RelationshipMeta<Release>;
return meta.clone(
parent: this is RelationshipMeta ? this as RelationshipMeta : null);
}
}
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
App _$AppFromJson(Map<String, dynamic> json) => App()
..id = json['id']
..pubkey = json['pubkey'] as String
..createdAt = DateTime.parse(json['createdAt'] as String)
..content = json['content'] as String
..kind = json['kind'] as int
..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>);
Map<String, dynamic> _$AppToJson(App instance) => <String, dynamic>{
'id': instance.id,
'pubkey': instance.pubkey,
'createdAt': instance.createdAt.toIso8601String(),
'content': instance.content,
'kind': instance.kind,
'tags': instance.tags,
'signature': instance.signature,
'releases': instance.releases,
};
+8 -17
View File
@@ -4,7 +4,8 @@ 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:ndk/ndk.dart' as ndk;
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/release.dart';
@@ -16,17 +17,9 @@ part 'file_metadata.g.dart';
final packageManager = AndroidPackageManager();
@DataRepository([NostrAdapter, FileMetadataAdapter],
fromJson: 'FileMetadata.fromMapFactory(map)', toJson: 'model.toMap()')
class FileMetadata extends BaseEvent<FileMetadata> with ndk.FileMetadata {
FileMetadata.fromMap(super.map) : super.fromMap();
factory FileMetadata.fromMapFactory(Map<String, dynamic> map) {
final m = FileMetadata.fromMap(map);
m.author = BelongsTo<User>.fromJson(map['author']);
return m;
}
@JsonSerializable()
@DataAdapter([NostrAdapter, FileMetadataAdapter])
class FileMetadata extends ZapstoreEvent<FileMetadata> with BaseFileMetadata {
late final BelongsTo<User> author;
late final BelongsTo<Release> release = BelongsTo();
@@ -66,16 +59,14 @@ class FileMetadata extends BaseEvent<FileMetadata> with ndk.FileMetadata {
}
}
mixin FileMetadataAdapter on RemoteAdapter<FileMetadata> {
mixin FileMetadataAdapter on Adapter<FileMetadata> {
@override
Future<DeserializedData<FileMetadata>> deserialize(Object? data) async {
DeserializedData<FileMetadata> deserialize(Object? data, {String? key}) {
final list = data is Iterable ? data : [data as Map];
for (final e in list) {
final map = e as Map<String, dynamic>;
map['author'] = map['pubkey'];
}
final result = await super.deserialize(data);
print(result);
return result;
return super.deserialize(data);
}
}
+44 -26
View File
@@ -3,12 +3,12 @@
part of 'file_metadata.dart';
// **************************************************************************
// RepositoryGenerator
// AdapterGenerator
// **************************************************************************
// ignore_for_file: non_constant_identifier_names, duplicate_ignore
mixin $FileMetadataLocalAdapter on LocalAdapter<FileMetadata> {
mixin _$FileMetadataAdapter on Adapter<FileMetadata> {
static final Map<String, RelationshipMeta> _kFileMetadataRelationshipMetas = {
'author': RelationshipMeta<User>(
name: 'author',
@@ -30,56 +30,74 @@ mixin $FileMetadataLocalAdapter on LocalAdapter<FileMetadata> {
_kFileMetadataRelationshipMetas;
@override
FileMetadata deserialize(map) {
FileMetadata deserializeLocal(map, {String? key}) {
map = transformDeserialize(map);
return FileMetadata.fromMapFactory(map);
return internalWrapStopInit(() => _$FileMetadataFromJson(map), key: key);
}
@override
Map<String, dynamic> serialize(model, {bool withRelationships = true}) {
final map = model.toMap();
Map<String, dynamic> serializeLocal(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 $FileMetadataAdapter = Adapter<FileMetadata>
with _$FileMetadataAdapter, NostrAdapter<FileMetadata>, FileMetadataAdapter;
class $FileMetadataRemoteAdapter = RemoteAdapter<FileMetadata>
with NostrAdapter<FileMetadata>, FileMetadataAdapter;
final fileMetadataAdapterProvider = Provider<Adapter<FileMetadata>>(
(ref) => $FileMetadataAdapter(ref, InternalHolder(_fileMetadataFinders)));
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> {
extension FileMetadataAdapterX on Adapter<FileMetadata> {
NostrAdapter<FileMetadata> get nostrAdapter =>
remoteAdapter as NostrAdapter<FileMetadata>;
FileMetadataAdapter get fileMetadataAdapter =>
remoteAdapter as FileMetadataAdapter;
this as NostrAdapter<FileMetadata>;
FileMetadataAdapter get fileMetadataAdapter => this as FileMetadataAdapter;
}
extension FileMetadataRelationshipGraphNodeX
on RelationshipGraphNode<FileMetadata> {
RelationshipGraphNode<User> get author {
final meta = $FileMetadataLocalAdapter
._kFileMetadataRelationshipMetas['author'] as RelationshipMeta<User>;
final meta = _$FileMetadataAdapter._kFileMetadataRelationshipMetas['author']
as RelationshipMeta<User>;
return meta.clone(
parent: this is RelationshipMeta ? this as RelationshipMeta : null);
}
RelationshipGraphNode<Release> get release {
final meta =
$FileMetadataLocalAdapter._kFileMetadataRelationshipMetas['release']
_$FileMetadataAdapter._kFileMetadataRelationshipMetas['release']
as RelationshipMeta<Release>;
return meta.clone(
parent: this is RelationshipMeta ? this as RelationshipMeta : null);
}
}
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
FileMetadata _$FileMetadataFromJson(Map<String, dynamic> json) => FileMetadata()
..id = json['id']
..pubkey = json['pubkey'] as String
..createdAt = DateTime.parse(json['createdAt'] as String)
..content = json['content'] as String
..kind = json['kind'] as int
..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>);
Map<String, dynamic> _$FileMetadataToJson(FileMetadata instance) =>
<String, dynamic>{
'id': instance.id,
'pubkey': instance.pubkey,
'createdAt': instance.createdAt.toIso8601String(),
'content': instance.content,
'kind': instance.kind,
'tags': instance.tags,
'signature': instance.signature,
'author': instance.author,
};
+18 -19
View File
@@ -1,29 +1,22 @@
import 'package:flutter_data/flutter_data.dart';
import 'package:ndk/ndk.dart' as ndk;
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/user.dart';
part 'release.g.dart';
@DataRepository([NostrAdapter, ReleaseAdapter],
fromJson: 'Release.fromMapFactory(map)', toJson: 'model.toMap()')
class Release extends BaseEvent<Release> with ndk.Release {
Release.fromMap(super.map) : super.fromMap();
String get identifier => tagMap['i']!.first;
factory Release.fromMapFactory(Map<String, dynamic> map) {
final m = Release.fromMap(map);
m.artifacts = HasMany<FileMetadata>.fromJson(map['artifacts']);
return m;
}
@JsonSerializable()
@DataAdapter([NostrAdapter, ReleaseAdapter])
class Release extends ZapstoreEvent<Release> with BaseRelease {
late final HasMany<FileMetadata> artifacts;
late final BelongsTo<App> app;
}
mixin ReleaseAdapter on RemoteAdapter<Release> {
mixin ReleaseAdapter on Adapter<Release> {
@override
Future<DeserializedData<Release>> deserialize(Object? data) {
DeserializedData<Release> deserialize(Object? data, {String? key}) {
final list = data is Iterable ? data : [data as Map];
for (final e in list) {
final map = e as Map<String, dynamic>;
@@ -31,9 +24,15 @@ mixin ReleaseAdapter on RemoteAdapter<Release> {
.where((e) => e.first == 'e')
.map((e) => e[1].toString());
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;
}
final result = super.deserialize(data);
return result;
return super.deserialize(data);
}
}
+58 -23
View File
@@ -3,12 +3,12 @@
part of 'release.dart';
// **************************************************************************
// RepositoryGenerator
// AdapterGenerator
// **************************************************************************
// ignore_for_file: non_constant_identifier_names, duplicate_ignore
mixin $ReleaseLocalAdapter on LocalAdapter<Release> {
mixin _$ReleaseAdapter on Adapter<Release> {
static final Map<String, RelationshipMeta> _kReleaseRelationshipMetas = {
'artifacts': RelationshipMeta<FileMetadata>(
name: 'artifacts',
@@ -16,6 +16,13 @@ mixin $ReleaseLocalAdapter on LocalAdapter<Release> {
type: 'fileMetadata',
kind: 'HasMany',
instance: (_) => (_ as Release).artifacts,
),
'app': RelationshipMeta<App>(
name: 'app',
inverseName: 'releases',
type: 'apps',
kind: 'BelongsTo',
instance: (_) => (_ as Release).app,
)
};
@@ -24,45 +31,73 @@ mixin $ReleaseLocalAdapter on LocalAdapter<Release> {
_kReleaseRelationshipMetas;
@override
Release deserialize(map) {
Release deserializeLocal(map, {String? key}) {
map = transformDeserialize(map);
return Release.fromMapFactory(map);
return internalWrapStopInit(() => _$ReleaseFromJson(map), key: key);
}
@override
Map<String, dynamic> serialize(model, {bool withRelationships = true}) {
final map = model.toMap();
Map<String, dynamic> serializeLocal(model, {bool withRelationships = true}) {
final map = _$ReleaseToJson(model);
return transformSerialize(map, withRelationships: withRelationships);
}
}
final _releasesFinders = <String, dynamic>{};
// ignore: must_be_immutable
class $ReleaseHiveLocalAdapter = HiveLocalAdapter<Release>
with $ReleaseLocalAdapter;
class $ReleaseAdapter = Adapter<Release>
with _$ReleaseAdapter, NostrAdapter<Release>, ReleaseAdapter;
class $ReleaseRemoteAdapter = RemoteAdapter<Release>
with NostrAdapter<Release>, ReleaseAdapter;
final releasesAdapterProvider = Provider<Adapter<Release>>(
(ref) => $ReleaseAdapter(ref, InternalHolder(_releasesFinders)));
final internalReleasesRemoteAdapterProvider = Provider<RemoteAdapter<Release>>(
(ref) => $ReleaseRemoteAdapter(
$ReleaseHiveLocalAdapter(ref), InternalHolder(_releasesFinders)));
final releasesRepositoryProvider =
Provider<Repository<Release>>((ref) => Repository<Release>(ref));
extension ReleaseDataRepositoryX on Repository<Release> {
NostrAdapter<Release> get nostrAdapter =>
remoteAdapter as NostrAdapter<Release>;
ReleaseAdapter get releaseAdapter => remoteAdapter as ReleaseAdapter;
extension ReleaseAdapterX on Adapter<Release> {
NostrAdapter<Release> get nostrAdapter => this as NostrAdapter<Release>;
ReleaseAdapter get releaseAdapter => this as ReleaseAdapter;
}
extension ReleaseRelationshipGraphNodeX on RelationshipGraphNode<Release> {
RelationshipGraphNode<FileMetadata> get artifacts {
final meta = $ReleaseLocalAdapter._kReleaseRelationshipMetas['artifacts']
final meta = _$ReleaseAdapter._kReleaseRelationshipMetas['artifacts']
as RelationshipMeta<FileMetadata>;
return meta.clone(
parent: this is RelationshipMeta ? this as RelationshipMeta : null);
}
RelationshipGraphNode<App> get app {
final meta = _$ReleaseAdapter._kReleaseRelationshipMetas['app']
as RelationshipMeta<App>;
return meta.clone(
parent: this is RelationshipMeta ? this as RelationshipMeta : null);
}
}
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Release _$ReleaseFromJson(Map<String, dynamic> json) => Release()
..id = json['id']
..pubkey = json['pubkey'] as String
..createdAt = DateTime.parse(json['createdAt'] as String)
..content = json['content'] as String
..kind = json['kind'] as int
..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>);
Map<String, dynamic> _$ReleaseToJson(Release instance) => <String, dynamic>{
'id': instance.id,
'pubkey': instance.pubkey,
'createdAt': instance.createdAt.toIso8601String(),
'content': instance.content,
'kind': instance.kind,
'tags': instance.tags,
'signature': instance.signature,
'artifacts': instance.artifacts,
'app': instance.app,
};
+53 -69
View File
@@ -1,23 +1,19 @@
import 'package:equatable/equatable.dart';
import 'package:flutter_data/flutter_data.dart';
import 'package:ndk/ndk.dart' as ndk;
import 'package:json_annotation/json_annotation.dart';
import 'package:purplebase/purplebase.dart';
part 'user.g.dart';
abstract class BaseEvent<T extends BaseEvent<T>> = ndk.Event
abstract class ZapstoreEvent<T extends ZapstoreEvent<T>> = BaseEvent
with DataModelMixin<T>;
@DataRepository([NostrAdapter, UserAdapter],
fromJson: 'User.fromMapFactory(map)', toJson: 'model.toMap()')
class User extends BaseEvent<User> with ndk.User, EquatableMixin {
@JsonSerializable()
@DataAdapter([NostrAdapter, UserAdapter])
class User extends ZapstoreEvent<User> with BaseUser, EquatableMixin {
User();
User.fromMap(super.map) : super.fromMap();
factory User.fromMapFactory(Map<String, dynamic> map) {
final user = User.fromMap(map);
user.following = HasMany<User>.fromJson(map['following']);
return user;
}
@DataRelationship(inverse: 'followers')
late final HasMany<User> following;
@DataRelationship(inverse: 'following')
@@ -29,14 +25,14 @@ class User extends BaseEvent<User> with ndk.User, EquatableMixin {
mixin UserAdapter on NostrAdapter<User> {
@override
Future<DeserializedData<User>> deserialize(Object? data) async {
DeserializedData<User> deserialize(Object? data, {String? key}) {
final users = <User>[];
final list = data is Iterable ? data : [data as Map];
for (final e in list) {
final map = e as Map<String, dynamic>;
if (map['kind'] == 3) {
for (final [_, id, ..._] in map['tags'] as Iterable) {
users.add(User.fromMapFactory({
users.add(User.fromMap({
'id': id,
'content': '',
'pubkey': id,
@@ -57,7 +53,7 @@ mixin UserAdapter on NostrAdapter<User> {
if (users.isNotEmpty) {
map['following'] = users.map((e) => e.id).toList();
}
final data0 = await super.deserialize(map);
final data0 = super.deserialize(map);
final user = data0.model;
if (user != null) {
users.add(user);
@@ -70,86 +66,80 @@ mixin UserAdapter on NostrAdapter<User> {
@override
Future<User?> findOne(Object id,
{bool? remote,
bool? background,
{bool remote = true,
bool background = false,
Map<String, dynamic>? params,
Map<String, String>? headers,
OnSuccessOne<User>? onSuccess,
OnErrorOne<User>? onError,
DataRequestLabel? label}) async {
if (remote == false) {
final key = graph.getKeyForId(internalType, id.toString());
return localAdapter.findOne(key!);
String? publicKey;
try {
publicKey = '$id'.hexKey;
} catch (_) {
String username;
String domain;
try {
[username, domain] = id.toString().split('@');
} catch (_) {
return null;
}
publicKey = await sendRequest<dynamic>(
Uri.parse('https://$domain/.well-known/nostr.json?name=$username'),
onSuccess: (response, label) {
return (response.body as Map)['names']?[username];
},
);
}
final req = ndk.Req(
authors: {id.toString()},
if (publicKey == null) {
return null;
}
final req = RelayRequest(
authors: {publicKey},
kinds: {kind, ...?params?.remove('kinds')},
tags: params ?? {},
);
print('[findOne] req $req');
final result = await notifier.query(req);
final data = await deserialize(result);
for (final m in data.models) {
print('saving model of kind ${m.kind}: ${m.content}');
m.init().saveLocal();
}
return data.models.firstWhere((e) => e.id == id);
final result =
await notifier.query(req, relayUrls: ['wss://relay.nostr.band']);
final data = deserialize(result);
return data.model?..saveLocal();
}
}
mixin NostrAdapter<T extends BaseEvent<T>> on RemoteAdapter<T> {
// void Function()? _closeSub;
late final ndk.FrameNotifier notifier;
@override
Future<void> onInitialized() async {
notifier = ref.read(ndk.frameProvider.notifier);
// _closeSub = notifier.addListener((state) async {
// if (state is ndk.EventFrame &&
// typeKind[internalType] == state.event['kind']) {
// final data = await deserialize(state.event);
// for (final m in [...data.models, ...data.included]) {
// print(
// 'r deserialize/save (k:${(m as BaseEvent).kind}) ${m.id} for type $internalType');
// // DataModel.adapterFor(m).saveLocal(m.init());
// }
// }
// });
super.onInitialized();
}
// @override
// void dispose() {
// _closeSub?.call();
// super.dispose();
// }
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
Future<DeserializedData<T>> deserialize(Object? data) async {
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 = await super.deserialize(map);
final newData = super.deserialize(map);
models.addAll(newData.models as Iterable<T>);
} else {
final newData = await adapters[xType]!.deserialize(map);
final newData = adapters[xType]!.deserialize(map);
included.addAll(newData.models as Iterable<DataModelMixin>);
}
}
@@ -171,22 +161,16 @@ mixin NostrAdapter<T extends BaseEvent<T>> on RemoteAdapter<T> {
OnErrorAll<T>? onError,
DataRequestLabel? label}) async {
if (remote == false) {
return localAdapter.findAll();
return findAllLocal();
}
final req = ndk.Req(
final req = RelayRequest(
kinds: {kind, ...?params?.remove('kinds')},
tags: params ?? {},
);
print('req $req');
final result = await notifier.query(req);
final data = await deserialize(result);
print('req result ${data.models.length}');
for (final m in data.models) {
print('saving model of kind ${m.kind}: ${m.content}');
m.init().saveLocal();
}
return data.models;
final deserialized = await deserializeAsync(result, save: true);
return deserialized.models;
}
}
+43 -22
View File
@@ -3,12 +3,12 @@
part of 'user.dart';
// **************************************************************************
// RepositoryGenerator
// AdapterGenerator
// **************************************************************************
// ignore_for_file: non_constant_identifier_names, duplicate_ignore
mixin $UserLocalAdapter on LocalAdapter<User> {
mixin _$UserAdapter on Adapter<User> {
static final Map<String, RelationshipMeta> _kUserRelationshipMetas = {
'following': RelationshipMeta<User>(
name: 'following',
@@ -31,50 +31,71 @@ mixin $UserLocalAdapter on LocalAdapter<User> {
_kUserRelationshipMetas;
@override
User deserialize(map) {
User deserializeLocal(map, {String? key}) {
map = transformDeserialize(map);
return User.fromMapFactory(map);
return internalWrapStopInit(() => _$UserFromJson(map), key: key);
}
@override
Map<String, dynamic> serialize(model, {bool withRelationships = true}) {
final map = model.toMap();
Map<String, dynamic> serializeLocal(model, {bool withRelationships = true}) {
final map = _$UserToJson(model);
return transformSerialize(map, withRelationships: withRelationships);
}
}
final _usersFinders = <String, dynamic>{};
// ignore: must_be_immutable
class $UserHiveLocalAdapter = HiveLocalAdapter<User> with $UserLocalAdapter;
class $UserAdapter = Adapter<User>
with _$UserAdapter, NostrAdapter<User>, UserAdapter;
class $UserRemoteAdapter = RemoteAdapter<User>
with NostrAdapter<User>, UserAdapter;
final usersAdapterProvider = Provider<Adapter<User>>(
(ref) => $UserAdapter(ref, InternalHolder(_usersFinders)));
final internalUsersRemoteAdapterProvider = Provider<RemoteAdapter<User>>(
(ref) => $UserRemoteAdapter(
$UserHiveLocalAdapter(ref), InternalHolder(_usersFinders)));
final usersRepositoryProvider =
Provider<Repository<User>>((ref) => Repository<User>(ref));
extension UserDataRepositoryX on Repository<User> {
NostrAdapter<User> get nostrAdapter => remoteAdapter as NostrAdapter<User>;
UserAdapter get userAdapter => remoteAdapter as UserAdapter;
extension UserAdapterX on Adapter<User> {
NostrAdapter<User> get nostrAdapter => this as NostrAdapter<User>;
UserAdapter get userAdapter => this as UserAdapter;
}
extension UserRelationshipGraphNodeX on RelationshipGraphNode<User> {
RelationshipGraphNode<User> get following {
final meta = $UserLocalAdapter._kUserRelationshipMetas['following']
final meta = _$UserAdapter._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']
final meta = _$UserAdapter._kUserRelationshipMetas['followers']
as RelationshipMeta<User>;
return meta.clone(
parent: this is RelationshipMeta ? this as RelationshipMeta : null);
}
}
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
User _$UserFromJson(Map<String, dynamic> json) => User()
..id = json['id']
..pubkey = json['pubkey'] as String
..createdAt = DateTime.parse(json['createdAt'] as String)
..content = json['content'] as String
..kind = json['kind'] as int
..tags = (json['tags'] as List<dynamic>)
.map((e) => (e as List<dynamic>).map((e) => e as String).toList())
.toList()
..signature = json['signature'] as String?
..following =
HasMany<User>.fromJson(json['following'] as Map<String, dynamic>);
Map<String, dynamic> _$UserToJson(User instance) => <String, dynamic>{
'id': instance.id,
'pubkey': instance.pubkey,
'createdAt': instance.createdAt.toIso8601String(),
'content': instance.content,
'kind': instance.kind,
'tags': instance.tags,
'signature': instance.signature,
'following': instance.following,
};
+144 -184
View File
@@ -1,212 +1,172 @@
import 'package:async_button_builder/async_button_builder.dart';
import 'package:expansion_tile_card/expansion_tile_card.dart';
// ignore_for_file: prefer_const_literals_to_create_immutables
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_hooks/flutter_hooks.dart';
import 'package:flutter_styled_toast/flutter_styled_toast.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:ndk/ndk.dart' as ndk;
import 'package:url_launcher/url_launcher_string.dart';
import 'package:zapstore/main.data.dart';
import 'package:zapstore/models/file_metadata.dart';
import 'package:zapstore/models/release.dart';
import 'package:zapstore/models/app.dart';
import 'package:zapstore/models/user.dart';
import 'package:zapstore/screens/profile_screen.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 Release release;
final App app;
const AppDetailScreen({
required this.release,
required this.app,
super.key,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
final artifacts = release.artifacts.toList();
return ListView.builder(
shrinkWrap: true,
itemCount: release.artifacts.length,
itemBuilder: (context, index) {
final event = artifacts[index];
return CardWidget(fileMetadata: event);
},
);
}
}
final user = User.fromMap({
'kind': 0,
'created_at': 112899202,
'pubkey':
'a9e95a4eb32b55441b222ae5674f063949bfd0759b82deb03d7cd262e82d5626',
'content': jsonEncode(
{'name': 'test', 'picture': 'https://picsum.photos/200/300'}),
'tags': [],
});
class CardWidget extends HookConsumerWidget {
final FileMetadata fileMetadata;
const CardWidget({super.key, required this.fileMetadata});
User? get author => fileMetadata.author.value;
@override
Widget build(BuildContext context, WidgetRef ref) {
final currentUser = ref.read(loggedInUser);
final fetchUser = useMemoized(() => ref.users.findOne(author!.id!));
useFuture(fetchUser);
final isWebOfTrust = [...?currentUser?.following.toList()]
.contains(fileMetadata.author.value);
return ExpansionTileCard(
leading: ClipRRect(
borderRadius: BorderRadius.circular(15.0),
child: Image.network(
fileMetadata.release.value!.image,
width: 80,
height: 80,
fit: BoxFit.cover,
errorBuilder: (context, err, _) => Text(err.toString()),
),
),
onExpansionChanged: (_) {
ref.users.findOne(author!.id!);
},
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
fileMetadata.content,
overflow: TextOverflow.ellipsis,
maxLines: 1,
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 26),
),
SizedBox(height: 10),
Row(
children: [
CircleAvatar(
backgroundImage: NetworkImage(author?.profilePicture ?? ''),
maxRadius: 10,
),
SizedBox(width: 10),
Text(
'${author?.name ?? author?.id}',
style: TextStyle(fontSize: 14),
),
],
)
],
),
return Column(
children: [
Align(
alignment: Alignment.centerLeft,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16.0,
vertical: 8.0,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (isWebOfTrust)
Text(
'You are following this author!',
style: TextStyle(color: Colors.green),
),
if (!isWebOfTrust)
Expanded(
child: CustomScrollView(
slivers: [
// SliverAppBar(
// pinned: true,
// leading: IconButton(
// icon: Icon(Icons.arrow_back),
// onPressed: () {
// context.pop();
// },
// ),
// ),
SliverList(
delegate: SliverChildListDelegate([
Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text('Author is not in your web of trust',
style: TextStyle(color: Colors.red)),
GestureDetector(
onTap: () => launchUrlString(
'https://primal.net/p/${author?.id.toString().npub}',
CircularImage(
url: 'https://picsum.photos/200/200',
size: 80,
radius: 25,
),
Gap(16),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AutoSizeText(
'Mutiny Wallet',
minFontSize: 16,
style: TextStyle(
fontSize: 28, fontWeight: FontWeight.bold),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
Gap(8),
PillWidget(text: '0.5.6'),
],
),
child: Text(
'see profile',
style: TextStyle(
color: Colors.blue,
decoration: TextDecoration.underline,
),
),
)
),
],
),
AsyncButtonBuilder(
loadingWidget: const Padding(
padding: EdgeInsets.all(8.0),
child: SizedBox(
height: 16.0,
width: 16.0,
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
),
Gap(16),
CarouselSlider(
options: CarouselOptions(
enableInfiniteScroll: false,
),
items: imgList
.map((item) => Padding(
padding: const EdgeInsets.only(right: 10),
child: Image.network(item,
fit: BoxFit.cover, width: 1000),
))
.toList(),
),
successWidget: const Padding(
padding: EdgeInsets.all(4.0),
child: Icon(
Icons.check,
color: Colors.purpleAccent,
Divider(height: 24),
MarkdownBody(
styleSheet: MarkdownStyleSheet(
h1: TextStyle(fontWeight: FontWeight.bold),
h2: TextStyle(fontWeight: FontWeight.bold),
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, ',
),
onPressed: () async {
try {
await fileMetadata.install();
showToastWidget(
Text(
'Success!',
style: TextStyle(fontSize: 20),
Gap(10),
AuthorContainer(user: user, text: 'Built by', oneLine: false),
Gap(10),
Divider(height: 24),
Container(
padding: EdgeInsets.all(10),
decoration: BoxDecoration(
color: Colors.grey[900],
borderRadius: BorderRadius.circular(10),
),
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [Text('Source'), Text('github link')],
),
// ignore: use_build_context_synchronously
context: context,
position: StyledToastPosition.center,
animation: StyledToastAnimation.scale,
reverseAnimation: StyledToastAnimation.fade,
duration: Duration(seconds: 4),
animDuration: Duration(seconds: 1),
curve: Curves.elasticOut,
reverseCurve: Curves.linear);
} on Exception catch (e) {
showToastWidget(
Text(
'Error! $e',
style: TextStyle(fontSize: 20),
),
Padding(
padding: const EdgeInsets.all(8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [Text('Github stars'), Text('456')],
),
// ignore: use_build_context_synchronously
context: context,
position: StyledToastPosition.center,
animation: StyledToastAnimation.scale,
reverseAnimation: StyledToastAnimation.fade,
duration: Duration(seconds: 4),
animDuration: Duration(seconds: 1),
curve: Curves.elasticOut,
reverseCurve: Curves.linear);
}
},
loadingSwitchInCurve: Curves.bounceInOut,
loadingTransitionBuilder: (child, animation) {
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(0, 1.0),
end: const Offset(0, 0),
).animate(animation),
child: child,
);
},
builder: (context, child, callback, state) {
return Material(
color: state.maybeWhen(
success: () => Colors.purple[100],
orElse: () => Colors.black12,
),
// This prevents the loading indicator showing below the
// button
clipBehavior: Clip.hardEdge,
shape: const StadiumBorder(),
child: InkWell(
onTap: callback,
child: child,
),
);
},
child: Padding(
padding: const EdgeInsets.all(12.0),
child: const Icon(Icons.download),
),
),
Padding(
padding: const EdgeInsets.all(8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [Text('Github forks'), Text('3')],
),
)
],
),
)
]),
),
],
),
),
Container(
height: 50.0,
padding: EdgeInsets.all(8),
// color: Colors.blue,
child: Center(
child: Expanded(
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
minimumSize: const Size.fromHeight(50),
),
],
onPressed: () {},
child: const Text('Install'),
),
),
),
),
+82 -41
View File
@@ -1,68 +1,109 @@
// ignore_for_file: prefer_const_literals_to_create_immutables
import 'package:flutter/material.dart';
import 'package:flutter_data/flutter_data.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
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 '../main.data.dart';
import 'package:zapstore/widgets/card.dart';
import 'package:zapstore/widgets/user_avatar.dart';
const kAndroidMimeType = 'application/vnd.android.package-archive';
final searchStateProvider =
StateProvider<DataState<List<App>>>((_) => DataState([]));
final screenInfoProvider = StateProvider(
(ref) => 'Welcome to zap.store!\nUse the search bar to find apps');
class SearchScreen extends HookConsumerWidget {
const SearchScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final searchState = useState<String?>(null);
final labelState = useState<DataRequestLabel?>(null);
final state = ref.watch(searchStateProvider);
final value = ref.fileMetadata.watchAll(remote: false);
final artifacts = value.model
.where((a) => a.release.isPresent && a.mimeType == kAndroidMimeType)
.where((a) => searchState.value != null
? a.release.value!.title
.toLowerCase()
.contains(searchState.value!.toLowerCase())
: true)
.toList();
if (state.isLoading) {
return Center(child: CircularProgressIndicator());
}
return Padding(
padding: const EdgeInsets.all(18.0),
child: Column(
children: [
SearchBar(
elevation: const MaterialStatePropertyAll(2.2),
onSubmitted: (query) async {
searchState.value = query;
final apps = state.model;
await ref.releases.findAll(params: {
'since': 1708658457,
});
await ref.fileMetadata.findAll(
params: {
// 'kinds': {1063},
// 'limit': 20,
// 'search': query,
'#m': [kAndroidMimeType],
'since': 1708658457,
return Column(
children: [
Row(
children: [
GestureDetector(
onTap: () => Scaffold.of(context).openDrawer(),
child: UserAvatar(),
),
Gap(20),
Expanded(
child: SearchBar(
shape: MaterialStateProperty.all(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(28),
),
),
leading: Padding(
padding: const EdgeInsets.fromLTRB(10, 8, 6, 6),
child: Icon(
Icons.search,
color: Colors.blueGrey,
size: 20,
),
),
hintText: 'Search for apps',
hintStyle: MaterialStateProperty.all(
TextStyle(color: Colors.grey[600])),
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});
// final releases = 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(),
// '#m': [kAndroidMimeType],
// },
// );
ref.read(searchStateProvider.notifier).state =
DataState(apps);
},
label: labelState.value,
);
},
),
),
],
),
Gap(10),
if (apps.isEmpty)
Expanded(
child: Center(
child: Text(
ref.read(screenInfoProvider),
textAlign: TextAlign.center,
),
),
),
const SizedBox(height: 20),
if (apps.isNotEmpty)
Expanded(
child: ListView.builder(
shrinkWrap: true,
itemCount: artifacts.length,
itemCount: apps.length,
itemBuilder: (context, index) {
final event = artifacts[index];
return CardWidget(fileMetadata: event);
final app = apps[index];
return CardWidget(app: app);
},
),
),
],
),
],
);
}
}
+19
View File
@@ -0,0 +1,19 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
class SettingsScreen extends HookConsumerWidget {
const SettingsScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final controller = useTextEditingController();
return Expanded(
child: TextField(
controller: controller,
autofocus: true,
maxLines: 10,
),
);
}
}
+19
View File
@@ -0,0 +1,19 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
class UpdatesScreen extends HookConsumerWidget {
const UpdatesScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final controller = useTextEditingController();
return Expanded(
child: TextField(
controller: controller,
autofocus: true,
maxLines: 10,
),
);
}
}
+4
View File
@@ -0,0 +1,4 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:zapstore/models/user.dart';
final loggedInUser = StateProvider<User?>((ref) => null);
+5
View File
@@ -0,0 +1,5 @@
import 'package:flutter/material.dart';
extension ContextX on BuildContext {
ThemeData get theme => Theme.of(this);
}
@@ -1,14 +1,16 @@
import 'package:android_package_manager/android_package_manager.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:ndk/ndk.dart' as ndk;
import 'package:zapstore/main.data.dart';
import 'package:zapstore/models/user.dart';
import 'package:zapstore/services/session_service.dart';
import 'package:zapstore/widgets/card.dart';
final loggedInUser = StateProvider<User?>((ref) => null);
class AppDrawer extends HookConsumerWidget {
AppDrawer({super.key});
class ProfileScreen extends HookConsumerWidget {
const ProfileScreen({super.key});
// final packageManager = AndroidPackageManager();
@override
Widget build(BuildContext context, WidgetRef ref) {
@@ -19,19 +21,30 @@ class ProfileScreen extends HookConsumerWidget {
padding: const EdgeInsets.all(12.0),
child: Column(
children: [
if (u2 == null) TextField(controller: controller),
SizedBox(height: 10),
// Text('npub or NIP-05'),
if (u2 == null) TextField(autocorrect: false, controller: controller),
Gap(5),
if (u2 == null)
ElevatedButton(
onPressed: () async {
final pubkey = controller.text.hexKey;
ref.read(loggedInUser.notifier).state =
await ref.users.findOne(pubkey, params: {
'kinds': {3}
});
await ref.users.findOne(controller.text);
},
child: Text('Log in with npub'),
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'),
),
if (u2 != null)
Expanded(
child: Column(
@@ -39,17 +52,18 @@ class ProfileScreen extends HookConsumerWidget {
Card(
child: ListTile(
dense: true,
leading: CircleAvatar(
backgroundImage: NetworkImage(u2.profilePicture ?? ''),
leading: CircularImage(
size: 20,
url: u2.avatarUrl,
),
title: Text(
u2.name ?? u2.id?.toString() ?? '',
),
subtitle: Text('following ${u2.following.length}'),
// subtitle: Text('following ${u2.following.length}'),
),
),
ElevatedButton(
onPressed: () {
onPressed: () async {
ref.read(loggedInUser.notifier).state = null;
controller.clear();
},
+170
View File
@@ -0,0 +1,170 @@
import 'dart:convert';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:zapstore/models/app.dart';
import 'package:zapstore/models/user.dart';
import 'package:zapstore/widgets/pill_widget.dart';
class CardWidget extends HookConsumerWidget {
final App app;
const CardWidget({super.key, required this.app});
@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),
child: GestureDetector(
onTap: () => context.go('/details', extra: app),
child: Padding(
padding: const EdgeInsets.all(12),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
CircularImage(
url: 'https://picsum.photos/200/200',
size: 80,
radius: 25,
),
Gap(16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Mutiny Wallet - Mutiny Wallet - Muti',
// '${app.name} - ${app.name}',
style:
TextStyle(fontSize: 20, 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',
style: TextStyle(
fontSize: 14, fontWeight: FontWeight.w300),
overflow: TextOverflow.ellipsis,
maxLines: 2,
softWrap: true,
),
),
Gap(10),
AuthorContainer(user: user),
AuthorContainer(user: user),
Gap(16),
TagsContainer(),
],
),
),
],
),
),
),
);
}
}
class TagsContainer extends StatelessWidget {
const TagsContainer({
super.key,
});
@override
Widget build(BuildContext context) {
return Row(
children: const [
PillWidget(text: 'nostr'),
Gap(6),
PillWidget(text: 'wallet'),
],
);
}
}
class AuthorContainer extends StatelessWidget {
final User user;
final String text;
final bool oneLine;
const AuthorContainer({
super.key,
required this.user,
this.text = 'Signed by',
this.oneLine = true,
});
String get name => user.name ?? user.npub;
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.only(top: 4, bottom: 4),
child: Row(
children: [
CircularImage(url: user.avatarUrl, size: oneLine ? 22 : 46),
Gap(10),
if (oneLine) Text('$text $name'),
if (!oneLine)
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(text),
Padding(
padding: const EdgeInsets.only(left: 1),
child:
Text(name, style: TextStyle(fontWeight: FontWeight.bold)),
),
],
)
],
),
);
}
}
class CircularImage extends StatelessWidget {
const CircularImage({
super.key,
this.url,
this.size = 22,
this.radius = 60,
});
final String? url;
final int size;
final int radius;
@override
Widget build(BuildContext context) {
return ClipRRect(
borderRadius: BorderRadius.circular(radius.toDouble()),
child: Container(
width: size.toDouble(),
height: size.toDouble(),
color: Colors.grey[850],
child: CachedNetworkImage(
imageUrl: url ?? '',
fit: BoxFit.cover,
),
),
);
}
}
+25
View File
@@ -0,0 +1,25 @@
import 'package:flutter/material.dart';
class PillWidget extends StatelessWidget {
final String text;
const PillWidget({super.key, required this.text});
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(12),
),
child: Text(
text,
style: TextStyle(
color: Colors.white,
fontSize: 11,
fontWeight: FontWeight.bold,
),
),
);
}
}
+18
View File
@@ -0,0 +1,18 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:zapstore/services/session_service.dart';
class UserAvatar extends HookConsumerWidget {
const UserAvatar({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final user = ref.watch(loggedInUser);
return CircleAvatar(
radius: 20,
// backgroundImage: AssetImage('assets/images/logo.png'),
backgroundColor: Colors.grey[850],
foregroundImage: user != null ? NetworkImage(user.avatarUrl!) : null,
);
}
}
@@ -8,6 +8,7 @@
#include <handy_window/handy_window_plugin.h>
#include <screen_retriever/screen_retriever_plugin.h>
#include <sqlite3_flutter_libs/sqlite3_flutter_libs_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h>
#include <window_manager/window_manager_plugin.h>
@@ -18,6 +19,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) screen_retriever_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverPlugin");
screen_retriever_plugin_register_with_registrar(screen_retriever_registrar);
g_autoptr(FlPluginRegistrar) sqlite3_flutter_libs_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "Sqlite3FlutterLibsPlugin");
sqlite3_flutter_libs_plugin_register_with_registrar(sqlite3_flutter_libs_registrar);
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
+1
View File
@@ -5,6 +5,7 @@
list(APPEND FLUTTER_PLUGIN_LIST
handy_window
screen_retriever
sqlite3_flutter_libs
url_launcher_linux
window_manager
)
@@ -7,12 +7,16 @@ import Foundation
import path_provider_foundation
import screen_retriever
import sqflite
import sqlite3_flutter_libs
import url_launcher_macos
import window_manager
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
ScreenRetrieverPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverPlugin"))
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
Sqlite3FlutterLibsPlugin.register(with: registry.registrar(forPlugin: "Sqlite3FlutterLibsPlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin"))
}
+38 -1
View File
@@ -5,6 +5,26 @@ PODS:
- FlutterMacOS
- screen_retriever (0.0.1):
- FlutterMacOS
- sqflite (0.0.3):
- Flutter
- FlutterMacOS
- "sqlite3 (3.45.3+1)":
- "sqlite3/common (= 3.45.3+1)"
- "sqlite3/common (3.45.3+1)"
- "sqlite3/fts5 (3.45.3+1)":
- sqlite3/common
- "sqlite3/perf-threadsafe (3.45.3+1)":
- sqlite3/common
- "sqlite3/rtree (3.45.3+1)":
- sqlite3/common
- sqlite3_flutter_libs (0.0.1):
- FlutterMacOS
- "sqlite3 (~> 3.45.3+1)"
- sqlite3/fts5
- sqlite3/perf-threadsafe
- sqlite3/rtree
- url_launcher_macos (0.0.1):
- FlutterMacOS
- window_manager (0.2.0):
- FlutterMacOS
@@ -12,8 +32,15 @@ DEPENDENCIES:
- FlutterMacOS (from `Flutter/ephemeral`)
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
- screen_retriever (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos`)
- sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/darwin`)
- sqlite3_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/macos`)
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
- window_manager (from `Flutter/ephemeral/.symlinks/plugins/window_manager/macos`)
SPEC REPOS:
trunk:
- sqlite3
EXTERNAL SOURCES:
FlutterMacOS:
:path: Flutter/ephemeral
@@ -21,6 +48,12 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
screen_retriever:
:path: Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos
sqflite:
:path: Flutter/ephemeral/.symlinks/plugins/sqflite/darwin
sqlite3_flutter_libs:
:path: Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/macos
url_launcher_macos:
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
window_manager:
:path: Flutter/ephemeral/.symlinks/plugins/window_manager/macos
@@ -28,8 +61,12 @@ SPEC CHECKSUMS:
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c
screen_retriever: 59634572a57080243dd1bf715e55b6c54f241a38
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
sqlite3: 02d1f07eaaa01f80a1c16b4b31dfcbb3345ee01a
sqlite3_flutter_libs: 8d204ef443cf0d5c1c8b058044eab53f3943a9c5
url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95
window_manager: 3a1844359a6295ab1e47659b1a777e36773cd6e8
PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367
COCOAPODS: 1.14.3
COCOAPODS: 1.15.2
+207 -88
View File
@@ -38,18 +38,18 @@ packages:
dependency: transitive
description:
name: archive
sha256: "22600aa1e926be775fa5fe7e6894e7fb3df9efda8891c73f70fb3262399a432d"
sha256: "0763b45fa9294197a2885c8567927e2830ade852e5c896fd4ab7e0e348d0f373"
url: "https://pub.dev"
source: hosted
version: "3.4.10"
version: "3.5.0"
args:
dependency: transitive
description:
name: args
sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596
sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a"
url: "https://pub.dev"
source: hosted
version: "2.4.2"
version: "2.5.0"
async:
dependency: transitive
description:
@@ -66,6 +66,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.0.0+1"
auto_size_text:
dependency: "direct main"
description:
name: auto_size_text
sha256: "3f5261cd3fb5f2a9ab4e2fc3fba84fd9fcaac8821f20a1d4e71f557521b22599"
url: "https://pub.dev"
source: hosted
version: "3.0.0"
bech32:
dependency: transitive
description:
@@ -126,10 +134,10 @@ packages:
dependency: "direct main"
description:
name: build_runner
sha256: "581bacf68f89ec8792f5e5a0b2c4decd1c948e97ce659dc783688c8a88fbec21"
sha256: "3ac61a79bfb6f6cc11f693591063a7f19a7af628dc52f141743edac5c16e8c22"
url: "https://pub.dev"
source: hosted
version: "2.4.8"
version: "2.4.9"
build_runner_core:
dependency: transitive
description:
@@ -150,10 +158,42 @@ packages:
dependency: transitive
description:
name: built_value
sha256: fedde275e0a6b798c3296963c5cd224e3e1b55d0e478d5b7e65e6b540f363a0e
sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb
url: "https://pub.dev"
source: hosted
version: "8.9.1"
version: "8.9.2"
cached_network_image:
dependency: "direct main"
description:
name: cached_network_image
sha256: "28ea9690a8207179c319965c13cd8df184d5ee721ae2ce60f398ced1219cea1f"
url: "https://pub.dev"
source: hosted
version: "3.3.1"
cached_network_image_platform_interface:
dependency: transitive
description:
name: cached_network_image_platform_interface
sha256: "9e90e78ae72caa874a323d78fa6301b3fb8fa7ea76a8f96dc5b5bf79f283bf2f"
url: "https://pub.dev"
source: hosted
version: "4.0.0"
cached_network_image_web:
dependency: transitive
description:
name: cached_network_image_web
sha256: "205d6a9f1862de34b93184f22b9d2d94586b2f05c581d546695e3d8f6a805cd7"
url: "https://pub.dev"
source: hosted
version: "1.2.0"
carousel_slider:
dependency: "direct main"
description:
name: carousel_slider
sha256: "9c695cc963bf1d04a47bd6021f68befce8970bcd61d24938e1fb0918cf5d9c42"
url: "https://pub.dev"
source: hosted
version: "4.2.1"
characters:
dependency: transitive
description:
@@ -222,18 +262,26 @@ packages:
dependency: "direct main"
description:
name: cupertino_icons
sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d
sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
url: "https://pub.dev"
source: hosted
version: "1.0.6"
version: "1.0.8"
dart_style:
dependency: transitive
description:
name: dart_style
sha256: "40ae61a5d43feea6d24bd22c0537a6629db858963b99b4bc1c3db80676f32368"
sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9"
url: "https://pub.dev"
source: hosted
version: "2.3.4"
version: "2.3.6"
device_apps:
dependency: "direct main"
description:
name: device_apps
sha256: e84dc74d55749993fd671148cc0bd53096e1be0c268fc364285511b1d8a4c19b
url: "https://pub.dev"
source: hosted
version: "2.2.0"
equatable:
dependency: "direct main"
description:
@@ -287,14 +335,21 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
flutter_cache_manager:
dependency: transitive
description:
name: flutter_cache_manager
sha256: "395d6b7831f21f3b989ebedbb785545932adb9afe2622c1ffacf7f4b53a7e544"
url: "https://pub.dev"
source: hosted
version: "3.3.2"
flutter_data:
dependency: "direct main"
description:
name: flutter_data
sha256: ac6aba32a7c67d4f8e1b979ddb33eb3bdad3612378e54446780ca1bfc3003cab
url: "https://pub.dev"
source: hosted
version: "1.6.0"
path: "../flutter_data"
relative: true
source: path
version: "2.0.0-rc3"
flutter_hooks:
dependency: "direct main"
description:
@@ -324,14 +379,22 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
flutter_markdown:
dependency: "direct main"
description:
name: flutter_markdown
sha256: "9921f9deda326f8a885e202b1e35237eadfc1345239a0f6f0f1ff287e047547f"
url: "https://pub.dev"
source: hosted
version: "0.7.1"
flutter_riverpod:
dependency: transitive
description:
name: flutter_riverpod
sha256: "4bce556b7ecbfea26109638d5237684538d4abc509d253e6c5c4c5733b360098"
sha256: "0f1974eff5bbe774bf1d870e406fc6f29e3d6f1c46bd9c58e7172ff68a785d7d"
url: "https://pub.dev"
source: hosted
version: "2.4.10"
version: "2.5.1"
flutter_styled_toast:
dependency: "direct main"
description:
@@ -362,10 +425,18 @@ packages:
dependency: transitive
description:
name: frontend_server_client
sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612"
sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
url: "https://pub.dev"
source: hosted
version: "3.2.0"
version: "4.0.0"
gap:
dependency: "direct main"
description:
name: gap
sha256: f19387d4e32f849394758b91377f9153a1b41d79513ef7668c088c77dbc6955d
url: "https://pub.dev"
source: hosted
version: "3.0.1"
glob:
dependency: transitive
description:
@@ -378,10 +449,10 @@ packages:
dependency: "direct main"
description:
name: go_router
sha256: "170c46e237d6eb0e6e9f0e8b3f56101e14fb64f787016e42edd74c39cf8b176a"
sha256: b465e99ce64ba75e61c8c0ce3d87b66d8ac07f0b35d0a7e0263fcfc10f99e836
url: "https://pub.dev"
source: hosted
version: "13.2.0"
version: "13.2.5"
graphs:
dependency: transitive
description:
@@ -406,22 +477,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.2.1"
hive:
dependency: transitive
description:
name: hive
sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941"
url: "https://pub.dev"
source: hosted
version: "2.2.3"
hooks_riverpod:
dependency: "direct main"
description:
name: hooks_riverpod
sha256: "758b07eba336e3cbacbd81dba481f2228a14102083fdde07045e8514e8054c49"
sha256: "45b2030a18bcd6dbd680c2c91bc3b33e3fe7c323e3acb5ecec93a613e2fbaa8a"
url: "https://pub.dev"
source: hosted
version: "2.4.10"
version: "2.5.1"
http:
dependency: "direct main"
description:
@@ -490,18 +553,18 @@ packages:
dependency: "direct main"
description:
name: json_annotation
sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
url: "https://pub.dev"
source: hosted
version: "4.8.1"
version: "4.9.0"
json_serializable:
dependency: "direct dev"
description:
name: json_serializable
sha256: aa1f5a8912615733e0fdc7a02af03308933c93235bdc8d50d0b0c8a8ccb0b969
sha256: ea1432d167339ea9b5bb153f0571d0039607a873d6e04e0117af043f14a1fd4b
url: "https://pub.dev"
source: hosted
version: "6.7.1"
version: "6.8.0"
leak_tracker:
dependency: transitive
description:
@@ -542,6 +605,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.2.0"
markdown:
dependency: transitive
description:
name: markdown
sha256: ef2a1298144e3f985cc736b22e0ccdaf188b5b3970648f2d9dc13efd1d9df051
url: "https://pub.dev"
source: hosted
version: "7.2.2"
matcher:
dependency: transitive
description:
@@ -574,13 +645,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.5"
ndk:
dependency: "direct main"
octo_image:
dependency: transitive
description:
path: "../ndk"
relative: true
source: path
version: "0.1.0"
name: octo_image
sha256: "45b40f99622f11901238e18d48f5f12ea36426d8eced9f4cbf58479c7aa2430d"
url: "https://pub.dev"
source: hosted
version: "2.0.0"
package_config:
dependency: transitive
description:
@@ -601,18 +673,18 @@ packages:
dependency: "direct main"
description:
name: path_provider
sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b
sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161
url: "https://pub.dev"
source: hosted
version: "2.1.2"
version: "2.1.3"
path_provider_android:
dependency: transitive
description:
name: path_provider_android
sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668"
sha256: a248d8146ee5983446bf03ed5ea8f6533129a12b11f12057ad1b4a67a2b3b41d
url: "https://pub.dev"
source: hosted
version: "2.2.2"
version: "2.2.4"
path_provider_foundation:
dependency: transitive
description:
@@ -649,10 +721,10 @@ packages:
dependency: "direct main"
description:
name: permission_handler
sha256: "74e962b7fad7ff75959161bb2c0ad8fe7f2568ee82621c9c2660b751146bfe44"
sha256: "18bf33f7fefbd812f37e72091a15575e72d5318854877e0e4035a24ac1113ecb"
url: "https://pub.dev"
source: hosted
version: "11.3.0"
version: "11.3.1"
permission_handler_android:
dependency: transitive
description:
@@ -665,10 +737,10 @@ packages:
dependency: transitive
description:
name: permission_handler_apple
sha256: bdafc6db74253abb63907f4e357302e6bb786ab41465e8635f362ee71fd8707b
sha256: e9ad66020b89ff1b63908f247c2c6f931c6e62699b756ef8b3c4569350cd8662
url: "https://pub.dev"
source: hosted
version: "9.4.0"
version: "9.4.4"
permission_handler_html:
dependency: transitive
description:
@@ -681,10 +753,10 @@ packages:
dependency: transitive
description:
name: permission_handler_platform_interface
sha256: "23dfba8447c076ab5be3dee9ceb66aad345c4a648f0cac292c77b1eb0e800b78"
sha256: "48d4fcf201a1dad93ee869ab0d4101d084f49136ec82a8a06ed9cfeacab9fd20"
url: "https://pub.dev"
source: hosted
version: "4.2.0"
version: "4.2.1"
permission_handler_windows:
dependency: transitive
description:
@@ -721,10 +793,10 @@ packages:
dependency: transitive
description:
name: pointycastle
sha256: "43ac87de6e10afabc85c445745a7b799e04de84cebaa4fd7bf55a5e1e9604d29"
sha256: "79fbafed02cfdbe85ef3fd06c7f4bc2cbcba0177e61b765264853d4253b21744"
url: "https://pub.dev"
source: hosted
version: "3.7.4"
version: "3.9.0"
pool:
dependency: transitive
description:
@@ -749,30 +821,29 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.2.3"
recase:
dependency: transitive
purplebase:
dependency: "direct main"
description:
name: recase
sha256: e4eb4ec2dcdee52dcf99cb4ceabaffc631d7424ee55e56f280bc039737f89213
url: "https://pub.dev"
source: hosted
version: "4.1.0"
retry:
dependency: transitive
description:
name: retry
sha256: "822e118d5b3aafed083109c72d5f484c6dc66707885e07c0fbcb8b986bba7efc"
url: "https://pub.dev"
source: hosted
version: "3.1.2"
path: "../purplebase"
relative: true
source: path
version: "0.1.0"
riverpod:
dependency: transitive
description:
name: riverpod
sha256: "548e2192eb7aeb826eb89387f814edb76594f3363e2c0bb99dd733d795ba3589"
sha256: f21b32ffd26a36555e501b04f4a5dca43ed59e16343f1a30c13632b2351dfa4d
url: "https://pub.dev"
source: hosted
version: "2.5.0"
version: "2.5.1"
rxdart:
dependency: transitive
description:
name: rxdart
sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb"
url: "https://pub.dev"
source: hosted
version: "0.27.7"
screen_retriever:
dependency: transitive
description:
@@ -834,6 +905,38 @@ packages:
url: "https://pub.dev"
source: hosted
version: "7.0.0"
sqflite:
dependency: transitive
description:
name: sqflite
sha256: "5ce2e1a15e822c3b4bfb5400455775e421da7098eed8adc8f26298ada7c9308c"
url: "https://pub.dev"
source: hosted
version: "2.3.3"
sqflite_common:
dependency: transitive
description:
name: sqflite_common
sha256: "3da423ce7baf868be70e2c0976c28a1bb2f73644268b7ffa7d2e08eab71f16a4"
url: "https://pub.dev"
source: hosted
version: "2.5.4"
sqlite3:
dependency: transitive
description:
name: sqlite3
sha256: "1abbeb84bf2b1a10e5e1138c913123c8aa9d83cd64e5f9a0dd847b3c83063202"
url: "https://pub.dev"
source: hosted
version: "2.4.2"
sqlite3_flutter_libs:
dependency: "direct main"
description:
name: sqlite3_flutter_libs
sha256: fb2a106a2ea6042fe57de2c47074cc31539a941819c91e105b864744605da3f5
url: "https://pub.dev"
source: hosted
version: "0.5.21"
stack_trace:
dependency: transitive
description:
@@ -874,6 +977,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.2.0"
synchronized:
dependency: transitive
description:
name: synchronized
sha256: "539ef412b170d65ecdafd780f924e5be3f60032a1128df156adad6c5b373d558"
url: "https://pub.dev"
source: hosted
version: "3.1.0+1"
term_glyph:
dependency: transitive
description:
@@ -910,26 +1021,26 @@ packages:
dependency: "direct main"
description:
name: url_launcher
sha256: "0ecc004c62fd3ed36a2ffcbe0dd9700aee63bd7532d0b642a488b1ec310f492e"
sha256: "6ce1e04375be4eed30548f10a315826fd933c1e493206eab82eed01f438c8d2e"
url: "https://pub.dev"
source: hosted
version: "6.2.5"
version: "6.2.6"
url_launcher_android:
dependency: transitive
description:
name: url_launcher_android
sha256: d4ed0711849dd8e33eb2dd69c25db0d0d3fdc37e0a62e629fe32f57a22db2745
sha256: "360a6ed2027f18b73c8d98e159dda67a61b7f2e0f6ec26e86c3ada33b0621775"
url: "https://pub.dev"
source: hosted
version: "6.3.0"
version: "6.3.1"
url_launcher_ios:
dependency: transitive
description:
name: url_launcher_ios
sha256: "75bb6fe3f60070407704282a2d295630cab232991eb52542b18347a8a941df03"
sha256: "9149d493b075ed740901f3ee844a38a00b33116c7c5c10d7fb27df8987fb51d5"
url: "https://pub.dev"
source: hosted
version: "6.2.4"
version: "6.2.5"
url_launcher_linux:
dependency: transitive
description:
@@ -958,10 +1069,10 @@ packages:
dependency: transitive
description:
name: url_launcher_web
sha256: "3692a459204a33e04bc94f5fb91158faf4f2c8903281ddd82915adecdb1a901d"
sha256: "8d9e750d8c9338601e709cd0885f95825086bd8b642547f26bda435aade95d8a"
url: "https://pub.dev"
source: hosted
version: "2.3.0"
version: "2.3.1"
url_launcher_windows:
dependency: transitive
description:
@@ -974,10 +1085,10 @@ packages:
dependency: transitive
description:
name: uuid
sha256: cd210a09f7c18cbe5a02511718e0334de6559871052c90a90c0cca46a4aa81c8
sha256: "814e9e88f21a176ae1359149021870e87f7cddaf633ab678a5d2b0bff7fd1ba8"
url: "https://pub.dev"
source: hosted
version: "4.3.3"
version: "4.4.0"
vector_math:
dependency: transitive
description:
@@ -1006,26 +1117,26 @@ packages:
dependency: transitive
description:
name: web
sha256: "1d9158c616048c38f712a6646e317a3426da10e884447626167240d45209cbad"
sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
url: "https://pub.dev"
source: hosted
version: "0.5.0"
version: "0.5.1"
web_socket_channel:
dependency: transitive
description:
name: web_socket_channel
sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b
sha256: "58c6666b342a38816b2e7e50ed0f1e261959630becd4c879c4f26bfa14aa5a42"
url: "https://pub.dev"
source: hosted
version: "2.4.0"
version: "2.4.5"
win32:
dependency: transitive
description:
name: win32
sha256: "464f5674532865248444b4c3daca12bd9bf2d7c47f759ce2617986e7229494a8"
sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb"
url: "https://pub.dev"
source: hosted
version: "5.2.0"
version: "5.5.0"
window_manager:
dependency: transitive
description:
@@ -1034,6 +1145,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.3.8"
ws:
dependency: transitive
description:
name: ws
sha256: bb40a7b9219324ac285133c6b3c11bc16015d9cd5889e6555b76bd3e3a64b3e7
url: "https://pub.dev"
source: hosted
version: "1.0.0"
xdg_directories:
dependency: transitive
description:
+23 -44
View File
@@ -20,6 +20,7 @@ version: 0.0.1
environment:
sdk: '>=3.3.0 <4.0.0'
dependencies:
flutter:
sdk: flutter
@@ -32,7 +33,6 @@ dependencies:
build_runner: ^2.4.8
collection: ^1.18.0
path_provider: ^2.1.2
ndk: ^0.0.1
expansion_tile_card: ^3.0.0
crypto: ^3.0.3
android_package_installer: ^0.0.2
@@ -45,68 +45,47 @@ dependencies:
flutter_styled_toast: ^2.2.1
equatable: ^2.0.5
url_launcher: ^6.2.5
device_apps: ^2.2.0
purplebase:
sqlite3_flutter_libs: ^0.5.20
carousel_slider: ^4.2.1
flutter_markdown: ^0.7.1
cached_network_image: ^3.3.1
gap: ^3.0.1
auto_size_text: ^3.0.0
dependency_overrides:
android_package_installer:
git:
url: https://github.com/ImranR98/android_package_installer
ref: main
ndk:
path: ../ndk
purplebase:
path: ../purplebase
flutter_data:
path: ../flutter_data
dev_dependencies:
flutter_test:
sdk: flutter
json_serializable: ^6.7.1
# The "flutter_lints" package below contains a set of recommended lints to
# encourage good coding practices. The lint set provided by the package is
# activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^2.0.0
flutter_launcher_icons: "^0.13.1"
# The following section is specific to Flutter packages.
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
assets:
- assets/images/logo.png
# To add assets to your application, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware
# For details regarding adding assets from package dependencies, see
# https://flutter.dev/assets-and-images/#from-packages
# To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/custom-fonts/#from-packages
fonts:
- family: Inter
fonts:
- asset: assets/fonts/Inter-Light.ttf
weight: 300
- asset: assets/fonts/Inter-Regular.ttf
- asset: assets/fonts/Inter-Italic.ttf
style: italic
- asset: assets/fonts/Inter-Bold.ttf
weight: 700
flutter_launcher_icons:
android: "launcher_icon"