This commit is contained in:
problematicconsumer
2024-01-12 22:45:52 +03:30
parent 835668b869
commit 3c38d6a88f
3 changed files with 202 additions and 197 deletions

View File

@@ -48,7 +48,7 @@ public class MethodHandler: NSObject, FlutterPlugin {
result(FlutterError(code: String(error.code), message: error.description, details: nil)) result(FlutterError(code: String(error.code), message: error.description, details: nil))
return return
} }
result(true) result("")
case "change_config_options": case "change_config_options":
guard let options = call.arguments as? String else { guard let options = call.arguments as? String else {
result(FlutterError(code: "INVALID_ARGS", message: nil, details: nil)) result(FlutterError(code: "INVALID_ARGS", message: nil, details: nil))

View File

@@ -86,158 +86,161 @@ class ProfileDetailsPage extends HookConsumerWidget with PresLogger {
return Stack( return Stack(
children: [ children: [
Scaffold( Scaffold(
body: CustomScrollView( body: SafeArea(
slivers: [ child: CustomScrollView(
SliverAppBar( slivers: [
title: Text(t.profile.detailsPageTitle), SliverAppBar(
pinned: true, title: Text(t.profile.detailsPageTitle),
actions: [ pinned: true,
if (state.isEditing) actions: [
PopupMenuButton( if (state.isEditing)
itemBuilder: (context) { PopupMenuButton(
return [ itemBuilder: (context) {
if (state.profile case RemoteProfileEntity()) return [
if (state.profile case RemoteProfileEntity())
PopupMenuItem(
child: Text(t.profile.update.buttonTxt),
onTap: () async {
await notifier.updateProfile();
},
),
PopupMenuItem( PopupMenuItem(
child: Text(t.profile.update.buttonTxt), child: Text(t.profile.delete.buttonTxt),
onTap: () async { onTap: () async {
await notifier.updateProfile(); final deleteConfirmed =
await showConfirmationDialog(
context,
title: t.profile.delete.buttonTxt,
message: t.profile.delete.confirmationMsg,
);
if (deleteConfirmed) {
await notifier.delete();
}
}, },
), ),
PopupMenuItem( ];
child: Text(t.profile.delete.buttonTxt), },
onTap: () async {
final deleteConfirmed =
await showConfirmationDialog(
context,
title: t.profile.delete.buttonTxt,
message: t.profile.delete.confirmationMsg,
);
if (deleteConfirmed) {
await notifier.delete();
}
},
),
];
},
),
],
),
Form(
autovalidateMode: state.showErrorMessages
? AutovalidateMode.always
: AutovalidateMode.disabled,
child: SliverList.list(
children: [
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
), ),
child: CustomTextFormField( ],
initialValue: state.profile.name, ),
onChanged: (value) => Form(
notifier.setField(name: value), autovalidateMode: state.showErrorMessages
validator: (value) => (value?.isEmpty ?? true) ? AutovalidateMode.always
? t.profile.detailsForm.emptyNameMsg : AutovalidateMode.disabled,
: null, child: SliverList.list(
label: t.profile.detailsForm.nameLabel, children: [
hint: t.profile.detailsForm.nameHint,
),
),
if (state.profile
case RemoteProfileEntity(
:final url,
:final options
)) ...[
Padding( Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 16, horizontal: 16,
vertical: 8, vertical: 8,
), ),
child: CustomTextFormField( child: CustomTextFormField(
initialValue: url, initialValue: state.profile.name,
onChanged: (value) => onChanged: (value) =>
notifier.setField(url: value), notifier.setField(name: value),
validator: (value) => validator: (value) => (value?.isEmpty ?? true)
(value != null && !isUrl(value)) ? t.profile.detailsForm.emptyNameMsg
? t.profile.detailsForm.invalidUrlMsg : null,
: null, label: t.profile.detailsForm.nameLabel,
label: t.profile.detailsForm.urlLabel, hint: t.profile.detailsForm.nameHint,
hint: t.profile.detailsForm.urlHint,
), ),
), ),
ListTile( if (state.profile
title: Text(t.profile.detailsForm.updateInterval), case RemoteProfileEntity(
subtitle: Text( :final url,
options?.updateInterval.toApproximateTime( :final options
isRelativeToNow: false, )) ...[
) ?? Padding(
t.general.toggle.disabled, padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
),
child: CustomTextFormField(
initialValue: url,
onChanged: (value) =>
notifier.setField(url: value),
validator: (value) =>
(value != null && !isUrl(value))
? t.profile.detailsForm.invalidUrlMsg
: null,
label: t.profile.detailsForm.urlLabel,
hint: t.profile.detailsForm.urlHint,
),
), ),
leading: const Icon(Icons.update), ListTile(
onTap: () async { title: Text(t.profile.detailsForm.updateInterval),
final intervalInHours = await SettingsInputDialog( subtitle: Text(
title: t.profile.detailsForm options?.updateInterval.toApproximateTime(
.updateIntervalDialogTitle, isRelativeToNow: false,
initialValue: options?.updateInterval.inHours, ) ??
optionalAction: ( t.general.toggle.disabled,
t.general.state.disable,
() =>
notifier.setField(updateInterval: none()),
),
validator: isPort,
mapTo: int.tryParse,
digitsOnly: true,
).show(context);
if (intervalInHours == null) return;
notifier.setField(
updateInterval: optionOf(intervalInHours),
);
},
),
],
if (state.isEditing)
ListTile(
title: Text(t.profile.detailsForm.lastUpdate),
subtitle: Text(state.profile.lastUpdate.format()),
dense: true,
),
],
),
),
SliverFillRemaining(
hasScrollBody: false,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 16,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
OverflowBar(
spacing: 12,
overflowAlignment: OverflowBarAlignment.end,
children: [
OutlinedButton(
onPressed: context.pop,
child: Text(
MaterialLocalizations.of(context)
.cancelButtonLabel,
),
), ),
FilledButton( leading: const Icon(Icons.update),
onPressed: notifier.save, onTap: () async {
child: Text(t.profile.save.buttonText), final intervalInHours =
), await SettingsInputDialog(
], title: t.profile.detailsForm
), .updateIntervalDialogTitle,
initialValue: options?.updateInterval.inHours,
optionalAction: (
t.general.state.disable,
() => notifier.setField(
updateInterval: none()),
),
validator: isPort,
mapTo: int.tryParse,
digitsOnly: true,
).show(context);
if (intervalInHours == null) return;
notifier.setField(
updateInterval: optionOf(intervalInHours),
);
},
),
],
if (state.isEditing)
ListTile(
title: Text(t.profile.detailsForm.lastUpdate),
subtitle: Text(state.profile.lastUpdate.format()),
dense: true,
),
], ],
), ),
), ),
), SliverFillRemaining(
], hasScrollBody: false,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 16,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
OverflowBar(
spacing: 12,
overflowAlignment: OverflowBarAlignment.end,
children: [
OutlinedButton(
onPressed: context.pop,
child: Text(
MaterialLocalizations.of(context)
.cancelButtonLabel,
),
),
FilledButton(
onPressed: notifier.save,
child: Text(t.profile.save.buttonText),
),
],
),
],
),
),
),
],
),
), ),
), ),
if (showLoadingOverlay) if (showLoadingOverlay)

