feat: zaps / profiles

This commit is contained in:
2025-05-09 12:23:39 +01:00
parent ab5bdcca69
commit 0ba0839863
25 changed files with 778 additions and 79 deletions

View File

@ -16,7 +16,9 @@ class HomePage extends StatelessWidget {
children: [
HeaderWidget(),
RxFilter<Nip01Event>(
filter: Filter(kinds: [30_311], limit: 10),
filters: [
Filter(kinds: [30_311], limit: 50),
],
builder: (ctx, state) {
if (state == null) {
return SizedBox.shrink();

View File

@ -4,10 +4,11 @@ import 'package:go_router/go_router.dart';
import 'package:ndk/shared/nips/nip19/nip19.dart';
import 'package:zap_stream_flutter/login.dart';
import 'package:zap_stream_flutter/main.dart';
import 'package:zap_stream_flutter/theme.dart';
import 'package:zap_stream_flutter/widgets/button.dart';
class LoginPage extends StatefulWidget {
const LoginPage({super.key});
@override
State<StatefulWidget> createState() => _LoginPage();
}
@ -42,7 +43,7 @@ class _LoginPage extends State<LoginPage> {
}
},
),
BasicButton.text("Login with Key"),
/*BasicButton.text("Login with Key"),
Container(
margin: EdgeInsets.symmetric(vertical: 20),
height: 1,
@ -50,7 +51,7 @@ class _LoginPage extends State<LoginPage> {
border: Border(bottom: BorderSide(color: LAYER_1)),
),
),
Text("Create Account"),
Text("Create Account"),*/
],
),
);

104
lib/pages/profile.dart Normal file
View File

@ -0,0 +1,104 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/widgets.dart';
import 'package:go_router/go_router.dart';
import 'package:ndk/ndk.dart';
import 'package:ndk/shared/nips/nip19/nip19.dart';
import 'package:zap_stream_flutter/imgproxy.dart';
import 'package:zap_stream_flutter/main.dart';
import 'package:zap_stream_flutter/rx_filter.dart';
import 'package:zap_stream_flutter/theme.dart';
import 'package:zap_stream_flutter/widgets/avatar.dart';
import 'package:zap_stream_flutter/widgets/button.dart';
import 'package:zap_stream_flutter/widgets/header.dart';
import 'package:zap_stream_flutter/widgets/profile.dart';
import 'package:zap_stream_flutter/widgets/stream_grid.dart';
class ProfilePage extends StatelessWidget {
final String pubkey;
const ProfilePage({super.key, required this.pubkey});
@override
Widget build(BuildContext context) {
final hexPubkey = Nip19.decode(pubkey);
return ProfileLoaderWidget(hexPubkey, (ctx, state) {
final profile = state.data ?? Metadata(pubKey: hexPubkey);
return SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 16,
children: [
HeaderWidget(),
if (profile.banner != null)
SizedBox(
height: 140,
width: double.maxFinite,
child: CachedNetworkImage(
imageUrl: proxyImg(context, profile.banner!),
fit: BoxFit.cover,
),
),
Row(
spacing: 8,
children: [
AvatarWidget(profile: profile, size: 80),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ProfileNameWidget(
profile: profile,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w600,
),
),
Text(
profile.about ?? "",
style: TextStyle(color: LAYER_5),
),
],
),
),
],
),
if (ndk.accounts.getPublicKey() == hexPubkey)
Row(
children: [
BasicButton.text(
"Logout",
onTap: () {
loginData.logout();
context.go("/");
},
),
],
),
Text(
"Past Streams",
style: TextStyle(fontSize: 20, fontWeight: FontWeight.w600),
),
RxFilter<Nip01Event>(
key: Key("profile-streams:$hexPubkey"),
relays: defaultRelays,
filters: [
Filter(kinds: [30_311], limit: 200, pTags: [hexPubkey]),
Filter(kinds: [30_311], limit: 200, authors: [hexPubkey]),
],
builder: (ctx, state) {
return StreamGrid(
events: state ?? [],
showLive: true,
showEnded: true,
showPlanned: true,
);
},
),
],
),
);
});
}
}

View File

@ -1,12 +1,15 @@
import 'package:flutter/widgets.dart';
import 'package:chewie/chewie.dart';
import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';
import 'package:wakelock_plus/wakelock_plus.dart';
import 'package:zap_stream_flutter/main.dart';
import 'package:zap_stream_flutter/theme.dart';
import 'package:zap_stream_flutter/utils.dart';
import 'package:zap_stream_flutter/widgets/button.dart';
import 'package:zap_stream_flutter/widgets/chat.dart';
import 'package:zap_stream_flutter/widgets/pill.dart';
import 'package:zap_stream_flutter/widgets/profile.dart';
import 'package:zap_stream_flutter/widgets/zap.dart';
class StreamPage extends StatefulWidget {
final StreamEvent stream;
@ -19,6 +22,7 @@ class StreamPage extends StatefulWidget {
class _StreamPage extends State<StreamPage> {
VideoPlayerController? _controller;
ChewieController? _chewieController;
@override
void initState() {
@ -34,10 +38,15 @@ class _StreamPage extends State<StreamPage> {
_controller = VideoPlayerController.networkUrl(
Uri.parse(url),
httpHeaders: Map.from({"user-agent": userAgent}),
videoPlayerOptions: VideoPlayerOptions(allowBackgroundPlayback: true),
);
() async {
await _controller!.initialize();
await _controller!.play();
_chewieController = ChewieController(
videoPlayerController: _controller!,
autoPlay: true,
);
setState(() {
// nothing
});
@ -64,8 +73,8 @@ class _StreamPage extends State<StreamPage> {
AspectRatio(
aspectRatio: 16 / 9,
child:
_controller != null
? VideoPlayer(_controller!)
_chewieController != null
? Chewie(controller: _chewieController!)
: Container(color: LAYER_1),
),
Text(
@ -76,15 +85,45 @@ class _StreamPage extends State<StreamPage> {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
ProfileWidget.pubkey(widget.stream.info.host),
PillWidget(
color: LAYER_1,
child: Text(
"${widget.stream.info.participants} viewers",
style: TextStyle(fontSize: 12, fontWeight: FontWeight.w500),
),
Row(
spacing: 8,
children: [
BasicButton(
Row(children: [Icon(Icons.bolt, size: 14), Text("Zap")]),
padding: EdgeInsets.symmetric(horizontal: 10, vertical: 2),
decoration: BoxDecoration(
color: PRIMARY_1,
borderRadius: DEFAULT_BR,
),
onTap: () {
showModalBottomSheet(
context: context,
constraints: BoxConstraints.expand(),
builder: (ctx) {
return ZapWidget(
pubkey: widget.stream.info.host,
target: widget.stream.event,
);
},
);
},
),
if (widget.stream.info.participants != null)
PillWidget(
color: LAYER_1,
child: Text(
"${widget.stream.info.participants} viewers",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
),
),
),
],
),
],
),
SizedBox(height: 10),
Expanded(child: ChatWidget(stream: widget.stream)),
],
);