💄 Hub now shows loading stautus of publishers / developers

This commit is contained in:
2025-12-06 21:28:19 +08:00
parent 9c370647dd
commit ac2cee10e5
2 changed files with 424 additions and 462 deletions

View File

@@ -98,8 +98,7 @@ class PublisherMemberListNotifier extends AsyncNotifier<List<SnPublisherMember>>
); );
totalCount = int.parse(response.headers.value('X-Total') ?? '0'); totalCount = int.parse(response.headers.value('X-Total') ?? '0');
final members = final members = response.data
response.data
.map((e) => SnPublisherMember.fromJson(e)) .map((e) => SnPublisherMember.fromJson(e))
.cast<SnPublisherMember>() .cast<SnPublisherMember>()
.toList(); .toList();
@@ -173,12 +172,10 @@ class PublisherSelector extends StatelessWidget {
iconStyleData: IconStyleData( iconStyleData: IconStyleData(
icon: Icon(Icons.arrow_drop_down), icon: Icon(Icons.arrow_drop_down),
iconSize: 19, iconSize: 19,
iconEnabledColor: iconEnabledColor: isWideScreen(context)
isWideScreen(context)
? null ? null
: Theme.of(context).appBarTheme.foregroundColor!, : Theme.of(context).appBarTheme.foregroundColor!,
iconDisabledColor: iconDisabledColor: isWideScreen(context)
isWideScreen(context)
? null ? null
: Theme.of(context).appBarTheme.foregroundColor!, : Theme.of(context).appBarTheme.foregroundColor!,
), ),
@@ -204,6 +201,13 @@ class _PublisherUnselectedWidget extends HookConsumerWidget {
child: Column( child: Column(
children: [ children: [
if (!hasPublishers) ...[ if (!hasPublishers) ...[
if (publishers.isLoading)
Padding(
padding: const EdgeInsets.all(8),
child: const CircularProgressIndicator(),
)
else
...([
const Icon( const Icon(
Symbols.info, Symbols.info,
fill: 1, fill: 1,
@@ -214,6 +218,7 @@ class _PublisherUnselectedWidget extends HookConsumerWidget {
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyLarge, style: Theme.of(context).textTheme.bodyLarge,
).tr(), ).tr(),
]),
const Gap(24), const Gap(24),
], ],
if (hasPublishers) if (hasPublishers)
@@ -288,14 +293,14 @@ class CreatorHubScreen extends HookConsumerWidget {
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
isScrollControlled: true, isScrollControlled: true,
builder: builder: (context) =>
(context) =>
EditPublisherScreen(name: currentPublisher.value!.name), EditPublisherScreen(name: currentPublisher.value!.name),
).then((value) async { ).then((value) async {
if (value == null) return; if (value == null) return;
final data = await ref.refresh(publishersManagedProvider.future); final data = await ref.refresh(publishersManagedProvider.future);
currentPublisher.value = currentPublisher.value = data
data.where((e) => e.id == currentPublisher.value!.id).firstOrNull; .where((e) => e.id == currentPublisher.value!.id)
.firstOrNull;
}); });
} }
@@ -315,9 +320,7 @@ class CreatorHubScreen extends HookConsumerWidget {
} }
final List<DropdownMenuItem<SnPublisher>> publishersMenu = publishers.when( final List<DropdownMenuItem<SnPublisher>> publishersMenu = publishers.when(
data: data: (data) => data
(data) =>
data
.map( .map(
(item) => DropdownMenuItem<SnPublisher>( (item) => DropdownMenuItem<SnPublisher>(
value: item, value: item,
@@ -329,8 +332,7 @@ class CreatorHubScreen extends HookConsumerWidget {
), ),
title: Text(item.nick), title: Text(item.nick),
subtitle: Text('@${item.name}'), subtitle: Text('@${item.name}'),
trailing: trailing: currentPublisher.value?.id == item.id
currentPublisher.value?.id == item.id
? const Icon(Icons.check) ? const Icon(Icons.check)
: null, : null,
contentPadding: EdgeInsets.symmetric(horizontal: 8), contentPadding: EdgeInsets.symmetric(horizontal: 8),
@@ -443,8 +445,7 @@ class CreatorHubScreen extends HookConsumerWidget {
showModalBottomSheet( showModalBottomSheet(
isScrollControlled: true, isScrollControlled: true,
context: context, context: context,
builder: builder: (context) => _PublisherMemberListSheet(
(context) => _PublisherMemberListSheet(
publisherUname: currentPublisher.value!.name, publisherUname: currentPublisher.value!.name,
), ),
); );
@@ -567,11 +568,9 @@ class CreatorHubScreen extends HookConsumerWidget {
child: ConstrainedBox( child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: maxWidth), constraints: BoxConstraints(maxWidth: maxWidth),
child: publisherStats.when( child: publisherStats.when(
data: data: (stats) => SingleChildScrollView(
(stats) => SingleChildScrollView(
padding: const EdgeInsets.symmetric(vertical: 24), padding: const EdgeInsets.symmetric(vertical: 24),
child: child: currentPublisher.value == null
currentPublisher.value == null
? ConstrainedBox( ? ConstrainedBox(
constraints: BoxConstraints(maxWidth: 640), constraints: BoxConstraints(maxWidth: 640),
child: _PublisherUnselectedWidget( child: _PublisherUnselectedWidget(
@@ -876,8 +875,7 @@ class _PublisherMemberListSheet extends HookConsumerWidget {
showModalBottomSheet( showModalBottomSheet(
isScrollControlled: true, isScrollControlled: true,
context: context, context: context,
builder: builder: (context) => _PublisherMemberRoleSheet(
(context) => _PublisherMemberRoleSheet(
publisherUname: publisherUname, publisherUname: publisherUname,
member: member, member: member,
), ),
@@ -991,12 +989,8 @@ class _PublisherMemberRoleSheet extends HookConsumerWidget {
onSelected: (int selection) { onSelected: (int selection) {
roleController.text = selection.toString(); roleController.text = selection.toString();
}, },
fieldViewBuilder: ( fieldViewBuilder:
context, (context, controller, focusNode, onFieldSubmitted) {
controller,
focusNode,
onFieldSubmitted,
) {
return TextField( return TextField(
controller: controller, controller: controller,
focusNode: focusNode, focusNode: focusNode,
@@ -1085,15 +1079,9 @@ class _PublisherInviteSheet extends HookConsumerWidget {
), ),
], ],
child: invites.when( child: invites.when(
data: data: (items) => items.isEmpty
(items) =>
items.isEmpty
? Center( ? Center(
child: child: Text('invitesEmpty', textAlign: TextAlign.center).tr(),
Text(
'invitesEmpty',
textAlign: TextAlign.center,
).tr(),
) )
: ListView.builder( : ListView.builder(
shrinkWrap: true, shrinkWrap: true,
@@ -1106,8 +1094,7 @@ class _PublisherInviteSheet extends HookConsumerWidget {
fallbackIcon: Symbols.group, fallbackIcon: Symbols.group,
), ),
title: Text(invite.publisher!.nick), title: Text(invite.publisher!.nick),
subtitle: subtitle: Text(
Text(
invite.role >= 100 invite.role >= 100
? 'permissionOwner' ? 'permissionOwner'
: invite.role >= 50 : invite.role >= 50
@@ -1131,8 +1118,7 @@ class _PublisherInviteSheet extends HookConsumerWidget {
}, },
), ),
loading: () => const Center(child: CircularProgressIndicator()), loading: () => const Center(child: CircularProgressIndicator()),
error: error: (error, _) => ResponseErrorWidget(
(error, _) => ResponseErrorWidget(
error: error, error: error,
onRetry: () => ref.invalidate(publisherInvitesProvider), onRetry: () => ref.invalidate(publisherInvitesProvider),
), ),

View File

@@ -68,8 +68,7 @@ class DeveloperHubScreen extends HookConsumerWidget {
developers.value?.firstOrNull, developers.value?.firstOrNull,
); );
final projects = final projects = currentDeveloper.value?.publisher?.name != null
currentDeveloper.value?.publisher?.name != null
? ref.watch( ? ref.watch(
devProjectsProvider(currentDeveloper.value!.publisher!.name), devProjectsProvider(currentDeveloper.value!.publisher!.name),
) )
@@ -126,8 +125,7 @@ class DeveloperHubScreen extends HookConsumerWidget {
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
isScrollControlled: true, isScrollControlled: true,
builder: builder: (context) => SheetScaffold(
(context) => SheetScaffold(
titleText: 'createProject'.tr(), titleText: 'createProject'.tr(),
child: ProjectForm( child: ProjectForm(
publisherName: publisherName:
@@ -211,9 +209,7 @@ class _MainContentSection extends HookConsumerWidget {
return Container( return Container(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
child: developerStats.when( child: developerStats.when(
data: data: (stats) => currentDeveloper == null
(stats) =>
currentDeveloper == null
? ConstrainedBox( ? ConstrainedBox(
constraints: BoxConstraints(maxWidth: 640), constraints: BoxConstraints(maxWidth: 640),
child: _DeveloperUnselectedWidget( child: _DeveloperUnselectedWidget(
@@ -229,9 +225,7 @@ class _MainContentSection extends HookConsumerWidget {
if (stats != null) ...[ if (stats != null) ...[
Text( Text(
'Overview', 'Overview',
style: Theme.of( style: Theme.of(context).textTheme.titleLarge?.copyWith(
context,
).textTheme.titleLarge?.copyWith(
color: Theme.of(context).colorScheme.onSurface, color: Theme.of(context).colorScheme.onSurface,
), ),
), ),
@@ -245,11 +239,9 @@ class _MainContentSection extends HookConsumerWidget {
children: [ children: [
Text( Text(
'Projects', 'Projects',
style: Theme.of( style: Theme.of(context).textTheme.titleLarge
context, ?.copyWith(
).textTheme.titleLarge?.copyWith( color: Theme.of(context).colorScheme.onSurface,
color:
Theme.of(context).colorScheme.onSurface,
), ),
), ),
const Spacer(), const Spacer(),
@@ -273,17 +265,13 @@ class _MainContentSection extends HookConsumerWidget {
// Projects List // Projects List
projects.value?.isNotEmpty ?? false projects.value?.isNotEmpty ?? false
? Column( ? Column(
children: children: projects.value!
projects.value!
.map( .map(
(project) => _ProjectListTile( (project) => _ProjectListTile(
project: project, project: project,
publisherName: publisherName:
currentDeveloper! currentDeveloper!.publisher!.name,
.publisher! onProjectSelected: onProjectSelected,
.name,
onProjectSelected:
onProjectSelected,
), ),
) )
.toList(), .toList(),
@@ -294,8 +282,7 @@ class _MainContentSection extends HookConsumerWidget {
child: Text( child: Text(
'No projects available', 'No projects available',
style: TextStyle( style: TextStyle(
color: color: Theme.of(context).colorScheme.onSurface,
Theme.of(context).colorScheme.onSurface,
fontSize: 16, fontSize: 16,
), ),
), ),
@@ -304,8 +291,7 @@ class _MainContentSection extends HookConsumerWidget {
), ),
), ),
loading: () => const Center(child: CircularProgressIndicator()), loading: () => const Center(child: CircularProgressIndicator()),
error: error: (err, stack) => ResponseErrorWidget(
(err, stack) => ResponseErrorWidget(
error: err, error: err,
onRetry: () { onRetry: () {
ref.invalidate( ref.invalidate(
@@ -335,9 +321,7 @@ class DeveloperSelector extends HookConsumerWidget {
final developers = ref.watch(developersProvider); final developers = ref.watch(developersProvider);
final List<DropdownMenuItem<SnDeveloper>> developersMenu = developers.when( final List<DropdownMenuItem<SnDeveloper>> developersMenu = developers.when(
data: data: (data) => data
(data) =>
data
.map( .map(
(item) => DropdownMenuItem<SnDeveloper>( (item) => DropdownMenuItem<SnDeveloper>(
value: item, value: item,
@@ -349,8 +333,7 @@ class DeveloperSelector extends HookConsumerWidget {
), ),
title: Text(item.publisher!.nick), title: Text(item.publisher!.nick),
subtitle: Text('@${item.publisher!.name}'), subtitle: Text('@${item.publisher!.name}'),
trailing: trailing: currentDeveloper?.id == item.id
currentDeveloper?.id == item.id
? const Icon(Icons.check) ? const Icon(Icons.check)
: null, : null,
contentPadding: EdgeInsets.symmetric(horizontal: 8), contentPadding: EdgeInsets.symmetric(horizontal: 8),
@@ -446,8 +429,7 @@ class ProjectSelector extends HookConsumerWidget {
return const SizedBox.shrink(); return const SizedBox.shrink();
} }
final List<DropdownMenuItem<DevProject>> projectsMenu = final List<DropdownMenuItem<DevProject>> projectsMenu = projects.value!
projects.value!
.map( .map(
(item) => DropdownMenuItem<DevProject>( (item) => DropdownMenuItem<DevProject>(
value: item, value: item,
@@ -469,8 +451,7 @@ class ProjectSelector extends HookConsumerWidget {
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
trailing: trailing: currentProject?.id == item.id
currentProject?.id == item.id
? const Icon(Icons.check) ? const Icon(Icons.check)
: null, : null,
contentPadding: EdgeInsets.symmetric(horizontal: 8), contentPadding: EdgeInsets.symmetric(horizontal: 8),
@@ -496,22 +477,21 @@ class ProjectSelector extends HookConsumerWidget {
final isWider = isWiderScreen(context); final isWider = isWiderScreen(context);
return projectsMenu return projectsMenu
.map( .map(
(e) => (e) => isWider
isWider
? Row( ? Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
CircleAvatar( CircleAvatar(
radius: 16, radius: 16,
backgroundColor: backgroundColor: Theme.of(
Theme.of(context).colorScheme.primary, context,
).colorScheme.primary,
child: Text( child: Text(
e.value?.name.isNotEmpty ?? false e.value?.name.isNotEmpty ?? false
? e.value!.name[0].toUpperCase() ? e.value!.name[0].toUpperCase()
: '?', : '?',
style: TextStyle( style: TextStyle(
color: color: Theme.of(context).colorScheme.onPrimary,
Theme.of(context).colorScheme.onPrimary,
), ),
), ),
), ),
@@ -519,8 +499,7 @@ class ProjectSelector extends HookConsumerWidget {
Text( Text(
e.value?.name ?? '?', e.value?.name ?? '?',
style: TextStyle( style: TextStyle(
color: color: Theme.of(
Theme.of(
context, context,
).appBarTheme.foregroundColor, ).appBarTheme.foregroundColor,
), ),
@@ -529,8 +508,7 @@ class ProjectSelector extends HookConsumerWidget {
).padding(right: 8) ).padding(right: 8)
: CircleAvatar( : CircleAvatar(
radius: 16, radius: 16,
backgroundColor: backgroundColor: Theme.of(context).colorScheme.primary,
Theme.of(context).colorScheme.primary,
child: Text( child: Text(
e.value?.name.isNotEmpty ?? false e.value?.name.isNotEmpty ?? false
? e.value!.name[0].toUpperCase() ? e.value!.name[0].toUpperCase()
@@ -590,8 +568,7 @@ class _ProjectListTile extends HookConsumerWidget {
subtitle: Text(project.description ?? ''), subtitle: Text(project.description ?? ''),
contentPadding: const EdgeInsets.only(left: 16, right: 17), contentPadding: const EdgeInsets.only(left: 16, right: 17),
trailing: PopupMenuButton( trailing: PopupMenuButton(
itemBuilder: itemBuilder: (context) => [
(context) => [
PopupMenuItem( PopupMenuItem(
value: 'edit', value: 'edit',
child: Row( child: Row(
@@ -608,10 +585,7 @@ class _ProjectListTile extends HookConsumerWidget {
children: [ children: [
const Icon(Symbols.delete, color: Colors.red), const Icon(Symbols.delete, color: Colors.red),
const SizedBox(width: 12), const SizedBox(width: 12),
Text( Text('delete', style: const TextStyle(color: Colors.red)).tr(),
'delete',
style: const TextStyle(color: Colors.red),
).tr(),
], ],
), ),
), ),
@@ -621,8 +595,7 @@ class _ProjectListTile extends HookConsumerWidget {
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
isScrollControlled: true, isScrollControlled: true,
builder: builder: (context) => SheetScaffold(
(context) => SheetScaffold(
titleText: 'editProject'.tr(), titleText: 'editProject'.tr(),
child: ProjectForm( child: ProjectForm(
publisherName: publisherName, publisherName: publisherName,
@@ -735,6 +708,13 @@ class _DeveloperUnselectedWidget extends HookConsumerWidget {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
if (!hasDevelopers) ...[ if (!hasDevelopers) ...[
if (developers.isLoading)
Padding(
padding: const EdgeInsets.all(8),
child: const CircularProgressIndicator(),
)
else
...([
const Icon( const Icon(
Symbols.info, Symbols.info,
fill: 1, fill: 1,
@@ -745,6 +725,7 @@ class _DeveloperUnselectedWidget extends HookConsumerWidget {
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyLarge, style: Theme.of(context).textTheme.bodyLarge,
).tr(), ).tr(),
]),
const Gap(24), const Gap(24),
], ],
if (hasDevelopers) if (hasDevelopers)
@@ -818,8 +799,7 @@ class ProjectForm extends HookConsumerWidget {
'description': descriptionController.text, 'description': descriptionController.text,
}; };
final resp = final resp = isEditing
isEditing
? await client.put( ? await client.put(
'/develop/developers/$publisherName/projects/${project!.id}', '/develop/developers/$publisherName/projects/${project!.id}',
data: data, data: data,
@@ -860,8 +840,8 @@ class ProjectForm extends HookConsumerWidget {
} }
return null; return null;
}, },
onTapOutside: onTapOutside: (_) =>
(_) => FocusManager.instance.primaryFocus?.unfocus(), FocusManager.instance.primaryFocus?.unfocus(),
), ),
TextFormField( TextFormField(
controller: slugController, controller: slugController,
@@ -878,8 +858,8 @@ class ProjectForm extends HookConsumerWidget {
} }
return null; return null;
}, },
onTapOutside: onTapOutside: (_) =>
(_) => FocusManager.instance.primaryFocus?.unfocus(), FocusManager.instance.primaryFocus?.unfocus(),
), ),
TextFormField( TextFormField(
controller: descriptionController, controller: descriptionController,
@@ -892,8 +872,8 @@ class ProjectForm extends HookConsumerWidget {
), ),
minLines: 3, minLines: 3,
maxLines: null, maxLines: null,
onTapOutside: onTapOutside: (_) =>
(_) => FocusManager.instance.primaryFocus?.unfocus(), FocusManager.instance.primaryFocus?.unfocus(),
), ),
], ],
), ),
@@ -934,12 +914,9 @@ class _DeveloperEnrollmentSheet extends HookConsumerWidget {
return SheetScaffold( return SheetScaffold(
titleText: 'enrollDeveloper'.tr(), titleText: 'enrollDeveloper'.tr(),
child: publishers.when( child: publishers.when(
data: data: (items) => items.isEmpty
(items) =>
items.isEmpty
? Center( ? Center(
child: child: Text(
Text(
'noDevelopersToEnroll', 'noDevelopersToEnroll',
textAlign: TextAlign.center, textAlign: TextAlign.center,
).tr(), ).tr(),
@@ -961,8 +938,7 @@ class _DeveloperEnrollmentSheet extends HookConsumerWidget {
}, },
), ),
loading: () => const Center(child: CircularProgressIndicator()), loading: () => const Center(child: CircularProgressIndicator()),
error: error: (error, _) => ResponseErrorWidget(
(error, _) => ResponseErrorWidget(
error: error, error: error,
onRetry: () => ref.invalidate(publishersManagedProvider), onRetry: () => ref.invalidate(publishersManagedProvider),
), ),