View File

@@ -43,73 +43,75 @@ class ProfilesOverviewModal extends HookConsumerWidget {
}, },
); );
return Stack( return SafeArea(
children: [ child: Stack(
CustomScrollView( children: [
controller: scrollController, CustomScrollView(
slivers: [ controller: scrollController,
switch (asyncProfiles) { slivers: [
AsyncData(value: final profiles) => SliverList.builder( switch (asyncProfiles) {
itemBuilder: (context, index) { AsyncData(value: final profiles) => SliverList.builder(
final profile = profiles[index]; itemBuilder: (context, index) {
return ProfileTile(profile: profile); final profile = profiles[index];
}, return ProfileTile(profile: profile);
itemCount: profiles.length, },
itemCount: profiles.length,
),
AsyncError(:final error) => SliverErrorBodyPlaceholder(
t.presentShortError(error),
),
AsyncLoading() => const SliverLoadingBodyPlaceholder(),
_ => const SliverToBoxAdapter(),
},
const SliverGap(48),
],
),
Positioned.fill(
child: Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Wrap(
alignment: WrapAlignment.center,
spacing: 8,
children: [
FilledButton.icon(
onPressed: () {
const AddProfileRoute().push(context);
},
icon: const Icon(Icons.add),
label: Text(t.profile.add.shortBtnTxt),
),
FilledButton.icon(
onPressed: () {
showDialog(
context: context,
builder: (context) {
return const ProfilesSortModal();
},
);
},
icon: const Icon(Icons.sort),
label: Text(t.general.sort),
),
FilledButton.icon(
onPressed: () async {
await ref
.read(
foregroundProfilesUpdateNotifierProvider.notifier,
)
.trigger();
},
icon: const Icon(Icons.update),
label: Text(t.profile.update.updateSubscriptions),
),
],
), ),
AsyncError(:final error) => SliverErrorBodyPlaceholder(
t.presentShortError(error),
),
AsyncLoading() => const SliverLoadingBodyPlaceholder(),
_ => const SliverToBoxAdapter(),
},
const SliverGap(48),
],
),
Positioned.fill(
child: Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Wrap(
alignment: WrapAlignment.center,
spacing: 8,
children: [
FilledButton.icon(
onPressed: () {
const AddProfileRoute().push(context);
},
icon: const Icon(Icons.add),
label: Text(t.profile.add.shortBtnTxt),
),
FilledButton.icon(
onPressed: () {
showDialog(
context: context,
builder: (context) {
return const ProfilesSortModal();
},
);
},
icon: const Icon(Icons.sort),
label: Text(t.general.sort),
),
FilledButton.icon(
onPressed: () async {
await ref
.read(
foregroundProfilesUpdateNotifierProvider.notifier,
)
.trigger();
},
icon: const Icon(Icons.update),
label: Text(t.profile.update.updateSubscriptions),
),
],
), ),
), ),
), ),
), ],
], ),
); );
} }
} }