|
|
|
@ -31,6 +31,10 @@ class _SessionScreenState extends State<SessionScreen> {
|
|
|
|
bool _useFirebaseAuth = false;
|
|
|
|
bool _useFirebaseAuth = false;
|
|
|
|
bool _useNostrLogin = false;
|
|
|
|
bool _useNostrLogin = false;
|
|
|
|
NostrKeyPair? _generatedKeyPair;
|
|
|
|
NostrKeyPair? _generatedKeyPair;
|
|
|
|
|
|
|
|
Uint8List? _avatarBytes;
|
|
|
|
|
|
|
|
bool _isLoadingAvatar = false;
|
|
|
|
|
|
|
|
String? _lastProfilePictureUrl; // Track URL to avoid reloading
|
|
|
|
|
|
|
|
DateTime? _avatarLastLoaded; // Track when avatar was last loaded
|
|
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
@override
|
|
|
|
void initState() {
|
|
|
|
void initState() {
|
|
|
|
@ -38,6 +42,30 @@ class _SessionScreenState extends State<SessionScreen> {
|
|
|
|
// Check if Firebase Auth is available
|
|
|
|
// Check if Firebase Auth is available
|
|
|
|
_useFirebaseAuth = ServiceLocator.instance.firebaseService?.isEnabled == true &&
|
|
|
|
_useFirebaseAuth = ServiceLocator.instance.firebaseService?.isEnabled == true &&
|
|
|
|
ServiceLocator.instance.firebaseService?.config.authEnabled == true;
|
|
|
|
ServiceLocator.instance.firebaseService?.config.authEnabled == true;
|
|
|
|
|
|
|
|
_loadAvatar();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
|
|
|
void didChangeDependencies() {
|
|
|
|
|
|
|
|
super.didChangeDependencies();
|
|
|
|
|
|
|
|
// Load avatar if not already loaded or if URL changed
|
|
|
|
|
|
|
|
final sessionService = ServiceLocator.instance.sessionService;
|
|
|
|
|
|
|
|
if (sessionService != null && sessionService.isLoggedIn) {
|
|
|
|
|
|
|
|
final currentUser = sessionService.currentUser;
|
|
|
|
|
|
|
|
final profilePictureUrl = currentUser?.nostrProfile?.picture;
|
|
|
|
|
|
|
|
// Load if URL changed or if we don't have cached bytes yet
|
|
|
|
|
|
|
|
if (profilePictureUrl != _lastProfilePictureUrl ||
|
|
|
|
|
|
|
|
(profilePictureUrl != null && _avatarBytes == null && !_isLoadingAvatar)) {
|
|
|
|
|
|
|
|
_loadAvatar();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} else if (_avatarBytes != null) {
|
|
|
|
|
|
|
|
// Clear avatar if logged out
|
|
|
|
|
|
|
|
setState(() {
|
|
|
|
|
|
|
|
_avatarBytes = null;
|
|
|
|
|
|
|
|
_lastProfilePictureUrl = null;
|
|
|
|
|
|
|
|
_avatarLastLoaded = null;
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
@override
|
|
|
|
@ -96,6 +124,7 @@ class _SessionScreenState extends State<SessionScreen> {
|
|
|
|
),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
);
|
|
|
|
setState(() {});
|
|
|
|
setState(() {});
|
|
|
|
|
|
|
|
_loadAvatar(); // Reload avatar after login
|
|
|
|
widget.onSessionChanged?.call();
|
|
|
|
widget.onSessionChanged?.call();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
return;
|
|
|
|
@ -138,6 +167,7 @@ class _SessionScreenState extends State<SessionScreen> {
|
|
|
|
),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
);
|
|
|
|
setState(() {});
|
|
|
|
setState(() {});
|
|
|
|
|
|
|
|
_loadAvatar(); // Reload avatar after login
|
|
|
|
// Notify parent that session state changed
|
|
|
|
// Notify parent that session state changed
|
|
|
|
widget.onSessionChanged?.call();
|
|
|
|
widget.onSessionChanged?.call();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@ -194,6 +224,7 @@ class _SessionScreenState extends State<SessionScreen> {
|
|
|
|
),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
);
|
|
|
|
setState(() {});
|
|
|
|
setState(() {});
|
|
|
|
|
|
|
|
_loadAvatar(); // Reload avatar after login
|
|
|
|
// Notify parent that session state changed
|
|
|
|
// Notify parent that session state changed
|
|
|
|
widget.onSessionChanged?.call();
|
|
|
|
widget.onSessionChanged?.call();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@ -243,7 +274,10 @@ class _SessionScreenState extends State<SessionScreen> {
|
|
|
|
content: Text('Logout successful'),
|
|
|
|
content: Text('Logout successful'),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
);
|
|
|
|
setState(() {});
|
|
|
|
setState(() {
|
|
|
|
|
|
|
|
_avatarBytes = null; // Clear avatar on logout
|
|
|
|
|
|
|
|
_lastProfilePictureUrl = null;
|
|
|
|
|
|
|
|
});
|
|
|
|
// Notify parent that session state changed
|
|
|
|
// Notify parent that session state changed
|
|
|
|
widget.onSessionChanged?.call();
|
|
|
|
widget.onSessionChanged?.call();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@ -265,7 +299,132 @@ class _SessionScreenState extends State<SessionScreen> {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Builds a profile picture widget using ImmichService for authenticated access.
|
|
|
|
/// Loads the avatar image and caches it in state.
|
|
|
|
|
|
|
|
Future<void> _loadAvatar() async {
|
|
|
|
|
|
|
|
final sessionService = ServiceLocator.instance.sessionService;
|
|
|
|
|
|
|
|
if (sessionService == null || !sessionService.isLoggedIn) {
|
|
|
|
|
|
|
|
// Clear avatar when logged out
|
|
|
|
|
|
|
|
if (mounted) {
|
|
|
|
|
|
|
|
setState(() {
|
|
|
|
|
|
|
|
_avatarBytes = null;
|
|
|
|
|
|
|
|
_isLoadingAvatar = false;
|
|
|
|
|
|
|
|
_lastProfilePictureUrl = null;
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
final currentUser = sessionService.currentUser;
|
|
|
|
|
|
|
|
if (currentUser == null) {
|
|
|
|
|
|
|
|
if (mounted) {
|
|
|
|
|
|
|
|
setState(() {
|
|
|
|
|
|
|
|
_avatarBytes = null;
|
|
|
|
|
|
|
|
_isLoadingAvatar = false;
|
|
|
|
|
|
|
|
_lastProfilePictureUrl = null;
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
final profilePictureUrl = currentUser.nostrProfile?.picture;
|
|
|
|
|
|
|
|
if (profilePictureUrl == null || profilePictureUrl.isEmpty) {
|
|
|
|
|
|
|
|
// Clear avatar if no profile picture
|
|
|
|
|
|
|
|
if (mounted) {
|
|
|
|
|
|
|
|
setState(() {
|
|
|
|
|
|
|
|
_avatarBytes = null;
|
|
|
|
|
|
|
|
_isLoadingAvatar = false;
|
|
|
|
|
|
|
|
_lastProfilePictureUrl = null;
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Don't reload if it's the same URL and we already have the bytes in memory
|
|
|
|
|
|
|
|
// Only reload if it's been more than 5 minutes since last load
|
|
|
|
|
|
|
|
// Note: MediaService handles disk caching, so we can rely on it for persistence
|
|
|
|
|
|
|
|
final shouldReload = profilePictureUrl != _lastProfilePictureUrl ||
|
|
|
|
|
|
|
|
_avatarBytes == null ||
|
|
|
|
|
|
|
|
(_avatarLastLoaded != null &&
|
|
|
|
|
|
|
|
DateTime.now().difference(_avatarLastLoaded!).inMinutes > 5);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!shouldReload && _avatarBytes != null) {
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Update last URL before loading (even if it fails, we don't want to retry immediately)
|
|
|
|
|
|
|
|
_lastProfilePictureUrl = profilePictureUrl;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (mounted) {
|
|
|
|
|
|
|
|
setState(() {
|
|
|
|
|
|
|
|
_isLoadingAvatar = true;
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
// Extract asset ID from Immich URL using regex
|
|
|
|
|
|
|
|
final mediaService = ServiceLocator.instance.mediaService;
|
|
|
|
|
|
|
|
if (mediaService != null) {
|
|
|
|
|
|
|
|
// Try to extract asset ID from URL (format: .../api/assets/{id}/original)
|
|
|
|
|
|
|
|
final assetIdMatch = RegExp(r'/api/assets/([^/]+)/').firstMatch(profilePictureUrl);
|
|
|
|
|
|
|
|
String? assetId;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (assetIdMatch != null) {
|
|
|
|
|
|
|
|
assetId = assetIdMatch.group(1);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
// For Blossom URLs, use the full URL
|
|
|
|
|
|
|
|
assetId = profilePictureUrl;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (assetId != null) {
|
|
|
|
|
|
|
|
// Use MediaService which has built-in disk caching
|
|
|
|
|
|
|
|
// MediaService will return cached bytes if available, otherwise fetch and cache
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
final bytes = await mediaService.fetchImageBytes(assetId, isThumbnail: true);
|
|
|
|
|
|
|
|
if (mounted && bytes != null && bytes.isNotEmpty) {
|
|
|
|
|
|
|
|
setState(() {
|
|
|
|
|
|
|
|
_avatarBytes = bytes;
|
|
|
|
|
|
|
|
_isLoadingAvatar = false;
|
|
|
|
|
|
|
|
_avatarLastLoaded = DateTime.now();
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
|
|
// If fetch fails (e.g., 404), log but don't mark URL as loaded
|
|
|
|
|
|
|
|
// This allows retry on next refresh if the URL becomes available
|
|
|
|
|
|
|
|
Logger.warning('Failed to fetch avatar from MediaService: $e');
|
|
|
|
|
|
|
|
if (mounted) {
|
|
|
|
|
|
|
|
setState(() {
|
|
|
|
|
|
|
|
_isLoadingAvatar = false;
|
|
|
|
|
|
|
|
_avatarBytes = null;
|
|
|
|
|
|
|
|
// Don't update _lastProfilePictureUrl on error - allows retry
|
|
|
|
|
|
|
|
// Only update if we successfully loaded
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return; // Return early to avoid showing placeholder immediately
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Fallback: try to fetch as regular image (for non-Immich URLs or shared links)
|
|
|
|
|
|
|
|
// For now, we'll just set loading to false
|
|
|
|
|
|
|
|
if (mounted) {
|
|
|
|
|
|
|
|
setState(() {
|
|
|
|
|
|
|
|
_isLoadingAvatar = false;
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
|
|
// Log error for debugging
|
|
|
|
|
|
|
|
Logger.warning('Failed to load avatar: $e');
|
|
|
|
|
|
|
|
if (mounted) {
|
|
|
|
|
|
|
|
setState(() {
|
|
|
|
|
|
|
|
_isLoadingAvatar = false;
|
|
|
|
|
|
|
|
_avatarBytes = null; // Clear on error
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// Builds a profile picture widget using cached avatar bytes.
|
|
|
|
Widget _buildProfilePicture(String? imageUrl, {double radius = 30}) {
|
|
|
|
Widget _buildProfilePicture(String? imageUrl, {double radius = 30}) {
|
|
|
|
if (imageUrl == null) {
|
|
|
|
if (imageUrl == null) {
|
|
|
|
return CircleAvatar(
|
|
|
|
return CircleAvatar(
|
|
|
|
@ -274,60 +433,34 @@ class _SessionScreenState extends State<SessionScreen> {
|
|
|
|
);
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
final mediaService = ServiceLocator.instance.mediaService;
|
|
|
|
if (_isLoadingAvatar) {
|
|
|
|
if (mediaService == null) {
|
|
|
|
|
|
|
|
return CircleAvatar(
|
|
|
|
return CircleAvatar(
|
|
|
|
radius: radius,
|
|
|
|
radius: radius,
|
|
|
|
backgroundColor: Colors.grey[300],
|
|
|
|
backgroundColor: Colors.grey[300],
|
|
|
|
child: const Icon(Icons.person, size: 50),
|
|
|
|
child: const CircularProgressIndicator(),
|
|
|
|
);
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Try to extract asset ID from Immich URL (format: .../api/assets/{id}/original)
|
|
|
|
if (_avatarBytes != null) {
|
|
|
|
final assetIdMatch = RegExp(r'/api/assets/([^/]+)/').firstMatch(imageUrl);
|
|
|
|
return CircleAvatar(
|
|
|
|
String? assetId;
|
|
|
|
radius: radius,
|
|
|
|
|
|
|
|
backgroundImage: MemoryImage(_avatarBytes!),
|
|
|
|
if (assetIdMatch != null) {
|
|
|
|
onBackgroundImageError: (_, __) {
|
|
|
|
assetId = assetIdMatch.group(1);
|
|
|
|
// Clear avatar on error
|
|
|
|
} else {
|
|
|
|
if (mounted) {
|
|
|
|
// For Blossom URLs, use the full URL
|
|
|
|
setState(() {
|
|
|
|
assetId = imageUrl;
|
|
|
|
_avatarBytes = null;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
if (assetId != null) {
|
|
|
|
},
|
|
|
|
// Use MediaService to fetch image with proper authentication
|
|
|
|
);
|
|
|
|
return FutureBuilder<Uint8List?>(
|
|
|
|
|
|
|
|
future: mediaService.fetchImageBytes(assetId, isThumbnail: true),
|
|
|
|
|
|
|
|
builder: (context, snapshot) {
|
|
|
|
|
|
|
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
|
|
|
|
|
|
|
return CircleAvatar(
|
|
|
|
|
|
|
|
radius: radius,
|
|
|
|
|
|
|
|
backgroundColor: Colors.grey[300],
|
|
|
|
|
|
|
|
child: const CircularProgressIndicator(),
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (snapshot.hasError || !snapshot.hasData || snapshot.data == null) {
|
|
|
|
|
|
|
|
return CircleAvatar(
|
|
|
|
|
|
|
|
radius: radius,
|
|
|
|
|
|
|
|
backgroundColor: Colors.grey[300],
|
|
|
|
|
|
|
|
child: const Icon(Icons.broken_image),
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return CircleAvatar(
|
|
|
|
|
|
|
|
radius: radius,
|
|
|
|
|
|
|
|
backgroundImage: MemoryImage(snapshot.data!),
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Fallback to direct network image if not an Immich URL or service unavailable
|
|
|
|
// Fallback: show placeholder if no cached bytes
|
|
|
|
return CircleAvatar(
|
|
|
|
return CircleAvatar(
|
|
|
|
radius: radius,
|
|
|
|
radius: radius,
|
|
|
|
backgroundImage: NetworkImage(imageUrl),
|
|
|
|
backgroundColor: Colors.grey[300],
|
|
|
|
onBackgroundImageError: (_, __) {},
|
|
|
|
child: const Icon(Icons.person, size: 50),
|
|
|
|
);
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@ -342,6 +475,7 @@ class _SessionScreenState extends State<SessionScreen> {
|
|
|
|
// If profile was updated, refresh the screen
|
|
|
|
// If profile was updated, refresh the screen
|
|
|
|
if (result == true && mounted) {
|
|
|
|
if (result == true && mounted) {
|
|
|
|
setState(() {});
|
|
|
|
setState(() {});
|
|
|
|
|
|
|
|
_loadAvatar(); // Reload avatar after profile update
|
|
|
|
// Also trigger the session changed callback to update parent
|
|
|
|
// Also trigger the session changed callback to update parent
|
|
|
|
widget.onSessionChanged?.call();
|
|
|
|
widget.onSessionChanged?.call();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@ -351,9 +485,36 @@ class _SessionScreenState extends State<SessionScreen> {
|
|
|
|
if (ServiceLocator.instance.sessionService == null) return;
|
|
|
|
if (ServiceLocator.instance.sessionService == null) return;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
await ServiceLocator.instance.sessionService!.refreshNostrProfile();
|
|
|
|
// Store the old profile picture URL to detect changes
|
|
|
|
|
|
|
|
final sessionService = ServiceLocator.instance.sessionService!;
|
|
|
|
|
|
|
|
final oldProfilePictureUrl = sessionService.currentUser?.nostrProfile?.picture;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
await sessionService.refreshNostrProfile();
|
|
|
|
|
|
|
|
|
|
|
|
if (mounted) {
|
|
|
|
if (mounted) {
|
|
|
|
setState(() {});
|
|
|
|
// Wait for relays to connect and profile to fully sync
|
|
|
|
|
|
|
|
// The profile refresh triggers relay reconnection which takes time
|
|
|
|
|
|
|
|
await Future.delayed(const Duration(milliseconds: 800));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Get the new profile picture URL after refresh
|
|
|
|
|
|
|
|
final newProfilePictureUrl = sessionService.currentUser?.nostrProfile?.picture;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Clear in-memory cache to force reload from MediaService disk cache or network
|
|
|
|
|
|
|
|
setState(() {
|
|
|
|
|
|
|
|
_avatarBytes = null;
|
|
|
|
|
|
|
|
_avatarLastLoaded = null;
|
|
|
|
|
|
|
|
// Only clear last URL if it actually changed, otherwise keep it to allow cache lookup
|
|
|
|
|
|
|
|
if (oldProfilePictureUrl != newProfilePictureUrl) {
|
|
|
|
|
|
|
|
_lastProfilePictureUrl = null;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Only reload avatar if we have a valid URL
|
|
|
|
|
|
|
|
if (newProfilePictureUrl != null && newProfilePictureUrl.isNotEmpty) {
|
|
|
|
|
|
|
|
// Reload avatar after profile refresh - MediaService will use disk cache if available
|
|
|
|
|
|
|
|
await _loadAvatar();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
|
|
const SnackBar(
|
|
|
|
const SnackBar(
|
|
|
|
content: Text('Session data refreshed'),
|
|
|
|
content: Text('Session data refreshed'),
|
|
|
|
@ -550,6 +711,18 @@ class _SessionScreenState extends State<SessionScreen> {
|
|
|
|
),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
],
|
|
|
|
const SizedBox(height: 24),
|
|
|
|
const SizedBox(height: 24),
|
|
|
|
|
|
|
|
// No Nostr account text
|
|
|
|
|
|
|
|
if (_useNostrLogin)
|
|
|
|
|
|
|
|
Padding(
|
|
|
|
|
|
|
|
padding: const EdgeInsets.only(bottom: 16),
|
|
|
|
|
|
|
|
child: Text(
|
|
|
|
|
|
|
|
'No Nostr account?',
|
|
|
|
|
|
|
|
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
|
|
|
|
|
|
|
color: Colors.grey[600],
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
textAlign: TextAlign.center,
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
),
|
|
|
|
// Login method selector
|
|
|
|
// Login method selector
|
|
|
|
SegmentedButton<bool>(
|
|
|
|
SegmentedButton<bool>(
|
|
|
|
segments: const [
|
|
|
|
segments: const [
|
|
|
|
@ -583,7 +756,7 @@ class _SessionScreenState extends State<SessionScreen> {
|
|
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
|
|
children: [
|
|
|
|
children: [
|
|
|
|
const Text(
|
|
|
|
const Text(
|
|
|
|
'Generate Key Pair',
|
|
|
|
'Generate a Key pair',
|
|
|
|
style: TextStyle(
|
|
|
|
style: TextStyle(
|
|
|
|
fontSize: 16,
|
|
|
|
fontSize: 16,
|
|
|
|
fontWeight: FontWeight.bold,
|
|
|
|
fontWeight: FontWeight.bold,
|
|
|
|
|