mirror of
https://github.com/block/buzz.git
synced 2026-10-31 06:21:34 +00:00
## Summary
- add poster-first, tap-to-toggle animated avatars on profile surfaces
while preserving transparent/static behavior elsewhere
- align mobile DM headers, membership actions, and invisible agent
recipient addressing with established desktop semantics
- polish titled sheets and status editing, preserve native iOS sheet
corners, and batch relay reads to improve review-build responsiveness
## Snapshots
<table>
<tr>
<th>Profile avatar</th>
<th>Agent DM header and composer</th>
</tr>
<tr>
<td><img
src="https://raw.githubusercontent.com/block/buzz/e8de7495451dbe1a393ab43c5e204ca7425f2ba5/pr-5401--profile-avatar.png"
width="360" alt="Mobile profile settings with animated avatar
surface"></td>
<td><img
src="https://raw.githubusercontent.com/block/buzz/e8de7495451dbe1a393ab43c5e204ca7425f2ba5/pr-5401--agent-dm.png"
width="360" alt="Agent direct message with masked presence and normal
composer"></td>
</tr>
<tr>
<th>Members sheet</th>
<th>Status editor</th>
</tr>
<tr>
<td><img
src="https://raw.githubusercontent.com/block/buzz/e8de7495451dbe1a393ab43c5e204ca7425f2ba5/pr-5401--members-sheet.png"
width="360" alt="Members bottom sheet with centered title and padded
content"></td>
<td><img
src="https://raw.githubusercontent.com/block/buzz/e8de7495451dbe1a393ab43c5e204ca7425f2ba5/pr-5401--status-sheet.png"
width="360" alt="Status editor bottom sheet with duration and quick
statuses"></td>
</tr>
<tr>
<th colspan="2">Switch Community</th>
</tr>
<tr>
<td colspan="2" align="center"><img
src="https://raw.githubusercontent.com/block/buzz/e8de7495451dbe1a393ab43c5e204ca7425f2ba5/pr-5401--switch-community.png"
width="720" alt="Switch Community bottom sheet with centered title and
aligned Edit action"></td>
</tr>
</table>
## Validation
- `just mobile-check`
- `just mobile-test` (1,283 tests)
- installed and reviewed isolated debug builds on iPhone and Pixel
---------
Signed-off-by: kenny lopez <klopez4212@gmail.com>
Signed-off-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz>
Signed-off-by: Kenny Lopez <klopez4212@gmail.com>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz>
Co-authored-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
121 lines
4.1 KiB
Dart
121 lines
4.1 KiB
Dart
import 'package:flutter/material.dart';
|
||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
||
|
||
import '../theme/theme.dart';
|
||
import '../widgets/sheet_divider.dart';
|
||
import '../widgets/modal_presentation.dart';
|
||
import 'reminder_service.dart';
|
||
import 'reminder_time_presets.dart';
|
||
|
||
/// Bottom sheet for "Remind me later": desktop-parity presets plus a native
|
||
/// date/time picker, publishing the same author-private kind-30300 reminder
|
||
/// events desktop reads.
|
||
void showRemindMeLaterSheet({
|
||
required BuildContext context,
|
||
required WidgetRef ref,
|
||
required ReminderTarget target,
|
||
}) {
|
||
final service = ref.read(reminderServiceProvider);
|
||
final messenger = ScaffoldMessenger.of(context);
|
||
if (service == null) {
|
||
messenger.showSnackBar(
|
||
const SnackBar(content: Text('Sign in to set reminders')),
|
||
);
|
||
return;
|
||
}
|
||
|
||
Future<void> submit(int notBefore) async {
|
||
try {
|
||
await service.createReminder(target: target, notBefore: notBefore);
|
||
messenger.showSnackBar(const SnackBar(content: Text('Reminder set')));
|
||
} catch (error) {
|
||
// Stable user-facing copy; the relay/crypto detail goes to the log.
|
||
debugPrint('[RemindMeLater] createReminder failed: $error');
|
||
messenger.showSnackBar(
|
||
const SnackBar(content: Text('Couldn’t set reminder. Try again.')),
|
||
);
|
||
}
|
||
}
|
||
|
||
showBuzzModalBottomSheet<void>(
|
||
context: context,
|
||
title: 'Remind me about this message',
|
||
showDragHandle: true,
|
||
builder: (sheetContext) => SafeArea(
|
||
child: IconTheme.merge(
|
||
data: const IconThemeData(size: 22),
|
||
child: SingleChildScrollView(
|
||
child: Padding(
|
||
padding: const EdgeInsets.fromLTRB(
|
||
Grid.gutter,
|
||
0,
|
||
Grid.gutter,
|
||
Grid.xs,
|
||
),
|
||
child: Column(
|
||
mainAxisSize: MainAxisSize.min,
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
for (final preset in reminderTimePresets)
|
||
ListTile(
|
||
leading: const Icon(LucideIcons.clock),
|
||
title: Text(preset.label),
|
||
onTap: () {
|
||
Navigator.of(sheetContext).pop();
|
||
submit(preset.getTimestamp());
|
||
},
|
||
),
|
||
const SheetDivider(),
|
||
ListTile(
|
||
leading: const Icon(LucideIcons.calendarClock),
|
||
title: const Text('Pick a date & time'),
|
||
onTap: () async {
|
||
final navigator = Navigator.of(sheetContext);
|
||
final timestamp = await _pickCustomDateTime(context);
|
||
// Cancelled or not-in-the-future: keep the preset sheet
|
||
// open so retrying doesn't mean long-pressing again.
|
||
if (timestamp == null) return;
|
||
if (navigator.mounted) navigator.pop();
|
||
await submit(timestamp);
|
||
},
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
/// Native date + time pickers chained together. Returns a future Unix
|
||
/// timestamp in seconds, or null when cancelled or not in the future.
|
||
Future<int?> _pickCustomDateTime(BuildContext context) async {
|
||
final now = DateTime.now();
|
||
final date = await showBuzzDialog<DateTime>(
|
||
context: context,
|
||
builder: (_) => DatePickerDialog(
|
||
initialDate: now,
|
||
firstDate: DateTime(now.year, now.month, now.day),
|
||
lastDate: now.add(const Duration(days: 365 * 2)),
|
||
),
|
||
);
|
||
if (date == null || !context.mounted) return null;
|
||
|
||
final time = await showBuzzDialog<TimeOfDay>(
|
||
context: context,
|
||
builder: (_) =>
|
||
const TimePickerDialog(initialTime: TimeOfDay(hour: 9, minute: 0)),
|
||
);
|
||
if (time == null) return null;
|
||
|
||
final timestamp = combineCustomDateTime(date, time.hour, time.minute);
|
||
if (timestamp == null && context.mounted) {
|
||
ScaffoldMessenger.of(
|
||
context,
|
||
).showSnackBar(const SnackBar(content: Text('Pick a time in the future')));
|
||
}
|
||
return timestamp;
|
||
}
|