Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions mobile/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ PODS:
- connectivity_plus (0.0.1):
- Flutter
- Flutter (1.0.0)
- flutter_secure_storage (6.0.0):
- flutter_secure_storage_darwin (10.0.0):
- Flutter
- FlutterMacOS
- image_picker_ios (0.0.1):
- Flutter
- mobile_scanner (7.0.0):
Expand All @@ -23,7 +24,7 @@ PODS:
DEPENDENCIES:
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
- Flutter (from `Flutter`)
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
- flutter_secure_storage_darwin (from `.symlinks/plugins/flutter_secure_storage_darwin/darwin`)
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
- mobile_scanner (from `.symlinks/plugins/mobile_scanner/darwin`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
Expand All @@ -36,8 +37,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/connectivity_plus/ios"
Flutter:
:path: Flutter
flutter_secure_storage:
:path: ".symlinks/plugins/flutter_secure_storage/ios"
flutter_secure_storage_darwin:
:path: ".symlinks/plugins/flutter_secure_storage_darwin/darwin"
image_picker_ios:
:path: ".symlinks/plugins/image_picker_ios/ios"
mobile_scanner:
Expand All @@ -54,7 +55,7 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13
flutter_secure_storage_darwin: acdb3f316ed05a3e68f856e0353b133eec373a23
image_picker_ios: e0ece4aa2a75771a7de3fa735d26d90817041326
mobile_scanner: 9157936403f5a0644ca3779a38ff8404c5434a93
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
Expand Down
8 changes: 7 additions & 1 deletion mobile/lib/features/home/home_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:lucide_icons_flutter/lucide_icons.dart';

import '../activity/activity_page.dart';
import '../channels/channels_page.dart';
import '../pulse/pulse_page.dart';
import '../search/search_page.dart';

class HomePage extends HookConsumerWidget {
Expand All @@ -14,7 +15,7 @@ class HomePage extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final tabIndex = useState(0);

const pages = [ChannelsPage(), ActivityPage(), SearchPage()];
const pages = [ChannelsPage(), PulsePage(), ActivityPage(), SearchPage()];

return Scaffold(
body: IndexedStack(index: tabIndex.value, children: pages),
Expand All @@ -27,6 +28,11 @@ class HomePage extends HookConsumerWidget {
selectedIcon: Icon(LucideIcons.house),
label: 'Home',
),
NavigationDestination(
icon: Icon(LucideIcons.radio),
selectedIcon: Icon(LucideIcons.radio),
label: 'Pulse',
),
NavigationDestination(
icon: Icon(LucideIcons.bell),
selectedIcon: Icon(LucideIcons.bell),
Expand Down
190 changes: 190 additions & 0 deletions mobile/lib/features/pulse/agent_activity_card.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:lucide_icons_flutter/lucide_icons.dart';

import '../../shared/theme/theme.dart';
import '../profile/user_cache_provider.dart';
import 'note_card.dart';
import 'pulse_models.dart';

class AgentActivityCard extends HookConsumerWidget {
final AgentNoteGroup group;
final Map<String, PulseReactionState> reactions;
final VoidCallback? onReactionChanged;

const AgentActivityCard({
super.key,
required this.group,
required this.reactions,
this.onReactionChanged,
});

@override
Widget build(BuildContext context, WidgetRef ref) {
final expanded = useState(group.notes.length == 1);
final profile =
ref.watch(userCacheProvider.select((cache) => cache[group.pubkey])) ??
ref.read(userCacheProvider.notifier).get(group.pubkey);
final name = profile?.label ?? _shortPubkey(group.pubkey);

return Container(
decoration: BoxDecoration(
color: context.colors.surfaceContainerHighest.withValues(alpha: 0.6),
borderRadius: BorderRadius.circular(Radii.lg),
border: Border.all(
color: context.colors.primary.withValues(alpha: 0.22),
),
),
child: Column(
children: [
InkWell(
onTap: group.notes.length > 1
? () => expanded.value = !expanded.value
: null,
borderRadius: const BorderRadius.vertical(
top: Radius.circular(Radii.lg),
),
child: Padding(
padding: const EdgeInsets.all(Grid.twelve),
child: Row(
children: [
Stack(
children: [
CircleAvatar(
radius: 18,
backgroundColor: context.colors.primaryContainer,
backgroundImage: profile?.avatarUrl != null
? NetworkImage(profile!.avatarUrl!)
: null,
child: profile?.avatarUrl == null
? const Icon(LucideIcons.bot, size: 18)
: null,
),
Positioned(
right: 0,
bottom: 0,
child: Container(
width: 10,
height: 10,
decoration: BoxDecoration(
color: context.appColors.success,
shape: BoxShape.circle,
border: Border.all(
color: context.colors.surfaceContainerHighest,
width: 1.5,
),
),
),
),
],
),
const SizedBox(width: Grid.xs),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Flexible(
child: Text(
name,
overflow: TextOverflow.ellipsis,
style: context.textTheme.labelLarge?.copyWith(
fontWeight: FontWeight.w700,
),
),
),
const SizedBox(width: Grid.half),
Container(
padding: const EdgeInsets.symmetric(
horizontal: Grid.half,
vertical: 2,
),
decoration: BoxDecoration(
color: context.colors.primary.withValues(
alpha: 0.12,
),
borderRadius: BorderRadius.circular(Radii.sm),
),
child: Text(
'BOT',
style: context.textTheme.labelSmall?.copyWith(
color: context.colors.primary,
fontWeight: FontWeight.w800,
fontSize: 10,
),
),
),
],
),
Text(
'${group.notes.length} update${group.notes.length == 1 ? '' : 's'} · ${formatPulseRelativeTime(group.latestAt)}',
style: context.textTheme.labelSmall?.copyWith(
color: context.colors.onSurfaceVariant,
),
),
],
),
),
if (group.notes.length > 1)
Icon(
expanded.value
? LucideIcons.chevronUp
: LucideIcons.chevronDown,
size: 18,
color: context.colors.onSurfaceVariant,
),
],
),
),
),
if (expanded.value)
Padding(
padding: const EdgeInsets.fromLTRB(Grid.xs, 0, Grid.xs, Grid.xs),
child: Column(
children: [
for (final note in group.notes) ...[
NoteCard(
note: note,
reaction:
reactions[note.id] ??
const PulseReactionState(
count: 0,
reactedByCurrentUser: false,
),
isAgent: true,
onReactionChanged: onReactionChanged,
),
if (note != group.notes.last)
const SizedBox(height: Grid.xxs),
],
],
),
)
else
Padding(
padding: const EdgeInsets.fromLTRB(
Grid.twelve,
0,
Grid.twelve,
Grid.twelve,
),
child: Align(
alignment: Alignment.centerLeft,
child: Text(
group.notes.first.content,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: context.textTheme.bodyMedium,
),
),
),
],
),
);
}
}

String _shortPubkey(String pubkey) =>
pubkey.length <= 8 ? pubkey : '${pubkey.substring(0, 8)}…';
Loading