🐛 Fix background image didn't apply in certain page
This commit is contained in:
@@ -4,6 +4,7 @@ import 'package:go_router/go_router.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:island/models/poll.dart';
|
import 'package:island/models/poll.dart';
|
||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
import 'package:island/widgets/poll/poll_feedback.dart';
|
import 'package:island/widgets/poll/poll_feedback.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
@@ -70,7 +71,7 @@ class CreatorPollListScreen extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
return Scaffold(
|
return AppScaffold(
|
||||||
appBar: AppBar(title: const Text('Polls')),
|
appBar: AppBar(title: const Text('Polls')),
|
||||||
floatingActionButton: FloatingActionButton(
|
floatingActionButton: FloatingActionButton(
|
||||||
onPressed: () => _createPoll(context),
|
onPressed: () => _createPoll(context),
|
||||||
|
@@ -114,10 +114,11 @@ class WebFeedEditScreen extends HookConsumerWidget {
|
|||||||
|
|
||||||
return feedAsync.when(
|
return feedAsync.when(
|
||||||
loading:
|
loading:
|
||||||
() =>
|
() => const AppScaffold(
|
||||||
const Scaffold(body: Center(child: CircularProgressIndicator())),
|
body: Center(child: CircularProgressIndicator()),
|
||||||
|
),
|
||||||
error:
|
error:
|
||||||
(error, stack) => Scaffold(
|
(error, stack) => AppScaffold(
|
||||||
appBar: AppBar(title: const Text('Error')),
|
appBar: AppBar(title: const Text('Error')),
|
||||||
body: Center(child: Text('Error: $error')),
|
body: Center(child: Text('Error: $error')),
|
||||||
),
|
),
|
||||||
|
@@ -9,6 +9,7 @@ import 'package:gap/gap.dart';
|
|||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
import 'package:island/models/poll.dart';
|
import 'package:island/models/poll.dart';
|
||||||
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
class PollEditorState {
|
class PollEditorState {
|
||||||
@@ -413,7 +414,7 @@ class PollEditorScreen extends ConsumerWidget {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return Scaffold(
|
return AppScaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(model.id == null ? 'Create Poll' : 'Edit Poll'),
|
title: Text(model.id == null ? 'Create Poll' : 'Edit Poll'),
|
||||||
actions: [
|
actions: [
|
||||||
@@ -428,175 +429,175 @@ class PollEditorScreen extends ConsumerWidget {
|
|||||||
const Gap(8),
|
const Gap(8),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: SafeArea(
|
body: Column(
|
||||||
child: Form(
|
children: [
|
||||||
key: ValueKey(model.id),
|
Expanded(
|
||||||
child: ListView(
|
child: Form(
|
||||||
padding: const EdgeInsets.all(16),
|
key: ValueKey(model.id),
|
||||||
children: [
|
child: ListView(
|
||||||
TextFormField(
|
padding: const EdgeInsets.all(16),
|
||||||
initialValue: model.title ?? '',
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
labelText: 'Title',
|
|
||||||
border: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(16)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
textInputAction: TextInputAction.next,
|
|
||||||
maxLength: 256,
|
|
||||||
onChanged: notifier.setTitle,
|
|
||||||
onTapOutside:
|
|
||||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
|
||||||
validator: (v) {
|
|
||||||
if (v == null || v.trim().isEmpty) {
|
|
||||||
return 'Title is required';
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
const Gap(12),
|
|
||||||
TextFormField(
|
|
||||||
initialValue: model.description ?? '',
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
labelText: 'Description',
|
|
||||||
alignLabelWithHint: true,
|
|
||||||
border: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(16)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
maxLines: 3,
|
|
||||||
maxLength: 4096,
|
|
||||||
onChanged: notifier.setDescription,
|
|
||||||
onTapOutside:
|
|
||||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
|
||||||
),
|
|
||||||
const Gap(12),
|
|
||||||
_EndDatePicker(
|
|
||||||
value: model.endedAt,
|
|
||||||
onChanged: notifier.setEndedAt,
|
|
||||||
),
|
|
||||||
const Gap(24),
|
|
||||||
Row(
|
|
||||||
children: [
|
children: [
|
||||||
Text(
|
TextFormField(
|
||||||
'Questions',
|
initialValue: model.title ?? '',
|
||||||
style: Theme.of(context).textTheme.titleLarge,
|
decoration: const InputDecoration(
|
||||||
),
|
labelText: 'Title',
|
||||||
const Spacer(),
|
border: OutlineInputBorder(
|
||||||
MenuAnchor(
|
borderRadius: BorderRadius.all(Radius.circular(16)),
|
||||||
builder: (context, controller, child) {
|
),
|
||||||
return FilledButton.icon(
|
),
|
||||||
onPressed: () {
|
textInputAction: TextInputAction.next,
|
||||||
controller.isOpen
|
maxLength: 256,
|
||||||
? controller.close()
|
onChanged: notifier.setTitle,
|
||||||
: controller.open();
|
onTapOutside:
|
||||||
},
|
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
icon: const Icon(Icons.add),
|
validator: (v) {
|
||||||
label: const Text('Add question'),
|
if (v == null || v.trim().isEmpty) {
|
||||||
);
|
return 'Title is required';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
},
|
},
|
||||||
menuChildren:
|
|
||||||
SnPollQuestionType.values
|
|
||||||
.map(
|
|
||||||
(t) => MenuItemButton(
|
|
||||||
leadingIcon: Icon(_iconForType(t)),
|
|
||||||
onPressed: () => notifier.addQuestion(t),
|
|
||||||
child: Text(_labelForType(t)),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
),
|
),
|
||||||
|
const Gap(12),
|
||||||
|
TextFormField(
|
||||||
|
initialValue: model.description ?? '',
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Description',
|
||||||
|
alignLabelWithHint: true,
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(16)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
maxLines: 3,
|
||||||
|
maxLength: 4096,
|
||||||
|
onChanged: notifier.setDescription,
|
||||||
|
onTapOutside:
|
||||||
|
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
),
|
||||||
|
const Gap(12),
|
||||||
|
_EndDatePicker(
|
||||||
|
value: model.endedAt,
|
||||||
|
onChanged: notifier.setEndedAt,
|
||||||
|
),
|
||||||
|
const Gap(24),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Questions',
|
||||||
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
MenuAnchor(
|
||||||
|
builder: (context, controller, child) {
|
||||||
|
return FilledButton.icon(
|
||||||
|
onPressed: () {
|
||||||
|
controller.isOpen
|
||||||
|
? controller.close()
|
||||||
|
: controller.open();
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.add),
|
||||||
|
label: const Text('Add question'),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
menuChildren:
|
||||||
|
SnPollQuestionType.values
|
||||||
|
.map(
|
||||||
|
(t) => MenuItemButton(
|
||||||
|
leadingIcon: Icon(_iconForType(t)),
|
||||||
|
onPressed: () => notifier.addQuestion(t),
|
||||||
|
child: Text(_labelForType(t)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const Gap(8),
|
||||||
|
if (model.questions.isEmpty)
|
||||||
|
_EmptyState(
|
||||||
|
title: 'No questions yet',
|
||||||
|
subtitle:
|
||||||
|
'Use "Add question" to start building your poll.',
|
||||||
|
)
|
||||||
|
else
|
||||||
|
ReorderableListView.builder(
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
itemCount: model.questions.length,
|
||||||
|
onReorder: (oldIndex, newIndex) {
|
||||||
|
// Convert to stepwise moves using provided functions
|
||||||
|
if (newIndex > oldIndex) newIndex -= 1;
|
||||||
|
final steps = newIndex - oldIndex;
|
||||||
|
if (steps == 0) return;
|
||||||
|
if (steps > 0) {
|
||||||
|
for (int i = 0; i < steps; i++) {
|
||||||
|
notifier.moveQuestionDown(oldIndex + i);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i > steps; i--) {
|
||||||
|
notifier.moveQuestionUp(oldIndex + i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
buildDefaultDragHandles: false,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final q = model.questions[index];
|
||||||
|
return Card(
|
||||||
|
key: ValueKey('q_$index'),
|
||||||
|
margin: const EdgeInsets.symmetric(vertical: 8),
|
||||||
|
clipBehavior: Clip.antiAlias,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
_QuestionHeader(
|
||||||
|
index: index,
|
||||||
|
question: q,
|
||||||
|
onMoveUp:
|
||||||
|
index > 0
|
||||||
|
? () => notifier.moveQuestionUp(index)
|
||||||
|
: null,
|
||||||
|
onMoveDown:
|
||||||
|
index < model.questions.length - 1
|
||||||
|
? () => notifier.moveQuestionDown(index)
|
||||||
|
: null,
|
||||||
|
onDelete: () => notifier.removeQuestion(index),
|
||||||
|
),
|
||||||
|
const Divider(height: 1),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: _QuestionEditor(
|
||||||
|
index: index,
|
||||||
|
question: q,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const Gap(96),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const Gap(8),
|
),
|
||||||
if (model.questions.isEmpty)
|
),
|
||||||
_EmptyState(
|
Row(
|
||||||
title: 'No questions yet',
|
children: [
|
||||||
subtitle: 'Use "Add question" to start building your poll.',
|
OutlinedButton.icon(
|
||||||
)
|
onPressed: () {
|
||||||
else
|
Navigator.of(context).maybePop();
|
||||||
ReorderableListView.builder(
|
},
|
||||||
shrinkWrap: true,
|
icon: const Icon(Icons.close),
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
label: const Text('Cancel'),
|
||||||
itemCount: model.questions.length,
|
),
|
||||||
onReorder: (oldIndex, newIndex) {
|
const Spacer(),
|
||||||
// Convert to stepwise moves using provided functions
|
FilledButton.icon(
|
||||||
if (newIndex > oldIndex) newIndex -= 1;
|
onPressed: () {
|
||||||
final steps = newIndex - oldIndex;
|
_submitPoll(context, ref);
|
||||||
if (steps == 0) return;
|
},
|
||||||
if (steps > 0) {
|
icon: const Icon(Icons.cloud_upload_outlined),
|
||||||
for (int i = 0; i < steps; i++) {
|
label: Text(model.id == null ? 'Create' : 'Update'),
|
||||||
notifier.moveQuestionDown(oldIndex + i);
|
),
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (int i = 0; i > steps; i--) {
|
|
||||||
notifier.moveQuestionUp(oldIndex + i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
buildDefaultDragHandles: false,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
final q = model.questions[index];
|
|
||||||
return Card(
|
|
||||||
key: ValueKey('q_$index'),
|
|
||||||
margin: const EdgeInsets.symmetric(vertical: 8),
|
|
||||||
clipBehavior: Clip.antiAlias,
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
_QuestionHeader(
|
|
||||||
index: index,
|
|
||||||
question: q,
|
|
||||||
onMoveUp:
|
|
||||||
index > 0
|
|
||||||
? () => notifier.moveQuestionUp(index)
|
|
||||||
: null,
|
|
||||||
onMoveDown:
|
|
||||||
index < model.questions.length - 1
|
|
||||||
? () => notifier.moveQuestionDown(index)
|
|
||||||
: null,
|
|
||||||
onDelete: () => notifier.removeQuestion(index),
|
|
||||||
),
|
|
||||||
const Divider(height: 1),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: _QuestionEditor(index: index, question: q),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
const Gap(96),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
),
|
|
||||||
bottomNavigationBar: Padding(
|
|
||||||
padding: EdgeInsets.fromLTRB(
|
|
||||||
16,
|
|
||||||
8,
|
|
||||||
16,
|
|
||||||
16 + MediaQuery.of(context).padding.bottom,
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
OutlinedButton.icon(
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).maybePop();
|
|
||||||
},
|
|
||||||
icon: const Icon(Icons.close),
|
|
||||||
label: const Text('Cancel'),
|
|
||||||
),
|
|
||||||
const Spacer(),
|
|
||||||
FilledButton.icon(
|
|
||||||
onPressed: () {
|
|
||||||
_submitPoll(context, ref);
|
|
||||||
},
|
|
||||||
icon: const Icon(Icons.cloud_upload_outlined),
|
|
||||||
label: Text(model.id == null ? 'Create' : 'Update'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user