nostr images fetching from API

This commit is contained in:
gitea
2025-11-06 00:45:58 +01:00
parent c37dce0222
commit 9650fc78a8
12 changed files with 2694 additions and 233 deletions
+218
View File
@@ -0,0 +1,218 @@
import 'package:flutter/material.dart';
import '../home/home_screen.dart';
import '../immich/immich_screen.dart';
import '../nostr_events/nostr_events_screen.dart';
import '../relay_management/relay_management_screen.dart';
import '../relay_management/relay_management_controller.dart';
import '../session/session_screen.dart';
import '../settings/settings_screen.dart';
import '../../data/nostr/nostr_service.dart';
import '../../data/sync/sync_engine.dart';
import '../../data/session/session_service.dart';
import '../../data/local/local_storage_service.dart';
import '../../data/firebase/firebase_service.dart';
/// Route names for the app navigation.
class AppRoutes {
static const String home = '/';
static const String immich = '/immich';
static const String nostrEvents = '/nostr-events';
static const String relayManagement = '/relay-management';
static const String session = '/session';
static const String settings = '/settings';
static const String login = '/login';
}
/// Route guard that requires authentication.
class AuthGuard {
final SessionService? sessionService;
AuthGuard(this.sessionService);
/// Checks if user is authenticated.
bool get isAuthenticated => sessionService?.isLoggedIn ?? false;
/// Redirects to login if not authenticated.
String? checkAuth(String route) {
if (_requiresAuth(route) && !isAuthenticated) {
return AppRoutes.login;
}
return null;
}
/// Checks if a route requires authentication.
bool _requiresAuth(String route) {
// Routes that require authentication
const protectedRoutes = [
AppRoutes.immich,
AppRoutes.nostrEvents,
AppRoutes.session,
];
return protectedRoutes.contains(route);
}
}
/// App router for managing navigation and route guards.
class AppRouter {
final SessionService? sessionService;
final LocalStorageService? localStorageService;
final NostrService? nostrService;
final SyncEngine? syncEngine;
final FirebaseService? firebaseService;
late final AuthGuard _authGuard;
AppRouter({
this.sessionService,
this.localStorageService,
this.nostrService,
this.syncEngine,
this.firebaseService,
}) {
_authGuard = AuthGuard(sessionService);
}
/// Generates routes for the app.
Route<dynamic>? generateRoute(RouteSettings settings) {
// Check route guard
final redirect = _authGuard.checkAuth(settings.name ?? '');
if (redirect != null) {
return MaterialPageRoute(
builder: (_) => _buildLoginScreen(),
settings: settings,
);
}
switch (settings.name) {
case AppRoutes.home:
return MaterialPageRoute(
builder: (_) => HomeScreen(
localStorageService: localStorageService,
),
settings: settings,
);
case AppRoutes.immich:
return MaterialPageRoute(
builder: (_) => ImmichScreen(
localStorageService: localStorageService,
),
settings: settings,
);
case AppRoutes.nostrEvents:
return MaterialPageRoute(
builder: (_) => NostrEventsScreen(
nostrService: nostrService,
syncEngine: syncEngine,
),
settings: settings,
);
case AppRoutes.relayManagement:
if (nostrService == null || syncEngine == null) {
return MaterialPageRoute(
builder: (_) => _buildErrorScreen('Nostr service not available'),
settings: settings,
);
}
return MaterialPageRoute(
builder: (_) => RelayManagementScreen(
controller: RelayManagementController(
nostrService: nostrService!,
syncEngine: syncEngine!,
),
),
settings: settings,
);
case AppRoutes.session:
return MaterialPageRoute(
builder: (_) => SessionScreen(
sessionService: sessionService,
firebaseService: firebaseService,
),
settings: settings,
);
case AppRoutes.settings:
return MaterialPageRoute(
builder: (_) => SettingsScreen(
firebaseService: firebaseService,
),
settings: settings,
);
case AppRoutes.login:
return MaterialPageRoute(
builder: (_) => _buildLoginScreen(),
settings: settings,
);
default:
return MaterialPageRoute(
builder: (_) => _buildErrorScreen('Route not found: ${settings.name}'),
settings: settings,
);
}
}
Widget _buildLoginScreen() {
return Scaffold(
appBar: AppBar(
title: const Text('Login Required'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icons.lock_outline,
size: 64,
color: Colors.grey,
),
const SizedBox(height: 16),
const Text(
'Please login to access this feature',
style: TextStyle(fontSize: 16),
),
const SizedBox(height: 24),
ElevatedButton(
onPressed: () {
// Navigation will be handled by the navigation scaffold
},
child: const Text('Go to Login'),
),
],
),
),
);
}
Widget _buildErrorScreen(String message) {
return Scaffold(
appBar: AppBar(
title: const Text('Error'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icons.error_outline,
size: 64,
color: Colors.red,
),
const SizedBox(height: 16),
Text(
message,
style: const TextStyle(fontSize: 16),
textAlign: TextAlign.center,
),
],
),
),
);
}
}
@@ -0,0 +1,178 @@
import 'package:flutter/material.dart';
import '../home/home_screen.dart';
import '../immich/immich_screen.dart';
import '../nostr_events/nostr_events_screen.dart';
import '../session/session_screen.dart';
import '../settings/settings_screen.dart';
import '../../data/session/session_service.dart';
import '../../data/local/local_storage_service.dart';
import '../../data/nostr/nostr_service.dart';
import '../../data/sync/sync_engine.dart';
import '../../data/firebase/firebase_service.dart';
import '../../data/immich/immich_service.dart';
/// Main navigation scaffold with bottom navigation bar.
class MainNavigationScaffold extends StatefulWidget {
final SessionService? sessionService;
final LocalStorageService? localStorageService;
final NostrService? nostrService;
final SyncEngine? syncEngine;
final FirebaseService? firebaseService;
final ImmichService? immichService;
const MainNavigationScaffold({
super.key,
this.sessionService,
this.localStorageService,
this.nostrService,
this.syncEngine,
this.firebaseService,
this.immichService,
});
@override
State<MainNavigationScaffold> createState() => _MainNavigationScaffoldState();
}
class _MainNavigationScaffoldState extends State<MainNavigationScaffold> {
int _currentIndex = 0;
int _loginStateVersion = 0; // Increment when login state changes
int? _pendingProtectedRoute; // Track if user was trying to access a protected route
void _onItemTapped(int index) {
setState(() {
_currentIndex = index;
// If accessing a protected route (Immich=1, Nostr=2) while not logged in, remember it
if ((index == 1 || index == 2) && !(widget.sessionService?.isLoggedIn ?? false)) {
_pendingProtectedRoute = index;
} else {
_pendingProtectedRoute = null;
}
});
}
/// Callback to notify that login state may have changed
void _onSessionStateChanged() {
setState(() {
_loginStateVersion++; // Force rebuild when login state changes
// If user just logged in and was trying to access a protected route, navigate there
if (widget.sessionService?.isLoggedIn == true && _pendingProtectedRoute != null) {
_currentIndex = _pendingProtectedRoute!;
_pendingProtectedRoute = null;
}
});
}
Widget _buildScreen(int index) {
switch (index) {
case 0:
return HomeScreen(
localStorageService: widget.localStorageService,
);
case 1:
// Check auth guard for Immich
if (!(widget.sessionService?.isLoggedIn ?? false)) {
return _buildLoginRequiredScreen();
}
return ImmichScreen(
localStorageService: widget.localStorageService,
immichService: widget.immichService,
);
case 2:
// Check auth guard for Nostr Events
if (!(widget.sessionService?.isLoggedIn ?? false)) {
return _buildLoginRequiredScreen();
}
return NostrEventsScreen(
nostrService: widget.nostrService,
syncEngine: widget.syncEngine,
);
case 3:
return SessionScreen(
sessionService: widget.sessionService,
firebaseService: widget.firebaseService,
onSessionChanged: _onSessionStateChanged,
);
case 4:
return SettingsScreen(
firebaseService: widget.firebaseService,
nostrService: widget.nostrService,
syncEngine: widget.syncEngine,
);
default:
return const SizedBox();
}
}
Widget _buildLoginRequiredScreen() {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icons.lock_outline,
size: 64,
color: Colors.grey,
),
const SizedBox(height: 16),
const Text(
'Please login to access this feature',
style: TextStyle(fontSize: 16),
),
const SizedBox(height: 24),
ElevatedButton(
onPressed: () {
setState(() {
_currentIndex = 3; // Navigate to Session tab
});
},
child: const Text('Go to Login'),
),
],
),
),
);
}
@override
Widget build(BuildContext context) {
// Use login state version to force IndexedStack rebuild when login changes
return Scaffold(
body: IndexedStack(
key: ValueKey('nav_$_currentIndex\_v$_loginStateVersion'),
index: _currentIndex,
children: List.generate(5, (index) => _buildScreen(index)),
),
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
currentIndex: _currentIndex,
onTap: _onItemTapped,
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.photo_library),
label: 'Immich',
),
BottomNavigationBarItem(
icon: Icon(Icons.cloud),
label: 'Nostr',
),
BottomNavigationBarItem(
icon: Icon(Icons.person),
label: 'Session',
),
BottomNavigationBarItem(
icon: Icon(Icons.settings),
label: 'Settings',
),
],
),
);
}
}