Make people view responsive on mobile
This commit is contained in:
@@ -46,121 +46,14 @@ class PeopleView extends ConsumerWidget {
|
||||
orElse: () => people.first,
|
||||
);
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(28, 24, 28, 28),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
flex: 5,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
'People',
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.headlineMedium,
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
'Track what matters to each relationship and follow through.',
|
||||
style: Theme.of(context).textTheme.bodyLarge
|
||||
?.copyWith(color: AppTheme.textSecondary),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
FilledButton.icon(
|
||||
onPressed: () => _handleAddPerson(context, ref),
|
||||
icon: const Icon(Icons.person_add_alt_1_rounded),
|
||||
label: const Text('Add'),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Expanded(
|
||||
child: ListView.separated(
|
||||
itemCount: people.length,
|
||||
separatorBuilder: (_, _) => const SizedBox(height: 12),
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final PersonProfile person = people[index];
|
||||
final bool isSelected = person.id == selected.id;
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
ref
|
||||
.read(selectedPersonIdProvider.notifier)
|
||||
.select(person.id);
|
||||
},
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
child: FrostedCard(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
_AvatarSeed(name: person.name),
|
||||
const SizedBox(width: 14),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
person.name,
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.titleMedium,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
person.relationship,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium
|
||||
?.copyWith(
|
||||
color: AppTheme.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
_ScorePill(score: person.affinityScore),
|
||||
if (isSelected)
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(left: 10),
|
||||
child: Icon(
|
||||
Icons.check_circle,
|
||||
color: Color(0xFF1AB6C8),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 18),
|
||||
Expanded(
|
||||
flex: 6,
|
||||
child: FrostedCard(
|
||||
child: _PersonDetail(
|
||||
person: selected,
|
||||
onEdit: () => _handleEditPerson(context, ref, selected),
|
||||
onDelete: () =>
|
||||
_handleDeletePerson(context, ref, selected.id),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
return LayoutBuilder(
|
||||
builder: (BuildContext context, BoxConstraints constraints) {
|
||||
final bool isCompact = constraints.maxWidth < 860;
|
||||
if (isCompact) {
|
||||
return _buildCompactLayout(context, ref, people, selected);
|
||||
}
|
||||
return _buildWideLayout(context, ref, people, selected);
|
||||
},
|
||||
);
|
||||
},
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
@@ -259,6 +152,104 @@ class PeopleView extends ConsumerWidget {
|
||||
await ref.read(localRepositoryProvider.notifier).deletePerson(personId);
|
||||
ref.read(selectedPersonIdProvider.notifier).clear();
|
||||
}
|
||||
|
||||
Widget _buildWideLayout(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
List<PersonProfile> people,
|
||||
PersonProfile selected,
|
||||
) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(28, 24, 28, 28),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
flex: 5,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
_PeopleHeader(
|
||||
onAdd: () => _handleAddPerson(context, ref),
|
||||
compact: false,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Expanded(
|
||||
child: ListView.separated(
|
||||
itemCount: people.length,
|
||||
separatorBuilder: (_, _) => const SizedBox(height: 12),
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final PersonProfile person = people[index];
|
||||
return _PersonListItem(
|
||||
person: person,
|
||||
selectedId: selected.id,
|
||||
onSelect: () {
|
||||
ref
|
||||
.read(selectedPersonIdProvider.notifier)
|
||||
.select(person.id);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 18),
|
||||
Expanded(
|
||||
flex: 6,
|
||||
child: FrostedCard(
|
||||
child: _PersonDetail(
|
||||
person: selected,
|
||||
onEdit: () => _handleEditPerson(context, ref, selected),
|
||||
onDelete: () => _handleDeletePerson(context, ref, selected.id),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCompactLayout(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
List<PersonProfile> people,
|
||||
PersonProfile selected,
|
||||
) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(18, 16, 18, 20),
|
||||
child: ListView(
|
||||
children: <Widget>[
|
||||
_PeopleHeader(
|
||||
onAdd: () => _handleAddPerson(context, ref),
|
||||
compact: true,
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
...people.map(
|
||||
(PersonProfile person) => Padding(
|
||||
padding: const EdgeInsets.only(bottom: 10),
|
||||
child: _PersonListItem(
|
||||
person: person,
|
||||
selectedId: selected.id,
|
||||
onSelect: () {
|
||||
ref.read(selectedPersonIdProvider.notifier).select(person.id);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
FrostedCard(
|
||||
child: _PersonDetail(
|
||||
person: selected,
|
||||
compact: true,
|
||||
onEdit: () => _handleEditPerson(context, ref, selected),
|
||||
onDelete: () => _handleDeletePerson(context, ref, selected.id),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _EmptyPeopleView extends StatelessWidget {
|
||||
@@ -303,52 +294,107 @@ class _PersonDetail extends StatelessWidget {
|
||||
required this.person,
|
||||
required this.onEdit,
|
||||
required this.onDelete,
|
||||
this.compact = false,
|
||||
});
|
||||
|
||||
final PersonProfile person;
|
||||
final VoidCallback onEdit;
|
||||
final VoidCallback onDelete;
|
||||
final bool compact;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Row(
|
||||
children: <Widget>[
|
||||
_AvatarSeed(name: person.name, size: 64),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
if (compact)
|
||||
Column(
|
||||
children: <Widget>[
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Text(
|
||||
person.name,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
person.relationship,
|
||||
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
||||
color: AppTheme.textSecondary,
|
||||
_AvatarSeed(name: person.name, size: 56),
|
||||
const SizedBox(width: 14),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
person.name,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
person.relationship,
|
||||
style: Theme.of(context).textTheme.bodyLarge
|
||||
?.copyWith(color: AppTheme.textSecondary),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: onEdit,
|
||||
icon: const Icon(Icons.edit_rounded),
|
||||
tooltip: 'Edit person',
|
||||
),
|
||||
IconButton(
|
||||
onPressed: onDelete,
|
||||
icon: const Icon(Icons.delete_outline_rounded),
|
||||
tooltip: 'Delete person',
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 22),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: <Widget>[
|
||||
IconButton(
|
||||
onPressed: onEdit,
|
||||
icon: const Icon(Icons.edit_rounded),
|
||||
tooltip: 'Edit person',
|
||||
),
|
||||
IconButton(
|
||||
onPressed: onDelete,
|
||||
icon: const Icon(Icons.delete_outline_rounded),
|
||||
tooltip: 'Delete person',
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
else
|
||||
Row(
|
||||
children: <Widget>[
|
||||
_AvatarSeed(name: person.name, size: 64),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
person.name,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
person.relationship,
|
||||
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
||||
color: AppTheme.textSecondary,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: onEdit,
|
||||
icon: const Icon(Icons.edit_rounded),
|
||||
tooltip: 'Edit person',
|
||||
),
|
||||
IconButton(
|
||||
onPressed: onDelete,
|
||||
icon: const Icon(Icons.delete_outline_rounded),
|
||||
tooltip: 'Delete person',
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: compact ? 14 : 22),
|
||||
Text('Next moment', style: Theme.of(context).textTheme.titleMedium),
|
||||
const SizedBox(height: 8),
|
||||
_DetailTag(
|
||||
@@ -379,6 +425,123 @@ class _PersonDetail extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _PeopleHeader extends StatelessWidget {
|
||||
const _PeopleHeader({required this.onAdd, required this.compact});
|
||||
|
||||
final VoidCallback onAdd;
|
||||
final bool compact;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final TextTheme textTheme = Theme.of(context).textTheme;
|
||||
if (compact) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text('People', style: textTheme.headlineMedium),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
'Track what matters to each relationship and follow through.',
|
||||
style: textTheme.bodyLarge?.copyWith(color: AppTheme.textSecondary),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
FilledButton.icon(
|
||||
onPressed: onAdd,
|
||||
icon: const Icon(Icons.person_add_alt_1_rounded),
|
||||
label: const Text('Add person'),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
return Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text('People', style: textTheme.headlineMedium),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
'Track what matters to each relationship and follow through.',
|
||||
style: textTheme.bodyLarge?.copyWith(
|
||||
color: AppTheme.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
FilledButton.icon(
|
||||
onPressed: onAdd,
|
||||
icon: const Icon(Icons.person_add_alt_1_rounded),
|
||||
label: const Text('Add'),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _PersonListItem extends StatelessWidget {
|
||||
const _PersonListItem({
|
||||
required this.person,
|
||||
required this.selectedId,
|
||||
required this.onSelect,
|
||||
});
|
||||
|
||||
final PersonProfile person;
|
||||
final String selectedId;
|
||||
final VoidCallback onSelect;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final bool isSelected = person.id == selectedId;
|
||||
return InkWell(
|
||||
onTap: onSelect,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
child: FrostedCard(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
_AvatarSeed(name: person.name),
|
||||
const SizedBox(width: 14),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
person.name,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
person.relationship,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: AppTheme.textSecondary,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
_ScorePill(score: person.affinityScore),
|
||||
if (isSelected)
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(left: 10),
|
||||
child: Icon(Icons.check_circle, color: Color(0xFF1AB6C8)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _PersonEditorDialog extends StatefulWidget {
|
||||
const _PersonEditorDialog({required this.title, this.initial});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user