diff --git a/lib/screens/developers/app_detail.dart b/lib/screens/developers/app_detail.dart index d2bfbe55..c395c85f 100644 --- a/lib/screens/developers/app_detail.dart +++ b/lib/screens/developers/app_detail.dart @@ -39,19 +39,18 @@ class AppDetailScreen extends HookConsumerWidget { actions: [ IconButton( icon: const Icon(Symbols.edit), - onPressed: - appData.value == null - ? null - : () { - context.pushNamed( - 'developerAppEdit', - pathParameters: { - 'name': publisherName, - 'projectId': projectId, - 'id': appId, - }, - ); - }, + onPressed: appData.value == null + ? null + : () { + context.pushNamed( + 'developerAppEdit', + pathParameters: { + 'name': publisherName, + 'projectId': projectId, + 'id': appId, + }, + ); + }, ), const Gap(8), ], @@ -85,24 +84,34 @@ class AppDetailScreen extends HookConsumerWidget { controller: tabController, physics: const NeverScrollableScrollPhysics(), children: [ - _AppOverview(app: app), - AppSecretsScreen( - publisherName: publisherName, - projectId: projectId, - appId: appId, + 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, - onRetry: - () => ref.invalidate( - customAppProvider(publisherName, projectId, appId), - ), - ), + error: (err, stack) => ResponseErrorWidget( + error: err, + 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,13 +135,12 @@ class _AppOverview extends StatelessWidget { children: [ Container( color: Theme.of(context).colorScheme.surfaceContainer, - child: - app.background != null - ? CloudFileWidget( - item: app.background!, - fit: BoxFit.cover, - ) - : const SizedBox.shrink(), + child: app.background != null + ? CloudFileWidget( + item: app.background!, + fit: BoxFit.cover, + ) + : const SizedBox.shrink(), ), Positioned( left: 20, diff --git a/lib/screens/developers/app_secrets.dart b/lib/screens/developers/app_secrets.dart index 9254955a..d37cdeb0 100644 --- a/lib/screens/developers/app_secrets.dart +++ b/lib/screens/developers/app_secrets.dart @@ -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,37 +54,36 @@ class AppSecretsScreen extends HookConsumerWidget { showModalBottomSheet( context: context, isScrollControlled: true, - builder: - (context) => SheetScaffold( - titleText: 'newSecretGenerated'.tr(), - child: Padding( - padding: const EdgeInsets.all(20.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - mainAxisSize: MainAxisSize.min, - children: [ - Text('copySecretHint'.tr()), - const SizedBox(height: 16), - Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surfaceContainer, - borderRadius: BorderRadius.circular(8), - ), - child: SelectableText(newSecret), - ), - const SizedBox(height: 20), - FilledButton.icon( - onPressed: () { - Clipboard.setData(ClipboardData(text: newSecret)); - }, - icon: const Icon(Symbols.copy_all), - label: Text('copy'.tr()), - ), - ], + builder: (context) => SheetScaffold( + titleText: 'newSecretGenerated'.tr(), + child: Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisSize: MainAxisSize.min, + children: [ + Text('copySecretHint'.tr()), + const SizedBox(height: 16), + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surfaceContainer, + borderRadius: BorderRadius.circular(8), + ), + child: SelectableText(newSecret), ), - ), + const SizedBox(height: 20), + FilledButton.icon( + onPressed: () { + Clipboard.setData(ClipboardData(text: newSecret)); + }, + icon: const Icon(Symbols.copy_all), + label: Text('copy'.tr()), + ), + ], ), + ), + ), ).whenComplete(() { ref.invalidate( customAppSecretsProvider(publisherName, projectId, appId), @@ -114,22 +114,38 @@ 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( - title: Text('isOidc'.tr()), - value: isOidc.value, - onChanged: (value) => isOidc.value = value, + 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( @@ -175,14 +191,9 @@ 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: [ ListTile( @@ -240,14 +251,12 @@ class AppSecretsScreen extends HookConsumerWidget { ); }, loading: () => const Center(child: CircularProgressIndicator()), - error: - (err, stack) => ResponseErrorWidget( - error: err, - onRetry: - () => ref.invalidate( - customAppSecretsProvider(publisherName, projectId, appId), - ), - ), + error: (err, stack) => ResponseErrorWidget( + error: err, + onRetry: () => ref.invalidate( + customAppSecretsProvider(publisherName, projectId, appId), + ), + ), ); } } diff --git a/lib/screens/developers/apps.dart b/lib/screens/developers/apps.dart index 86a0d547..36bfeffc 100644 --- a/lib/screens/developers/apps.dart +++ b/lib/screens/developers/apps.dart @@ -76,15 +76,14 @@ class CustomAppsScreen extends HookConsumerWidget { showModalBottomSheet( context: context, isScrollControlled: true, - builder: - (context) => SheetScaffold( - titleText: 'createCustomApp'.tr(), - child: NewCustomAppScreen( - publisherName: publisherName, - projectId: projectId, - isModal: true, - ), - ), + builder: (context) => SheetScaffold( + titleText: 'createCustomApp'.tr(), + child: NewCustomAppScreen( + publisherName: publisherName, + projectId: projectId, + isModal: true, + ), + ), ); }, icon: const Icon(Symbols.add), @@ -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,15 +107,14 @@ class CustomAppsScreen extends HookConsumerWidget { showModalBottomSheet( context: context, isScrollControlled: true, - builder: - (context) => SheetScaffold( - titleText: 'createCustomApp'.tr(), - child: NewCustomAppScreen( - publisherName: publisherName, - projectId: projectId, - isModal: true, - ), - ), + builder: (context) => SheetScaffold( + titleText: 'createCustomApp'.tr(), + child: NewCustomAppScreen( + publisherName: publisherName, + projectId: projectId, + isModal: true, + ), + ), ); }, icon: const Icon(Symbols.add), @@ -146,31 +142,20 @@ class CustomAppsScreen extends HookConsumerWidget { }, 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, - ), - ), - ], + if (app.background != null) + AspectRatio( + aspectRatio: 16 / 7, + child: CloudFileWidget( + item: app.background!, + fit: BoxFit.cover, + ).clipRRect(topLeft: 8, topRight: 8), ), - ), ListTile( title: Text(app.name), + leading: ProfilePictureWidget( + fileId: app.picture?.id, + fallbackIcon: Symbols.apps, + ), subtitle: Text( app.slug, style: GoogleFonts.robotoMono(fontSize: 12), @@ -180,52 +165,48 @@ class CustomAppsScreen extends HookConsumerWidget { right: 12, ), trailing: PopupMenuButton( - itemBuilder: - (context) => [ - PopupMenuItem( - value: 'edit', - child: Row( - children: [ - const Icon(Symbols.edit), - const SizedBox(width: 12), - Text('edit').tr(), - ], + 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, ), - ), - 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(), - ], - ), - ), - ], + 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, - ), - ), + builder: (context) => SheetScaffold( + titleText: 'editCustomApp'.tr(), + child: EditAppScreen( + publisherName: publisherName, + projectId: projectId, + id: app.id, + isModal: true, + ), + ), ); } else if (value == 'delete') { showConfirmAlert( @@ -264,14 +245,11 @@ class CustomAppsScreen extends HookConsumerWidget { ); }, loading: () => const Center(child: CircularProgressIndicator()), - error: - (err, stack) => ResponseErrorWidget( - error: err, - onRetry: - () => ref.invalidate( - customAppsProvider(publisherName, projectId), - ), - ), + error: (err, stack) => ResponseErrorWidget( + error: err, + onRetry: () => + ref.invalidate(customAppsProvider(publisherName, projectId)), + ), ); } } diff --git a/lib/screens/developers/bot_detail.dart b/lib/screens/developers/bot_detail.dart index d40dca24..c05d161e 100644 --- a/lib/screens/developers/bot_detail.dart +++ b/lib/screens/developers/bot_detail.dart @@ -36,19 +36,18 @@ class BotDetailScreen extends HookConsumerWidget { actions: [ IconButton( icon: const Icon(Symbols.edit), - onPressed: - botData.value == null - ? null - : () { - context.pushNamed( - 'developerBotEdit', - pathParameters: { - 'name': publisherName, - 'projectId': projectId, - 'id': botId, - }, - ); - }, + onPressed: botData.value == null + ? null + : () { + context.pushNamed( + 'developerBotEdit', + pathParameters: { + 'name': publisherName, + 'projectId': projectId, + 'id': botId, + }, + ); + }, ), ], bottom: TabBar( @@ -84,24 +83,33 @@ class BotDetailScreen extends HookConsumerWidget { controller: tabController, physics: const NeverScrollableScrollPhysics(), children: [ - _BotOverview(bot: bot), - BotKeysScreen( - publisherName: publisherName, - projectId: projectId, - botId: botId, + 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, - onRetry: - () => ref.invalidate( - botProvider(publisherName, projectId, botId), - ), - ), + error: (err, stack) => ResponseErrorWidget( + error: err, + onRetry: () => + ref.invalidate(botProvider(publisherName, projectId, botId)), + ), ), ); } @@ -124,13 +132,12 @@ class _BotOverview extends StatelessWidget { children: [ Container( color: Theme.of(context).colorScheme.surfaceContainer, - child: - bot.account.profile.background != null - ? CloudFileWidget( - item: bot.account.profile.background!, - fit: BoxFit.cover, - ) - : const SizedBox.shrink(), + child: bot.account.profile.background != null + ? CloudFileWidget( + item: bot.account.profile.background!, + fit: BoxFit.cover, + ) + : const SizedBox.shrink(), ), Positioned( left: 20, diff --git a/lib/screens/developers/bot_keys.dart b/lib/screens/developers/bot_keys.dart index 52ad32d7..4ea8d864 100644 --- a/lib/screens/developers/bot_keys.dart +++ b/lib/screens/developers/bot_keys.dart @@ -53,37 +53,36 @@ class BotKeysScreen extends HookConsumerWidget { showModalBottomSheet( context: context, isScrollControlled: true, - builder: - (context) => SheetScaffold( - titleText: 'newKeyGenerated'.tr(), - child: Padding( - padding: const EdgeInsets.all(20.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - mainAxisSize: MainAxisSize.min, - children: [ - Text('copyKeyHint'.tr()), - const SizedBox(height: 16), - Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surfaceContainer, - borderRadius: BorderRadius.circular(8), - ), - child: SelectableText(token), - ), - const SizedBox(height: 20), - FilledButton.icon( - onPressed: () { - Clipboard.setData(ClipboardData(text: token)); - }, - icon: const Icon(Symbols.copy_all), - label: Text('copy'.tr()), - ), - ], + builder: (context) => SheetScaffold( + titleText: 'newKeyGenerated'.tr(), + child: Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisSize: MainAxisSize.min, + children: [ + Text('copyKeyHint'.tr()), + const SizedBox(height: 16), + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surfaceContainer, + borderRadius: BorderRadius.circular(8), + ), + child: SelectableText(token), ), - ), + const SizedBox(height: 20), + FilledButton.icon( + onPressed: () { + Clipboard.setData(ClipboardData(text: token)); + }, + icon: const Icon(Symbols.copy_all), + label: Text('copy'.tr()), + ), + ], ), + ), + ), ).whenComplete(() { ref.invalidate(botKeysProvider(publisherName, projectId, botId)); }); @@ -94,45 +93,50 @@ class BotKeysScreen extends HookConsumerWidget { showModalBottomSheet( context: context, isScrollControlled: true, - builder: - (context) => SheetScaffold( - titleText: 'newBotKey'.tr(), - child: Padding( - padding: const EdgeInsets.all(20.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - mainAxisSize: MainAxisSize.min, - children: [ - TextFormField( - controller: keyNameController, - decoration: InputDecoration(labelText: 'keyName'.tr()), - autofocus: true, + builder: (context) => SheetScaffold( + heightFactor: 0.7, + titleText: 'newBotKey'.tr(), + child: Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisSize: MainAxisSize.min, + children: [ + TextFormField( + controller: keyNameController, + decoration: InputDecoration( + labelText: 'keyName'.tr(), + border: const OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(12)), ), - const SizedBox(height: 20), - FilledButton.icon( - onPressed: () async { - if (keyNameController.text.isEmpty) return; - final keyName = keyNameController.text; - Navigator.pop(context); // Close the sheet - try { - final client = ref.read(apiClientProvider); - final resp = await client.post( - '/develop/developers/$publisherName/projects/$projectId/bots/$botId/keys', - data: {'label': keyName}, - ); - final newApiKey = SnAccountApiKey.fromJson(resp.data); - showNewKeySheet(newApiKey); - } catch (e) { - showErrorAlert(e.toString()); - } - }, - icon: const Icon(Symbols.add), - label: Text('create'.tr()), - ), - ], + ), + autofocus: true, ), - ), + const SizedBox(height: 20), + FilledButton.icon( + onPressed: () async { + if (keyNameController.text.isEmpty) return; + final keyName = keyNameController.text; + Navigator.pop(context); // Close the sheet + try { + final client = ref.read(apiClientProvider); + final resp = await client.post( + '/develop/developers/$publisherName/projects/$projectId/bots/$botId/keys', + data: {'label': keyName}, + ); + final newApiKey = SnAccountApiKey.fromJson(resp.data); + showNewKeySheet(newApiKey); + } catch (e) { + showErrorAlert(e.toString()); + } + }, + icon: const Icon(Symbols.add), + label: Text('create'.tr()), + ), + ], ), + ), + ), ); } @@ -189,92 +193,79 @@ 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 - ? Center(child: Text('noBotKeys'.tr())) - : RefreshIndicator( - onRefresh: - () => ref.refresh( - botKeysProvider( - publisherName, - projectId, - botId, - ).future, + child: data.isEmpty + ? Center(child: Text('noBotKeys'.tr())) + : RefreshIndicator( + onRefresh: () => ref.refresh( + botKeysProvider(publisherName, projectId, botId).future, + ), + child: ListView.builder( + padding: EdgeInsets.zero, + itemCount: data.length, + itemBuilder: (context, index) { + final apiKey = data[index]; + return ListTile( + title: Text(apiKey.label), + subtitle: Text(apiKey.createdAt.formatSystem()), + contentPadding: EdgeInsets.only( + left: 16, + right: 12, ), - child: ListView.builder( - padding: EdgeInsets.zero, - itemCount: data.length, - itemBuilder: (context, index) { - final apiKey = data[index]; - return ListTile( - title: Text(apiKey.label), - subtitle: Text(apiKey.createdAt.formatSystem()), - contentPadding: EdgeInsets.only( - left: 16, - right: 12, - ), - trailing: PopupMenuButton( - itemBuilder: - (context) => [ - PopupMenuItem( - value: 'rotate', - child: Row( - children: [ - const Icon(Symbols.refresh), - const Gap(12), - Text('rotateKey'.tr()), - ], - ), + trailing: PopupMenuButton( + itemBuilder: (context) => [ + PopupMenuItem( + value: 'rotate', + child: Row( + children: [ + const Icon(Symbols.refresh), + const Gap(12), + Text('rotateKey'.tr()), + ], + ), + ), + PopupMenuItem( + value: 'revoke', + child: Row( + children: [ + const Icon( + Symbols.delete, + color: Colors.red, ), - PopupMenuItem( - value: 'revoke', - child: Row( - children: [ - const Icon( - Symbols.delete, - color: Colors.red, - ), - const Gap(12), - Text( - 'revoke'.tr(), - style: TextStyle( - color: Colors.red, - ), - ), - ], - ), + const Gap(12), + Text( + 'revoke'.tr(), + style: TextStyle(color: Colors.red), ), ], - onSelected: (value) { - if (value == 'rotate') { - rotateKey(apiKey.id); - } else if (value == 'revoke') { - revokeKey(apiKey.id); - } - }, - ), - ); - }, - ), + ), + ), + ], + onSelected: (value) { + if (value == 'rotate') { + rotateKey(apiKey.id); + } else if (value == 'revoke') { + revokeKey(apiKey.id); + } + }, + ), + ); + }, ), + ), ), ], ); }, loading: () => const Center(child: CircularProgressIndicator()), - error: - (err, stack) => ResponseErrorWidget( - error: err, - onRetry: - () => ref.invalidate( - botKeysProvider(publisherName, projectId, botId), - ), - ), + error: (err, stack) => ResponseErrorWidget( + error: err, + onRetry: () => + ref.invalidate(botKeysProvider(publisherName, projectId, botId)), + ), ); } } diff --git a/lib/screens/developers/bots.dart b/lib/screens/developers/bots.dart index a5f97dbb..84ef7f4f 100644 --- a/lib/screens/developers/bots.dart +++ b/lib/screens/developers/bots.dart @@ -54,15 +54,14 @@ class BotsScreen extends HookConsumerWidget { showModalBottomSheet( context: context, isScrollControlled: true, - builder: - (context) => SheetScaffold( - titleText: 'createBot'.tr(), - child: NewBotScreen( - publisherName: publisherName, - projectId: projectId, - isModal: true, - ), - ), + builder: (context) => SheetScaffold( + titleText: 'createBot'.tr(), + child: NewBotScreen( + publisherName: publisherName, + projectId: projectId, + isModal: true, + ), + ), ); }, icon: const Icon(Symbols.add), @@ -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,15 +85,14 @@ class BotsScreen extends HookConsumerWidget { showModalBottomSheet( context: context, isScrollControlled: true, - builder: - (context) => SheetScaffold( - titleText: 'createBot'.tr(), - child: NewBotScreen( - publisherName: publisherName, - projectId: projectId, - isModal: true, - ), - ), + builder: (context) => SheetScaffold( + titleText: 'createBot'.tr(), + child: NewBotScreen( + publisherName: publisherName, + projectId: projectId, + isModal: true, + ), + ), ); }, icon: const Icon(Symbols.add), @@ -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)), - ), - 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) => [ + 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), + ), + 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) => [ PopupMenuItem( value: 'edit', child: Row( @@ -152,13 +157,12 @@ class BotsScreen extends HookConsumerWidget { ), ), ], - onSelected: (value) { - if (value == 'edit') { - showModalBottomSheet( - context: context, - isScrollControlled: true, - builder: - (context) => SheetScaffold( + onSelected: (value) { + if (value == 'edit') { + showModalBottomSheet( + context: context, + isScrollControlled: true, + builder: (context) => SheetScaffold( titleText: 'editBot'.tr(), child: EditBotScreen( publisherName: publisherName, @@ -167,36 +171,40 @@ class BotsScreen extends HookConsumerWidget { isModal: true, ), ), - ); - } else if (value == 'delete') { - showConfirmAlert( - 'deleteBotHint'.tr(), - 'deleteBot'.tr(), - isDanger: true, - ).then((confirm) { - if (confirm) { - final client = ref.read(apiClientProvider); - client.delete( - '/develop/developers/$publisherName/projects/$projectId/bots/${bot.id}', - ); - ref.invalidate( - botsProvider(publisherName, projectId), ); + } else if (value == 'delete') { + showConfirmAlert( + 'deleteBotHint'.tr(), + 'deleteBot'.tr(), + isDanger: true, + ).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, + }, + ), + onTap: () { + context.pushNamed( + 'developerBotDetail', + pathParameters: { + 'name': publisherName, + 'projectId': projectId, + 'botId': bot.id, + }, + ); }, - ); - }, + ), + ], ), ); }, @@ -207,12 +215,10 @@ class BotsScreen extends HookConsumerWidget { ); }, loading: () => const Center(child: CircularProgressIndicator()), - error: - (err, stack) => ResponseErrorWidget( - error: err, - onRetry: - () => ref.invalidate(botsProvider(publisherName, projectId)), - ), + error: (err, stack) => ResponseErrorWidget( + error: err, + onRetry: () => ref.invalidate(botsProvider(publisherName, projectId)), + ), ); } } diff --git a/lib/screens/developers/project_detail_view.dart b/lib/screens/developers/project_detail_view.dart index c6374edc..3bec8a8a 100644 --- a/lib/screens/developers/project_detail_view.dart +++ b/lib/screens/developers/project_detail_view.dart @@ -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) - ? null - : NavigationRailLabelType.selected, + 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), diff --git a/lib/widgets/post/compose_sheet.dart b/lib/widgets/post/compose_sheet.dart index cfcbd2a5..a81e848c 100644 --- a/lib/widgets/post/compose_sheet.dart +++ b/lib/widgets/post/compose_sheet.dart @@ -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(