feat(profile): profile photo or seed-illustration avatar
Profiles had only a coloured-initial disc. Now each person can set an avatar — a real photo OR one of our own seed illustrations (so pseudonymity stays the default; no photo required). - ui/avatar.dart: the one-string value scheme carried in the kind:0 'picture' — a 'data:' photo thumbnail, a 'tane:seed:<glyph>' token, or empty. The Nostr ProfileTransport already published 'picture'. - Photos ride inline as a tiny thumbnail (reuses offerThumbnailDataUri, 24 KB cap) — no media server, like offer photos. - avatar_edit.dart: pick/take a photo, choose a seed illustration, or remove; ProfileStore stores it; ProfileScreen shows a big editable avatar and publishes it. - PeerAvatar renders photo / illustration / initial fallback. - ProfileCache gains picture/setPicture; the inbox caches peers' avatars alongside their names. - i18n avatar block (en/es/pt/ast). Tests: value scheme + PeerAvatar render modes, store + cache round-trips. Follow-up: render peers' avatars in chat/market/your-people (thread the cached picture into PeerAvatar at each call site).
This commit is contained in:
parent
d28454b010
commit
461bc3cb36
19 changed files with 375 additions and 12 deletions
|
|
@ -9,6 +9,8 @@ import '../services/social_account_store.dart';
|
|||
import '../services/profile_store.dart';
|
||||
import '../services/social_connection.dart';
|
||||
import '../services/social_service.dart';
|
||||
import 'avatar_edit.dart';
|
||||
import 'peer_avatar.dart';
|
||||
import 'qr_view.dart';
|
||||
import 'restart_widget.dart';
|
||||
import 'theme.dart';
|
||||
|
|
@ -44,6 +46,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||
final _name = TextEditingController();
|
||||
final _about = TextEditingController();
|
||||
final _g1 = TextEditingController();
|
||||
String _avatar = '';
|
||||
bool _loading = true;
|
||||
bool _saving = false;
|
||||
bool _switching = false;
|
||||
|
|
@ -62,6 +65,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||
_name.text = await widget.profileStore.name();
|
||||
_about.text = await widget.profileStore.about();
|
||||
_g1.text = await widget.profileStore.g1();
|
||||
_avatar = await widget.profileStore.avatar();
|
||||
final max = await widget.accounts.maxCreated();
|
||||
final ids = <({int account, String npub})>[];
|
||||
for (var i = 0; i <= max; i++) {
|
||||
|
|
@ -108,6 +112,11 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||
await _switchTo(next, confirm: false); // creating one is already explicit
|
||||
}
|
||||
|
||||
Future<void> _editAvatar() async {
|
||||
final result = await showAvatarPicker(context, current: _avatar);
|
||||
if (result != null && mounted) setState(() => _avatar = result);
|
||||
}
|
||||
|
||||
Future<void> _copyId() async {
|
||||
final messenger = ScaffoldMessenger.of(context);
|
||||
final t = context.t;
|
||||
|
|
@ -120,8 +129,8 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||
final messenger = ScaffoldMessenger.of(context);
|
||||
final t = context.t;
|
||||
setState(() => _saving = true);
|
||||
await widget.profileStore
|
||||
.save(name: _name.text, about: _about.text, g1: _g1.text);
|
||||
await widget.profileStore.save(
|
||||
name: _name.text, about: _about.text, g1: _g1.text, avatar: _avatar);
|
||||
|
||||
// Best-effort publish over the shared connection so peers see the name.
|
||||
try {
|
||||
|
|
@ -129,6 +138,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||
await session?.profile.publish(
|
||||
name: _name.text.trim(),
|
||||
about: _about.text.trim(),
|
||||
picture: _avatar,
|
||||
g1: _g1.text.trim(),
|
||||
);
|
||||
} catch (_) {
|
||||
|
|
@ -157,6 +167,33 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
|||
: ListView(
|
||||
padding: const EdgeInsets.all(20),
|
||||
children: [
|
||||
Center(
|
||||
child: GestureDetector(
|
||||
key: const Key('profile.avatar'),
|
||||
onTap: _editAvatar,
|
||||
child: Stack(
|
||||
alignment: AlignmentDirectional.bottomEnd,
|
||||
children: [
|
||||
PeerAvatar(
|
||||
pubkey: widget.social.publicKeyHex,
|
||||
name: _name.text,
|
||||
picture: _avatar,
|
||||
radius: 44,
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(5),
|
||||
decoration: const BoxDecoration(
|
||||
color: seedGreen,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(Icons.edit,
|
||||
size: 15, color: Colors.white),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
_IdentityCard(npub: widget.social.npub, onCopy: _copyId),
|
||||
const SizedBox(height: 24),
|
||||
TextField(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue