Compare commits
	
		
			2 Commits
		
	
	
		
			1232318a5d
			...
			4beda9200e
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 4beda9200e | |||
| 7dfe411053 | 
| @@ -643,6 +643,14 @@ | |||||||
|   "enrollDeveloperHint": "Enroll one of your publishers to become a developer.", |   "enrollDeveloperHint": "Enroll one of your publishers to become a developer.", | ||||||
|   "noPublishersToEnroll": "You don't have any publishers that can be enrolled as a developer.", |   "noPublishersToEnroll": "You don't have any publishers that can be enrolled as a developer.", | ||||||
|   "totalCustomApps": "Total Custom Apps", |   "totalCustomApps": "Total Custom Apps", | ||||||
|  |   "projects": "Projects", | ||||||
|  |   "noProjects": "No projects found.", | ||||||
|  |   "deleteProject": "Delete Project", | ||||||
|  |   "deleteProjectHint": "Are you sure you want to delete this project? This action cannot be undone.", | ||||||
|  |   "createProject": "Create Project", | ||||||
|  |   "editProject": "Edit Project", | ||||||
|  |   "projectDetails": "Project Details", | ||||||
|  |   "createBot": "Create Bot", | ||||||
|   "customApps": "Custom Apps", |   "customApps": "Custom Apps", | ||||||
|   "noCustomApps": "No custom apps yet.", |   "noCustomApps": "No custom apps yet.", | ||||||
|   "createCustomApp": "Create Custom App", |   "createCustomApp": "Create Custom App", | ||||||
|   | |||||||
							
								
								
									
										69
									
								
								lib/models/bot.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								lib/models/bot.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,69 @@ | |||||||
|  | import 'package:freezed_annotation/freezed_annotation.dart'; | ||||||
|  | import 'package:island/models/file.dart'; | ||||||
|  | import 'package:island/models/account.dart'; | ||||||
|  |  | ||||||
|  | part 'bot.freezed.dart'; | ||||||
|  | part 'bot.g.dart'; | ||||||
|  |  | ||||||
|  | @freezed | ||||||
|  | sealed class Bot with _$Bot { | ||||||
|  |   const factory Bot({ | ||||||
|  |     @Default('') String id, | ||||||
|  |     @Default('') String name, | ||||||
|  |     @Default('') String slug, | ||||||
|  |     String? description, | ||||||
|  |     @Default(0) int status, | ||||||
|  |     SnCloudFile? picture, | ||||||
|  |     SnCloudFile? background, | ||||||
|  |     SnVerificationMark? verification, | ||||||
|  |     BotConfig? config, | ||||||
|  |     BotLinks? links, | ||||||
|  |     @Default('') String publisherId, | ||||||
|  |     @Default('') String appId, | ||||||
|  |     DateTime? createdAt, | ||||||
|  |     DateTime? updatedAt, | ||||||
|  |   }) = _Bot; | ||||||
|  |  | ||||||
|  |   factory Bot.fromJson(Map<String, dynamic> json) => _$BotFromJson(json); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @freezed | ||||||
|  | sealed class BotConfig with _$BotConfig { | ||||||
|  |   const factory BotConfig({ | ||||||
|  |     @Default(false) bool isPublic, | ||||||
|  |     @Default(false) bool isInteractive, | ||||||
|  |     @Default([]) List<String> allowedRealms, | ||||||
|  |     @Default([]) List<String> allowedChatTypes, | ||||||
|  |     @Default({}) Map<String, dynamic> metadata, | ||||||
|  |   }) = _BotConfig; | ||||||
|  |  | ||||||
|  |   factory BotConfig.fromJson(Map<String, dynamic> json) => | ||||||
|  |       _$BotConfigFromJson(json); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @freezed | ||||||
|  | sealed class BotLinks with _$BotLinks { | ||||||
|  |   const factory BotLinks({ | ||||||
|  |     String? website, | ||||||
|  |     String? documentation, | ||||||
|  |     String? privacyPolicy, | ||||||
|  |     String? termsOfService, | ||||||
|  |   }) = _BotLinks; | ||||||
|  |  | ||||||
|  |   factory BotLinks.fromJson(Map<String, dynamic> json) => | ||||||
|  |       _$BotLinksFromJson(json); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @freezed | ||||||
|  | sealed class BotSecret with _$BotSecret { | ||||||
|  |   const factory BotSecret({ | ||||||
|  |     @Default('') String id, | ||||||
|  |     @Default('') String secret, | ||||||
|  |     String? description, | ||||||
|  |     DateTime? expiredAt, | ||||||
|  |     @Default('') String botId, | ||||||
|  |   }) = _BotSecret; | ||||||
|  |  | ||||||
|  |   factory BotSecret.fromJson(Map<String, dynamic> json) => | ||||||
|  |       _$BotSecretFromJson(json); | ||||||
|  | } | ||||||
							
								
								
									
										1252
									
								
								lib/models/bot.freezed.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1252
									
								
								lib/models/bot.freezed.dart
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										123
									
								
								lib/models/bot.g.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								lib/models/bot.g.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,123 @@ | |||||||
|  | // GENERATED CODE - DO NOT MODIFY BY HAND | ||||||
|  |  | ||||||
|  | part of 'bot.dart'; | ||||||
|  |  | ||||||
|  | // ************************************************************************** | ||||||
|  | // JsonSerializableGenerator | ||||||
|  | // ************************************************************************** | ||||||
|  |  | ||||||
|  | _Bot _$BotFromJson(Map<String, dynamic> json) => _Bot( | ||||||
|  |   id: json['id'] as String? ?? '', | ||||||
|  |   name: json['name'] as String? ?? '', | ||||||
|  |   slug: json['slug'] as String? ?? '', | ||||||
|  |   description: json['description'] as String?, | ||||||
|  |   status: (json['status'] as num?)?.toInt() ?? 0, | ||||||
|  |   picture: | ||||||
|  |       json['picture'] == null | ||||||
|  |           ? null | ||||||
|  |           : SnCloudFile.fromJson(json['picture'] as Map<String, dynamic>), | ||||||
|  |   background: | ||||||
|  |       json['background'] == null | ||||||
|  |           ? null | ||||||
|  |           : SnCloudFile.fromJson(json['background'] as Map<String, dynamic>), | ||||||
|  |   verification: | ||||||
|  |       json['verification'] == null | ||||||
|  |           ? null | ||||||
|  |           : SnVerificationMark.fromJson( | ||||||
|  |             json['verification'] as Map<String, dynamic>, | ||||||
|  |           ), | ||||||
|  |   config: | ||||||
|  |       json['config'] == null | ||||||
|  |           ? null | ||||||
|  |           : BotConfig.fromJson(json['config'] as Map<String, dynamic>), | ||||||
|  |   links: | ||||||
|  |       json['links'] == null | ||||||
|  |           ? null | ||||||
|  |           : BotLinks.fromJson(json['links'] as Map<String, dynamic>), | ||||||
|  |   publisherId: json['publisher_id'] as String? ?? '', | ||||||
|  |   appId: json['app_id'] as String? ?? '', | ||||||
|  |   createdAt: | ||||||
|  |       json['created_at'] == null | ||||||
|  |           ? null | ||||||
|  |           : DateTime.parse(json['created_at'] as String), | ||||||
|  |   updatedAt: | ||||||
|  |       json['updated_at'] == null | ||||||
|  |           ? null | ||||||
|  |           : DateTime.parse(json['updated_at'] as String), | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | Map<String, dynamic> _$BotToJson(_Bot instance) => <String, dynamic>{ | ||||||
|  |   'id': instance.id, | ||||||
|  |   'name': instance.name, | ||||||
|  |   'slug': instance.slug, | ||||||
|  |   'description': instance.description, | ||||||
|  |   'status': instance.status, | ||||||
|  |   'picture': instance.picture?.toJson(), | ||||||
|  |   'background': instance.background?.toJson(), | ||||||
|  |   'verification': instance.verification?.toJson(), | ||||||
|  |   'config': instance.config?.toJson(), | ||||||
|  |   'links': instance.links?.toJson(), | ||||||
|  |   'publisher_id': instance.publisherId, | ||||||
|  |   'app_id': instance.appId, | ||||||
|  |   'created_at': instance.createdAt?.toIso8601String(), | ||||||
|  |   'updated_at': instance.updatedAt?.toIso8601String(), | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | _BotConfig _$BotConfigFromJson(Map<String, dynamic> json) => _BotConfig( | ||||||
|  |   isPublic: json['is_public'] as bool? ?? false, | ||||||
|  |   isInteractive: json['is_interactive'] as bool? ?? false, | ||||||
|  |   allowedRealms: | ||||||
|  |       (json['allowed_realms'] as List<dynamic>?) | ||||||
|  |           ?.map((e) => e as String) | ||||||
|  |           .toList() ?? | ||||||
|  |       const [], | ||||||
|  |   allowedChatTypes: | ||||||
|  |       (json['allowed_chat_types'] as List<dynamic>?) | ||||||
|  |           ?.map((e) => e as String) | ||||||
|  |           .toList() ?? | ||||||
|  |       const [], | ||||||
|  |   metadata: json['metadata'] as Map<String, dynamic>? ?? const {}, | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | Map<String, dynamic> _$BotConfigToJson(_BotConfig instance) => | ||||||
|  |     <String, dynamic>{ | ||||||
|  |       'is_public': instance.isPublic, | ||||||
|  |       'is_interactive': instance.isInteractive, | ||||||
|  |       'allowed_realms': instance.allowedRealms, | ||||||
|  |       'allowed_chat_types': instance.allowedChatTypes, | ||||||
|  |       'metadata': instance.metadata, | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  | _BotLinks _$BotLinksFromJson(Map<String, dynamic> json) => _BotLinks( | ||||||
|  |   website: json['website'] as String?, | ||||||
|  |   documentation: json['documentation'] as String?, | ||||||
|  |   privacyPolicy: json['privacy_policy'] as String?, | ||||||
|  |   termsOfService: json['terms_of_service'] as String?, | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | Map<String, dynamic> _$BotLinksToJson(_BotLinks instance) => <String, dynamic>{ | ||||||
|  |   'website': instance.website, | ||||||
|  |   'documentation': instance.documentation, | ||||||
|  |   'privacy_policy': instance.privacyPolicy, | ||||||
|  |   'terms_of_service': instance.termsOfService, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | _BotSecret _$BotSecretFromJson(Map<String, dynamic> json) => _BotSecret( | ||||||
|  |   id: json['id'] as String? ?? '', | ||||||
|  |   secret: json['secret'] as String? ?? '', | ||||||
|  |   description: json['description'] as String?, | ||||||
|  |   expiredAt: | ||||||
|  |       json['expired_at'] == null | ||||||
|  |           ? null | ||||||
|  |           : DateTime.parse(json['expired_at'] as String), | ||||||
|  |   botId: json['bot_id'] as String? ?? '', | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | Map<String, dynamic> _$BotSecretToJson(_BotSecret instance) => | ||||||
|  |     <String, dynamic>{ | ||||||
|  |       'id': instance.id, | ||||||
|  |       'secret': instance.secret, | ||||||
|  |       'description': instance.description, | ||||||
|  |       'expired_at': instance.expiredAt?.toIso8601String(), | ||||||
|  |       'bot_id': instance.botId, | ||||||
|  |     }; | ||||||
							
								
								
									
										23
									
								
								lib/models/dev_project.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								lib/models/dev_project.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | |||||||
|  |  | ||||||
|  | class DevProject { | ||||||
|  |   final String id; | ||||||
|  |   final String slug; | ||||||
|  |   final String name; | ||||||
|  |   final String? description; | ||||||
|  |  | ||||||
|  |   DevProject({ | ||||||
|  |     required this.id, | ||||||
|  |     required this.slug, | ||||||
|  |     required this.name, | ||||||
|  |     this.description, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   factory DevProject.fromJson(Map<String, dynamic> json) { | ||||||
|  |     return DevProject( | ||||||
|  |       id: json['id'], | ||||||
|  |       slug: json['slug'], | ||||||
|  |       name: json['name'], | ||||||
|  |       description: json['description'], | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -7,10 +7,14 @@ import 'package:go_router/go_router.dart'; | |||||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
| import 'package:island/screens/about.dart'; | import 'package:island/screens/about.dart'; | ||||||
| import 'package:island/screens/account/credits.dart'; | import 'package:island/screens/account/credits.dart'; | ||||||
| import 'package:island/screens/developers/apps.dart'; |  | ||||||
| import 'package:island/screens/developers/edit_app.dart'; | import 'package:island/screens/developers/edit_app.dart'; | ||||||
|  | import 'package:island/screens/developers/edit_bot.dart'; | ||||||
| import 'package:island/screens/developers/new_app.dart'; | import 'package:island/screens/developers/new_app.dart'; | ||||||
| import 'package:island/screens/developers/hub.dart'; | import 'package:island/screens/developers/hub.dart'; | ||||||
|  | import 'package:island/screens/developers/projects.dart'; | ||||||
|  | import 'package:island/screens/developers/edit_project.dart'; | ||||||
|  | import 'package:island/screens/developers/new_project.dart'; | ||||||
|  | import 'package:island/screens/developers/project_detail.dart'; | ||||||
| import 'package:island/screens/discovery/articles.dart'; | import 'package:island/screens/discovery/articles.dart'; | ||||||
| import 'package:island/screens/posts/post_categories_list.dart'; | import 'package:island/screens/posts/post_categories_list.dart'; | ||||||
| import 'package:island/screens/posts/post_category_detail.dart'; | import 'package:island/screens/posts/post_category_detail.dart'; | ||||||
| @@ -291,30 +295,90 @@ final routerProvider = Provider<GoRouter>((ref) { | |||||||
|                 builder: (context, state) => const DeveloperHubScreen(), |                 builder: (context, state) => const DeveloperHubScreen(), | ||||||
|               ), |               ), | ||||||
|               GoRoute( |               GoRoute( | ||||||
|                 name: 'developerApps', |                 name: 'developerProjects', | ||||||
|                 path: '/developers/:name/apps', |                 path: '/developers/:name/projects', | ||||||
|                 builder: |                 builder: | ||||||
|                     (context, state) => CustomAppsScreen( |                     (context, state) => DevProjectsScreen( | ||||||
|                       publisherName: state.pathParameters['name']!, |                       publisherName: state.pathParameters['name']!, | ||||||
|                     ), |                     ), | ||||||
|               ), |               ), | ||||||
|  |               GoRoute( | ||||||
|  |                 name: 'developerProjectNew', | ||||||
|  |                 path: '/developers/:name/projects/new', | ||||||
|  |                 builder: | ||||||
|  |                     (context, state) => NewProjectScreen( | ||||||
|  |                       publisherName: state.pathParameters['name']!, | ||||||
|  |                     ), | ||||||
|  |               ), | ||||||
|  |               GoRoute( | ||||||
|  |                 name: 'developerProjectEdit', | ||||||
|  |                 path: '/developers/:name/projects/:id/edit', | ||||||
|  |                 builder: | ||||||
|  |                     (context, state) => EditProjectScreen( | ||||||
|  |                       publisherName: state.pathParameters['name']!, | ||||||
|  |                       id: state.pathParameters['id']!, | ||||||
|  |                     ), | ||||||
|  |               ), | ||||||
|  |               GoRoute( | ||||||
|  |                 name: 'developerProjectDetail', | ||||||
|  |                 path: '/developers/:name/projects/:projectId', | ||||||
|  |                 builder: | ||||||
|  |                     (context, state) => ProjectDetailScreen( | ||||||
|  |                       publisherName: state.pathParameters['name']!, | ||||||
|  |                       projectId: state.pathParameters['projectId']!, | ||||||
|  |                     ), | ||||||
|  |                 routes: [ | ||||||
|                   GoRoute( |                   GoRoute( | ||||||
|                     name: 'developerAppNew', |                     name: 'developerAppNew', | ||||||
|                 path: '/developers/:name/apps/new', |                     path: 'apps/new', | ||||||
|                     builder: |                     builder: | ||||||
|                         (context, state) => NewCustomAppScreen( |                         (context, state) => NewCustomAppScreen( | ||||||
|                           publisherName: state.pathParameters['name']!, |                           publisherName: state.pathParameters['name']!, | ||||||
|  |                           projectId: state.pathParameters['projectId']!, | ||||||
|                         ), |                         ), | ||||||
|                   ), |                   ), | ||||||
|                   GoRoute( |                   GoRoute( | ||||||
|                     name: 'developerAppEdit', |                     name: 'developerAppEdit', | ||||||
|                 path: '/developers/:name/apps/:id', |                     path: 'apps/:id/edit', | ||||||
|                     builder: |                     builder: | ||||||
|                         (context, state) => EditAppScreen( |                         (context, state) => EditAppScreen( | ||||||
|                           publisherName: state.pathParameters['name']!, |                           publisherName: state.pathParameters['name']!, | ||||||
|  |                           projectId: state.pathParameters['projectId']!, | ||||||
|                           id: state.pathParameters['id']!, |                           id: state.pathParameters['id']!, | ||||||
|                         ), |                         ), | ||||||
|                   ), |                   ), | ||||||
|  |                   GoRoute( | ||||||
|  |                     name: 'developerBotNew', | ||||||
|  |                     path: 'bots/new', | ||||||
|  |                     builder: | ||||||
|  |                         (context, state) => EditBotScreen( | ||||||
|  |                           publisherName: state.pathParameters['name']!, | ||||||
|  |                           projectId: state.pathParameters['projectId']!, | ||||||
|  |                         ), | ||||||
|  |                   ), | ||||||
|  |                   GoRoute( | ||||||
|  |                     name: 'developerBotEdit', | ||||||
|  |                     path: 'bots/:id/edit', | ||||||
|  |                     builder: | ||||||
|  |                         (context, state) => EditBotScreen( | ||||||
|  |                           publisherName: state.pathParameters['name']!, | ||||||
|  |                           projectId: state.pathParameters['projectId']!, | ||||||
|  |                           id: state.pathParameters['id']!, | ||||||
|  |                         ), | ||||||
|  |                   ), | ||||||
|  |                   GoRoute( | ||||||
|  |                     name: 'developerBotDetail', | ||||||
|  |                     path: 'bots/:id/detail', | ||||||
|  |                     builder: | ||||||
|  |                         (context, state) => EditBotScreen( | ||||||
|  |                           // Assuming EditBotScreen can also serve as a detail view | ||||||
|  |                           publisherName: state.pathParameters['name']!, | ||||||
|  |                           projectId: state.pathParameters['projectId']!, | ||||||
|  |                           id: state.pathParameters['id']!, | ||||||
|  |                         ), | ||||||
|  |                   ), | ||||||
|  |                 ], | ||||||
|  |               ), | ||||||
|             ], |             ], | ||||||
|           ), |           ), | ||||||
|  |  | ||||||
|   | |||||||
| @@ -6,7 +6,6 @@ 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/widgets/alert.dart'; | import 'package:island/widgets/alert.dart'; | ||||||
| import 'package:island/widgets/app_scaffold.dart'; |  | ||||||
| import 'package:island/widgets/content/cloud_files.dart'; | import 'package:island/widgets/content/cloud_files.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'; | ||||||
| @@ -16,43 +15,65 @@ import 'package:styled_widget/styled_widget.dart'; | |||||||
| part 'apps.g.dart'; | part 'apps.g.dart'; | ||||||
|  |  | ||||||
| @riverpod | @riverpod | ||||||
| Future<List<CustomApp>> customApps(Ref ref, String publisherName) async { | Future<List<CustomApp>> customApps( | ||||||
|  |   Ref ref, | ||||||
|  |   String publisherName, | ||||||
|  |   String projectId, | ||||||
|  | ) async { | ||||||
|   final client = ref.watch(apiClientProvider); |   final client = ref.watch(apiClientProvider); | ||||||
|   final resp = await client.get('/develop/developers/$publisherName/apps'); |   final resp = await client.get( | ||||||
|   return resp.data.map((e) => CustomApp.fromJson(e)).cast<CustomApp>().toList(); |     '/develop/developers/$publisherName/projects/$projectId/apps', | ||||||
|  |   ); | ||||||
|  |   return (resp.data as List) | ||||||
|  |       .map((e) => CustomApp.fromJson(e)) | ||||||
|  |       .cast<CustomApp>() | ||||||
|  |       .toList(); | ||||||
| } | } | ||||||
|  |  | ||||||
| class CustomAppsScreen extends HookConsumerWidget { | class CustomAppsScreen extends HookConsumerWidget { | ||||||
|   final String publisherName; |   final String publisherName; | ||||||
|   const CustomAppsScreen({super.key, required this.publisherName}); |   final String projectId; | ||||||
|  |   const CustomAppsScreen({ | ||||||
|  |     super.key, | ||||||
|  |     required this.publisherName, | ||||||
|  |     required this.projectId, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context, WidgetRef ref) { |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|     final apps = ref.watch(customAppsProvider(publisherName)); |     final apps = ref.watch(customAppsProvider(publisherName, projectId)); | ||||||
|  |  | ||||||
|     return AppScaffold( |     return apps.when( | ||||||
|       appBar: AppBar( |       data: (data) { | ||||||
|         title: Text('customApps').tr(), |         if (data.isEmpty) { | ||||||
|         actions: [ |           return Center( | ||||||
|           IconButton( |             child: Column( | ||||||
|             icon: const Icon(Symbols.add), |               mainAxisAlignment: MainAxisAlignment.center, | ||||||
|  |               children: [ | ||||||
|  |                 Text('noCustomApps').tr(), | ||||||
|  |                 const SizedBox(height: 16), | ||||||
|  |                 ElevatedButton.icon( | ||||||
|                   onPressed: () { |                   onPressed: () { | ||||||
|                     context.pushNamed( |                     context.pushNamed( | ||||||
|                       'developerAppNew', |                       'developerAppNew', | ||||||
|                 pathParameters: {'name': publisherName}, |                       pathParameters: { | ||||||
|  |                         'name': publisherName, | ||||||
|  |                         'projectId': projectId, | ||||||
|  |                       }, | ||||||
|                     ); |                     ); | ||||||
|                   }, |                   }, | ||||||
|  |                   icon: const Icon(Symbols.add), | ||||||
|  |                   label: Text('createCustomApp').tr(), | ||||||
|                 ), |                 ), | ||||||
|               ], |               ], | ||||||
|             ), |             ), | ||||||
|       body: apps.when( |           ); | ||||||
|         data: (data) { |  | ||||||
|           if (data.isEmpty) { |  | ||||||
|             return Center(child: Text('noCustomApps').tr()); |  | ||||||
|         } |         } | ||||||
|         return RefreshIndicator( |         return RefreshIndicator( | ||||||
|           onRefresh: |           onRefresh: | ||||||
|                 () => ref.refresh(customAppsProvider(publisherName).future), |               () => ref.refresh( | ||||||
|  |                 customAppsProvider(publisherName, projectId).future, | ||||||
|  |               ), | ||||||
|           child: ListView.builder( |           child: ListView.builder( | ||||||
|             padding: EdgeInsets.only(top: 4), |             padding: EdgeInsets.only(top: 4), | ||||||
|             itemCount: data.length, |             itemCount: data.length, | ||||||
| @@ -128,6 +149,7 @@ class CustomAppsScreen extends HookConsumerWidget { | |||||||
|                               'developerAppEdit', |                               'developerAppEdit', | ||||||
|                               pathParameters: { |                               pathParameters: { | ||||||
|                                 'name': publisherName, |                                 'name': publisherName, | ||||||
|  |                                 'projectId': projectId, | ||||||
|                                 'id': app.id, |                                 'id': app.id, | ||||||
|                               }, |                               }, | ||||||
|                             ); |                             ); | ||||||
| @@ -139,10 +161,10 @@ class CustomAppsScreen extends HookConsumerWidget { | |||||||
|                               if (confirm) { |                               if (confirm) { | ||||||
|                                 final client = ref.read(apiClientProvider); |                                 final client = ref.read(apiClientProvider); | ||||||
|                                 client.delete( |                                 client.delete( | ||||||
|                                     '/develop/developers/$publisherName/apps/${app.id}', |                                   '/develop/developers/$publisherName/projects/$projectId/apps/${app.id}', | ||||||
|                                 ); |                                 ); | ||||||
|                                 ref.invalidate( |                                 ref.invalidate( | ||||||
|                                     customAppsProvider(publisherName), |                                   customAppsProvider(publisherName, projectId), | ||||||
|                                 ); |                                 ); | ||||||
|                               } |                               } | ||||||
|                             }); |                             }); | ||||||
| @@ -161,7 +183,9 @@ class CustomAppsScreen extends HookConsumerWidget { | |||||||
|       error: |       error: | ||||||
|           (err, stack) => ResponseErrorWidget( |           (err, stack) => ResponseErrorWidget( | ||||||
|             error: err, |             error: err, | ||||||
|               onRetry: () => ref.invalidate(customAppsProvider(publisherName)), |             onRetry: | ||||||
|  |                 () => ref.invalidate( | ||||||
|  |                   customAppsProvider(publisherName, projectId), | ||||||
|                 ), |                 ), | ||||||
|           ), |           ), | ||||||
|     ); |     ); | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ part of 'apps.dart'; | |||||||
| // RiverpodGenerator | // RiverpodGenerator | ||||||
| // ************************************************************************** | // ************************************************************************** | ||||||
|  |  | ||||||
| String _$customAppsHash() => r'c6ac78060eb51a2b208a749a81ecbe0a9c608ce1'; | String _$customAppsHash() => r'c36e5ee59f16a29220dc0e9fba65e579d341a28f'; | ||||||
|  |  | ||||||
| /// Copied from Dart SDK | /// Copied from Dart SDK | ||||||
| class _SystemHash { | class _SystemHash { | ||||||
| @@ -39,15 +39,15 @@ class CustomAppsFamily extends Family<AsyncValue<List<CustomApp>>> { | |||||||
|   const CustomAppsFamily(); |   const CustomAppsFamily(); | ||||||
|  |  | ||||||
|   /// See also [customApps]. |   /// See also [customApps]. | ||||||
|   CustomAppsProvider call(String publisherName) { |   CustomAppsProvider call(String publisherName, String projectId) { | ||||||
|     return CustomAppsProvider(publisherName); |     return CustomAppsProvider(publisherName, projectId); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   CustomAppsProvider getProviderOverride( |   CustomAppsProvider getProviderOverride( | ||||||
|     covariant CustomAppsProvider provider, |     covariant CustomAppsProvider provider, | ||||||
|   ) { |   ) { | ||||||
|     return call(provider.publisherName); |     return call(provider.publisherName, provider.projectId); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   static const Iterable<ProviderOrFamily>? _dependencies = null; |   static const Iterable<ProviderOrFamily>? _dependencies = null; | ||||||
| @@ -68,9 +68,9 @@ class CustomAppsFamily extends Family<AsyncValue<List<CustomApp>>> { | |||||||
| /// See also [customApps]. | /// See also [customApps]. | ||||||
| class CustomAppsProvider extends AutoDisposeFutureProvider<List<CustomApp>> { | class CustomAppsProvider extends AutoDisposeFutureProvider<List<CustomApp>> { | ||||||
|   /// See also [customApps]. |   /// See also [customApps]. | ||||||
|   CustomAppsProvider(String publisherName) |   CustomAppsProvider(String publisherName, String projectId) | ||||||
|     : this._internal( |     : this._internal( | ||||||
|         (ref) => customApps(ref as CustomAppsRef, publisherName), |         (ref) => customApps(ref as CustomAppsRef, publisherName, projectId), | ||||||
|         from: customAppsProvider, |         from: customAppsProvider, | ||||||
|         name: r'customAppsProvider', |         name: r'customAppsProvider', | ||||||
|         debugGetCreateSourceHash: |         debugGetCreateSourceHash: | ||||||
| @@ -80,6 +80,7 @@ class CustomAppsProvider extends AutoDisposeFutureProvider<List<CustomApp>> { | |||||||
|         dependencies: CustomAppsFamily._dependencies, |         dependencies: CustomAppsFamily._dependencies, | ||||||
|         allTransitiveDependencies: CustomAppsFamily._allTransitiveDependencies, |         allTransitiveDependencies: CustomAppsFamily._allTransitiveDependencies, | ||||||
|         publisherName: publisherName, |         publisherName: publisherName, | ||||||
|  |         projectId: projectId, | ||||||
|       ); |       ); | ||||||
|  |  | ||||||
|   CustomAppsProvider._internal( |   CustomAppsProvider._internal( | ||||||
| @@ -90,9 +91,11 @@ class CustomAppsProvider extends AutoDisposeFutureProvider<List<CustomApp>> { | |||||||
|     required super.debugGetCreateSourceHash, |     required super.debugGetCreateSourceHash, | ||||||
|     required super.from, |     required super.from, | ||||||
|     required this.publisherName, |     required this.publisherName, | ||||||
|  |     required this.projectId, | ||||||
|   }) : super.internal(); |   }) : super.internal(); | ||||||
|  |  | ||||||
|   final String publisherName; |   final String publisherName; | ||||||
|  |   final String projectId; | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Override overrideWith( |   Override overrideWith( | ||||||
| @@ -108,6 +111,7 @@ class CustomAppsProvider extends AutoDisposeFutureProvider<List<CustomApp>> { | |||||||
|         allTransitiveDependencies: null, |         allTransitiveDependencies: null, | ||||||
|         debugGetCreateSourceHash: null, |         debugGetCreateSourceHash: null, | ||||||
|         publisherName: publisherName, |         publisherName: publisherName, | ||||||
|  |         projectId: projectId, | ||||||
|       ), |       ), | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| @@ -119,13 +123,16 @@ class CustomAppsProvider extends AutoDisposeFutureProvider<List<CustomApp>> { | |||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   bool operator ==(Object other) { |   bool operator ==(Object other) { | ||||||
|     return other is CustomAppsProvider && other.publisherName == publisherName; |     return other is CustomAppsProvider && | ||||||
|  |         other.publisherName == publisherName && | ||||||
|  |         other.projectId == projectId; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   int get hashCode { |   int get hashCode { | ||||||
|     var hash = _SystemHash.combine(0, runtimeType.hashCode); |     var hash = _SystemHash.combine(0, runtimeType.hashCode); | ||||||
|     hash = _SystemHash.combine(hash, publisherName.hashCode); |     hash = _SystemHash.combine(hash, publisherName.hashCode); | ||||||
|  |     hash = _SystemHash.combine(hash, projectId.hashCode); | ||||||
|  |  | ||||||
|     return _SystemHash.finish(hash); |     return _SystemHash.finish(hash); | ||||||
|   } |   } | ||||||
| @@ -136,6 +143,9 @@ class CustomAppsProvider extends AutoDisposeFutureProvider<List<CustomApp>> { | |||||||
| mixin CustomAppsRef on AutoDisposeFutureProviderRef<List<CustomApp>> { | mixin CustomAppsRef on AutoDisposeFutureProviderRef<List<CustomApp>> { | ||||||
|   /// The parameter `publisherName` of this provider. |   /// The parameter `publisherName` of this provider. | ||||||
|   String get publisherName; |   String get publisherName; | ||||||
|  |  | ||||||
|  |   /// The parameter `projectId` of this provider. | ||||||
|  |   String get projectId; | ||||||
| } | } | ||||||
|  |  | ||||||
| class _CustomAppsProviderElement | class _CustomAppsProviderElement | ||||||
| @@ -145,6 +155,8 @@ class _CustomAppsProviderElement | |||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   String get publisherName => (origin as CustomAppsProvider).publisherName; |   String get publisherName => (origin as CustomAppsProvider).publisherName; | ||||||
|  |   @override | ||||||
|  |   String get projectId => (origin as CustomAppsProvider).projectId; | ||||||
| } | } | ||||||
|  |  | ||||||
| // ignore_for_file: type=lint | // ignore_for_file: type=lint | ||||||
|   | |||||||
							
								
								
									
										162
									
								
								lib/screens/developers/bots.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										162
									
								
								lib/screens/developers/bots.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,162 @@ | |||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:easy_localization/easy_localization.dart'; | ||||||
|  | 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/widgets/alert.dart'; | ||||||
|  | import 'package:island/widgets/content/cloud_files.dart'; | ||||||
|  | import 'package:island/widgets/response.dart'; | ||||||
|  | import 'package:material_symbols_icons/symbols.dart'; | ||||||
|  | import 'package:riverpod_annotation/riverpod_annotation.dart'; | ||||||
|  |  | ||||||
|  | part 'bots.g.dart'; | ||||||
|  |  | ||||||
|  | @riverpod | ||||||
|  | Future<List<Bot>> bots(Ref ref, String publisherName, String projectId) async { | ||||||
|  |   final client = ref.watch(apiClientProvider); | ||||||
|  |   final resp = await client.get( | ||||||
|  |     '/develop/developers/$publisherName/projects/$projectId/bots', | ||||||
|  |   ); | ||||||
|  |   return (resp.data as List).map((e) => Bot.fromJson(e)).cast<Bot>().toList(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class BotsScreen extends HookConsumerWidget { | ||||||
|  |   final String publisherName; | ||||||
|  |   final String projectId; | ||||||
|  |   const BotsScreen({ | ||||||
|  |     super.key, | ||||||
|  |     required this.publisherName, | ||||||
|  |     required this.projectId, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     final botsList = ref.watch(botsProvider(publisherName, projectId)); | ||||||
|  |  | ||||||
|  |     return botsList.when( | ||||||
|  |       data: (data) { | ||||||
|  |         if (data.isEmpty) { | ||||||
|  |           return Center( | ||||||
|  |             child: Column( | ||||||
|  |               mainAxisAlignment: MainAxisAlignment.center, | ||||||
|  |               children: [ | ||||||
|  |                 Text('noBots').tr(), | ||||||
|  |                 const SizedBox(height: 16), | ||||||
|  |                 ElevatedButton.icon( | ||||||
|  |                   onPressed: () { | ||||||
|  |                     context.pushNamed( | ||||||
|  |                       'developerBotNew', | ||||||
|  |                       pathParameters: { | ||||||
|  |                         'name': publisherName, | ||||||
|  |                         'projectId': projectId, | ||||||
|  |                       }, | ||||||
|  |                     ); | ||||||
|  |                   }, | ||||||
|  |                   icon: const Icon(Symbols.add), | ||||||
|  |                   label: Text('createBot').tr(), | ||||||
|  |                 ), | ||||||
|  |               ], | ||||||
|  |             ), | ||||||
|  |           ); | ||||||
|  |         } | ||||||
|  |         return RefreshIndicator( | ||||||
|  |           onRefresh: | ||||||
|  |               () => ref.refresh(botsProvider(publisherName, projectId).future), | ||||||
|  |           child: ListView.builder( | ||||||
|  |             padding: const EdgeInsets.only(top: 4), | ||||||
|  |             itemCount: data.length, | ||||||
|  |             itemBuilder: (context, index) { | ||||||
|  |               final bot = data[index]; | ||||||
|  |               return Card( | ||||||
|  |                 margin: const EdgeInsets.all(8.0), | ||||||
|  |                 child: ListTile( | ||||||
|  |                   leading: CircleAvatar( | ||||||
|  |                     child: | ||||||
|  |                         bot.picture != null | ||||||
|  |                             ? CloudFileWidget(item: bot.picture!) | ||||||
|  |                             : const Icon(Symbols.smart_toy), | ||||||
|  |                   ), | ||||||
|  |                   title: Text(bot.name), | ||||||
|  |                   subtitle: Text(bot.description ?? ''), | ||||||
|  |                   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), | ||||||
|  |                             ); | ||||||
|  |                           } | ||||||
|  |                         }); | ||||||
|  |                       } | ||||||
|  |                     }, | ||||||
|  |                   ), | ||||||
|  |                   onTap: () { | ||||||
|  |                     context.pushNamed( | ||||||
|  |                       'developerBotDetail', | ||||||
|  |                       pathParameters: { | ||||||
|  |                         'name': publisherName, | ||||||
|  |                         'projectId': projectId, | ||||||
|  |                         'id': bot.id, | ||||||
|  |                       }, | ||||||
|  |                     ); | ||||||
|  |                   }, | ||||||
|  |                 ), | ||||||
|  |               ); | ||||||
|  |             }, | ||||||
|  |           ), | ||||||
|  |         ); | ||||||
|  |       }, | ||||||
|  |       loading: () => const Center(child: CircularProgressIndicator()), | ||||||
|  |       error: | ||||||
|  |           (err, stack) => ResponseErrorWidget( | ||||||
|  |             error: err, | ||||||
|  |             onRetry: | ||||||
|  |                 () => ref.invalidate(botsProvider(publisherName, projectId)), | ||||||
|  |           ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										156
									
								
								lib/screens/developers/bots.g.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										156
									
								
								lib/screens/developers/bots.g.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,156 @@ | |||||||
|  | // GENERATED CODE - DO NOT MODIFY BY HAND | ||||||
|  |  | ||||||
|  | part of 'bots.dart'; | ||||||
|  |  | ||||||
|  | // ************************************************************************** | ||||||
|  | // RiverpodGenerator | ||||||
|  | // ************************************************************************** | ||||||
|  |  | ||||||
|  | String _$botsHash() => r'a54c8b4df23f94754398706779044903fcca6eea'; | ||||||
|  |  | ||||||
|  | /// Copied from Dart SDK | ||||||
|  | class _SystemHash { | ||||||
|  |   _SystemHash._(); | ||||||
|  |  | ||||||
|  |   static int combine(int hash, int value) { | ||||||
|  |     // ignore: parameter_assignments | ||||||
|  |     hash = 0x1fffffff & (hash + value); | ||||||
|  |     // ignore: parameter_assignments | ||||||
|  |     hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); | ||||||
|  |     return hash ^ (hash >> 6); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   static int finish(int hash) { | ||||||
|  |     // ignore: parameter_assignments | ||||||
|  |     hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); | ||||||
|  |     // ignore: parameter_assignments | ||||||
|  |     hash = hash ^ (hash >> 11); | ||||||
|  |     return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// See also [bots]. | ||||||
|  | @ProviderFor(bots) | ||||||
|  | const botsProvider = BotsFamily(); | ||||||
|  |  | ||||||
|  | /// See also [bots]. | ||||||
|  | class BotsFamily extends Family<AsyncValue<List<Bot>>> { | ||||||
|  |   /// See also [bots]. | ||||||
|  |   const BotsFamily(); | ||||||
|  |  | ||||||
|  |   /// See also [bots]. | ||||||
|  |   BotsProvider call(String publisherName, String projectId) { | ||||||
|  |     return BotsProvider(publisherName, projectId); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   BotsProvider getProviderOverride(covariant BotsProvider provider) { | ||||||
|  |     return call(provider.publisherName, provider.projectId); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   static const Iterable<ProviderOrFamily>? _dependencies = null; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Iterable<ProviderOrFamily>? get dependencies => _dependencies; | ||||||
|  |  | ||||||
|  |   static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Iterable<ProviderOrFamily>? get allTransitiveDependencies => | ||||||
|  |       _allTransitiveDependencies; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String? get name => r'botsProvider'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// See also [bots]. | ||||||
|  | class BotsProvider extends AutoDisposeFutureProvider<List<Bot>> { | ||||||
|  |   /// See also [bots]. | ||||||
|  |   BotsProvider(String publisherName, String projectId) | ||||||
|  |     : this._internal( | ||||||
|  |         (ref) => bots(ref as BotsRef, publisherName, projectId), | ||||||
|  |         from: botsProvider, | ||||||
|  |         name: r'botsProvider', | ||||||
|  |         debugGetCreateSourceHash: | ||||||
|  |             const bool.fromEnvironment('dart.vm.product') ? null : _$botsHash, | ||||||
|  |         dependencies: BotsFamily._dependencies, | ||||||
|  |         allTransitiveDependencies: BotsFamily._allTransitiveDependencies, | ||||||
|  |         publisherName: publisherName, | ||||||
|  |         projectId: projectId, | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |   BotsProvider._internal( | ||||||
|  |     super._createNotifier, { | ||||||
|  |     required super.name, | ||||||
|  |     required super.dependencies, | ||||||
|  |     required super.allTransitiveDependencies, | ||||||
|  |     required super.debugGetCreateSourceHash, | ||||||
|  |     required super.from, | ||||||
|  |     required this.publisherName, | ||||||
|  |     required this.projectId, | ||||||
|  |   }) : super.internal(); | ||||||
|  |  | ||||||
|  |   final String publisherName; | ||||||
|  |   final String projectId; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Override overrideWith(FutureOr<List<Bot>> Function(BotsRef provider) create) { | ||||||
|  |     return ProviderOverride( | ||||||
|  |       origin: this, | ||||||
|  |       override: BotsProvider._internal( | ||||||
|  |         (ref) => create(ref as BotsRef), | ||||||
|  |         from: from, | ||||||
|  |         name: null, | ||||||
|  |         dependencies: null, | ||||||
|  |         allTransitiveDependencies: null, | ||||||
|  |         debugGetCreateSourceHash: null, | ||||||
|  |         publisherName: publisherName, | ||||||
|  |         projectId: projectId, | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   AutoDisposeFutureProviderElement<List<Bot>> createElement() { | ||||||
|  |     return _BotsProviderElement(this); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   bool operator ==(Object other) { | ||||||
|  |     return other is BotsProvider && | ||||||
|  |         other.publisherName == publisherName && | ||||||
|  |         other.projectId == projectId; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   int get hashCode { | ||||||
|  |     var hash = _SystemHash.combine(0, runtimeType.hashCode); | ||||||
|  |     hash = _SystemHash.combine(hash, publisherName.hashCode); | ||||||
|  |     hash = _SystemHash.combine(hash, projectId.hashCode); | ||||||
|  |  | ||||||
|  |     return _SystemHash.finish(hash); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @Deprecated('Will be removed in 3.0. Use Ref instead') | ||||||
|  | // ignore: unused_element | ||||||
|  | mixin BotsRef on AutoDisposeFutureProviderRef<List<Bot>> { | ||||||
|  |   /// The parameter `publisherName` of this provider. | ||||||
|  |   String get publisherName; | ||||||
|  |  | ||||||
|  |   /// The parameter `projectId` of this provider. | ||||||
|  |   String get projectId; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _BotsProviderElement extends AutoDisposeFutureProviderElement<List<Bot>> | ||||||
|  |     with BotsRef { | ||||||
|  |   _BotsProviderElement(super.provider); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String get publisherName => (origin as BotsProvider).publisherName; | ||||||
|  |   @override | ||||||
|  |   String get projectId => (origin as BotsProvider).projectId; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ignore_for_file: type=lint | ||||||
|  | // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package | ||||||
| @@ -22,21 +22,37 @@ import 'package:island/widgets/content/sheet.dart'; | |||||||
| part 'edit_app.g.dart'; | part 'edit_app.g.dart'; | ||||||
|  |  | ||||||
| @riverpod | @riverpod | ||||||
| Future<CustomApp?> customApp(Ref ref, String publisherName, String id) async { | Future<CustomApp?> customApp( | ||||||
|  |   Ref ref, | ||||||
|  |   String publisherName, | ||||||
|  |   String projectId, | ||||||
|  |   String id, | ||||||
|  | ) async { | ||||||
|   final client = ref.watch(apiClientProvider); |   final client = ref.watch(apiClientProvider); | ||||||
|   final resp = await client.get('/develop/developers/$publisherName/apps/$id'); |   final resp = await client.get( | ||||||
|  |     '/develop/developers/$publisherName/projects/$projectId/apps/$id', | ||||||
|  |   ); | ||||||
|   return CustomApp.fromJson(resp.data); |   return CustomApp.fromJson(resp.data); | ||||||
| } | } | ||||||
|  |  | ||||||
| class EditAppScreen extends HookConsumerWidget { | class EditAppScreen extends HookConsumerWidget { | ||||||
|   final String publisherName; |   final String publisherName; | ||||||
|  |   final String projectId; | ||||||
|   final String? id; |   final String? id; | ||||||
|   const EditAppScreen({super.key, required this.publisherName, this.id}); |   const EditAppScreen({ | ||||||
|  |     super.key, | ||||||
|  |     required this.publisherName, | ||||||
|  |     required this.projectId, | ||||||
|  |     this.id, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context, WidgetRef ref) { |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|     final isNew = id == null; |     final isNew = id == null; | ||||||
|     final app = isNew ? null : ref.watch(customAppProvider(publisherName, id!)); |     final app = | ||||||
|  |         isNew | ||||||
|  |             ? null | ||||||
|  |             : ref.watch(customAppProvider(publisherName, projectId, id!)); | ||||||
|  |  | ||||||
|     final formKey = useMemoized(() => GlobalKey<FormState>()); |     final formKey = useMemoized(() => GlobalKey<FormState>()); | ||||||
|  |  | ||||||
| @@ -283,16 +299,16 @@ class EditAppScreen extends HookConsumerWidget { | |||||||
|       }; |       }; | ||||||
|       if (isNew) { |       if (isNew) { | ||||||
|         await client.post( |         await client.post( | ||||||
|           '/develop/developers/$publisherName/apps', |           '/develop/developers/$publisherName/projects/$projectId/apps', | ||||||
|           data: data, |           data: data, | ||||||
|         ); |         ); | ||||||
|       } else { |       } else { | ||||||
|         await client.patch( |         await client.patch( | ||||||
|           '/develop/developers/$publisherName/apps/$id', |           '/develop/developers/$publisherName/projects/$projectId/apps/$id', | ||||||
|           data: data, |           data: data, | ||||||
|         ); |         ); | ||||||
|       } |       } | ||||||
|       ref.invalidate(customAppsProvider(publisherName)); |       ref.invalidate(customAppsProvider(publisherName, projectId)); | ||||||
|       if (context.mounted) { |       if (context.mounted) { | ||||||
|         Navigator.pop(context); |         Navigator.pop(context); | ||||||
|       } |       } | ||||||
| @@ -309,7 +325,9 @@ class EditAppScreen extends HookConsumerWidget { | |||||||
|               ? ResponseErrorWidget( |               ? ResponseErrorWidget( | ||||||
|                 error: app!.error, |                 error: app!.error, | ||||||
|                 onRetry: |                 onRetry: | ||||||
|                     () => ref.invalidate(customAppProvider(publisherName, id!)), |                     () => ref.invalidate( | ||||||
|  |                       customAppProvider(publisherName, projectId, id!), | ||||||
|  |                     ), | ||||||
|               ) |               ) | ||||||
|               : SingleChildScrollView( |               : SingleChildScrollView( | ||||||
|                 child: Column( |                 child: Column( | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ part of 'edit_app.dart'; | |||||||
| // RiverpodGenerator | // RiverpodGenerator | ||||||
| // ************************************************************************** | // ************************************************************************** | ||||||
|  |  | ||||||
| String _$customAppHash() => r'42ad937b8439c793e3c5c35568bb5fa4da017df3'; | String _$customAppHash() => r'17b3d1385e59bc5ee7f13fb0f11c56cf8a9ba41f'; | ||||||
|  |  | ||||||
| /// Copied from Dart SDK | /// Copied from Dart SDK | ||||||
| class _SystemHash { | class _SystemHash { | ||||||
| @@ -39,13 +39,13 @@ class CustomAppFamily extends Family<AsyncValue<CustomApp?>> { | |||||||
|   const CustomAppFamily(); |   const CustomAppFamily(); | ||||||
|  |  | ||||||
|   /// See also [customApp]. |   /// See also [customApp]. | ||||||
|   CustomAppProvider call(String publisherName, String id) { |   CustomAppProvider call(String publisherName, String projectId, String id) { | ||||||
|     return CustomAppProvider(publisherName, id); |     return CustomAppProvider(publisherName, projectId, id); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   CustomAppProvider getProviderOverride(covariant CustomAppProvider provider) { |   CustomAppProvider getProviderOverride(covariant CustomAppProvider provider) { | ||||||
|     return call(provider.publisherName, provider.id); |     return call(provider.publisherName, provider.projectId, provider.id); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   static const Iterable<ProviderOrFamily>? _dependencies = null; |   static const Iterable<ProviderOrFamily>? _dependencies = null; | ||||||
| @@ -66,9 +66,9 @@ class CustomAppFamily extends Family<AsyncValue<CustomApp?>> { | |||||||
| /// See also [customApp]. | /// See also [customApp]. | ||||||
| class CustomAppProvider extends AutoDisposeFutureProvider<CustomApp?> { | class CustomAppProvider extends AutoDisposeFutureProvider<CustomApp?> { | ||||||
|   /// See also [customApp]. |   /// See also [customApp]. | ||||||
|   CustomAppProvider(String publisherName, String id) |   CustomAppProvider(String publisherName, String projectId, String id) | ||||||
|     : this._internal( |     : this._internal( | ||||||
|         (ref) => customApp(ref as CustomAppRef, publisherName, id), |         (ref) => customApp(ref as CustomAppRef, publisherName, projectId, id), | ||||||
|         from: customAppProvider, |         from: customAppProvider, | ||||||
|         name: r'customAppProvider', |         name: r'customAppProvider', | ||||||
|         debugGetCreateSourceHash: |         debugGetCreateSourceHash: | ||||||
| @@ -78,6 +78,7 @@ class CustomAppProvider extends AutoDisposeFutureProvider<CustomApp?> { | |||||||
|         dependencies: CustomAppFamily._dependencies, |         dependencies: CustomAppFamily._dependencies, | ||||||
|         allTransitiveDependencies: CustomAppFamily._allTransitiveDependencies, |         allTransitiveDependencies: CustomAppFamily._allTransitiveDependencies, | ||||||
|         publisherName: publisherName, |         publisherName: publisherName, | ||||||
|  |         projectId: projectId, | ||||||
|         id: id, |         id: id, | ||||||
|       ); |       ); | ||||||
|  |  | ||||||
| @@ -89,10 +90,12 @@ class CustomAppProvider extends AutoDisposeFutureProvider<CustomApp?> { | |||||||
|     required super.debugGetCreateSourceHash, |     required super.debugGetCreateSourceHash, | ||||||
|     required super.from, |     required super.from, | ||||||
|     required this.publisherName, |     required this.publisherName, | ||||||
|  |     required this.projectId, | ||||||
|     required this.id, |     required this.id, | ||||||
|   }) : super.internal(); |   }) : super.internal(); | ||||||
|  |  | ||||||
|   final String publisherName; |   final String publisherName; | ||||||
|  |   final String projectId; | ||||||
|   final String id; |   final String id; | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
| @@ -109,6 +112,7 @@ class CustomAppProvider extends AutoDisposeFutureProvider<CustomApp?> { | |||||||
|         allTransitiveDependencies: null, |         allTransitiveDependencies: null, | ||||||
|         debugGetCreateSourceHash: null, |         debugGetCreateSourceHash: null, | ||||||
|         publisherName: publisherName, |         publisherName: publisherName, | ||||||
|  |         projectId: projectId, | ||||||
|         id: id, |         id: id, | ||||||
|       ), |       ), | ||||||
|     ); |     ); | ||||||
| @@ -123,6 +127,7 @@ class CustomAppProvider extends AutoDisposeFutureProvider<CustomApp?> { | |||||||
|   bool operator ==(Object other) { |   bool operator ==(Object other) { | ||||||
|     return other is CustomAppProvider && |     return other is CustomAppProvider && | ||||||
|         other.publisherName == publisherName && |         other.publisherName == publisherName && | ||||||
|  |         other.projectId == projectId && | ||||||
|         other.id == id; |         other.id == id; | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -130,6 +135,7 @@ class CustomAppProvider extends AutoDisposeFutureProvider<CustomApp?> { | |||||||
|   int get hashCode { |   int get hashCode { | ||||||
|     var hash = _SystemHash.combine(0, runtimeType.hashCode); |     var hash = _SystemHash.combine(0, runtimeType.hashCode); | ||||||
|     hash = _SystemHash.combine(hash, publisherName.hashCode); |     hash = _SystemHash.combine(hash, publisherName.hashCode); | ||||||
|  |     hash = _SystemHash.combine(hash, projectId.hashCode); | ||||||
|     hash = _SystemHash.combine(hash, id.hashCode); |     hash = _SystemHash.combine(hash, id.hashCode); | ||||||
|  |  | ||||||
|     return _SystemHash.finish(hash); |     return _SystemHash.finish(hash); | ||||||
| @@ -142,6 +148,9 @@ mixin CustomAppRef on AutoDisposeFutureProviderRef<CustomApp?> { | |||||||
|   /// The parameter `publisherName` of this provider. |   /// The parameter `publisherName` of this provider. | ||||||
|   String get publisherName; |   String get publisherName; | ||||||
|  |  | ||||||
|  |   /// The parameter `projectId` of this provider. | ||||||
|  |   String get projectId; | ||||||
|  |  | ||||||
|   /// The parameter `id` of this provider. |   /// The parameter `id` of this provider. | ||||||
|   String get id; |   String get id; | ||||||
| } | } | ||||||
| @@ -154,6 +163,8 @@ class _CustomAppProviderElement | |||||||
|   @override |   @override | ||||||
|   String get publisherName => (origin as CustomAppProvider).publisherName; |   String get publisherName => (origin as CustomAppProvider).publisherName; | ||||||
|   @override |   @override | ||||||
|  |   String get projectId => (origin as CustomAppProvider).projectId; | ||||||
|  |   @override | ||||||
|   String get id => (origin as CustomAppProvider).id; |   String get id => (origin as CustomAppProvider).id; | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										279
									
								
								lib/screens/developers/edit_bot.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										279
									
								
								lib/screens/developers/edit_bot.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,279 @@ | |||||||
|  | import 'package:croppy/croppy.dart' hide cropImage; | ||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:easy_localization/easy_localization.dart'; | ||||||
|  | import 'package:flutter_hooks/flutter_hooks.dart'; | ||||||
|  | import 'package:go_router/go_router.dart'; | ||||||
|  | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
|  | import 'package:image_picker/image_picker.dart'; | ||||||
|  | import 'package:island/models/bot.dart'; | ||||||
|  | import 'package:island/models/file.dart'; | ||||||
|  | import 'package:island/pods/config.dart'; | ||||||
|  | import 'package:island/pods/network.dart'; | ||||||
|  | import 'package:island/services/file.dart'; | ||||||
|  | import 'package:island/widgets/alert.dart'; | ||||||
|  | import 'package:island/widgets/app_scaffold.dart'; | ||||||
|  | import 'package:island/widgets/content/cloud_files.dart'; | ||||||
|  | import 'package:island/widgets/response.dart'; | ||||||
|  | import 'package:material_symbols_icons/symbols.dart'; | ||||||
|  | import 'package:riverpod_annotation/riverpod_annotation.dart'; | ||||||
|  | import 'package:styled_widget/styled_widget.dart'; | ||||||
|  |  | ||||||
|  | part 'edit_bot.g.dart'; | ||||||
|  |  | ||||||
|  | @riverpod | ||||||
|  | Future<Bot?> bot( | ||||||
|  |   Ref ref, | ||||||
|  |   String publisherName, | ||||||
|  |   String projectId, | ||||||
|  |   String id, | ||||||
|  | ) async { | ||||||
|  |   final client = ref.watch(apiClientProvider); | ||||||
|  |   final resp = await client.get( | ||||||
|  |     '/develop/developers/$publisherName/projects/$projectId/bots/$id', | ||||||
|  |   ); | ||||||
|  |   return Bot.fromJson(resp.data); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class EditBotScreen extends HookConsumerWidget { | ||||||
|  |   final String publisherName; | ||||||
|  |   final String projectId; | ||||||
|  |   final String? id; | ||||||
|  |   const EditBotScreen({ | ||||||
|  |     super.key, | ||||||
|  |     required this.publisherName, | ||||||
|  |     required this.projectId, | ||||||
|  |     this.id, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     final isNew = id == null; | ||||||
|  |     final botData = | ||||||
|  |         isNew ? null : ref.watch(botProvider(publisherName, projectId, id!)); | ||||||
|  |  | ||||||
|  |     final formKey = useMemoized(() => GlobalKey<FormState>()); | ||||||
|  |     final submitting = useState(false); | ||||||
|  |  | ||||||
|  |     final nameController = useTextEditingController(); | ||||||
|  |     final slugController = useTextEditingController(); | ||||||
|  |     final descriptionController = useTextEditingController(); | ||||||
|  |     final picture = useState<SnCloudFile?>(null); | ||||||
|  |     final websiteController = useTextEditingController(); | ||||||
|  |     final documentationController = useTextEditingController(); | ||||||
|  |  | ||||||
|  |     final isPublic = useState(false); | ||||||
|  |     final isInteractive = useState(false); | ||||||
|  |  | ||||||
|  |     useEffect(() { | ||||||
|  |       if (botData?.value != null) { | ||||||
|  |         nameController.text = botData!.value!.name; | ||||||
|  |         slugController.text = botData.value!.slug; | ||||||
|  |         descriptionController.text = botData.value!.description ?? ''; | ||||||
|  |         picture.value = botData.value!.picture; | ||||||
|  |         websiteController.text = botData.value!.links?.website ?? ''; | ||||||
|  |         documentationController.text = | ||||||
|  |             botData.value!.links?.documentation ?? ''; | ||||||
|  |         isPublic.value = botData.value!.config?.isPublic ?? false; | ||||||
|  |         isInteractive.value = botData.value!.config?.isInteractive ?? false; | ||||||
|  |       } | ||||||
|  |       return null; | ||||||
|  |     }, [botData]); | ||||||
|  |  | ||||||
|  |     void setPicture() async { | ||||||
|  |       showLoadingModal(context); | ||||||
|  |       var result = await ref | ||||||
|  |           .read(imagePickerProvider) | ||||||
|  |           .pickImage(source: ImageSource.gallery); | ||||||
|  |       if (result == null) { | ||||||
|  |         if (context.mounted) hideLoadingModal(context); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |       if (!context.mounted) return; | ||||||
|  |       hideLoadingModal(context); | ||||||
|  |  | ||||||
|  |       result = await cropImage( | ||||||
|  |         context, | ||||||
|  |         image: result, | ||||||
|  |         allowedAspectRatios: [const CropAspectRatio(height: 1, width: 1)], | ||||||
|  |       ); | ||||||
|  |       if (result == null) { | ||||||
|  |         if (context.mounted) hideLoadingModal(context); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |       if (!context.mounted) return; | ||||||
|  |       showLoadingModal(context); | ||||||
|  |  | ||||||
|  |       submitting.value = true; | ||||||
|  |       try { | ||||||
|  |         final baseUrl = ref.watch(serverUrlProvider); | ||||||
|  |         final token = await getToken(ref.watch(tokenProvider)); | ||||||
|  |         if (token == null) throw ArgumentError('Token is null'); | ||||||
|  |         final cloudFile = | ||||||
|  |             await putMediaToCloud( | ||||||
|  |               fileData: UniversalFile( | ||||||
|  |                 data: result, | ||||||
|  |                 type: UniversalFileType.image, | ||||||
|  |               ), | ||||||
|  |               atk: token, | ||||||
|  |               baseUrl: baseUrl, | ||||||
|  |               filename: result.name, | ||||||
|  |               mimetype: result.mimeType ?? 'image/jpeg', | ||||||
|  |             ).future; | ||||||
|  |         if (cloudFile == null) { | ||||||
|  |           throw ArgumentError('Failed to upload the file...'); | ||||||
|  |         } | ||||||
|  |         picture.value = cloudFile; | ||||||
|  |       } catch (err) { | ||||||
|  |         showErrorAlert(err); | ||||||
|  |       } finally { | ||||||
|  |         if (context.mounted) hideLoadingModal(context); | ||||||
|  |         submitting.value = false; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     void performAction() async { | ||||||
|  |       final client = ref.read(apiClientProvider); | ||||||
|  |       final data = { | ||||||
|  |         'name': nameController.text, | ||||||
|  |         'slug': slugController.text, | ||||||
|  |         'description': descriptionController.text, | ||||||
|  |         'picture_id': picture.value?.id, | ||||||
|  |         'config': { | ||||||
|  |           'is_public': isPublic.value, | ||||||
|  |           'is_interactive': isInteractive.value, | ||||||
|  |         }, | ||||||
|  |         'links': { | ||||||
|  |           'website': | ||||||
|  |               websiteController.text.isNotEmpty ? websiteController.text : null, | ||||||
|  |           'documentation': | ||||||
|  |               documentationController.text.isNotEmpty | ||||||
|  |                   ? documentationController.text | ||||||
|  |                   : null, | ||||||
|  |         }, | ||||||
|  |       }; | ||||||
|  |  | ||||||
|  |       if (isNew) { | ||||||
|  |         await client.post( | ||||||
|  |           '/develop/developers/$publisherName/projects/$projectId/bots', | ||||||
|  |           data: data, | ||||||
|  |         ); | ||||||
|  |       } else { | ||||||
|  |         await client.patch( | ||||||
|  |           '/develop/developers/$publisherName/projects/$projectId/bots/$id', | ||||||
|  |           data: data, | ||||||
|  |         ); | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       if (context.mounted) { | ||||||
|  |         context.pop(); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return AppScaffold( | ||||||
|  |       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: 1, | ||||||
|  |                       child: GestureDetector( | ||||||
|  |                         onTap: setPicture, | ||||||
|  |                         child: Container( | ||||||
|  |                           color: | ||||||
|  |                               Theme.of( | ||||||
|  |                                 context, | ||||||
|  |                               ).colorScheme.surfaceContainerHigh, | ||||||
|  |                           child: | ||||||
|  |                               picture.value != null | ||||||
|  |                                   ? CloudFileWidget( | ||||||
|  |                                     item: picture.value!, | ||||||
|  |                                     fit: BoxFit.cover, | ||||||
|  |                                   ) | ||||||
|  |                                   : const Icon(Symbols.smart_toy, size: 48), | ||||||
|  |                         ), | ||||||
|  |                       ), | ||||||
|  |                     ).padding(bottom: 32), | ||||||
|  |                     Form( | ||||||
|  |                       key: formKey, | ||||||
|  |                       child: Column( | ||||||
|  |                         children: [ | ||||||
|  |                           TextFormField( | ||||||
|  |                             controller: nameController, | ||||||
|  |                             decoration: InputDecoration(labelText: 'name'.tr()), | ||||||
|  |                           ), | ||||||
|  |                           const SizedBox(height: 16), | ||||||
|  |                           TextFormField( | ||||||
|  |                             controller: slugController, | ||||||
|  |                             decoration: InputDecoration( | ||||||
|  |                               labelText: 'slug'.tr(), | ||||||
|  |                               helperText: 'slugHint'.tr(), | ||||||
|  |                             ), | ||||||
|  |                           ), | ||||||
|  |                           const SizedBox(height: 16), | ||||||
|  |                           TextFormField( | ||||||
|  |                             controller: descriptionController, | ||||||
|  |                             decoration: InputDecoration( | ||||||
|  |                               labelText: 'description'.tr(), | ||||||
|  |                               alignLabelWithHint: true, | ||||||
|  |                             ), | ||||||
|  |                             maxLines: 3, | ||||||
|  |                           ), | ||||||
|  |                           const SizedBox(height: 16), | ||||||
|  |                           TextFormField( | ||||||
|  |                             controller: websiteController, | ||||||
|  |                             decoration: InputDecoration( | ||||||
|  |                               labelText: 'websiteUrl'.tr(), | ||||||
|  |                               hintText: 'https://example.com', | ||||||
|  |                             ), | ||||||
|  |                             keyboardType: TextInputType.url, | ||||||
|  |                           ), | ||||||
|  |                           const SizedBox(height: 16), | ||||||
|  |                           TextFormField( | ||||||
|  |                             controller: documentationController, | ||||||
|  |                             decoration: InputDecoration( | ||||||
|  |                               labelText: 'documentationUrl'.tr(), | ||||||
|  |                               hintText: 'https://example.com/docs', | ||||||
|  |                             ), | ||||||
|  |                             keyboardType: TextInputType.url, | ||||||
|  |                           ), | ||||||
|  |                           const SizedBox(height: 16), | ||||||
|  |                           SwitchListTile( | ||||||
|  |                             title: Text('isPublic').tr(), | ||||||
|  |                             value: isPublic.value, | ||||||
|  |                             onChanged: (value) => isPublic.value = value, | ||||||
|  |                           ), | ||||||
|  |                           SwitchListTile( | ||||||
|  |                             title: Text('isInteractive').tr(), | ||||||
|  |                             value: isInteractive.value, | ||||||
|  |                             onChanged: (value) => isInteractive.value = 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), | ||||||
|  |                     ), | ||||||
|  |                   ], | ||||||
|  |                 ), | ||||||
|  |               ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										167
									
								
								lib/screens/developers/edit_bot.g.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										167
									
								
								lib/screens/developers/edit_bot.g.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,167 @@ | |||||||
|  | // GENERATED CODE - DO NOT MODIFY BY HAND | ||||||
|  |  | ||||||
|  | part of 'edit_bot.dart'; | ||||||
|  |  | ||||||
|  | // ************************************************************************** | ||||||
|  | // RiverpodGenerator | ||||||
|  | // ************************************************************************** | ||||||
|  |  | ||||||
|  | String _$botHash() => r'a3e412ed575c513434bc718b7920db1d017111f4'; | ||||||
|  |  | ||||||
|  | /// Copied from Dart SDK | ||||||
|  | class _SystemHash { | ||||||
|  |   _SystemHash._(); | ||||||
|  |  | ||||||
|  |   static int combine(int hash, int value) { | ||||||
|  |     // ignore: parameter_assignments | ||||||
|  |     hash = 0x1fffffff & (hash + value); | ||||||
|  |     // ignore: parameter_assignments | ||||||
|  |     hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); | ||||||
|  |     return hash ^ (hash >> 6); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   static int finish(int hash) { | ||||||
|  |     // ignore: parameter_assignments | ||||||
|  |     hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); | ||||||
|  |     // ignore: parameter_assignments | ||||||
|  |     hash = hash ^ (hash >> 11); | ||||||
|  |     return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// See also [bot]. | ||||||
|  | @ProviderFor(bot) | ||||||
|  | const botProvider = BotFamily(); | ||||||
|  |  | ||||||
|  | /// See also [bot]. | ||||||
|  | class BotFamily extends Family<AsyncValue<Bot?>> { | ||||||
|  |   /// See also [bot]. | ||||||
|  |   const BotFamily(); | ||||||
|  |  | ||||||
|  |   /// See also [bot]. | ||||||
|  |   BotProvider call(String publisherName, String projectId, String id) { | ||||||
|  |     return BotProvider(publisherName, projectId, id); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   BotProvider getProviderOverride(covariant BotProvider provider) { | ||||||
|  |     return call(provider.publisherName, provider.projectId, provider.id); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   static const Iterable<ProviderOrFamily>? _dependencies = null; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Iterable<ProviderOrFamily>? get dependencies => _dependencies; | ||||||
|  |  | ||||||
|  |   static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Iterable<ProviderOrFamily>? get allTransitiveDependencies => | ||||||
|  |       _allTransitiveDependencies; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String? get name => r'botProvider'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// See also [bot]. | ||||||
|  | class BotProvider extends AutoDisposeFutureProvider<Bot?> { | ||||||
|  |   /// See also [bot]. | ||||||
|  |   BotProvider(String publisherName, String projectId, String id) | ||||||
|  |     : this._internal( | ||||||
|  |         (ref) => bot(ref as BotRef, publisherName, projectId, id), | ||||||
|  |         from: botProvider, | ||||||
|  |         name: r'botProvider', | ||||||
|  |         debugGetCreateSourceHash: | ||||||
|  |             const bool.fromEnvironment('dart.vm.product') ? null : _$botHash, | ||||||
|  |         dependencies: BotFamily._dependencies, | ||||||
|  |         allTransitiveDependencies: BotFamily._allTransitiveDependencies, | ||||||
|  |         publisherName: publisherName, | ||||||
|  |         projectId: projectId, | ||||||
|  |         id: id, | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |   BotProvider._internal( | ||||||
|  |     super._createNotifier, { | ||||||
|  |     required super.name, | ||||||
|  |     required super.dependencies, | ||||||
|  |     required super.allTransitiveDependencies, | ||||||
|  |     required super.debugGetCreateSourceHash, | ||||||
|  |     required super.from, | ||||||
|  |     required this.publisherName, | ||||||
|  |     required this.projectId, | ||||||
|  |     required this.id, | ||||||
|  |   }) : super.internal(); | ||||||
|  |  | ||||||
|  |   final String publisherName; | ||||||
|  |   final String projectId; | ||||||
|  |   final String id; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Override overrideWith(FutureOr<Bot?> Function(BotRef provider) create) { | ||||||
|  |     return ProviderOverride( | ||||||
|  |       origin: this, | ||||||
|  |       override: BotProvider._internal( | ||||||
|  |         (ref) => create(ref as BotRef), | ||||||
|  |         from: from, | ||||||
|  |         name: null, | ||||||
|  |         dependencies: null, | ||||||
|  |         allTransitiveDependencies: null, | ||||||
|  |         debugGetCreateSourceHash: null, | ||||||
|  |         publisherName: publisherName, | ||||||
|  |         projectId: projectId, | ||||||
|  |         id: id, | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   AutoDisposeFutureProviderElement<Bot?> createElement() { | ||||||
|  |     return _BotProviderElement(this); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   bool operator ==(Object other) { | ||||||
|  |     return other is BotProvider && | ||||||
|  |         other.publisherName == publisherName && | ||||||
|  |         other.projectId == projectId && | ||||||
|  |         other.id == id; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   int get hashCode { | ||||||
|  |     var hash = _SystemHash.combine(0, runtimeType.hashCode); | ||||||
|  |     hash = _SystemHash.combine(hash, publisherName.hashCode); | ||||||
|  |     hash = _SystemHash.combine(hash, projectId.hashCode); | ||||||
|  |     hash = _SystemHash.combine(hash, id.hashCode); | ||||||
|  |  | ||||||
|  |     return _SystemHash.finish(hash); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @Deprecated('Will be removed in 3.0. Use Ref instead') | ||||||
|  | // ignore: unused_element | ||||||
|  | mixin BotRef on AutoDisposeFutureProviderRef<Bot?> { | ||||||
|  |   /// The parameter `publisherName` of this provider. | ||||||
|  |   String get publisherName; | ||||||
|  |  | ||||||
|  |   /// The parameter `projectId` of this provider. | ||||||
|  |   String get projectId; | ||||||
|  |  | ||||||
|  |   /// The parameter `id` of this provider. | ||||||
|  |   String get id; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _BotProviderElement extends AutoDisposeFutureProviderElement<Bot?> | ||||||
|  |     with BotRef { | ||||||
|  |   _BotProviderElement(super.provider); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String get publisherName => (origin as BotProvider).publisherName; | ||||||
|  |   @override | ||||||
|  |   String get projectId => (origin as BotProvider).projectId; | ||||||
|  |   @override | ||||||
|  |   String get id => (origin as BotProvider).id; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ignore_for_file: type=lint | ||||||
|  | // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package | ||||||
							
								
								
									
										130
									
								
								lib/screens/developers/edit_project.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								lib/screens/developers/edit_project.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,130 @@ | |||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:easy_localization/easy_localization.dart'; | ||||||
|  | import 'package:flutter_hooks/flutter_hooks.dart'; | ||||||
|  | import 'package:go_router/go_router.dart'; | ||||||
|  | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
|  | import 'package:island/models/dev_project.dart'; | ||||||
|  | import 'package:island/pods/network.dart'; | ||||||
|  | import 'package:island/screens/developers/projects.dart'; | ||||||
|  | import 'package:island/widgets/app_scaffold.dart'; | ||||||
|  | import 'package:island/widgets/response.dart'; | ||||||
|  | import 'package:material_symbols_icons/symbols.dart'; | ||||||
|  | import 'package:riverpod_annotation/riverpod_annotation.dart'; | ||||||
|  | import 'package:styled_widget/styled_widget.dart'; | ||||||
|  |  | ||||||
|  | part 'edit_project.g.dart'; | ||||||
|  |  | ||||||
|  | @riverpod | ||||||
|  | Future<DevProject?> devProject(Ref ref, String pubName, String id) async { | ||||||
|  |   final client = ref.watch(apiClientProvider); | ||||||
|  |   final resp = await client.get('/develop/developers/$pubName/projects/$id'); | ||||||
|  |   return DevProject.fromJson(resp.data); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class EditProjectScreen extends HookConsumerWidget { | ||||||
|  |   final String publisherName; | ||||||
|  |   final String? id; | ||||||
|  |   const EditProjectScreen({super.key, required this.publisherName, this.id}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     final isNew = id == null; | ||||||
|  |     final projectData = | ||||||
|  |         isNew ? null : ref.watch(devProjectProvider(publisherName, id!)); | ||||||
|  |  | ||||||
|  |     final formKey = useMemoized(() => GlobalKey<FormState>()); | ||||||
|  |     final submitting = useState(false); | ||||||
|  |  | ||||||
|  |     final nameController = useTextEditingController(); | ||||||
|  |     final slugController = useTextEditingController(); | ||||||
|  |     final descriptionController = useTextEditingController(); | ||||||
|  |  | ||||||
|  |     useEffect(() { | ||||||
|  |       if (projectData?.value != null) { | ||||||
|  |         nameController.text = projectData!.value!.name; | ||||||
|  |         slugController.text = projectData.value!.slug; | ||||||
|  |         descriptionController.text = projectData.value!.description ?? ''; | ||||||
|  |       } | ||||||
|  |       return null; | ||||||
|  |     }, [projectData]); | ||||||
|  |  | ||||||
|  |     void performAction() async { | ||||||
|  |       final client = ref.read(apiClientProvider); | ||||||
|  |       final data = { | ||||||
|  |         'name': nameController.text, | ||||||
|  |         'slug': slugController.text, | ||||||
|  |         'description': descriptionController.text, | ||||||
|  |       }; | ||||||
|  |       if (isNew) { | ||||||
|  |         await client.post( | ||||||
|  |           '/develop/developers/$publisherName/projects', | ||||||
|  |           data: data, | ||||||
|  |         ); | ||||||
|  |       } else { | ||||||
|  |         await client.put( | ||||||
|  |           '/develop/developers/$publisherName/projects/$id', | ||||||
|  |           data: data, | ||||||
|  |         ); | ||||||
|  |       } | ||||||
|  |       ref.invalidate(devProjectsProvider(publisherName)); | ||||||
|  |       if (context.mounted) { | ||||||
|  |         context.pop(); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return AppScaffold( | ||||||
|  |       appBar: AppBar( | ||||||
|  |         title: Text(isNew ? 'createProject'.tr() : 'editProject'.tr()), | ||||||
|  |       ), | ||||||
|  |       body: | ||||||
|  |           projectData == null && !isNew | ||||||
|  |               ? const Center(child: CircularProgressIndicator()) | ||||||
|  |               : projectData?.hasError == true && !isNew | ||||||
|  |               ? ResponseErrorWidget( | ||||||
|  |                 error: projectData!.error, | ||||||
|  |                 onRetry: | ||||||
|  |                     () => | ||||||
|  |                         ref.invalidate(devProjectProvider(publisherName, id!)), | ||||||
|  |               ) | ||||||
|  |               : SingleChildScrollView( | ||||||
|  |                 child: Form( | ||||||
|  |                   key: formKey, | ||||||
|  |                   child: Column( | ||||||
|  |                     children: [ | ||||||
|  |                       TextFormField( | ||||||
|  |                         controller: nameController, | ||||||
|  |                         decoration: InputDecoration(labelText: 'name'.tr()), | ||||||
|  |                       ), | ||||||
|  |                       const SizedBox(height: 16), | ||||||
|  |                       TextFormField( | ||||||
|  |                         controller: slugController, | ||||||
|  |                         decoration: InputDecoration( | ||||||
|  |                           labelText: 'slug'.tr(), | ||||||
|  |                           helperText: 'slugHint'.tr(), | ||||||
|  |                         ), | ||||||
|  |                       ), | ||||||
|  |                       const SizedBox(height: 16), | ||||||
|  |                       TextFormField( | ||||||
|  |                         controller: descriptionController, | ||||||
|  |                         decoration: InputDecoration( | ||||||
|  |                           labelText: 'description'.tr(), | ||||||
|  |                           alignLabelWithHint: true, | ||||||
|  |                         ), | ||||||
|  |                         maxLines: 3, | ||||||
|  |                       ), | ||||||
|  |                       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), | ||||||
|  |                 ), | ||||||
|  |               ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										163
									
								
								lib/screens/developers/edit_project.g.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										163
									
								
								lib/screens/developers/edit_project.g.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,163 @@ | |||||||
|  | // GENERATED CODE - DO NOT MODIFY BY HAND | ||||||
|  |  | ||||||
|  | part of 'edit_project.dart'; | ||||||
|  |  | ||||||
|  | // ************************************************************************** | ||||||
|  | // RiverpodGenerator | ||||||
|  | // ************************************************************************** | ||||||
|  |  | ||||||
|  | String _$devProjectHash() => r'fc68254c6e598e3fa05c86c36f1469c0b689bc43'; | ||||||
|  |  | ||||||
|  | /// Copied from Dart SDK | ||||||
|  | class _SystemHash { | ||||||
|  |   _SystemHash._(); | ||||||
|  |  | ||||||
|  |   static int combine(int hash, int value) { | ||||||
|  |     // ignore: parameter_assignments | ||||||
|  |     hash = 0x1fffffff & (hash + value); | ||||||
|  |     // ignore: parameter_assignments | ||||||
|  |     hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); | ||||||
|  |     return hash ^ (hash >> 6); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   static int finish(int hash) { | ||||||
|  |     // ignore: parameter_assignments | ||||||
|  |     hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); | ||||||
|  |     // ignore: parameter_assignments | ||||||
|  |     hash = hash ^ (hash >> 11); | ||||||
|  |     return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// See also [devProject]. | ||||||
|  | @ProviderFor(devProject) | ||||||
|  | const devProjectProvider = DevProjectFamily(); | ||||||
|  |  | ||||||
|  | /// See also [devProject]. | ||||||
|  | class DevProjectFamily extends Family<AsyncValue<DevProject?>> { | ||||||
|  |   /// See also [devProject]. | ||||||
|  |   const DevProjectFamily(); | ||||||
|  |  | ||||||
|  |   /// See also [devProject]. | ||||||
|  |   DevProjectProvider call(String pubName, String id) { | ||||||
|  |     return DevProjectProvider(pubName, id); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   DevProjectProvider getProviderOverride( | ||||||
|  |     covariant DevProjectProvider provider, | ||||||
|  |   ) { | ||||||
|  |     return call(provider.pubName, provider.id); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   static const Iterable<ProviderOrFamily>? _dependencies = null; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Iterable<ProviderOrFamily>? get dependencies => _dependencies; | ||||||
|  |  | ||||||
|  |   static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Iterable<ProviderOrFamily>? get allTransitiveDependencies => | ||||||
|  |       _allTransitiveDependencies; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String? get name => r'devProjectProvider'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// See also [devProject]. | ||||||
|  | class DevProjectProvider extends AutoDisposeFutureProvider<DevProject?> { | ||||||
|  |   /// See also [devProject]. | ||||||
|  |   DevProjectProvider(String pubName, String id) | ||||||
|  |     : this._internal( | ||||||
|  |         (ref) => devProject(ref as DevProjectRef, pubName, id), | ||||||
|  |         from: devProjectProvider, | ||||||
|  |         name: r'devProjectProvider', | ||||||
|  |         debugGetCreateSourceHash: | ||||||
|  |             const bool.fromEnvironment('dart.vm.product') | ||||||
|  |                 ? null | ||||||
|  |                 : _$devProjectHash, | ||||||
|  |         dependencies: DevProjectFamily._dependencies, | ||||||
|  |         allTransitiveDependencies: DevProjectFamily._allTransitiveDependencies, | ||||||
|  |         pubName: pubName, | ||||||
|  |         id: id, | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |   DevProjectProvider._internal( | ||||||
|  |     super._createNotifier, { | ||||||
|  |     required super.name, | ||||||
|  |     required super.dependencies, | ||||||
|  |     required super.allTransitiveDependencies, | ||||||
|  |     required super.debugGetCreateSourceHash, | ||||||
|  |     required super.from, | ||||||
|  |     required this.pubName, | ||||||
|  |     required this.id, | ||||||
|  |   }) : super.internal(); | ||||||
|  |  | ||||||
|  |   final String pubName; | ||||||
|  |   final String id; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Override overrideWith( | ||||||
|  |     FutureOr<DevProject?> Function(DevProjectRef provider) create, | ||||||
|  |   ) { | ||||||
|  |     return ProviderOverride( | ||||||
|  |       origin: this, | ||||||
|  |       override: DevProjectProvider._internal( | ||||||
|  |         (ref) => create(ref as DevProjectRef), | ||||||
|  |         from: from, | ||||||
|  |         name: null, | ||||||
|  |         dependencies: null, | ||||||
|  |         allTransitiveDependencies: null, | ||||||
|  |         debugGetCreateSourceHash: null, | ||||||
|  |         pubName: pubName, | ||||||
|  |         id: id, | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   AutoDisposeFutureProviderElement<DevProject?> createElement() { | ||||||
|  |     return _DevProjectProviderElement(this); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   bool operator ==(Object other) { | ||||||
|  |     return other is DevProjectProvider && | ||||||
|  |         other.pubName == pubName && | ||||||
|  |         other.id == id; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   int get hashCode { | ||||||
|  |     var hash = _SystemHash.combine(0, runtimeType.hashCode); | ||||||
|  |     hash = _SystemHash.combine(hash, pubName.hashCode); | ||||||
|  |     hash = _SystemHash.combine(hash, id.hashCode); | ||||||
|  |  | ||||||
|  |     return _SystemHash.finish(hash); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @Deprecated('Will be removed in 3.0. Use Ref instead') | ||||||
|  | // ignore: unused_element | ||||||
|  | mixin DevProjectRef on AutoDisposeFutureProviderRef<DevProject?> { | ||||||
|  |   /// The parameter `pubName` of this provider. | ||||||
|  |   String get pubName; | ||||||
|  |  | ||||||
|  |   /// The parameter `id` of this provider. | ||||||
|  |   String get id; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _DevProjectProviderElement | ||||||
|  |     extends AutoDisposeFutureProviderElement<DevProject?> | ||||||
|  |     with DevProjectRef { | ||||||
|  |   _DevProjectProviderElement(super.provider); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String get pubName => (origin as DevProjectProvider).pubName; | ||||||
|  |   @override | ||||||
|  |   String get id => (origin as DevProjectProvider).id; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ignore_for_file: type=lint | ||||||
|  | // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package | ||||||
| @@ -235,15 +235,15 @@ class DeveloperHubScreen extends HookConsumerWidget { | |||||||
|                             ).padding(vertical: 12, horizontal: 12), |                             ).padding(vertical: 12, horizontal: 12), | ||||||
|                           ListTile( |                           ListTile( | ||||||
|                             minTileHeight: 48, |                             minTileHeight: 48, | ||||||
|                             title: Text('customApps').tr(), |                             title: Text('projects').tr(), | ||||||
|                             trailing: Icon(Symbols.chevron_right), |                             trailing: const Icon(Symbols.chevron_right), | ||||||
|                             leading: const Icon(Symbols.apps), |                             leading: const Icon(Symbols.folder_managed), | ||||||
|                             contentPadding: EdgeInsets.symmetric( |                             contentPadding: const EdgeInsets.symmetric( | ||||||
|                               horizontal: 24, |                               horizontal: 24, | ||||||
|                             ), |                             ), | ||||||
|                             onTap: () { |                             onTap: () { | ||||||
|                               context.pushNamed( |                               context.pushNamed( | ||||||
|                                 'developerApps', |                                 'developerProjects', | ||||||
|                                 pathParameters: { |                                 pathParameters: { | ||||||
|                                   'name': |                                   'name': | ||||||
|                                       currentDeveloper.value!.publisher!.name, |                                       currentDeveloper.value!.publisher!.name, | ||||||
|   | |||||||
| @@ -3,10 +3,11 @@ import 'package:island/screens/developers/edit_app.dart'; | |||||||
|  |  | ||||||
| class NewCustomAppScreen extends StatelessWidget { | class NewCustomAppScreen extends StatelessWidget { | ||||||
|   final String publisherName; |   final String publisherName; | ||||||
|   const NewCustomAppScreen({super.key, required this.publisherName}); |   final String projectId; | ||||||
|  |   const NewCustomAppScreen({super.key, required this.publisherName, required this.projectId}); | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     return EditAppScreen(publisherName: publisherName); |     return EditAppScreen(publisherName: publisherName, projectId: projectId); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										13
									
								
								lib/screens/developers/new_project.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								lib/screens/developers/new_project.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | |||||||
|  |  | ||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:island/screens/developers/edit_project.dart'; | ||||||
|  |  | ||||||
|  | class NewProjectScreen extends StatelessWidget { | ||||||
|  |   final String publisherName; | ||||||
|  |   const NewProjectScreen({super.key, required this.publisherName}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) { | ||||||
|  |     return EditProjectScreen(publisherName: publisherName); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										71
									
								
								lib/screens/developers/project_detail.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								lib/screens/developers/project_detail.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,71 @@ | |||||||
|  |  | ||||||
|  | import 'package:easy_localization/easy_localization.dart'; | ||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:go_router/go_router.dart'; | ||||||
|  | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
|  | import 'package:island/screens/developers/apps.dart'; | ||||||
|  | import 'package:island/screens/developers/bots.dart'; | ||||||
|  | import 'package:island/widgets/app_scaffold.dart'; | ||||||
|  | import 'package:material_symbols_icons/symbols.dart'; | ||||||
|  |  | ||||||
|  | class ProjectDetailScreen extends HookConsumerWidget { | ||||||
|  |   final String publisherName; | ||||||
|  |   final String projectId; | ||||||
|  |  | ||||||
|  |   const ProjectDetailScreen({ | ||||||
|  |     super.key, | ||||||
|  |     required this.publisherName, | ||||||
|  |     required this.projectId, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     return DefaultTabController( | ||||||
|  |       length: 2, | ||||||
|  |       child: AppScaffold( | ||||||
|  |         appBar: AppBar( | ||||||
|  |           title: Text('projectDetails').tr(), | ||||||
|  |           actions: [ | ||||||
|  |             IconButton( | ||||||
|  |               icon: const Icon(Symbols.add), | ||||||
|  |               onPressed: () { | ||||||
|  |                 // Get current tab index | ||||||
|  |                 final tabController = DefaultTabController.of(context); | ||||||
|  |                 final index = tabController.index; | ||||||
|  |                 if (index == 0) { | ||||||
|  |                   context.pushNamed( | ||||||
|  |                     'developerAppNew', | ||||||
|  |                     pathParameters: { | ||||||
|  |                       'name': publisherName, | ||||||
|  |                       'projectId': projectId | ||||||
|  |                     }, | ||||||
|  |                   ); | ||||||
|  |                 } else { | ||||||
|  |                   context.pushNamed( | ||||||
|  |                     'developerBotNew', | ||||||
|  |                     pathParameters: { | ||||||
|  |                       'name': publisherName, | ||||||
|  |                       'projectId': projectId | ||||||
|  |                     }, | ||||||
|  |                   ); | ||||||
|  |                 } | ||||||
|  |               }, | ||||||
|  |             ), | ||||||
|  |           ], | ||||||
|  |           bottom: TabBar( | ||||||
|  |             tabs: [ | ||||||
|  |               Tab(text: 'customApps'.tr()), | ||||||
|  |               Tab(text: 'bots'.tr()), | ||||||
|  |             ], | ||||||
|  |           ), | ||||||
|  |         ), | ||||||
|  |         body: TabBarView( | ||||||
|  |           children: [ | ||||||
|  |             CustomAppsScreen(publisherName: publisherName, projectId: projectId), | ||||||
|  |             BotsScreen(publisherName: publisherName, projectId: projectId), | ||||||
|  |           ], | ||||||
|  |         ), | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										144
									
								
								lib/screens/developers/projects.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								lib/screens/developers/projects.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,144 @@ | |||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:easy_localization/easy_localization.dart'; | ||||||
|  | import 'package:go_router/go_router.dart'; | ||||||
|  | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
|  | import 'package:island/models/dev_project.dart'; | ||||||
|  | import 'package:island/pods/network.dart'; | ||||||
|  | import 'package:island/widgets/alert.dart'; | ||||||
|  | import 'package:island/widgets/app_scaffold.dart'; | ||||||
|  | import 'package:island/widgets/response.dart'; | ||||||
|  | import 'package:material_symbols_icons/symbols.dart'; | ||||||
|  | import 'package:riverpod_annotation/riverpod_annotation.dart'; | ||||||
|  |  | ||||||
|  | part 'projects.g.dart'; | ||||||
|  |  | ||||||
|  | @riverpod | ||||||
|  | Future<List<DevProject>> devProjects(Ref ref, String pubName) async { | ||||||
|  |   final client = ref.watch(apiClientProvider); | ||||||
|  |   final resp = await client.get('/develop/developers/$pubName/projects'); | ||||||
|  |   return (resp.data as List) | ||||||
|  |       .map((e) => DevProject.fromJson(e)) | ||||||
|  |       .cast<DevProject>() | ||||||
|  |       .toList(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class DevProjectsScreen extends HookConsumerWidget { | ||||||
|  |   final String publisherName; | ||||||
|  |   const DevProjectsScreen({super.key, required this.publisherName}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context, WidgetRef ref) { | ||||||
|  |     final projects = ref.watch(devProjectsProvider(publisherName)); | ||||||
|  |  | ||||||
|  |     return AppScaffold( | ||||||
|  |       appBar: AppBar( | ||||||
|  |         title: Text('projects').tr(), | ||||||
|  |         actions: [ | ||||||
|  |           IconButton( | ||||||
|  |             icon: const Icon(Symbols.add), | ||||||
|  |             onPressed: () { | ||||||
|  |               context.pushNamed( | ||||||
|  |                 'developerProjectNew', | ||||||
|  |                 pathParameters: {'name': publisherName}, | ||||||
|  |               ); | ||||||
|  |             }, | ||||||
|  |           ), | ||||||
|  |         ], | ||||||
|  |       ), | ||||||
|  |       body: projects.when( | ||||||
|  |         data: (data) { | ||||||
|  |           if (data.isEmpty) { | ||||||
|  |             return Center(child: Text('noProjects').tr()); | ||||||
|  |           } | ||||||
|  |           return RefreshIndicator( | ||||||
|  |             onRefresh: | ||||||
|  |                 () => ref.refresh(devProjectsProvider(publisherName).future), | ||||||
|  |             child: ListView.builder( | ||||||
|  |               padding: EdgeInsets.only(top: 4), | ||||||
|  |               itemCount: data.length, | ||||||
|  |               itemBuilder: (context, index) { | ||||||
|  |                 final project = data[index]; | ||||||
|  |                 return Card( | ||||||
|  |                   margin: const EdgeInsets.all(8.0), | ||||||
|  |                   child: ListTile( | ||||||
|  |                     title: Text(project.name), | ||||||
|  |                     subtitle: Text(project.description ?? ''), | ||||||
|  |                     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( | ||||||
|  |                             'developerProjectEdit', | ||||||
|  |                             pathParameters: { | ||||||
|  |                               'name': publisherName, | ||||||
|  |                               'id': project.id, | ||||||
|  |                             }, | ||||||
|  |                           ); | ||||||
|  |                         } else if (value == 'delete') { | ||||||
|  |                           showConfirmAlert( | ||||||
|  |                             'deleteProjectHint'.tr(), | ||||||
|  |                             'deleteProject'.tr(), | ||||||
|  |                           ).then((confirm) { | ||||||
|  |                             if (confirm) { | ||||||
|  |                               final client = ref.read(apiClientProvider); | ||||||
|  |                               client.delete( | ||||||
|  |                                 '/develop/developers/$publisherName/projects/${project.id}', | ||||||
|  |                               ); | ||||||
|  |                               ref.invalidate( | ||||||
|  |                                 devProjectsProvider(publisherName), | ||||||
|  |                               ); | ||||||
|  |                             } | ||||||
|  |                           }); | ||||||
|  |                         } | ||||||
|  |                       }, | ||||||
|  |                     ), | ||||||
|  |                     onTap: () { | ||||||
|  |                       context.pushNamed( | ||||||
|  |                         'developerProjectDetail', | ||||||
|  |                         pathParameters: { | ||||||
|  |                           'name': publisherName, | ||||||
|  |                           'projectId': project.id, | ||||||
|  |                         }, | ||||||
|  |                       ); | ||||||
|  |                     }, | ||||||
|  |                   ), | ||||||
|  |                 ); | ||||||
|  |               }, | ||||||
|  |             ), | ||||||
|  |           ); | ||||||
|  |         }, | ||||||
|  |         loading: () => const Center(child: CircularProgressIndicator()), | ||||||
|  |         error: | ||||||
|  |             (err, stack) => ResponseErrorWidget( | ||||||
|  |               error: err, | ||||||
|  |               onRetry: () => ref.invalidate(devProjectsProvider(publisherName)), | ||||||
|  |             ), | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										151
									
								
								lib/screens/developers/projects.g.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								lib/screens/developers/projects.g.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,151 @@ | |||||||
|  | // GENERATED CODE - DO NOT MODIFY BY HAND | ||||||
|  |  | ||||||
|  | part of 'projects.dart'; | ||||||
|  |  | ||||||
|  | // ************************************************************************** | ||||||
|  | // RiverpodGenerator | ||||||
|  | // ************************************************************************** | ||||||
|  |  | ||||||
|  | String _$devProjectsHash() => r'4c86ea5c3c02185514dbfa32804f1529f68d56c7'; | ||||||
|  |  | ||||||
|  | /// Copied from Dart SDK | ||||||
|  | class _SystemHash { | ||||||
|  |   _SystemHash._(); | ||||||
|  |  | ||||||
|  |   static int combine(int hash, int value) { | ||||||
|  |     // ignore: parameter_assignments | ||||||
|  |     hash = 0x1fffffff & (hash + value); | ||||||
|  |     // ignore: parameter_assignments | ||||||
|  |     hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); | ||||||
|  |     return hash ^ (hash >> 6); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   static int finish(int hash) { | ||||||
|  |     // ignore: parameter_assignments | ||||||
|  |     hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); | ||||||
|  |     // ignore: parameter_assignments | ||||||
|  |     hash = hash ^ (hash >> 11); | ||||||
|  |     return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// See also [devProjects]. | ||||||
|  | @ProviderFor(devProjects) | ||||||
|  | const devProjectsProvider = DevProjectsFamily(); | ||||||
|  |  | ||||||
|  | /// See also [devProjects]. | ||||||
|  | class DevProjectsFamily extends Family<AsyncValue<List<DevProject>>> { | ||||||
|  |   /// See also [devProjects]. | ||||||
|  |   const DevProjectsFamily(); | ||||||
|  |  | ||||||
|  |   /// See also [devProjects]. | ||||||
|  |   DevProjectsProvider call(String pubName) { | ||||||
|  |     return DevProjectsProvider(pubName); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   DevProjectsProvider getProviderOverride( | ||||||
|  |     covariant DevProjectsProvider provider, | ||||||
|  |   ) { | ||||||
|  |     return call(provider.pubName); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   static const Iterable<ProviderOrFamily>? _dependencies = null; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Iterable<ProviderOrFamily>? get dependencies => _dependencies; | ||||||
|  |  | ||||||
|  |   static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Iterable<ProviderOrFamily>? get allTransitiveDependencies => | ||||||
|  |       _allTransitiveDependencies; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String? get name => r'devProjectsProvider'; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// See also [devProjects]. | ||||||
|  | class DevProjectsProvider extends AutoDisposeFutureProvider<List<DevProject>> { | ||||||
|  |   /// See also [devProjects]. | ||||||
|  |   DevProjectsProvider(String pubName) | ||||||
|  |     : this._internal( | ||||||
|  |         (ref) => devProjects(ref as DevProjectsRef, pubName), | ||||||
|  |         from: devProjectsProvider, | ||||||
|  |         name: r'devProjectsProvider', | ||||||
|  |         debugGetCreateSourceHash: | ||||||
|  |             const bool.fromEnvironment('dart.vm.product') | ||||||
|  |                 ? null | ||||||
|  |                 : _$devProjectsHash, | ||||||
|  |         dependencies: DevProjectsFamily._dependencies, | ||||||
|  |         allTransitiveDependencies: DevProjectsFamily._allTransitiveDependencies, | ||||||
|  |         pubName: pubName, | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |   DevProjectsProvider._internal( | ||||||
|  |     super._createNotifier, { | ||||||
|  |     required super.name, | ||||||
|  |     required super.dependencies, | ||||||
|  |     required super.allTransitiveDependencies, | ||||||
|  |     required super.debugGetCreateSourceHash, | ||||||
|  |     required super.from, | ||||||
|  |     required this.pubName, | ||||||
|  |   }) : super.internal(); | ||||||
|  |  | ||||||
|  |   final String pubName; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Override overrideWith( | ||||||
|  |     FutureOr<List<DevProject>> Function(DevProjectsRef provider) create, | ||||||
|  |   ) { | ||||||
|  |     return ProviderOverride( | ||||||
|  |       origin: this, | ||||||
|  |       override: DevProjectsProvider._internal( | ||||||
|  |         (ref) => create(ref as DevProjectsRef), | ||||||
|  |         from: from, | ||||||
|  |         name: null, | ||||||
|  |         dependencies: null, | ||||||
|  |         allTransitiveDependencies: null, | ||||||
|  |         debugGetCreateSourceHash: null, | ||||||
|  |         pubName: pubName, | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   AutoDisposeFutureProviderElement<List<DevProject>> createElement() { | ||||||
|  |     return _DevProjectsProviderElement(this); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   bool operator ==(Object other) { | ||||||
|  |     return other is DevProjectsProvider && other.pubName == pubName; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   int get hashCode { | ||||||
|  |     var hash = _SystemHash.combine(0, runtimeType.hashCode); | ||||||
|  |     hash = _SystemHash.combine(hash, pubName.hashCode); | ||||||
|  |  | ||||||
|  |     return _SystemHash.finish(hash); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @Deprecated('Will be removed in 3.0. Use Ref instead') | ||||||
|  | // ignore: unused_element | ||||||
|  | mixin DevProjectsRef on AutoDisposeFutureProviderRef<List<DevProject>> { | ||||||
|  |   /// The parameter `pubName` of this provider. | ||||||
|  |   String get pubName; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _DevProjectsProviderElement | ||||||
|  |     extends AutoDisposeFutureProviderElement<List<DevProject>> | ||||||
|  |     with DevProjectsRef { | ||||||
|  |   _DevProjectsProviderElement(super.provider); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String get pubName => (origin as DevProjectsProvider).pubName; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ignore_for_file: type=lint | ||||||
|  | // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package | ||||||
							
								
								
									
										1169
									
								
								swagger-develop.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1169
									
								
								swagger-develop.json
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
		Reference in New Issue
	
	Block a user