Refine backendless share intake
This commit is contained in:
@@ -4,6 +4,8 @@ import 'package:relationship_saver/core/config/app_theme.dart';
|
||||
import 'package:relationship_saver/features/local/local_models.dart';
|
||||
import 'package:relationship_saver/features/local/local_repository.dart';
|
||||
import 'package:relationship_saver/features/share_intake/share_capture_models.dart';
|
||||
import 'package:relationship_saver/features/share_intake/share_capture_review_sheet.dart';
|
||||
import 'package:relationship_saver/features/share_intake/share_payload_parser.dart';
|
||||
import 'package:relationship_saver/features/shared/frosted_card.dart';
|
||||
|
||||
/// Review unresolved shared messages and map them to a person profile.
|
||||
@@ -45,13 +47,25 @@ class _ShareInboxViewState extends ConsumerState<ShareInboxView> {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
'Share Inbox',
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
Wrap(
|
||||
spacing: 12,
|
||||
runSpacing: 10,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
'Capture',
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
),
|
||||
FilledButton.icon(
|
||||
onPressed: _submitting ? null : _addTextCapture,
|
||||
icon: const Icon(Icons.edit_note_rounded),
|
||||
label: const Text('Add Text'),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
'Resolve shared messages that could not be matched confidently.',
|
||||
'Share messages here, paste copied text, then choose or create the right person.',
|
||||
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
||||
color: AppTheme.textSecondary,
|
||||
),
|
||||
@@ -59,7 +73,9 @@ class _ShareInboxViewState extends ConsumerState<ShareInboxView> {
|
||||
const SizedBox(height: 14),
|
||||
Expanded(
|
||||
child: localState.sharedInbox.isEmpty
|
||||
? _EmptyInbox(compact: compact)
|
||||
? SingleChildScrollView(
|
||||
child: _EmptyInbox(compact: compact),
|
||||
)
|
||||
: ListView.separated(
|
||||
itemCount: localState.sharedInbox.length,
|
||||
separatorBuilder: (_, _) =>
|
||||
@@ -178,13 +194,8 @@ class _ShareInboxViewState extends ConsumerState<ShareInboxView> {
|
||||
}
|
||||
final _CreateProfileDraft? draft = await showDialog<_CreateProfileDraft>(
|
||||
context: context,
|
||||
builder: (BuildContext context) => _CreateProfileDialog(
|
||||
initialName:
|
||||
entry.sourceDisplayName ??
|
||||
entry.sourceUserId ??
|
||||
entry.sourceThreadId ??
|
||||
'',
|
||||
),
|
||||
builder: (BuildContext context) =>
|
||||
_CreateProfileDialog(detectedName: entry.sourceDisplayName),
|
||||
);
|
||||
if (draft == null || !mounted) {
|
||||
return;
|
||||
@@ -232,6 +243,38 @@ class _ShareInboxViewState extends ConsumerState<ShareInboxView> {
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _addTextCapture() async {
|
||||
if (_submitting) {
|
||||
return;
|
||||
}
|
||||
final String? rawText = await showDialog<String>(
|
||||
context: context,
|
||||
builder: (BuildContext context) => const _AddTextDialog(),
|
||||
);
|
||||
if (rawText == null || rawText.trim().isEmpty || !mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
final SharedPayload payload = SharePayloadParser.parse(
|
||||
rawText: rawText,
|
||||
sourceApp: 'manual',
|
||||
platform: 'manual',
|
||||
createdAt: DateTime.now(),
|
||||
receivedAt: DateTime.now(),
|
||||
);
|
||||
final SharedMessageIngestResult? result = await showShareCaptureReviewSheet(
|
||||
context,
|
||||
ref: ref,
|
||||
payload: payload,
|
||||
);
|
||||
if (result == null || !mounted) {
|
||||
return;
|
||||
}
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text(_captureResultMessage(result))));
|
||||
}
|
||||
|
||||
Future<void> _runRepositoryAction(Future<void> Function() action) async {
|
||||
setState(() {
|
||||
_submitting = true;
|
||||
@@ -453,6 +496,60 @@ class _ShareInboxViewState extends ConsumerState<ShareInboxView> {
|
||||
}
|
||||
}
|
||||
|
||||
class _AddTextDialog extends StatefulWidget {
|
||||
const _AddTextDialog();
|
||||
|
||||
@override
|
||||
State<_AddTextDialog> createState() => _AddTextDialogState();
|
||||
}
|
||||
|
||||
class _AddTextDialogState extends State<_AddTextDialog> {
|
||||
late final TextEditingController _controller;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = TextEditingController();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: const Text('Add Text'),
|
||||
content: SizedBox(
|
||||
width: 520,
|
||||
child: TextField(
|
||||
controller: _controller,
|
||||
autofocus: true,
|
||||
minLines: 5,
|
||||
maxLines: 10,
|
||||
textInputAction: TextInputAction.newline,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Shared or copied text',
|
||||
hintText: 'Paste a message, note, link, preference, or date...',
|
||||
),
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: const Text('Cancel'),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: () => Navigator.of(context).pop(_controller.text),
|
||||
child: const Text('Review'),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CandidateReview {
|
||||
const _CandidateReview({
|
||||
required this.person,
|
||||
@@ -1073,6 +1170,16 @@ String _factTypeLabel(CapturedFactType type) {
|
||||
};
|
||||
}
|
||||
|
||||
String _captureResultMessage(SharedMessageIngestResult result) {
|
||||
if (result.isQueuedForResolution) {
|
||||
return 'Saved to Capture inbox.';
|
||||
}
|
||||
if (result.createdProfile) {
|
||||
return 'Saved and created ${result.profileName}.';
|
||||
}
|
||||
return 'Saved to ${result.profileName}.';
|
||||
}
|
||||
|
||||
class _EmptyInbox extends StatelessWidget {
|
||||
const _EmptyInbox({required this.compact});
|
||||
|
||||
@@ -1103,7 +1210,7 @@ class _EmptyInbox extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
'Incoming shared captures that need manual person mapping or fact triage will appear here.',
|
||||
'Share a message into the app or use Add Text to start building your private relationship notes.',
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.bodyMedium?.copyWith(color: AppTheme.textSecondary),
|
||||
@@ -1132,9 +1239,9 @@ class _CreateProfileDraft {
|
||||
}
|
||||
|
||||
class _CreateProfileDialog extends StatefulWidget {
|
||||
const _CreateProfileDialog({required this.initialName});
|
||||
const _CreateProfileDialog({this.detectedName});
|
||||
|
||||
final String initialName;
|
||||
final String? detectedName;
|
||||
|
||||
@override
|
||||
State<_CreateProfileDialog> createState() => _CreateProfileDialogState();
|
||||
@@ -1149,7 +1256,7 @@ class _CreateProfileDialogState extends State<_CreateProfileDialog> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_nameController = TextEditingController(text: widget.initialName);
|
||||
_nameController = TextEditingController();
|
||||
_relationshipController = TextEditingController(text: 'Contact');
|
||||
_locationController = TextEditingController();
|
||||
_aliasesController = TextEditingController();
|
||||
@@ -1172,9 +1279,21 @@ class _CreateProfileDialogState extends State<_CreateProfileDialog> {
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
if (widget.detectedName?.trim().isNotEmpty == true) ...<Widget>[
|
||||
Text(
|
||||
'Detected message author: ${widget.detectedName!.trim()}',
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.bodySmall?.copyWith(color: AppTheme.textSecondary),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
TextField(
|
||||
controller: _nameController,
|
||||
decoration: const InputDecoration(labelText: 'Name'),
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Person name',
|
||||
hintText: 'Enter the real contact',
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
TextField(
|
||||
|
||||
Reference in New Issue
Block a user