From da2d3f7f171331b72e84139a254d15e6c25506e0 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sun, 2 Nov 2025 21:04:35 +0800 Subject: [PATCH] :recycle: Make bot management into sheet --- lib/screens/developers/bots.dart | 60 ++- lib/screens/developers/edit_bot.dart | 507 ++++++++++-------- lib/screens/developers/new_bot.dart | 15 +- .../developers/project_detail_view.dart | 4 +- 4 files changed, 340 insertions(+), 246 deletions(-) diff --git a/lib/screens/developers/bots.dart b/lib/screens/developers/bots.dart index b45277b8..ff49e4f8 100644 --- a/lib/screens/developers/bots.dart +++ b/lib/screens/developers/bots.dart @@ -5,8 +5,11 @@ import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:island/models/bot.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/content/cloud_files.dart'; +import 'package:island/widgets/content/sheet.dart'; import 'package:island/widgets/response.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; @@ -48,12 +51,18 @@ class BotsScreen extends HookConsumerWidget { const SizedBox(height: 16), ElevatedButton.icon( onPressed: () { - context.pushNamed( - 'developerBotNew', - pathParameters: { - 'name': publisherName, - 'projectId': projectId, - }, + showModalBottomSheet( + context: context, + isScrollControlled: true, + builder: + (context) => SheetScaffold( + titleText: 'createBot'.tr(), + child: NewBotScreen( + publisherName: publisherName, + projectId: projectId, + isModal: true, + ), + ), ); }, icon: const Icon(Symbols.add), @@ -74,12 +83,18 @@ class BotsScreen extends HookConsumerWidget { title: Text('bots').tr().padding(horizontal: 8), trailing: IconButton( onPressed: () { - context.pushNamed( - 'developerBotNew', - pathParameters: { - 'name': publisherName, - 'projectId': projectId, - }, + showModalBottomSheet( + context: context, + isScrollControlled: true, + builder: + (context) => SheetScaffold( + titleText: 'createBot'.tr(), + child: NewBotScreen( + publisherName: publisherName, + projectId: projectId, + isModal: true, + ), + ), ); }, icon: const Icon(Symbols.add), @@ -93,7 +108,6 @@ class BotsScreen extends HookConsumerWidget { itemBuilder: (context, index) { final bot = data[index]; return Card( - margin: const EdgeInsets.all(8.0), child: ListTile( shape: const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(8.0)), @@ -140,13 +154,19 @@ class BotsScreen extends HookConsumerWidget { ], onSelected: (value) { if (value == 'edit') { - context.pushNamed( - 'developerBotEdit', - pathParameters: { - 'name': publisherName, - 'projectId': projectId, - 'id': bot.id, - }, + 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( diff --git a/lib/screens/developers/edit_bot.dart b/lib/screens/developers/edit_bot.dart index d2f760da..c23e54b0 100644 --- a/lib/screens/developers/edit_bot.dart +++ b/lib/screens/developers/edit_bot.dart @@ -38,11 +38,13 @@ class EditBotScreen extends HookConsumerWidget { final String publisherName; final String projectId; final String? id; + final bool isModal; const EditBotScreen({ super.key, required this.publisherName, required this.projectId, this.id, + this.isModal = false, }); @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( isNoBackground: false, appBar: AppBar(title: Text(isNew ? 'createBot'.tr() : 'editBot'.tr())), - body: - 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), - ), - ], - ), - ), + body: bodyContent, ); } } diff --git a/lib/screens/developers/new_bot.dart b/lib/screens/developers/new_bot.dart index 9d93e080..0cc9c2f8 100644 --- a/lib/screens/developers/new_bot.dart +++ b/lib/screens/developers/new_bot.dart @@ -1,14 +1,23 @@ - import 'package:flutter/material.dart'; import 'package:island/screens/developers/edit_bot.dart'; class NewBotScreen extends StatelessWidget { final String publisherName; 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 Widget build(BuildContext context) { - return EditBotScreen(publisherName: publisherName, projectId: projectId); + return EditBotScreen( + publisherName: publisherName, + projectId: projectId, + isModal: isModal, + ); } } diff --git a/lib/screens/developers/project_detail_view.dart b/lib/screens/developers/project_detail_view.dart index 55608d66..a20879c2 100644 --- a/lib/screens/developers/project_detail_view.dart +++ b/lib/screens/developers/project_detail_view.dart @@ -58,8 +58,8 @@ class ProjectDetailView extends HookConsumerWidget { leading: Container( width: 256, padding: EdgeInsets.only( - left: 16, - right: 16, + left: 24, + right: 24, bottom: 8, top: 2, ),