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

View File

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