Compare commits
4 Commits
f542d9fa97
...
59d38c0d8d
| Author | SHA1 | Date | |
|---|---|---|---|
|
59d38c0d8d
|
|||
|
bd2247ce86
|
|||
|
da2d3f7f17
|
|||
|
7497b77384
|
@@ -1309,5 +1309,6 @@
|
|||||||
"presenceTypeGaming": "Playing",
|
"presenceTypeGaming": "Playing",
|
||||||
"presenceTypeMusic": "Listening to Music",
|
"presenceTypeMusic": "Listening to Music",
|
||||||
"presenceTypeWorkout": "Working out",
|
"presenceTypeWorkout": "Working out",
|
||||||
"articleCompose": "Compose Article"
|
"articleCompose": "Compose Article",
|
||||||
|
"backToHub": "Back to Hub"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -158,11 +158,11 @@
|
|||||||
"checkIn": "签到",
|
"checkIn": "签到",
|
||||||
"checkInNone": "尚未签到",
|
"checkInNone": "尚未签到",
|
||||||
"checkInNoneHint": "通过签到获取您的财富提示和每日奖励。",
|
"checkInNoneHint": "通过签到获取您的财富提示和每日奖励。",
|
||||||
"checkInResultLevel0": "最差运气",
|
"checkInResultLevel0": "大凶",
|
||||||
"checkInResultLevel1": "坏运气",
|
"checkInResultLevel1": "凶",
|
||||||
"checkInResultLevel2": "一个普通的日常",
|
"checkInResultLevel2": "中平",
|
||||||
"checkInResultLevel3": "好运",
|
"checkInResultLevel3": "吉",
|
||||||
"checkInResultLevel4": "最佳运气",
|
"checkInResultLevel4": "大吉",
|
||||||
"checkInActivityTitle": "{} 在 {} 签到并获得了 {}",
|
"checkInActivityTitle": "{} 在 {} 签到并获得了 {}",
|
||||||
"eventCalander": "活动日历",
|
"eventCalander": "活动日历",
|
||||||
"eventCalanderEmpty": "该日无活动。",
|
"eventCalanderEmpty": "该日无活动。",
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
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/response.dart';
|
import 'package:island/widgets/response.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';
|
||||||
@@ -68,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),
|
||||||
@@ -83,129 +94,171 @@ class CustomAppsScreen extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return RefreshIndicator(
|
return ExtendedRefreshIndicator(
|
||||||
onRefresh:
|
onRefresh:
|
||||||
() => ref.refresh(
|
() => ref.refresh(
|
||||||
customAppsProvider(publisherName, projectId).future,
|
customAppsProvider(publisherName, projectId).future,
|
||||||
),
|
),
|
||||||
child: ListView.builder(
|
child: Column(
|
||||||
padding: EdgeInsets.only(top: 4),
|
children: [
|
||||||
itemCount: data.length,
|
const Gap(8),
|
||||||
itemBuilder: (context, index) {
|
Card(
|
||||||
final app = data[index];
|
child: ListTile(
|
||||||
return Card(
|
title: Text('customApps').tr().padding(horizontal: 8),
|
||||||
margin: const EdgeInsets.all(8.0),
|
trailing: IconButton(
|
||||||
clipBehavior: Clip.antiAlias,
|
onPressed: () {
|
||||||
child: InkWell(
|
showModalBottomSheet(
|
||||||
onTap: () {
|
context: context,
|
||||||
context.pushNamed(
|
isScrollControlled: true,
|
||||||
'developerAppDetail',
|
builder:
|
||||||
pathParameters: {
|
(context) => SheetScaffold(
|
||||||
'name': publisherName,
|
titleText: 'createCustomApp'.tr(),
|
||||||
'projectId': projectId,
|
child: NewCustomAppScreen(
|
||||||
'appId': app.id,
|
publisherName: publisherName,
|
||||||
},
|
projectId: projectId,
|
||||||
);
|
isModal: true,
|
||||||
},
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
height: 150,
|
|
||||||
child: Stack(
|
|
||||||
fit: StackFit.expand,
|
|
||||||
children: [
|
|
||||||
if (app.background != null)
|
|
||||||
CloudFileWidget(
|
|
||||||
item: app.background!,
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
).clipRRect(topLeft: 8, topRight: 8),
|
|
||||||
if (app.picture != null)
|
|
||||||
Positioned(
|
|
||||||
left: 16,
|
|
||||||
bottom: 16,
|
|
||||||
child: ProfilePictureWidget(
|
|
||||||
fileId: app.picture!.id,
|
|
||||||
radius: 40,
|
|
||||||
fallbackIcon: Symbols.apps,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
icon: const Icon(Symbols.add),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: ListView.builder(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
itemCount: data.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final app = data[index];
|
||||||
|
return Card(
|
||||||
|
clipBehavior: Clip.antiAlias,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
context.pushNamed(
|
||||||
|
'developerAppDetail',
|
||||||
|
pathParameters: {
|
||||||
|
'name': publisherName,
|
||||||
|
'projectId': projectId,
|
||||||
|
'appId': app.id,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
height: 150,
|
||||||
|
child: Stack(
|
||||||
|
fit: StackFit.expand,
|
||||||
|
children: [
|
||||||
|
if (app.background != null)
|
||||||
|
CloudFileWidget(
|
||||||
|
item: app.background!,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
).clipRRect(topLeft: 8, topRight: 8),
|
||||||
|
if (app.picture != null)
|
||||||
|
Positioned(
|
||||||
|
left: 16,
|
||||||
|
bottom: 16,
|
||||||
|
child: ProfilePictureWidget(
|
||||||
|
fileId: app.picture!.id,
|
||||||
|
radius: 40,
|
||||||
|
fallbackIcon: Symbols.apps,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
title: Text(app.name),
|
||||||
|
subtitle: Text(
|
||||||
|
app.slug,
|
||||||
|
style: GoogleFonts.robotoMono(fontSize: 12),
|
||||||
|
),
|
||||||
|
contentPadding: EdgeInsets.only(
|
||||||
|
left: 20,
|
||||||
|
right: 12,
|
||||||
|
),
|
||||||
|
trailing: PopupMenuButton(
|
||||||
|
itemBuilder:
|
||||||
|
(context) => [
|
||||||
|
PopupMenuItem(
|
||||||
|
value: 'edit',
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Symbols.edit),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Text('edit').tr(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
PopupMenuItem(
|
||||||
|
value: 'delete',
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(
|
||||||
|
Symbols.delete,
|
||||||
|
color: Colors.red,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Text(
|
||||||
|
'delete',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.red,
|
||||||
|
),
|
||||||
|
).tr(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
onSelected: (value) {
|
||||||
|
if (value == 'edit') {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
builder:
|
||||||
|
(context) => SheetScaffold(
|
||||||
|
titleText: 'editCustomApp'.tr(),
|
||||||
|
child: EditAppScreen(
|
||||||
|
publisherName: publisherName,
|
||||||
|
projectId: projectId,
|
||||||
|
id: app.id,
|
||||||
|
isModal: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else if (value == 'delete') {
|
||||||
|
showConfirmAlert(
|
||||||
|
'deleteCustomAppHint'.tr(),
|
||||||
|
'deleteCustomApp'.tr(),
|
||||||
|
).then((confirm) {
|
||||||
|
if (confirm) {
|
||||||
|
final client = ref.read(
|
||||||
|
apiClientProvider,
|
||||||
|
);
|
||||||
|
client.delete(
|
||||||
|
'/develop/developers/$publisherName/projects/$projectId/apps/${app.id}',
|
||||||
|
);
|
||||||
|
ref.invalidate(
|
||||||
|
customAppsProvider(
|
||||||
|
publisherName,
|
||||||
|
projectId,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
ListTile(
|
);
|
||||||
title: Text(app.name),
|
},
|
||||||
subtitle: Text(
|
|
||||||
app.slug,
|
|
||||||
style: GoogleFonts.robotoMono(fontSize: 12),
|
|
||||||
),
|
|
||||||
contentPadding: EdgeInsets.only(left: 20, right: 12),
|
|
||||||
trailing: PopupMenuButton(
|
|
||||||
itemBuilder:
|
|
||||||
(context) => [
|
|
||||||
PopupMenuItem(
|
|
||||||
value: 'edit',
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
const Icon(Symbols.edit),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
Text('edit').tr(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
PopupMenuItem(
|
|
||||||
value: 'delete',
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
const Icon(
|
|
||||||
Symbols.delete,
|
|
||||||
color: Colors.red,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
Text(
|
|
||||||
'delete',
|
|
||||||
style: TextStyle(color: Colors.red),
|
|
||||||
).tr(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
onSelected: (value) {
|
|
||||||
if (value == 'edit') {
|
|
||||||
context.pushNamed(
|
|
||||||
'developerAppEdit',
|
|
||||||
pathParameters: {
|
|
||||||
'name': publisherName,
|
|
||||||
'projectId': projectId,
|
|
||||||
'id': app.id,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else if (value == 'delete') {
|
|
||||||
showConfirmAlert(
|
|
||||||
'deleteCustomAppHint'.tr(),
|
|
||||||
'deleteCustomApp'.tr(),
|
|
||||||
).then((confirm) {
|
|
||||||
if (confirm) {
|
|
||||||
final client = ref.read(apiClientProvider);
|
|
||||||
client.delete(
|
|
||||||
'/develop/developers/$publisherName/projects/$projectId/apps/${app.id}',
|
|
||||||
);
|
|
||||||
ref.invalidate(
|
|
||||||
customAppsProvider(
|
|
||||||
publisherName,
|
|
||||||
projectId,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
},
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,15 +1,20 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
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/bot.dart';
|
import 'package:island/models/bot.dart';
|
||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
|
import 'package:island/screens/developers/edit_bot.dart';
|
||||||
|
import 'package:island/screens/developers/new_bot.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/response.dart';
|
import 'package:island/widgets/response.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';
|
||||||
import 'package:island/widgets/extended_refresh_indicator.dart';
|
import 'package:island/widgets/extended_refresh_indicator.dart';
|
||||||
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
|
||||||
part 'bots.g.dart';
|
part 'bots.g.dart';
|
||||||
|
|
||||||
@@ -46,12 +51,18 @@ class BotsScreen extends HookConsumerWidget {
|
|||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
ElevatedButton.icon(
|
ElevatedButton.icon(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
context.pushNamed(
|
showModalBottomSheet(
|
||||||
'developerBotNew',
|
context: context,
|
||||||
pathParameters: {
|
isScrollControlled: true,
|
||||||
'name': publisherName,
|
builder:
|
||||||
'projectId': projectId,
|
(context) => SheetScaffold(
|
||||||
},
|
titleText: 'createBot'.tr(),
|
||||||
|
child: NewBotScreen(
|
||||||
|
publisherName: publisherName,
|
||||||
|
projectId: projectId,
|
||||||
|
isModal: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
icon: const Icon(Symbols.add),
|
icon: const Icon(Symbols.add),
|
||||||
@@ -64,95 +75,133 @@ class BotsScreen extends HookConsumerWidget {
|
|||||||
return ExtendedRefreshIndicator(
|
return ExtendedRefreshIndicator(
|
||||||
onRefresh:
|
onRefresh:
|
||||||
() => ref.refresh(botsProvider(publisherName, projectId).future),
|
() => ref.refresh(botsProvider(publisherName, projectId).future),
|
||||||
child: ListView.builder(
|
child: Column(
|
||||||
padding: const EdgeInsets.only(top: 4),
|
children: [
|
||||||
itemCount: data.length,
|
const Gap(8),
|
||||||
itemBuilder: (context, index) {
|
Card(
|
||||||
final bot = data[index];
|
|
||||||
return Card(
|
|
||||||
margin: const EdgeInsets.all(8.0),
|
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
shape: const RoundedRectangleBorder(
|
title: Text('bots').tr().padding(horizontal: 8),
|
||||||
borderRadius: BorderRadius.all(Radius.circular(8.0)),
|
trailing: IconButton(
|
||||||
),
|
onPressed: () {
|
||||||
leading: CircleAvatar(
|
showModalBottomSheet(
|
||||||
child:
|
context: context,
|
||||||
bot.account.profile.picture != null
|
isScrollControlled: true,
|
||||||
? ProfilePictureWidget(
|
builder:
|
||||||
file: bot.account.profile.picture!,
|
(context) => SheetScaffold(
|
||||||
)
|
titleText: 'createBot'.tr(),
|
||||||
: const Icon(Symbols.smart_toy),
|
child: NewBotScreen(
|
||||||
),
|
publisherName: publisherName,
|
||||||
title: Text(bot.account.nick),
|
projectId: projectId,
|
||||||
subtitle: Text(bot.account.name),
|
isModal: true,
|
||||||
trailing: PopupMenuButton(
|
),
|
||||||
itemBuilder:
|
|
||||||
(context) => [
|
|
||||||
PopupMenuItem(
|
|
||||||
value: 'edit',
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
const Icon(Symbols.edit),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
Text('edit').tr(),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
PopupMenuItem(
|
|
||||||
value: 'delete',
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
const Icon(Symbols.delete, color: Colors.red),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
Text(
|
|
||||||
'delete',
|
|
||||||
style: TextStyle(color: Colors.red),
|
|
||||||
).tr(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
onSelected: (value) {
|
|
||||||
if (value == 'edit') {
|
|
||||||
context.pushNamed(
|
|
||||||
'developerBotEdit',
|
|
||||||
pathParameters: {
|
|
||||||
'name': publisherName,
|
|
||||||
'projectId': projectId,
|
|
||||||
'id': bot.id,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else if (value == 'delete') {
|
|
||||||
showConfirmAlert(
|
|
||||||
'deleteBotHint'.tr(),
|
|
||||||
'deleteBot'.tr(),
|
|
||||||
).then((confirm) {
|
|
||||||
if (confirm) {
|
|
||||||
final client = ref.read(apiClientProvider);
|
|
||||||
client.delete(
|
|
||||||
'/develop/developers/$publisherName/projects/$projectId/bots/${bot.id}',
|
|
||||||
);
|
|
||||||
ref.invalidate(
|
|
||||||
botsProvider(publisherName, projectId),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
icon: const Icon(Symbols.add),
|
||||||
),
|
),
|
||||||
onTap: () {
|
),
|
||||||
context.pushNamed(
|
),
|
||||||
'developerBotDetail',
|
Expanded(
|
||||||
pathParameters: {
|
child: ListView.builder(
|
||||||
'name': publisherName,
|
padding: EdgeInsets.zero,
|
||||||
'projectId': projectId,
|
itemCount: data.length,
|
||||||
'botId': bot.id,
|
itemBuilder: (context, index) {
|
||||||
},
|
final bot = data[index];
|
||||||
|
return Card(
|
||||||
|
child: ListTile(
|
||||||
|
shape: const RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(8.0)),
|
||||||
|
),
|
||||||
|
leading: CircleAvatar(
|
||||||
|
child:
|
||||||
|
bot.account.profile.picture != null
|
||||||
|
? ProfilePictureWidget(
|
||||||
|
file: bot.account.profile.picture!,
|
||||||
|
)
|
||||||
|
: const Icon(Symbols.smart_toy),
|
||||||
|
),
|
||||||
|
title: Text(bot.account.nick),
|
||||||
|
subtitle: Text(bot.account.name),
|
||||||
|
trailing: PopupMenuButton(
|
||||||
|
itemBuilder:
|
||||||
|
(context) => [
|
||||||
|
PopupMenuItem(
|
||||||
|
value: 'edit',
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Symbols.edit),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Text('edit').tr(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
PopupMenuItem(
|
||||||
|
value: 'delete',
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(
|
||||||
|
Symbols.delete,
|
||||||
|
color: Colors.red,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Text(
|
||||||
|
'delete',
|
||||||
|
style: TextStyle(color: Colors.red),
|
||||||
|
).tr(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
onSelected: (value) {
|
||||||
|
if (value == 'edit') {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
builder:
|
||||||
|
(context) => SheetScaffold(
|
||||||
|
titleText: 'editBot'.tr(),
|
||||||
|
child: EditBotScreen(
|
||||||
|
publisherName: publisherName,
|
||||||
|
projectId: projectId,
|
||||||
|
id: bot.id,
|
||||||
|
isModal: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else if (value == 'delete') {
|
||||||
|
showConfirmAlert(
|
||||||
|
'deleteBotHint'.tr(),
|
||||||
|
'deleteBot'.tr(),
|
||||||
|
).then((confirm) {
|
||||||
|
if (confirm) {
|
||||||
|
final client = ref.read(apiClientProvider);
|
||||||
|
client.delete(
|
||||||
|
'/develop/developers/$publisherName/projects/$projectId/bots/${bot.id}',
|
||||||
|
);
|
||||||
|
ref.invalidate(
|
||||||
|
botsProvider(publisherName, projectId),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
context.pushNamed(
|
||||||
|
'developerBotDetail',
|
||||||
|
pathParameters: {
|
||||||
|
'name': publisherName,
|
||||||
|
'projectId': projectId,
|
||||||
|
'botId': bot.id,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
},
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,11 +38,13 @@ class EditBotScreen extends HookConsumerWidget {
|
|||||||
final String publisherName;
|
final String publisherName;
|
||||||
final String projectId;
|
final String projectId;
|
||||||
final String? id;
|
final String? id;
|
||||||
|
final bool isModal;
|
||||||
const EditBotScreen({
|
const EditBotScreen({
|
||||||
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
|
||||||
@@ -191,230 +193,293 @@ class EditBotScreen extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final bodyContent =
|
||||||
|
botData == null && !isNew
|
||||||
|
? const Center(child: CircularProgressIndicator())
|
||||||
|
: botData?.hasError == true && !isNew
|
||||||
|
? ResponseErrorWidget(
|
||||||
|
error: botData!.error,
|
||||||
|
onRetry:
|
||||||
|
() => ref.invalidate(
|
||||||
|
botProvider(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.smart_toy,
|
||||||
|
),
|
||||||
|
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),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
TextFormField(
|
||||||
|
controller: nickController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'nickname'.tr(),
|
||||||
|
alignLabelWithHint: true,
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
TextFormField(
|
||||||
|
controller: slugController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'slug'.tr(),
|
||||||
|
helperText: 'slugHint'.tr(),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
TextFormField(
|
||||||
|
controller: bioController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'bio'.tr(),
|
||||||
|
alignLabelWithHint: true,
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
maxLines: 3,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Row(
|
||||||
|
spacing: 16,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: TextFormField(
|
||||||
|
controller: firstNameController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'firstName'.tr(),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: TextFormField(
|
||||||
|
controller: middleNameController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'middleName'.tr(),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: TextFormField(
|
||||||
|
controller: lastNameController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'lastName'.tr(),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Row(
|
||||||
|
spacing: 16,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: TextFormField(
|
||||||
|
controller: genderController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'gender'.tr(),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: TextFormField(
|
||||||
|
controller: pronounsController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'pronouns'.tr(),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Row(
|
||||||
|
spacing: 16,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: TextFormField(
|
||||||
|
controller: locationController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'location'.tr(),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: TextFormField(
|
||||||
|
controller: timeZoneController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'timeZone'.tr(),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () async {
|
||||||
|
final date = await showDatePicker(
|
||||||
|
context: context,
|
||||||
|
initialDate: birthday.value ?? DateTime.now(),
|
||||||
|
firstDate: DateTime(1900),
|
||||||
|
lastDate: DateTime.now(),
|
||||||
|
);
|
||||||
|
if (date != null) {
|
||||||
|
birthday.value = date;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(
|
||||||
|
color: Theme.of(context).dividerColor,
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'birthday'.tr(),
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).hintColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
birthday.value != null
|
||||||
|
? DateFormat.yMMMd().format(
|
||||||
|
birthday.value!,
|
||||||
|
)
|
||||||
|
: 'Select a date'.tr(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
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(title: Text(isNew ? 'createBot'.tr() : 'editBot'.tr())),
|
appBar: AppBar(title: Text(isNew ? 'createBot'.tr() : 'editBot'.tr())),
|
||||||
body:
|
body: bodyContent,
|
||||||
botData == null && !isNew
|
|
||||||
? const Center(child: CircularProgressIndicator())
|
|
||||||
: botData?.hasError == true && !isNew
|
|
||||||
? ResponseErrorWidget(
|
|
||||||
error: botData!.error,
|
|
||||||
onRetry:
|
|
||||||
() => ref.invalidate(
|
|
||||||
botProvider(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.smart_toy,
|
|
||||||
),
|
|
||||||
onTap: () {
|
|
||||||
setPicture('picture');
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
).padding(bottom: 32),
|
|
||||||
Form(
|
|
||||||
key: formKey,
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
TextFormField(
|
|
||||||
controller: nameController,
|
|
||||||
decoration: InputDecoration(labelText: 'name'.tr()),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
TextFormField(
|
|
||||||
controller: nickController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'nickname'.tr(),
|
|
||||||
alignLabelWithHint: true,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
TextFormField(
|
|
||||||
controller: slugController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'slug'.tr(),
|
|
||||||
helperText: 'slugHint'.tr(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
TextFormField(
|
|
||||||
controller: bioController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'bio'.tr(),
|
|
||||||
alignLabelWithHint: true,
|
|
||||||
),
|
|
||||||
maxLines: 3,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Row(
|
|
||||||
spacing: 16,
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: TextFormField(
|
|
||||||
controller: firstNameController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'firstName'.tr(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: TextFormField(
|
|
||||||
controller: middleNameController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'middleName'.tr(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: TextFormField(
|
|
||||||
controller: lastNameController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'lastName'.tr(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Row(
|
|
||||||
spacing: 16,
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: TextFormField(
|
|
||||||
controller: genderController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'gender'.tr(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: TextFormField(
|
|
||||||
controller: pronounsController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'pronouns'.tr(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Row(
|
|
||||||
spacing: 16,
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: TextFormField(
|
|
||||||
controller: locationController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'location'.tr(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: TextFormField(
|
|
||||||
controller: timeZoneController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'timeZone'.tr(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
GestureDetector(
|
|
||||||
onTap: () async {
|
|
||||||
final date = await showDatePicker(
|
|
||||||
context: context,
|
|
||||||
initialDate: birthday.value ?? DateTime.now(),
|
|
||||||
firstDate: DateTime(1900),
|
|
||||||
lastDate: DateTime.now(),
|
|
||||||
);
|
|
||||||
if (date != null) {
|
|
||||||
birthday.value = date;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
border: Border(
|
|
||||||
bottom: BorderSide(
|
|
||||||
color: Theme.of(context).dividerColor,
|
|
||||||
width: 1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'birthday'.tr(),
|
|
||||||
style: TextStyle(
|
|
||||||
color: Theme.of(context).hintColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
birthday.value != null
|
|
||||||
? DateFormat.yMMMd().format(
|
|
||||||
birthday.value!,
|
|
||||||
)
|
|
||||||
: 'Select a date'.tr(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
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),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import 'dart:math';
|
|
||||||
|
|
||||||
import 'package:dropdown_button2/dropdown_button2.dart';
|
import 'package:dropdown_button2/dropdown_button2.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -99,15 +97,6 @@ class DeveloperHubScreen extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
body: Column(
|
body: Column(
|
||||||
children: [
|
children: [
|
||||||
if (currentProject.value == null)
|
|
||||||
...([
|
|
||||||
// Welcome Section
|
|
||||||
_WelcomeSection(currentDeveloper: currentDeveloper.value),
|
|
||||||
|
|
||||||
// Navigation Tabs
|
|
||||||
_NavigationTabs(),
|
|
||||||
]),
|
|
||||||
|
|
||||||
// Main Content
|
// Main Content
|
||||||
if (currentProject.value != null)
|
if (currentProject.value != null)
|
||||||
Expanded(
|
Expanded(
|
||||||
@@ -195,162 +184,6 @@ class _ConsoleAppBar extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Welcome Section
|
|
||||||
class _WelcomeSection extends StatelessWidget {
|
|
||||||
final SnDeveloper? currentDeveloper;
|
|
||||||
|
|
||||||
const _WelcomeSection({required this.currentDeveloper});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
|
||||||
return Stack(
|
|
||||||
children: [
|
|
||||||
Positioned.fill(
|
|
||||||
child: Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
gradient: LinearGradient(
|
|
||||||
colors:
|
|
||||||
isDark
|
|
||||||
? [
|
|
||||||
Theme.of(context).colorScheme.surfaceContainerHighest,
|
|
||||||
Theme.of(context).colorScheme.surfaceContainerLow,
|
|
||||||
]
|
|
||||||
: [const Color(0xFFE8F0FE), const Color(0xFFF1F3F4)],
|
|
||||||
begin: Alignment.topLeft,
|
|
||||||
end: Alignment.bottomRight,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Positioned(
|
|
||||||
right: 16,
|
|
||||||
top: 0,
|
|
||||||
bottom: 0,
|
|
||||||
child: _RandomStickerImage(
|
|
||||||
width: 180,
|
|
||||||
height: 180,
|
|
||||||
).opacity(isWideScreen(context) ? 1 : 0.5),
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
height: 180,
|
|
||||||
width: double.infinity,
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 16),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'Good morning!',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 32,
|
|
||||||
fontWeight: FontWeight.w400,
|
|
||||||
color: Theme.of(context).colorScheme.onSurface,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Gap(4),
|
|
||||||
Text(
|
|
||||||
currentDeveloper != null
|
|
||||||
? "You're working as ${currentDeveloper!.publisher!.nick}"
|
|
||||||
: "Choose a developer and continue.",
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Random Sticker Image Widget
|
|
||||||
class _RandomStickerImage extends StatelessWidget {
|
|
||||||
final double? width;
|
|
||||||
final double? height;
|
|
||||||
|
|
||||||
const _RandomStickerImage({this.width, this.height});
|
|
||||||
|
|
||||||
static const List<String> _stickers = [
|
|
||||||
'assets/images/stickers/clap.png',
|
|
||||||
'assets/images/stickers/confuse.png',
|
|
||||||
'assets/images/stickers/pray.png',
|
|
||||||
'assets/images/stickers/thumb_up.png',
|
|
||||||
];
|
|
||||||
|
|
||||||
String _getRandomSticker() {
|
|
||||||
final random = Random();
|
|
||||||
return _stickers[random.nextInt(_stickers.length)];
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Image.asset(
|
|
||||||
_getRandomSticker(),
|
|
||||||
width: width ?? 80,
|
|
||||||
height: height ?? 80,
|
|
||||||
fit: BoxFit.contain,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Navigation Tabs
|
|
||||||
class _NavigationTabs extends StatelessWidget {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Container(
|
|
||||||
decoration: BoxDecoration(color: Theme.of(context).colorScheme.surface),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
const Gap(24),
|
|
||||||
_NavTabItem(title: 'Dashboard', isActive: true),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _NavTabItem extends StatelessWidget {
|
|
||||||
final String title;
|
|
||||||
final bool isActive;
|
|
||||||
|
|
||||||
const _NavTabItem({required this.title, this.isActive = false});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Container(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
border: Border(
|
|
||||||
bottom: BorderSide(
|
|
||||||
color:
|
|
||||||
isActive
|
|
||||||
? Theme.of(context).colorScheme.primary
|
|
||||||
: Colors.transparent,
|
|
||||||
width: 2,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
title,
|
|
||||||
style: TextStyle(
|
|
||||||
color:
|
|
||||||
isActive
|
|
||||||
? Theme.of(context).colorScheme.primary
|
|
||||||
: Theme.of(context).colorScheme.onSurface,
|
|
||||||
fontWeight: isActive ? FontWeight.w500 : FontWeight.w400,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Main Content Section
|
// Main Content Section
|
||||||
class _MainContentSection extends HookConsumerWidget {
|
class _MainContentSection extends HookConsumerWidget {
|
||||||
final SnDeveloper? currentDeveloper;
|
final SnDeveloper? currentDeveloper;
|
||||||
|
|||||||
@@ -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,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,23 @@
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:island/screens/developers/edit_bot.dart';
|
import 'package:island/screens/developers/edit_bot.dart';
|
||||||
|
|
||||||
class NewBotScreen extends StatelessWidget {
|
class NewBotScreen extends StatelessWidget {
|
||||||
final String publisherName;
|
final String publisherName;
|
||||||
final String projectId;
|
final String projectId;
|
||||||
const NewBotScreen({super.key, required this.publisherName, required this.projectId});
|
final bool isModal;
|
||||||
|
const NewBotScreen({
|
||||||
|
super.key,
|
||||||
|
required this.publisherName,
|
||||||
|
required this.projectId,
|
||||||
|
this.isModal = false,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return EditBotScreen(publisherName: publisherName, projectId: projectId);
|
return EditBotScreen(
|
||||||
|
publisherName: publisherName,
|
||||||
|
projectId: projectId,
|
||||||
|
isModal: isModal,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:island/models/dev_project.dart';
|
import 'package:island/models/dev_project.dart';
|
||||||
import 'package:island/screens/developers/apps.dart';
|
import 'package:island/screens/developers/apps.dart';
|
||||||
import 'package:island/screens/developers/bots.dart';
|
import 'package:island/screens/developers/bots.dart';
|
||||||
import 'package:island/services/responsive.dart';
|
import 'package:island/services/responsive.dart';
|
||||||
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
|
||||||
class ProjectDetailView extends HookConsumerWidget {
|
class ProjectDetailView extends HookConsumerWidget {
|
||||||
final String publisherName;
|
final String publisherName;
|
||||||
@@ -54,6 +56,37 @@ class ProjectDetailView extends HookConsumerWidget {
|
|||||||
label: Text('bots'.tr()),
|
label: Text('bots'.tr()),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
leading: Container(
|
||||||
|
width: isWiderScreen(context) ? 256 : 80,
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
left: 24,
|
||||||
|
right: 24,
|
||||||
|
bottom: 8,
|
||||||
|
top: 2,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border(
|
||||||
|
bottom: BorderSide(
|
||||||
|
color: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.onSurface.withOpacity(0.12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: onBackToHub,
|
||||||
|
icon: const Icon(Icons.arrow_back),
|
||||||
|
iconSize: 16,
|
||||||
|
visualDensity: VisualDensity.compact,
|
||||||
|
),
|
||||||
|
if (isWiderScreen(context))
|
||||||
|
Expanded(child: Text("backToHub").tr()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -69,6 +102,7 @@ class ProjectDetailView extends HookConsumerWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const Gap(4),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@@ -97,7 +131,7 @@ class ProjectDetailView extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
BotsScreen(publisherName: publisherName, projectId: project.id),
|
BotsScreen(publisherName: publisherName, projectId: project.id),
|
||||||
],
|
],
|
||||||
),
|
).padding(horizontal: 8),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -737,7 +737,7 @@ class ActivityListNotifier extends _$ActivityListNotifier
|
|||||||
};
|
};
|
||||||
|
|
||||||
final response = await client.get(
|
final response = await client.get(
|
||||||
'/sphere/activities',
|
'/sphere/timeline',
|
||||||
queryParameters: queryParameters,
|
queryParameters: queryParameters,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user