♻️ Refactor the app management to use sheet
This commit is contained in:
@@ -6,8 +6,11 @@ import 'package:google_fonts/google_fonts.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:island/models/custom_app.dart';
|
import 'package:island/models/custom_app.dart';
|
||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
|
import 'package:island/screens/developers/edit_app.dart';
|
||||||
|
import 'package:island/screens/developers/new_app.dart';
|
||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
import 'package:island/widgets/content/cloud_files.dart';
|
import 'package:island/widgets/content/cloud_files.dart';
|
||||||
|
import 'package:island/widgets/content/sheet.dart';
|
||||||
import 'package:island/widgets/extended_refresh_indicator.dart';
|
import 'package:island/widgets/extended_refresh_indicator.dart';
|
||||||
import 'package:island/widgets/response.dart';
|
import 'package:island/widgets/response.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
@@ -70,12 +73,18 @@ class CustomAppsScreen extends HookConsumerWidget {
|
|||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
ElevatedButton.icon(
|
ElevatedButton.icon(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
context.pushNamed(
|
showModalBottomSheet(
|
||||||
'developerAppNew',
|
context: context,
|
||||||
pathParameters: {
|
isScrollControlled: true,
|
||||||
'name': publisherName,
|
builder:
|
||||||
'projectId': projectId,
|
(context) => SheetScaffold(
|
||||||
},
|
titleText: 'createCustomApp'.tr(),
|
||||||
|
child: NewCustomAppScreen(
|
||||||
|
publisherName: publisherName,
|
||||||
|
projectId: projectId,
|
||||||
|
isModal: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
icon: const Icon(Symbols.add),
|
icon: const Icon(Symbols.add),
|
||||||
@@ -98,12 +107,18 @@ class CustomAppsScreen extends HookConsumerWidget {
|
|||||||
title: Text('customApps').tr().padding(horizontal: 8),
|
title: Text('customApps').tr().padding(horizontal: 8),
|
||||||
trailing: IconButton(
|
trailing: IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
context.pushNamed(
|
showModalBottomSheet(
|
||||||
'developerAppNew',
|
context: context,
|
||||||
pathParameters: {
|
isScrollControlled: true,
|
||||||
'name': publisherName,
|
builder:
|
||||||
'projectId': projectId,
|
(context) => SheetScaffold(
|
||||||
},
|
titleText: 'createCustomApp'.tr(),
|
||||||
|
child: NewCustomAppScreen(
|
||||||
|
publisherName: publisherName,
|
||||||
|
projectId: projectId,
|
||||||
|
isModal: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
icon: const Icon(Symbols.add),
|
icon: const Icon(Symbols.add),
|
||||||
@@ -198,13 +213,19 @@ class CustomAppsScreen extends HookConsumerWidget {
|
|||||||
],
|
],
|
||||||
onSelected: (value) {
|
onSelected: (value) {
|
||||||
if (value == 'edit') {
|
if (value == 'edit') {
|
||||||
context.pushNamed(
|
showModalBottomSheet(
|
||||||
'developerAppEdit',
|
context: context,
|
||||||
pathParameters: {
|
isScrollControlled: true,
|
||||||
'name': publisherName,
|
builder:
|
||||||
'projectId': projectId,
|
(context) => SheetScaffold(
|
||||||
'id': app.id,
|
titleText: 'editCustomApp'.tr(),
|
||||||
},
|
child: EditAppScreen(
|
||||||
|
publisherName: publisherName,
|
||||||
|
projectId: projectId,
|
||||||
|
id: app.id,
|
||||||
|
isModal: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
} else if (value == 'delete') {
|
} else if (value == 'delete') {
|
||||||
showConfirmAlert(
|
showConfirmAlert(
|
||||||
|
|||||||
@@ -39,11 +39,13 @@ class EditAppScreen extends HookConsumerWidget {
|
|||||||
final String publisherName;
|
final String publisherName;
|
||||||
final String projectId;
|
final String projectId;
|
||||||
final String? id;
|
final String? id;
|
||||||
|
final bool isModal;
|
||||||
const EditAppScreen({
|
const EditAppScreen({
|
||||||
super.key,
|
super.key,
|
||||||
required this.publisherName,
|
required this.publisherName,
|
||||||
required this.projectId,
|
required this.projectId,
|
||||||
this.id,
|
this.id,
|
||||||
|
this.isModal = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -177,7 +179,12 @@ class EditAppScreen extends HookConsumerWidget {
|
|||||||
children: [
|
children: [
|
||||||
TextFormField(
|
TextFormField(
|
||||||
controller: scopeController,
|
controller: scopeController,
|
||||||
decoration: InputDecoration(labelText: 'scopeName'.tr()),
|
decoration: InputDecoration(
|
||||||
|
labelText: 'scopeName'.tr(),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
FilledButton.tonalIcon(
|
FilledButton.tonalIcon(
|
||||||
@@ -220,6 +227,9 @@ class EditAppScreen extends HookConsumerWidget {
|
|||||||
hintText: 'https://example.com/auth/callback',
|
hintText: 'https://example.com/auth/callback',
|
||||||
helperText: 'redirectUriHint'.tr(),
|
helperText: 'redirectUriHint'.tr(),
|
||||||
helperMaxLines: 3,
|
helperMaxLines: 3,
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
keyboardType: TextInputType.url,
|
keyboardType: TextInputType.url,
|
||||||
validator: (value) {
|
validator: (value) {
|
||||||
@@ -316,270 +326,298 @@ class EditAppScreen extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final bodyContent =
|
||||||
|
app == null && !isNew
|
||||||
|
? const Center(child: CircularProgressIndicator())
|
||||||
|
: app?.hasError == true && !isNew
|
||||||
|
? ResponseErrorWidget(
|
||||||
|
error: app!.error,
|
||||||
|
onRetry:
|
||||||
|
() => ref.invalidate(
|
||||||
|
customAppProvider(publisherName, projectId, id!),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
AspectRatio(
|
||||||
|
aspectRatio: 16 / 7,
|
||||||
|
child: Stack(
|
||||||
|
clipBehavior: Clip.none,
|
||||||
|
fit: StackFit.expand,
|
||||||
|
children: [
|
||||||
|
GestureDetector(
|
||||||
|
child: Container(
|
||||||
|
color:
|
||||||
|
Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.surfaceContainerHigh,
|
||||||
|
child:
|
||||||
|
background.value != null
|
||||||
|
? CloudFileWidget(
|
||||||
|
item: background.value!,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
)
|
||||||
|
: const SizedBox.shrink(),
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
setPicture('background');
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
left: 20,
|
||||||
|
bottom: -32,
|
||||||
|
child: GestureDetector(
|
||||||
|
child: ProfilePictureWidget(
|
||||||
|
fileId: picture.value?.id,
|
||||||
|
radius: 40,
|
||||||
|
fallbackIcon: Symbols.apps,
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
setPicture('picture');
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
).padding(bottom: 32),
|
||||||
|
Form(
|
||||||
|
key: formKey,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
TextFormField(
|
||||||
|
controller: nameController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'name'.tr(),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onTapOutside:
|
||||||
|
(_) =>
|
||||||
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
TextFormField(
|
||||||
|
controller: slugController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'slug'.tr(),
|
||||||
|
helperText: 'slugHint'.tr(),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onTapOutside:
|
||||||
|
(_) =>
|
||||||
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
TextFormField(
|
||||||
|
controller: descriptionController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'description'.tr(),
|
||||||
|
alignLabelWithHint: true,
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
maxLines: 3,
|
||||||
|
onTapOutside:
|
||||||
|
(_) =>
|
||||||
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
ExpansionPanelList(
|
||||||
|
expansionCallback: (index, isExpanded) {
|
||||||
|
switch (index) {
|
||||||
|
case 0:
|
||||||
|
enableLinks.value = isExpanded;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
oauthEnabled.value = isExpanded;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
ExpansionPanel(
|
||||||
|
headerBuilder:
|
||||||
|
(context, isExpanded) =>
|
||||||
|
ListTile(title: Text('appLinks').tr()),
|
||||||
|
body: Column(
|
||||||
|
spacing: 16,
|
||||||
|
children: [
|
||||||
|
TextFormField(
|
||||||
|
controller: homePageController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'homePageUrl'.tr(),
|
||||||
|
hintText: 'https://example.com',
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
keyboardType: TextInputType.url,
|
||||||
|
),
|
||||||
|
TextFormField(
|
||||||
|
controller: privacyPolicyController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'privacyPolicyUrl'.tr(),
|
||||||
|
hintText: 'https://example.com/privacy',
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
keyboardType: TextInputType.url,
|
||||||
|
),
|
||||||
|
TextFormField(
|
||||||
|
controller: termsController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'termsOfServiceUrl'.tr(),
|
||||||
|
hintText: 'https://example.com/terms',
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
keyboardType: TextInputType.url,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 16, bottom: 24),
|
||||||
|
isExpanded: enableLinks.value,
|
||||||
|
),
|
||||||
|
ExpansionPanel(
|
||||||
|
headerBuilder:
|
||||||
|
(context, isExpanded) =>
|
||||||
|
ListTile(title: Text('oauthConfig').tr()),
|
||||||
|
body: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text('redirectUris'.tr()),
|
||||||
|
Card(
|
||||||
|
margin: const EdgeInsets.symmetric(
|
||||||
|
vertical: 8,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
...redirectUris.value.map(
|
||||||
|
(uri) => ListTile(
|
||||||
|
title: Text(uri),
|
||||||
|
trailing: IconButton(
|
||||||
|
icon: const Icon(Symbols.delete),
|
||||||
|
onPressed: () {
|
||||||
|
redirectUris.value =
|
||||||
|
redirectUris.value
|
||||||
|
.where((u) => u != uri)
|
||||||
|
.toList();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (redirectUris.value.isNotEmpty)
|
||||||
|
const Divider(height: 1),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Symbols.add),
|
||||||
|
title: Text('addRedirectUri'.tr()),
|
||||||
|
onTap: showAddRedirectUriDialog,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(
|
||||||
|
8,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Text('allowedScopes'.tr()),
|
||||||
|
Card(
|
||||||
|
margin: const EdgeInsets.symmetric(
|
||||||
|
vertical: 8,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
...allowedScopes.value.map(
|
||||||
|
(scope) => ListTile(
|
||||||
|
title: Text(scope),
|
||||||
|
trailing: IconButton(
|
||||||
|
icon: const Icon(Symbols.delete),
|
||||||
|
onPressed: () {
|
||||||
|
allowedScopes.value =
|
||||||
|
allowedScopes.value
|
||||||
|
.where(
|
||||||
|
(s) => s != scope,
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (allowedScopes.value.isNotEmpty)
|
||||||
|
const Divider(height: 1),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Symbols.add),
|
||||||
|
title: Text('add').tr(),
|
||||||
|
onTap: showAddScopeDialog,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
SwitchListTile(
|
||||||
|
title: Text('requirePkce'.tr()),
|
||||||
|
value: requirePkce.value,
|
||||||
|
onChanged:
|
||||||
|
(value) => requirePkce.value = value,
|
||||||
|
),
|
||||||
|
SwitchListTile(
|
||||||
|
title: Text('allowOfflineAccess'.tr()),
|
||||||
|
value: allowOfflineAccess.value,
|
||||||
|
onChanged:
|
||||||
|
(value) =>
|
||||||
|
allowOfflineAccess.value = value,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 16, bottom: 24),
|
||||||
|
isExpanded: oauthEnabled.value,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
child: TextButton.icon(
|
||||||
|
onPressed: submitting.value ? null : performAction,
|
||||||
|
label: Text('saveChanges'.tr()),
|
||||||
|
icon: const Icon(Symbols.save),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(all: 24),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isModal) {
|
||||||
|
return bodyContent;
|
||||||
|
}
|
||||||
|
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
isNoBackground: false,
|
isNoBackground: false,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(isNew ? 'createCustomApp'.tr() : 'editCustomApp'.tr()),
|
title: Text(isNew ? 'createCustomApp'.tr() : 'editCustomApp'.tr()),
|
||||||
),
|
),
|
||||||
body:
|
body: bodyContent,
|
||||||
app == null && !isNew
|
|
||||||
? const Center(child: CircularProgressIndicator())
|
|
||||||
: app?.hasError == true && !isNew
|
|
||||||
? ResponseErrorWidget(
|
|
||||||
error: app!.error,
|
|
||||||
onRetry:
|
|
||||||
() => ref.invalidate(
|
|
||||||
customAppProvider(publisherName, projectId, id!),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: SingleChildScrollView(
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
AspectRatio(
|
|
||||||
aspectRatio: 16 / 7,
|
|
||||||
child: Stack(
|
|
||||||
clipBehavior: Clip.none,
|
|
||||||
fit: StackFit.expand,
|
|
||||||
children: [
|
|
||||||
GestureDetector(
|
|
||||||
child: Container(
|
|
||||||
color:
|
|
||||||
Theme.of(
|
|
||||||
context,
|
|
||||||
).colorScheme.surfaceContainerHigh,
|
|
||||||
child:
|
|
||||||
background.value != null
|
|
||||||
? CloudFileWidget(
|
|
||||||
item: background.value!,
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
)
|
|
||||||
: const SizedBox.shrink(),
|
|
||||||
),
|
|
||||||
onTap: () {
|
|
||||||
setPicture('background');
|
|
||||||
},
|
|
||||||
),
|
|
||||||
Positioned(
|
|
||||||
left: 20,
|
|
||||||
bottom: -32,
|
|
||||||
child: GestureDetector(
|
|
||||||
child: ProfilePictureWidget(
|
|
||||||
fileId: picture.value?.id,
|
|
||||||
radius: 40,
|
|
||||||
fallbackIcon: Symbols.apps,
|
|
||||||
),
|
|
||||||
onTap: () {
|
|
||||||
setPicture('picture');
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
).padding(bottom: 32),
|
|
||||||
Form(
|
|
||||||
key: formKey,
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
TextFormField(
|
|
||||||
controller: nameController,
|
|
||||||
decoration: InputDecoration(labelText: 'name'.tr()),
|
|
||||||
onTapOutside:
|
|
||||||
(_) =>
|
|
||||||
FocusManager.instance.primaryFocus
|
|
||||||
?.unfocus(),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
TextFormField(
|
|
||||||
controller: slugController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'slug'.tr(),
|
|
||||||
helperText: 'slugHint'.tr(),
|
|
||||||
),
|
|
||||||
onTapOutside:
|
|
||||||
(_) =>
|
|
||||||
FocusManager.instance.primaryFocus
|
|
||||||
?.unfocus(),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
TextFormField(
|
|
||||||
controller: descriptionController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'description'.tr(),
|
|
||||||
alignLabelWithHint: true,
|
|
||||||
),
|
|
||||||
maxLines: 3,
|
|
||||||
onTapOutside:
|
|
||||||
(_) =>
|
|
||||||
FocusManager.instance.primaryFocus
|
|
||||||
?.unfocus(),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
ExpansionPanelList(
|
|
||||||
expansionCallback: (index, isExpanded) {
|
|
||||||
switch (index) {
|
|
||||||
case 0:
|
|
||||||
enableLinks.value = isExpanded;
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
oauthEnabled.value = isExpanded;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
children: [
|
|
||||||
ExpansionPanel(
|
|
||||||
headerBuilder:
|
|
||||||
(context, isExpanded) =>
|
|
||||||
ListTile(title: Text('appLinks').tr()),
|
|
||||||
body: Column(
|
|
||||||
spacing: 16,
|
|
||||||
children: [
|
|
||||||
TextFormField(
|
|
||||||
controller: homePageController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'homePageUrl'.tr(),
|
|
||||||
hintText: 'https://example.com',
|
|
||||||
),
|
|
||||||
keyboardType: TextInputType.url,
|
|
||||||
),
|
|
||||||
TextFormField(
|
|
||||||
controller: privacyPolicyController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'privacyPolicyUrl'.tr(),
|
|
||||||
hintText: 'https://example.com/privacy',
|
|
||||||
),
|
|
||||||
keyboardType: TextInputType.url,
|
|
||||||
),
|
|
||||||
TextFormField(
|
|
||||||
controller: termsController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'termsOfServiceUrl'.tr(),
|
|
||||||
hintText: 'https://example.com/terms',
|
|
||||||
),
|
|
||||||
keyboardType: TextInputType.url,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
).padding(horizontal: 16, bottom: 24),
|
|
||||||
isExpanded: enableLinks.value,
|
|
||||||
),
|
|
||||||
ExpansionPanel(
|
|
||||||
headerBuilder:
|
|
||||||
(context, isExpanded) => ListTile(
|
|
||||||
title: Text('oauthConfig').tr(),
|
|
||||||
),
|
|
||||||
body: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text('redirectUris'.tr()),
|
|
||||||
Card(
|
|
||||||
margin: const EdgeInsets.symmetric(
|
|
||||||
vertical: 8,
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
...redirectUris.value.map(
|
|
||||||
(uri) => ListTile(
|
|
||||||
title: Text(uri),
|
|
||||||
trailing: IconButton(
|
|
||||||
icon: const Icon(
|
|
||||||
Symbols.delete,
|
|
||||||
),
|
|
||||||
onPressed: () {
|
|
||||||
redirectUris.value =
|
|
||||||
redirectUris.value
|
|
||||||
.where(
|
|
||||||
(u) => u != uri,
|
|
||||||
)
|
|
||||||
.toList();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (redirectUris.value.isNotEmpty)
|
|
||||||
const Divider(height: 1),
|
|
||||||
ListTile(
|
|
||||||
leading: const Icon(Symbols.add),
|
|
||||||
title: Text('addRedirectUri'.tr()),
|
|
||||||
onTap: showAddRedirectUriDialog,
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius:
|
|
||||||
BorderRadius.circular(8),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Text('allowedScopes'.tr()),
|
|
||||||
Card(
|
|
||||||
margin: const EdgeInsets.symmetric(
|
|
||||||
vertical: 8,
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
...allowedScopes.value.map(
|
|
||||||
(scope) => ListTile(
|
|
||||||
title: Text(scope),
|
|
||||||
trailing: IconButton(
|
|
||||||
icon: const Icon(
|
|
||||||
Symbols.delete,
|
|
||||||
),
|
|
||||||
onPressed: () {
|
|
||||||
allowedScopes.value =
|
|
||||||
allowedScopes.value
|
|
||||||
.where(
|
|
||||||
(s) => s != scope,
|
|
||||||
)
|
|
||||||
.toList();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (allowedScopes.value.isNotEmpty)
|
|
||||||
const Divider(height: 1),
|
|
||||||
ListTile(
|
|
||||||
leading: const Icon(Symbols.add),
|
|
||||||
title: Text('add').tr(),
|
|
||||||
onTap: showAddScopeDialog,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
SwitchListTile(
|
|
||||||
title: Text('requirePkce'.tr()),
|
|
||||||
value: requirePkce.value,
|
|
||||||
onChanged:
|
|
||||||
(value) => requirePkce.value = value,
|
|
||||||
),
|
|
||||||
SwitchListTile(
|
|
||||||
title: Text('allowOfflineAccess'.tr()),
|
|
||||||
value: allowOfflineAccess.value,
|
|
||||||
onChanged:
|
|
||||||
(value) =>
|
|
||||||
allowOfflineAccess.value = value,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
).padding(horizontal: 16, bottom: 24),
|
|
||||||
isExpanded: oauthEnabled.value,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Align(
|
|
||||||
alignment: Alignment.centerRight,
|
|
||||||
child: TextButton.icon(
|
|
||||||
onPressed:
|
|
||||||
submitting.value ? null : performAction,
|
|
||||||
label: Text('saveChanges'.tr()),
|
|
||||||
icon: const Icon(Symbols.save),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
).padding(all: 24),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,20 @@ import 'package:island/screens/developers/edit_app.dart';
|
|||||||
class NewCustomAppScreen extends StatelessWidget {
|
class NewCustomAppScreen extends StatelessWidget {
|
||||||
final String publisherName;
|
final String publisherName;
|
||||||
final String projectId;
|
final String projectId;
|
||||||
const NewCustomAppScreen({super.key, required this.publisherName, required this.projectId});
|
final bool isModal;
|
||||||
|
const NewCustomAppScreen({
|
||||||
|
super.key,
|
||||||
|
required this.publisherName,
|
||||||
|
required this.projectId,
|
||||||
|
this.isModal = false,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return EditAppScreen(publisherName: publisherName, projectId: projectId);
|
return EditAppScreen(
|
||||||
|
publisherName: publisherName,
|
||||||
|
projectId: projectId,
|
||||||
|
isModal: isModal,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user