feat: stream cards

closes #21
This commit is contained in:
2025-05-14 13:46:14 +01:00
parent eefbbc2f73
commit 465c6f222e
6 changed files with 130 additions and 7 deletions

View File

@ -10,6 +10,7 @@ import 'package:zap_stream_flutter/utils.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/nostr_text.dart';
import 'package:zap_stream_flutter/widgets/profile.dart';
import 'package:zap_stream_flutter/widgets/stream_grid.dart';
@ -53,9 +54,15 @@ class ProfilePage extends StatelessWidget {
fontWeight: FontWeight.w600,
),
),
Text(
profile.about ?? "",
style: TextStyle(color: LAYER_5),
Text.rich(
TextSpan(
style: TextStyle(color: LAYER_5),
children: textToSpans(
profile.about ?? "",
[],
profile.pubKey,
),
),
),
],
),

View File

@ -101,7 +101,10 @@ class _StreamPage extends State<StreamPage> {
aspectRatio: 16 / 9,
child:
_chewieController != null
? Chewie(controller: _chewieController!)
? Chewie(
key: Key("stream:player:${stream.aTag}"),
controller: _chewieController!,
)
: Container(
color: LAYER_1,
child:
@ -163,9 +166,8 @@ class _StreamPage extends State<StreamPage> {
),
GestureDetector(
onTap: () {
showModalBottomSheet(
showBottomSheet(
context: context,
constraints: BoxConstraints.expand(),
builder: (context) => StreamInfoWidget(stream: stream),
);
},

View File

@ -0,0 +1,95 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_markdown_plus/flutter_markdown_plus.dart';
import 'package:flutter_svg/svg.dart';
import 'package:ndk/ndk.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:zap_stream_flutter/imgproxy.dart';
import 'package:zap_stream_flutter/rx_filter.dart';
import 'package:zap_stream_flutter/theme.dart';
import 'package:zap_stream_flutter/utils.dart';
import 'package:zap_stream_flutter/widgets/nostr_text.dart';
class StreamCardsWidget extends StatelessWidget {
final StreamEvent stream;
const StreamCardsWidget({super.key, required this.stream});
@override
Widget build(BuildContext context) {
return RxFilter<Nip01Event>(
Key("stream:cards:${stream.aTag}"),
filters: [
Filter(kinds: [17_777], authors: [stream.info.host], limit: 1),
],
builder: (context, state) {
final cardList = state?.firstOrNull;
if (cardList == null) return SizedBox();
final cardIds = cardList.getTags("a");
return RxFilter<Nip01Event>(
Key("stream:cards:${stream.aTag}:cards"),
filters: [
Filter(
kinds: [37_777],
authors: [stream.info.host],
dTags: cardIds.map((i) => i.split(":").last).toList(),
),
],
builder: (context, state) {
final cards = state ?? [];
return Column(
spacing: 8,
children: cards.map((c) => _streamCard(context, c)).toList(),
);
},
);
},
);
}
Widget _streamCard(BuildContext context, Nip01Event card) {
final title = card.getFirstTag("title") ?? card.getFirstTag("subject");
final image = card.getFirstTag("image");
final link = card.getFirstTag("r");
return Container(
padding: EdgeInsets.all(8),
width: double.maxFinite,
decoration: BoxDecoration(color: LAYER_2, borderRadius: DEFAULT_BR),
child: Column(
spacing: 8,
children: [
if (title?.isNotEmpty ?? false)
Text(
title!,
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
if (image?.isNotEmpty ?? false)
link != null
? GestureDetector(
onTap: () {
launchUrl(Uri.parse(link));
},
child: CachedNetworkImage(
imageUrl: proxyImg(context, image!),
errorWidget:
(_, _, _) =>
SvgPicture.asset("assets/svg/logo.svg", height: 40),
),
)
: CachedNetworkImage(imageUrl: proxyImg(context, image!)),
MarkdownBody(
data: card.content,
onTapLink: (text, href, title) {
if (href != null) {
launchUrl(Uri.parse(href));
}
},
),
],
),
);
}
}

View File

@ -3,6 +3,7 @@ import 'package:intl/intl.dart';
import 'package:zap_stream_flutter/theme.dart';
import 'package:zap_stream_flutter/utils.dart';
import 'package:zap_stream_flutter/widgets/profile.dart';
import 'package:zap_stream_flutter/widgets/stream_cards.dart';
class StreamInfoWidget extends StatelessWidget {
final StreamEvent stream;
@ -38,6 +39,7 @@ class StreamInfoWidget extends StatelessWidget {
),
if (stream.info.summary?.isNotEmpty ?? false)
Text(stream.info.summary!),
StreamCardsWidget(stream: stream),
],
),
);

View File

@ -294,6 +294,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "5.0.0"
flutter_markdown_plus:
dependency: "direct main"
description:
name: flutter_markdown_plus
sha256: fe74214c5ac2f850d93efda290dcde3f18006e90a87caa9e3e6c13222a5db4de
url: "https://pub.dev"
source: hosted
version: "1.0.3"
flutter_plugin_android_lifecycle:
dependency: transitive
description:
@ -544,6 +552,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.0"
markdown:
dependency: transitive
description:
name: markdown
sha256: "935e23e1ff3bc02d390bad4d4be001208ee92cc217cb5b5a6c19bc14aaa318c1"
url: "https://pub.dev"
source: hosted
version: "7.3.0"
matcher:
dependency: transitive
description:
@ -1210,4 +1226,4 @@ packages:
version: "1.2.0"
sdks:
dart: ">=3.7.2 <4.0.0"
flutter: ">=3.27.0"
flutter: ">=3.27.1"

View File

@ -32,6 +32,7 @@ dependencies:
emoji_picker_flutter: ^4.3.0
bech32: ^0.2.2
intl: ^0.20.2
flutter_markdown_plus: ^1.0.3
dependency_overrides:
ndk: