mirror of
https://github.com/nostrlabs-io/zap-stream-flutter.git
synced 2025-06-15 11:48:21 +00:00
refactor: minor profile loader improvement
This commit is contained in:
@ -6,6 +6,7 @@ import 'package:zap_stream_flutter/const.dart';
|
||||
import 'package:zap_stream_flutter/theme.dart';
|
||||
import 'package:zap_stream_flutter/widgets/avatar_upload.dart';
|
||||
import 'package:zap_stream_flutter/widgets/button.dart';
|
||||
import 'package:zap_stream_flutter/widgets/profile.dart';
|
||||
|
||||
class SettingsProfilePage extends StatelessWidget {
|
||||
final TextEditingController _picture = TextEditingController();
|
||||
@ -22,97 +23,94 @@ class SettingsProfilePage extends StatelessWidget {
|
||||
final pubkey = ndk.accounts.getPublicKey();
|
||||
if (pubkey == null) return Text(t.settings.profile.error.logged_out);
|
||||
|
||||
return FutureBuilder(
|
||||
future: ndk.metadata.loadMetadata(pubkey),
|
||||
builder: (context, state) {
|
||||
if (state.hasData) {
|
||||
_name.text = state.data!.name ?? "";
|
||||
_about.text = state.data!.about ?? "";
|
||||
_nip5.text = state.data!.nip05 ?? "";
|
||||
_lud16.text = state.data!.lud16 ?? "";
|
||||
_picture.text = state.data!.picture ?? "";
|
||||
}
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: _loading,
|
||||
builder: (context, v, _) {
|
||||
return Column(
|
||||
spacing: 16,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Center(
|
||||
child: AvatarUpload(
|
||||
key: Key("avatar:${_picture.text}"),
|
||||
avatar: _picture.text.isEmpty ? null : _picture.text,
|
||||
onUpload: (i) {
|
||||
_picture.text = i;
|
||||
},
|
||||
),
|
||||
),
|
||||
TextField(
|
||||
controller: _name,
|
||||
readOnly: v,
|
||||
decoration: InputDecoration(
|
||||
labelText: t.settings.profile.display_name,
|
||||
fillColor: LAYER_1,
|
||||
filled: true,
|
||||
),
|
||||
),
|
||||
TextField(
|
||||
controller: _about,
|
||||
readOnly: v,
|
||||
decoration: InputDecoration(
|
||||
labelText: t.settings.profile.about,
|
||||
fillColor: LAYER_1,
|
||||
filled: true,
|
||||
),
|
||||
),
|
||||
TextField(
|
||||
controller: _nip5,
|
||||
readOnly: v,
|
||||
decoration: InputDecoration(
|
||||
labelText: t.settings.profile.nip05,
|
||||
fillColor: LAYER_1,
|
||||
filled: true,
|
||||
),
|
||||
),
|
||||
TextField(
|
||||
controller: _lud16,
|
||||
readOnly: v,
|
||||
decoration: InputDecoration(
|
||||
labelText: t.settings.profile.lud16,
|
||||
fillColor: LAYER_1,
|
||||
filled: true,
|
||||
),
|
||||
),
|
||||
BasicButton.text(
|
||||
t.button.save,
|
||||
disabled: v,
|
||||
onTap: (context) async {
|
||||
_loading.value = true;
|
||||
try {
|
||||
final newMeta = Metadata(
|
||||
pubKey: pubkey,
|
||||
name: _name.text.isEmpty ? null : _name.text,
|
||||
about: _about.text.isEmpty ? null : _about.text,
|
||||
picture: _picture.text.isEmpty ? null : _picture.text,
|
||||
nip05: _nip5.text.isEmpty ? null : _nip5.text,
|
||||
lud16: _lud16.text.isEmpty ? null : _lud16.text,
|
||||
);
|
||||
await ndk.metadata.broadcastMetadata(newMeta);
|
||||
if (context.mounted) {
|
||||
context.pop();
|
||||
}
|
||||
} finally {
|
||||
_loading.value = false;
|
||||
}
|
||||
return ProfileLoaderWidget(pubkey, (context, state) {
|
||||
if (state.hasData) {
|
||||
_name.text = state.data!.name ?? "";
|
||||
_about.text = state.data!.about ?? "";
|
||||
_nip5.text = state.data!.nip05 ?? "";
|
||||
_lud16.text = state.data!.lud16 ?? "";
|
||||
_picture.text = state.data!.picture ?? "";
|
||||
}
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: _loading,
|
||||
builder: (context, v, _) {
|
||||
return Column(
|
||||
spacing: 16,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Center(
|
||||
child: AvatarUpload(
|
||||
key: Key("avatar:${_picture.text}"),
|
||||
avatar: _picture.text.isEmpty ? null : _picture.text,
|
||||
onUpload: (i) {
|
||||
_picture.text = i;
|
||||
},
|
||||
),
|
||||
if (v) Center(child: CircularProgressIndicator()),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
),
|
||||
TextField(
|
||||
controller: _name,
|
||||
readOnly: v,
|
||||
decoration: InputDecoration(
|
||||
labelText: t.settings.profile.display_name,
|
||||
fillColor: LAYER_1,
|
||||
filled: true,
|
||||
),
|
||||
),
|
||||
TextField(
|
||||
controller: _about,
|
||||
readOnly: v,
|
||||
decoration: InputDecoration(
|
||||
labelText: t.settings.profile.about,
|
||||
fillColor: LAYER_1,
|
||||
filled: true,
|
||||
),
|
||||
),
|
||||
TextField(
|
||||
controller: _nip5,
|
||||
readOnly: v,
|
||||
decoration: InputDecoration(
|
||||
labelText: t.settings.profile.nip05,
|
||||
fillColor: LAYER_1,
|
||||
filled: true,
|
||||
),
|
||||
),
|
||||
TextField(
|
||||
controller: _lud16,
|
||||
readOnly: v,
|
||||
decoration: InputDecoration(
|
||||
labelText: t.settings.profile.lud16,
|
||||
fillColor: LAYER_1,
|
||||
filled: true,
|
||||
),
|
||||
),
|
||||
BasicButton.text(
|
||||
t.button.save,
|
||||
disabled: v,
|
||||
onTap: (context) async {
|
||||
_loading.value = true;
|
||||
try {
|
||||
final newMeta = Metadata(
|
||||
pubKey: pubkey,
|
||||
name: _name.text.isEmpty ? null : _name.text,
|
||||
about: _about.text.isEmpty ? null : _about.text,
|
||||
picture: _picture.text.isEmpty ? null : _picture.text,
|
||||
nip05: _nip5.text.isEmpty ? null : _nip5.text,
|
||||
lud16: _lud16.text.isEmpty ? null : _lud16.text,
|
||||
);
|
||||
await ndk.metadata.broadcastMetadata(newMeta);
|
||||
if (context.mounted) {
|
||||
context.pop();
|
||||
}
|
||||
} finally {
|
||||
_loading.value = false;
|
||||
}
|
||||
},
|
||||
),
|
||||
if (v) Center(child: CircularProgressIndicator()),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -23,33 +23,31 @@ class ChatMessageWidget extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FutureBuilder(
|
||||
future: ndk.metadata.loadMetadata(msg.pubKey),
|
||||
builder: (ctx, state) {
|
||||
final profile = state.data ?? Metadata(pubKey: msg.pubKey);
|
||||
return GestureDetector(
|
||||
onLongPress: () {
|
||||
if (ndk.accounts.canSign) {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
constraints: BoxConstraints.expand(),
|
||||
builder:
|
||||
(ctx) => ChatModalWidget(
|
||||
profile: profile,
|
||||
event: msg,
|
||||
stream: stream,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 2, vertical: 4),
|
||||
child: Column(
|
||||
spacing: 2,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_chatText(profile),
|
||||
/*RxFilter<Nip01Event>(
|
||||
return ProfileLoaderWidget(msg.pubKey, (ctx, state) {
|
||||
final profile = state.data ?? Metadata(pubKey: msg.pubKey);
|
||||
return GestureDetector(
|
||||
onLongPress: () {
|
||||
if (ndk.accounts.canSign) {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
constraints: BoxConstraints.expand(),
|
||||
builder:
|
||||
(ctx) => ChatModalWidget(
|
||||
profile: profile,
|
||||
event: msg,
|
||||
stream: stream,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 2, vertical: 4),
|
||||
child: Column(
|
||||
spacing: 2,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_chatText(profile),
|
||||
/*RxFilter<Nip01Event>(
|
||||
Key(msg.id),
|
||||
filters: [
|
||||
Filter(kinds: [9735, 7], eTags: [msg.id]),
|
||||
@ -58,12 +56,11 @@ class ChatMessageWidget extends StatelessWidget {
|
||||
return ChatReactions(msg: msg, events: state ?? []);
|
||||
},
|
||||
),*/
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Widget _chatText(Metadata profile) {
|
||||
|
@ -5,6 +5,9 @@ import 'package:ndk/shared/nips/nip19/nip19.dart';
|
||||
import 'package:zap_stream_flutter/const.dart';
|
||||
import 'package:zap_stream_flutter/widgets/avatar.dart';
|
||||
|
||||
// create simple sync cache to avoid extra re-draw
|
||||
final Map<String, Metadata> syncProfileCache = {};
|
||||
|
||||
class ProfileLoaderWidget extends StatelessWidget {
|
||||
final String pubkey;
|
||||
final AsyncWidgetBuilder<Metadata?> builder;
|
||||
@ -14,8 +17,18 @@ class ProfileLoaderWidget extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FutureBuilder(
|
||||
key: super.key ?? Key("profile-loader:$pubkey"),
|
||||
future: ndk.metadata.loadMetadata(pubkey),
|
||||
key: super.key,
|
||||
initialData:
|
||||
syncProfileCache.containsKey(pubkey)
|
||||
? syncProfileCache[pubkey]
|
||||
: null,
|
||||
future: () async {
|
||||
final profile = await ndk.metadata.loadMetadata(pubkey);
|
||||
if (profile != null) {
|
||||
syncProfileCache[pubkey] = profile;
|
||||
}
|
||||
return profile;
|
||||
}(),
|
||||
builder: builder,
|
||||
);
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:ndk/ndk.dart';
|
||||
import 'package:zap_stream_flutter/i18n/strings.g.dart';
|
||||
import 'package:zap_stream_flutter/imgproxy.dart';
|
||||
import 'package:zap_stream_flutter/theme.dart';
|
||||
@ -79,28 +80,31 @@ class StreamTileWidget extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
spacing: 8,
|
||||
children: [
|
||||
AvatarWidget.pubkey(stream.info.host),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
stream.info.title ?? "",
|
||||
overflow: TextOverflow.clip,
|
||||
style: TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
ProfileNameWidget.pubkey(
|
||||
stream.info.host,
|
||||
style: TextStyle(color: LAYER_4),
|
||||
),
|
||||
],
|
||||
ProfileLoaderWidget(stream.info.host, (context, state) {
|
||||
final profile = state.data ?? Metadata(pubKey: stream.info.host);
|
||||
return Row(
|
||||
spacing: 8,
|
||||
children: [
|
||||
AvatarWidget(profile: profile),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
stream.info.title ?? "",
|
||||
overflow: TextOverflow.clip,
|
||||
style: TextStyle(fontWeight: FontWeight.w500),
|
||||
),
|
||||
ProfileNameWidget(
|
||||
profile: profile,
|
||||
style: TextStyle(color: LAYER_4),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
Reference in New Issue
Block a user