diff --git a/analysis_options.yaml b/analysis_options.yaml
index dff2f71..fb1fb95 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -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
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index c2fe2f6..ab46def 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -53,6 +53,7 @@
+
diff --git a/assets/fonts/Inter-Bold.ttf b/assets/fonts/Inter-Bold.ttf
new file mode 100644
index 0000000..15e908f
Binary files /dev/null and b/assets/fonts/Inter-Bold.ttf differ
diff --git a/assets/fonts/Inter-Italic.ttf b/assets/fonts/Inter-Italic.ttf
new file mode 100644
index 0000000..e0421ee
Binary files /dev/null and b/assets/fonts/Inter-Italic.ttf differ
diff --git a/assets/fonts/Inter-Light.ttf b/assets/fonts/Inter-Light.ttf
new file mode 100644
index 0000000..31ac788
Binary files /dev/null and b/assets/fonts/Inter-Light.ttf differ
diff --git a/assets/fonts/Inter-Regular.ttf b/assets/fonts/Inter-Regular.ttf
new file mode 100644
index 0000000..c544be4
Binary files /dev/null and b/assets/fonts/Inter-Regular.ttf differ
diff --git a/build.yaml b/build.yaml
new file mode 100644
index 0000000..e69de29
diff --git a/lib/main.dart b/lib/main.dart
index ec6d847..7204ce0 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -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();
final _searchNavigatorKey = GlobalKey(debugLabel: 'search');
final _updatesNavigatorKey = GlobalKey(debugLabel: 'updates');
-final _profileNavigatorKey = GlobalKey(debugLabel: 'profile');
-final _notificationsNavigatorKey =
- GlobalKey(debugLabel: 'notifications');
+final _settingsNavigatorKey = GlobalKey(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(
+ // 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((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({
+ required BuildContext context,
+ required GoRouterState state,
+ required Widget child,
+}) {
+ return CustomTransitionPage(
+ key: state.pageKey,
+ child: child,
+ transitionsBuilder: (context, animation, secondaryAnimation, child) {
+ return SlideTransition(
+ position:
+ Tween(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,
+ ),
+ );
+ },
+ );
+}
diff --git a/lib/main.data.dart b/lib/main.data.dart
index 3275b85..de65e8e 100644
--- a/lib/main.data.dart
+++ b/lib/main.data.dart
@@ -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? baseDirFn, List? 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 = >>{
+ 'apps': appsAdapterProvider,
+'fileMetadata': fileMetadataAdapterProvider,
+'releases': releasesAdapterProvider,
+'users': usersAdapterProvider
};
-final repositoryProviders = >>{
- 'fileMetadata': fileMetadataRepositoryProvider,
-'releases': releasesRepositoryProvider,
-'users': usersRepositoryProvider
-};
-
-final repositoryInitializerProvider =
- FutureProvider((ref) async {
- DataHelpers.setInternalType('fileMetadata');
- DataHelpers.setInternalType('releases');
- DataHelpers.setInternalType('users');
- final adapters = {'fileMetadata': ref.watch(internalFileMetadataRemoteAdapterProvider), 'releases': ref.watch(internalReleasesRemoteAdapterProvider), 'users': ref.watch(internalUsersRemoteAdapterProvider)};
- final remotes = {'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 get fileMetadata => watch(fileMetadataRepositoryProvider)..remoteAdapter.internalWatch = watch;
- Repository get releases => watch(releasesRepositoryProvider)..remoteAdapter.internalWatch = watch;
- Repository get users => watch(usersRepositoryProvider)..remoteAdapter.internalWatch = watch;
+extension AdapterWidgetRefX on WidgetRef {
+ Adapter get apps => watch(appsAdapterProvider)..internalWatch = watch;
+ Adapter get fileMetadata => watch(fileMetadataAdapterProvider)..internalWatch = watch;
+ Adapter get releases => watch(releasesAdapterProvider)..internalWatch = watch;
+ Adapter get users => watch(usersAdapterProvider)..internalWatch = watch;
}
-extension RepositoryRefX on Ref {
+extension AdapterRefX on Ref {
- Repository get fileMetadata => watch(fileMetadataRepositoryProvider)..remoteAdapter.internalWatch = watch as Watcher;
- Repository get releases => watch(releasesRepositoryProvider)..remoteAdapter.internalWatch = watch as Watcher;
- Repository get users => watch(usersRepositoryProvider)..remoteAdapter.internalWatch = watch as Watcher;
+ Adapter get apps => watch(appsAdapterProvider)..internalWatch = watch as Watcher;
+ Adapter get fileMetadata => watch(fileMetadataAdapterProvider)..internalWatch = watch as Watcher;
+ Adapter get releases => watch(releasesAdapterProvider)..internalWatch = watch as Watcher;
+ Adapter get users => watch(usersAdapterProvider)..internalWatch = watch as Watcher;
}
\ No newline at end of file
diff --git a/lib/models/app.dart b/lib/models/app.dart
new file mode 100644
index 0000000..c05bee3
--- /dev/null
+++ b/lib/models/app.dart
@@ -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 with BaseApp, EquatableMixin {
+ late final HasMany releases;
+ App();
+ factory App.fromJson(Map json) => _$AppFromJson(json);
+ @override
+ List