💄 Optimize designs in developer hub

This commit is contained in:
2025-12-06 21:39:50 +08:00
parent ac2cee10e5
commit fe386163f4
8 changed files with 450 additions and 430 deletions

View File

@@ -39,8 +39,7 @@ class AppDetailScreen extends HookConsumerWidget {
actions: [
IconButton(
icon: const Icon(Symbols.edit),
onPressed:
appData.value == null
onPressed: appData.value == null
? null
: () {
context.pushNamed(
@@ -85,21 +84,31 @@ class AppDetailScreen extends HookConsumerWidget {
controller: tabController,
physics: const NeverScrollableScrollPhysics(),
children: [
_AppOverview(app: app),
AppSecretsScreen(
Align(
alignment: Alignment.topCenter,
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 640),
child: _AppOverview(app: app),
),
),
Align(
alignment: Alignment.topCenter,
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 640),
child: AppSecretsScreen(
publisherName: publisherName,
projectId: projectId,
appId: appId,
),
),
),
],
);
},
loading: () => const Center(child: CircularProgressIndicator()),
error:
(err, stack) => ResponseErrorWidget(
error: (err, stack) => ResponseErrorWidget(
error: err,
onRetry:
() => ref.invalidate(
onRetry: () => ref.invalidate(
customAppProvider(publisherName, projectId, appId),
),
),
@@ -115,6 +124,7 @@ class _AppOverview extends StatelessWidget {
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
padding: EdgeInsets.zero,
child: Column(
children: [
AspectRatio(
@@ -125,8 +135,7 @@ class _AppOverview extends StatelessWidget {
children: [
Container(
color: Theme.of(context).colorScheme.surfaceContainer,
child:
app.background != null
child: app.background != null
? CloudFileWidget(
item: app.background!,
fit: BoxFit.cover,

View File

@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/custom_app_secret.dart';
import 'package:island/pods/network.dart';
@@ -53,8 +54,7 @@ class AppSecretsScreen extends HookConsumerWidget {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder:
(context) => SheetScaffold(
builder: (context) => SheetScaffold(
titleText: 'newSecretGenerated'.tr(),
child: Padding(
padding: const EdgeInsets.all(20.0),
@@ -114,23 +114,39 @@ class AppSecretsScreen extends HookConsumerWidget {
controller: descriptionController,
decoration: InputDecoration(
labelText: 'description'.tr(),
border: OutlineInputBorder(
borderRadius: const BorderRadius.all(
Radius.circular(12),
),
),
),
autofocus: true,
),
const SizedBox(height: 20),
const Gap(16),
TextFormField(
controller: expiresInController,
decoration: InputDecoration(
labelText: 'expiresIn'.tr(),
border: OutlineInputBorder(
borderRadius: const BorderRadius.all(
Radius.circular(12),
),
),
),
keyboardType: TextInputType.number,
),
const SizedBox(height: 20),
SwitchListTile(
const Gap(16),
Card(
margin: EdgeInsets.zero,
child: SwitchListTile(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
title: Text('isOidc'.tr()),
value: isOidc.value,
onChanged: (value) => isOidc.value = value,
),
),
const SizedBox(height: 20),
FilledButton.icon(
onPressed: () async {
@@ -175,13 +191,8 @@ class AppSecretsScreen extends HookConsumerWidget {
return secrets.when(
data: (data) {
return RefreshIndicator(
onRefresh:
() => ref.refresh(
customAppSecretsProvider(
publisherName,
projectId,
appId,
).future,
onRefresh: () => ref.refresh(
customAppSecretsProvider(publisherName, projectId, appId).future,
),
child: Column(
children: [
@@ -240,11 +251,9 @@ class AppSecretsScreen extends HookConsumerWidget {
);
},
loading: () => const Center(child: CircularProgressIndicator()),
error:
(err, stack) => ResponseErrorWidget(
error: (err, stack) => ResponseErrorWidget(
error: err,
onRetry:
() => ref.invalidate(
onRetry: () => ref.invalidate(
customAppSecretsProvider(publisherName, projectId, appId),
),
),

View File

@@ -76,8 +76,7 @@ class CustomAppsScreen extends HookConsumerWidget {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder:
(context) => SheetScaffold(
builder: (context) => SheetScaffold(
titleText: 'createCustomApp'.tr(),
child: NewCustomAppScreen(
publisherName: publisherName,
@@ -95,10 +94,8 @@ class CustomAppsScreen extends HookConsumerWidget {
);
}
return ExtendedRefreshIndicator(
onRefresh:
() => ref.refresh(
customAppsProvider(publisherName, projectId).future,
),
onRefresh: () =>
ref.refresh(customAppsProvider(publisherName, projectId).future),
child: Column(
children: [
const Gap(8),
@@ -110,8 +107,7 @@ class CustomAppsScreen extends HookConsumerWidget {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder:
(context) => SheetScaffold(
builder: (context) => SheetScaffold(
titleText: 'createCustomApp'.tr(),
child: NewCustomAppScreen(
publisherName: publisherName,
@@ -145,32 +141,21 @@ class CustomAppsScreen extends HookConsumerWidget {
);
},
child: Column(
children: [
SizedBox(
height: 150,
child: Stack(
fit: StackFit.expand,
children: [
if (app.background != null)
CloudFileWidget(
AspectRatio(
aspectRatio: 16 / 7,
child: 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),
leading: ProfilePictureWidget(
fileId: app.picture?.id,
fallbackIcon: Symbols.apps,
),
subtitle: Text(
app.slug,
style: GoogleFonts.robotoMono(fontSize: 12),
@@ -180,8 +165,7 @@ class CustomAppsScreen extends HookConsumerWidget {
right: 12,
),
trailing: PopupMenuButton(
itemBuilder:
(context) => [
itemBuilder: (context) => [
PopupMenuItem(
value: 'edit',
child: Row(
@@ -203,9 +187,7 @@ class CustomAppsScreen extends HookConsumerWidget {
const SizedBox(width: 12),
Text(
'delete',
style: TextStyle(
color: Colors.red,
),
style: TextStyle(color: Colors.red),
).tr(),
],
),
@@ -216,8 +198,7 @@ class CustomAppsScreen extends HookConsumerWidget {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder:
(context) => SheetScaffold(
builder: (context) => SheetScaffold(
titleText: 'editCustomApp'.tr(),
child: EditAppScreen(
publisherName: publisherName,
@@ -264,13 +245,10 @@ class CustomAppsScreen extends HookConsumerWidget {
);
},
loading: () => const Center(child: CircularProgressIndicator()),
error:
(err, stack) => ResponseErrorWidget(
error: (err, stack) => ResponseErrorWidget(
error: err,
onRetry:
() => ref.invalidate(
customAppsProvider(publisherName, projectId),
),
onRetry: () =>
ref.invalidate(customAppsProvider(publisherName, projectId)),
),
);
}

View File

@@ -36,8 +36,7 @@ class BotDetailScreen extends HookConsumerWidget {
actions: [
IconButton(
icon: const Icon(Symbols.edit),
onPressed:
botData.value == null
onPressed: botData.value == null
? null
: () {
context.pushNamed(
@@ -84,23 +83,32 @@ class BotDetailScreen extends HookConsumerWidget {
controller: tabController,
physics: const NeverScrollableScrollPhysics(),
children: [
_BotOverview(bot: bot),
BotKeysScreen(
Align(
alignment: Alignment.topCenter,
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 640),
child: _BotOverview(bot: bot),
),
),
Align(
alignment: Alignment.topCenter,
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 640),
child: BotKeysScreen(
publisherName: publisherName,
projectId: projectId,
botId: botId,
),
),
),
],
);
},
loading: () => const Center(child: CircularProgressIndicator()),
error:
(err, stack) => ResponseErrorWidget(
error: (err, stack) => ResponseErrorWidget(
error: err,
onRetry:
() => ref.invalidate(
botProvider(publisherName, projectId, botId),
),
onRetry: () =>
ref.invalidate(botProvider(publisherName, projectId, botId)),
),
),
);
@@ -124,8 +132,7 @@ class _BotOverview extends StatelessWidget {
children: [
Container(
color: Theme.of(context).colorScheme.surfaceContainer,
child:
bot.account.profile.background != null
child: bot.account.profile.background != null
? CloudFileWidget(
item: bot.account.profile.background!,
fit: BoxFit.cover,

View File

@@ -53,8 +53,7 @@ class BotKeysScreen extends HookConsumerWidget {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder:
(context) => SheetScaffold(
builder: (context) => SheetScaffold(
titleText: 'newKeyGenerated'.tr(),
child: Padding(
padding: const EdgeInsets.all(20.0),
@@ -94,8 +93,8 @@ class BotKeysScreen extends HookConsumerWidget {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder:
(context) => SheetScaffold(
builder: (context) => SheetScaffold(
heightFactor: 0.7,
titleText: 'newBotKey'.tr(),
child: Padding(
padding: const EdgeInsets.all(20.0),
@@ -105,7 +104,12 @@ class BotKeysScreen extends HookConsumerWidget {
children: [
TextFormField(
controller: keyNameController,
decoration: InputDecoration(labelText: 'keyName'.tr()),
decoration: InputDecoration(
labelText: 'keyName'.tr(),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(12)),
),
),
autofocus: true,
),
const SizedBox(height: 20),
@@ -189,22 +193,15 @@ class BotKeysScreen extends HookConsumerWidget {
ListTile(
leading: const Icon(Symbols.add),
title: Text('newBotKey'.tr()),
trailing: const Icon(Symbols.chevron_right),
onTap: createKey,
),
const Divider(height: 1),
Expanded(
child:
data.isEmpty
child: data.isEmpty
? Center(child: Text('noBotKeys'.tr()))
: RefreshIndicator(
onRefresh:
() => ref.refresh(
botKeysProvider(
publisherName,
projectId,
botId,
).future,
onRefresh: () => ref.refresh(
botKeysProvider(publisherName, projectId, botId).future,
),
child: ListView.builder(
padding: EdgeInsets.zero,
@@ -219,8 +216,7 @@ class BotKeysScreen extends HookConsumerWidget {
right: 12,
),
trailing: PopupMenuButton(
itemBuilder:
(context) => [
itemBuilder: (context) => [
PopupMenuItem(
value: 'rotate',
child: Row(
@@ -242,9 +238,7 @@ class BotKeysScreen extends HookConsumerWidget {
const Gap(12),
Text(
'revoke'.tr(),
style: TextStyle(
color: Colors.red,
),
style: TextStyle(color: Colors.red),
),
],
),
@@ -267,13 +261,10 @@ class BotKeysScreen extends HookConsumerWidget {
);
},
loading: () => const Center(child: CircularProgressIndicator()),
error:
(err, stack) => ResponseErrorWidget(
error: (err, stack) => ResponseErrorWidget(
error: err,
onRetry:
() => ref.invalidate(
botKeysProvider(publisherName, projectId, botId),
),
onRetry: () =>
ref.invalidate(botKeysProvider(publisherName, projectId, botId)),
),
);
}

View File

@@ -54,8 +54,7 @@ class BotsScreen extends HookConsumerWidget {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder:
(context) => SheetScaffold(
builder: (context) => SheetScaffold(
titleText: 'createBot'.tr(),
child: NewBotScreen(
publisherName: publisherName,
@@ -73,8 +72,8 @@ class BotsScreen extends HookConsumerWidget {
);
}
return ExtendedRefreshIndicator(
onRefresh:
() => ref.refresh(botsProvider(publisherName, projectId).future),
onRefresh: () =>
ref.refresh(botsProvider(publisherName, projectId).future),
child: Column(
children: [
const Gap(8),
@@ -86,8 +85,7 @@ class BotsScreen extends HookConsumerWidget {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder:
(context) => SheetScaffold(
builder: (context) => SheetScaffold(
titleText: 'createBot'.tr(),
child: NewBotScreen(
publisherName: publisherName,
@@ -108,23 +106,30 @@ class BotsScreen extends HookConsumerWidget {
itemBuilder: (context, index) {
final bot = data[index];
return Card(
child: ListTile(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(8.0)),
child: Column(
children: [
if (bot.account.profile.background != null)
AspectRatio(
aspectRatio: 16 / 7,
child: CloudFileWidget(
item: bot.account.profile.background!,
fit: BoxFit.cover,
).clipRRect(topLeft: 8, topRight: 8),
),
leading: CircleAvatar(
child:
bot.account.profile.picture != null
? ProfilePictureWidget(
file: bot.account.profile.picture!,
)
: const Icon(Symbols.smart_toy),
ListTile(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(8.0),
),
),
leading: ProfilePictureWidget(
fallbackIcon: Symbols.smart_toy,
file: bot.account.profile.picture,
),
title: Text(bot.account.nick),
subtitle: Text(bot.account.name),
trailing: PopupMenuButton(
itemBuilder:
(context) => [
itemBuilder: (context) => [
PopupMenuItem(
value: 'edit',
child: Row(
@@ -157,8 +162,7 @@ class BotsScreen extends HookConsumerWidget {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder:
(context) => SheetScaffold(
builder: (context) => SheetScaffold(
titleText: 'editBot'.tr(),
child: EditBotScreen(
publisherName: publisherName,
@@ -175,7 +179,9 @@ class BotsScreen extends HookConsumerWidget {
isDanger: true,
).then((confirm) {
if (confirm) {
final client = ref.read(apiClientProvider);
final client = ref.read(
apiClientProvider,
);
client.delete(
'/develop/developers/$publisherName/projects/$projectId/bots/${bot.id}',
);
@@ -198,6 +204,8 @@ class BotsScreen extends HookConsumerWidget {
);
},
),
],
),
);
},
),
@@ -207,11 +215,9 @@ class BotsScreen extends HookConsumerWidget {
);
},
loading: () => const Center(child: CircularProgressIndicator()),
error:
(err, stack) => ResponseErrorWidget(
error: (err, stack) => ResponseErrorWidget(
error: err,
onRetry:
() => ref.invalidate(botsProvider(publisherName, projectId)),
onRetry: () => ref.invalidate(botsProvider(publisherName, projectId)),
),
);
}

View File

@@ -24,6 +24,16 @@ class ProjectDetailView extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final tabController = useTabController(initialLength: 2);
final currentDest = useState(0);
useEffect(() {
tabController.addListener(() {
if (tabController.indexIsChanging) {
currentDest.value = tabController.index;
}
});
return null;
});
final isWide = isWideScreen(context);
@@ -38,14 +48,13 @@ class ProjectDetailView extends HookConsumerWidget {
child: NavigationRail(
extended: isWiderScreen(context),
scrollable: true,
labelType:
isWiderScreen(context)
labelType: isWiderScreen(context)
? null
: NavigationRailLabelType.selected,
backgroundColor: Colors.transparent,
selectedIndex: tabController.index,
onDestinationSelected:
(index) => tabController.animateTo(index),
selectedIndex: currentDest.value,
onDestinationSelected: (index) =>
tabController.animateTo(index),
destinations: [
NavigationRailDestination(
icon: Icon(Icons.apps),

View File

@@ -1,4 +1,7 @@
import 'dart:io';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:go_router/go_router.dart';
@@ -8,6 +11,7 @@ import 'package:island/models/post.dart';
import 'package:island/screens/posts/compose.dart';
import 'package:island/screens/posts/post_detail.dart';
import 'package:island/services/compose_storage_db.dart';
import 'package:island/services/responsive.dart';
import 'package:island/widgets/content/sheet.dart';
import 'package:island/widgets/post/compose_card.dart';
import 'package:island/widgets/post/compose_shared.dart';
@@ -171,7 +175,14 @@ class PostComposeSheet extends HookConsumerWidget {
),
];
// Tablet will show a virtual keyboard, so we adjust the height factor accordingly
final isTablet =
isWideScreen(context) &&
!kIsWeb &&
(Platform.isAndroid || Platform.isAndroid);
return SheetScaffold(
heightFactor: isTablet ? 0.95 : 0.8,
titleText: 'postCompose'.tr(),
actions: actions,
child: PostComposeCard(