feat(block2): profiles — your identity card + name/about (NIP-01 kind:0)
Drawer 'Profile' is now live. - commons_core: ProfileTransport + UserProfile + NostrProfileTransport (kind:0 publish/fetch), exposed as SocialSession.profile. Round-trip tested against the in-process relay (fast dart test). - app: ProfileStore (local name/about, keystore) + ProfileScreen — shows your shareable npub with copy, edits display name + 'about', saves locally and (when online) publishes kind:0 so peers can recognise you instead of a raw key. - Drawer 'Profile' -> /profile (gated like Market/Chat). - i18n en/es/pt. ProfileStore + profile transport covered by plain/dart tests. Analyzer clean (both packages).
This commit is contained in:
parent
7f1c520960
commit
8ef587176f
19 changed files with 562 additions and 4 deletions
|
|
@ -18,10 +18,12 @@ export 'src/social/nostr/nostr_channel.dart';
|
|||
export 'src/social/nostr/nostr_connection.dart';
|
||||
export 'src/social/nostr/nostr_message_transport.dart';
|
||||
export 'src/social/nostr/nostr_offer_transport.dart';
|
||||
export 'src/social/nostr/nostr_profile_transport.dart';
|
||||
export 'src/social/nostr/nostr_trust_transport.dart';
|
||||
export 'src/social/nostr/relay_pool.dart';
|
||||
export 'src/social/offer.dart';
|
||||
export 'src/social/offer_transport.dart';
|
||||
export 'src/social/profile_transport.dart';
|
||||
export 'src/social/trust_transport.dart';
|
||||
export 'src/social/web_of_trust.dart';
|
||||
export 'src/value/quantity.dart';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,61 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:nostr/nostr.dart';
|
||||
|
||||
import '../profile_transport.dart';
|
||||
import 'nostr_channel.dart';
|
||||
|
||||
/// Nostr NIP-01 kind:0 backend for [ProfileTransport], on the shared channel.
|
||||
class NostrProfileTransport implements ProfileTransport {
|
||||
NostrProfileTransport(this._conn);
|
||||
|
||||
final NostrChannel _conn;
|
||||
|
||||
/// NIP-01 set_metadata (replaceable).
|
||||
static const kindMetadata = 0;
|
||||
|
||||
@override
|
||||
Future<void> publish({
|
||||
String name = '',
|
||||
String about = '',
|
||||
String picture = '',
|
||||
}) async {
|
||||
final content = jsonEncode({
|
||||
if (name.isNotEmpty) 'name': name,
|
||||
if (about.isNotEmpty) 'about': about,
|
||||
if (picture.isNotEmpty) 'picture': picture,
|
||||
});
|
||||
final event = Event.from(
|
||||
kind: kindMetadata,
|
||||
content: content,
|
||||
tags: const [],
|
||||
secretKey: _conn.privateKeyHex,
|
||||
);
|
||||
await _conn.publish(event);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<UserProfile?> fetch(String pubkeyHex) async {
|
||||
final events = await _conn.reqOnce(
|
||||
Filter(kinds: const [kindMetadata], authors: [pubkeyHex], limit: 1),
|
||||
);
|
||||
if (events.isEmpty) return null;
|
||||
// Newest wins if several slipped through.
|
||||
events.sort((a, b) => b.createdAt.compareTo(a.createdAt));
|
||||
final event = events.first;
|
||||
try {
|
||||
final map = jsonDecode(event.content) as Map<String, dynamic>;
|
||||
return UserProfile(
|
||||
pubkeyHex: pubkeyHex,
|
||||
name: (map['name'] as String?) ?? '',
|
||||
about: (map['about'] as String?) ?? '',
|
||||
picture: (map['picture'] as String?) ?? '',
|
||||
);
|
||||
} catch (_) {
|
||||
return UserProfile(pubkeyHex: pubkeyHex);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() => _conn.close();
|
||||
}
|
||||
30
packages/commons_core/lib/src/social/profile_transport.dart
Normal file
30
packages/commons_core/lib/src/social/profile_transport.dart
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
/// A user's public profile (NIP-01 kind:0 metadata) — the human name others see
|
||||
/// instead of a raw key. Only what the person chooses to publish.
|
||||
class UserProfile {
|
||||
const UserProfile({
|
||||
required this.pubkeyHex,
|
||||
this.name = '',
|
||||
this.about = '',
|
||||
this.picture = '',
|
||||
});
|
||||
|
||||
final String pubkeyHex;
|
||||
final String name;
|
||||
final String about;
|
||||
final String picture;
|
||||
|
||||
bool get isEmpty => name.isEmpty && about.isEmpty && picture.isEmpty;
|
||||
}
|
||||
|
||||
/// Publish/fetch public profile metadata. Kind:0 is replaceable — publishing
|
||||
/// again overwrites. Part of the social layer (identity is generic core), a
|
||||
/// sibling of the offer/message/trust transports over the same connection.
|
||||
abstract interface class ProfileTransport {
|
||||
/// Publishes (replaces) this identity's profile.
|
||||
Future<void> publish({String name, String about, String picture});
|
||||
|
||||
/// Fetches [pubkeyHex]'s latest profile, or null if they never published one.
|
||||
Future<UserProfile?> fetch(String pubkeyHex);
|
||||
|
||||
Future<void> close();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue