💄 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,11 +98,10 @@ 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();
return members; return members;
} }
@@ -173,14 +172,12 @@ 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: isWideScreen(context)
iconDisabledColor: ? null
isWideScreen(context) : Theme.of(context).appBarTheme.foregroundColor!,
? null
: Theme.of(context).appBarTheme.foregroundColor!,
), ),
), ),
); );
@@ -204,16 +201,24 @@ class _PublisherUnselectedWidget extends HookConsumerWidget {
child: Column( child: Column(
children: [ children: [
if (!hasPublishers) ...[ if (!hasPublishers) ...[
const Icon( if (publishers.isLoading)
Symbols.info, Padding(
fill: 1, padding: const EdgeInsets.all(8),
size: 32, child: const CircularProgressIndicator(),
).padding(bottom: 6, top: 24), )
Text( else
'creatorHubUnselectedHint', ...([
textAlign: TextAlign.center, const Icon(
style: Theme.of(context).textTheme.bodyLarge, Symbols.info,
).tr(), fill: 1,
size: 32,
).padding(bottom: 6, top: 24),
Text(
'creatorHubUnselectedHint',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyLarge,
).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,29 +320,26 @@ class CreatorHubScreen extends HookConsumerWidget {
} }
final List<DropdownMenuItem<SnPublisher>> publishersMenu = publishers.when( final List<DropdownMenuItem<SnPublisher>> publishersMenu = publishers.when(
data: data: (data) => data
(data) => .map(
data (item) => DropdownMenuItem<SnPublisher>(
.map( value: item,
(item) => DropdownMenuItem<SnPublisher>( child: ListTile(
value: item, minTileHeight: 48,
child: ListTile( leading: ProfilePictureWidget(
minTileHeight: 48, radius: 16,
leading: ProfilePictureWidget( fileId: item.picture?.id,
radius: 16, ),
fileId: item.picture?.id, title: Text(item.nick),
), subtitle: Text('@${item.name}'),
title: Text(item.nick), trailing: currentPublisher.value?.id == item.id
subtitle: Text('@${item.name}'), ? const Icon(Icons.check)
trailing: : null,
currentPublisher.value?.id == item.id contentPadding: EdgeInsets.symmetric(horizontal: 8),
? const Icon(Icons.check) ),
: null, ),
contentPadding: EdgeInsets.symmetric(horizontal: 8), )
), .toList(),
),
)
.toList(),
loading: () => [], loading: () => [],
error: (_, _) => [], error: (_, _) => [],
); );
@@ -443,10 +445,9 @@ 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,51 +568,49 @@ 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: currentPublisher.value == null
child: ? ConstrainedBox(
currentPublisher.value == null constraints: BoxConstraints(maxWidth: 640),
? ConstrainedBox( child: _PublisherUnselectedWidget(
constraints: BoxConstraints(maxWidth: 640), onPublisherSelected: (publisher) {
child: _PublisherUnselectedWidget( currentPublisher.value = publisher;
onPublisherSelected: (publisher) { },
currentPublisher.value = publisher; ),
}, ).center()
), : isWide
).center() ? Column(
: isWide spacing: 8,
? Column( children: [
spacing: 8, const SizedBox.shrink(),
children: [ PublisherSelector(
const SizedBox.shrink(), currentPublisher: currentPublisher.value,
PublisherSelector( publishersMenu: publishersMenu,
currentPublisher: currentPublisher.value, onChanged: (value) {
publishersMenu: publishersMenu, currentPublisher.value = value;
onChanged: (value) { },
currentPublisher.value = value; ),
}, if (stats != null)
), _PublisherStatsWidget(
if (stats != null) stats: stats,
_PublisherStatsWidget( heatmap: publisherHeatmap.value,
stats: stats, ).padding(horizontal: 12),
heatmap: publisherHeatmap.value, buildNavigationWidget(true),
).padding(horizontal: 12), ],
buildNavigationWidget(true), )
], : Column(
) spacing: 12,
: Column( children: [
spacing: 12, if (stats != null)
children: [ _PublisherStatsWidget(
if (stats != null) stats: stats,
_PublisherStatsWidget( heatmap: publisherHeatmap.value,
stats: stats, ).padding(horizontal: 16),
heatmap: publisherHeatmap.value, buildNavigationWidget(false),
).padding(horizontal: 16), ],
buildNavigationWidget(false), ),
], ),
),
),
loading: () => const Center(child: CircularProgressIndicator()), loading: () => const Center(child: CircularProgressIndicator()),
error: (_, _) => const SizedBox.shrink(), error: (_, _) => const SizedBox.shrink(),
), ),
@@ -876,11 +875,10 @@ 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, ),
),
).then((value) { ).then((value) {
if (value != null) { if (value != null) {
memberNotifier.refresh(); memberNotifier.refresh();
@@ -991,23 +989,19 @@ 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, return TextField(
focusNode, controller: controller,
onFieldSubmitted, focusNode: focusNode,
) { keyboardType: TextInputType.number,
return TextField( decoration: InputDecoration(
controller: controller, labelText: 'memberRole'.tr(),
focusNode: focusNode, helperText: 'memberRoleHint'.tr(),
keyboardType: TextInputType.number, ),
decoration: InputDecoration( onTapOutside: (event) => focusNode.unfocus(),
labelText: 'memberRole'.tr(), );
helperText: 'memberRoleHint'.tr(), },
),
onTapOutside: (event) => focusNode.unfocus(),
);
},
), ),
const Gap(16), const Gap(16),
FilledButton.icon( FilledButton.icon(
@@ -1085,57 +1079,49 @@ class _PublisherInviteSheet extends HookConsumerWidget {
), ),
], ],
child: invites.when( child: invites.when(
data: data: (items) => items.isEmpty
(items) => ? Center(
items.isEmpty child: Text('invitesEmpty', textAlign: TextAlign.center).tr(),
? Center( )
child: : ListView.builder(
Text( shrinkWrap: true,
'invitesEmpty', itemCount: items.length,
textAlign: TextAlign.center, itemBuilder: (context, index) {
).tr(), final invite = items[index];
) return ListTile(
: ListView.builder( leading: ProfilePictureWidget(
shrinkWrap: true, fileId: invite.publisher!.picture?.id,
itemCount: items.length, fallbackIcon: Symbols.group,
itemBuilder: (context, index) {
final invite = items[index];
return ListTile(
leading: ProfilePictureWidget(
fileId: invite.publisher!.picture?.id,
fallbackIcon: Symbols.group,
),
title: Text(invite.publisher!.nick),
subtitle:
Text(
invite.role >= 100
? 'permissionOwner'
: invite.role >= 50
? 'permissionModerator'
: 'permissionMember',
).tr(),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Symbols.check),
onPressed: () => acceptInvite(invite),
),
IconButton(
icon: const Icon(Symbols.close),
onPressed: () => declineInvite(invite),
),
],
),
);
},
), ),
title: Text(invite.publisher!.nick),
subtitle: Text(
invite.role >= 100
? 'permissionOwner'
: invite.role >= 50
? 'permissionModerator'
: 'permissionMember',
).tr(),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Symbols.check),
onPressed: () => acceptInvite(invite),
),
IconButton(
icon: const Icon(Symbols.close),
onPressed: () => declineInvite(invite),
),
],
),
);
},
),
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,12 +68,11 @@ 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), )
) : const AsyncValue<List<DevProject>>.data([]);
: const AsyncValue<List<DevProject>>.data([]);
final currentProject = useState<DevProject?>( final currentProject = useState<DevProject?>(
projects.value?.where((p) => p.id == initialProjectId).firstOrNull, projects.value?.where((p) => p.id == initialProjectId).firstOrNull,
@@ -126,14 +125,13 @@ 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: currentDeveloper.value!.publisher!.name,
currentDeveloper.value!.publisher!.name, ),
), ),
),
).then((value) { ).then((value) {
if (value != null) { if (value != null) {
ref.invalidate( ref.invalidate(
@@ -211,108 +209,96 @@ 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) => ? ConstrainedBox(
currentDeveloper == null constraints: BoxConstraints(maxWidth: 640),
? ConstrainedBox( child: _DeveloperUnselectedWidget(
constraints: BoxConstraints(maxWidth: 640), onDeveloperSelected: onDeveloperSelected,
child: _DeveloperUnselectedWidget( ),
onDeveloperSelected: onDeveloperSelected, ).center()
: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Developer Stats
if (stats != null) ...[
Text(
'Overview',
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: Theme.of(context).colorScheme.onSurface,
),
), ),
).center() const Gap(16),
: Padding( _DeveloperStatsWidget(stats: stats),
padding: const EdgeInsets.all(16), const Gap(24),
child: Column( ],
crossAxisAlignment: CrossAxisAlignment.start,
children: [ // Projects Section
// Developer Stats Row(
if (stats != null) ...[ children: [
Text( Text(
'Overview', '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(),
ElevatedButton.icon(
onPressed: onCreateProject,
icon: const Icon(Symbols.add),
label: const Text('Create Project'),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF1A73E8),
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
), ),
const Gap(16),
_DeveloperStatsWidget(stats: stats),
const Gap(24),
],
// Projects Section
Row(
children: [
Text(
'Projects',
style: Theme.of(
context,
).textTheme.titleLarge?.copyWith(
color:
Theme.of(context).colorScheme.onSurface,
),
),
const Spacer(),
ElevatedButton.icon(
onPressed: onCreateProject,
icon: const Icon(Symbols.add),
label: const Text('Create Project'),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF1A73E8),
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
),
),
),
],
), ),
const Gap(16), ),
],
// Projects List
projects.value?.isNotEmpty ?? false
? Column(
children:
projects.value!
.map(
(project) => _ProjectListTile(
project: project,
publisherName:
currentDeveloper!
.publisher!
.name,
onProjectSelected:
onProjectSelected,
),
)
.toList(),
)
: Container(
padding: const EdgeInsets.all(48),
alignment: Alignment.center,
child: Text(
'No projects available',
style: TextStyle(
color:
Theme.of(context).colorScheme.onSurface,
fontSize: 16,
),
),
),
],
),
), ),
const Gap(16),
// Projects List
projects.value?.isNotEmpty ?? false
? Column(
children: projects.value!
.map(
(project) => _ProjectListTile(
project: project,
publisherName:
currentDeveloper!.publisher!.name,
onProjectSelected: onProjectSelected,
),
)
.toList(),
)
: Container(
padding: const EdgeInsets.all(48),
alignment: Alignment.center,
child: Text(
'No projects available',
style: TextStyle(
color: Theme.of(context).colorScheme.onSurface,
fontSize: 16,
),
),
),
],
),
),
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( developerStatsProvider(currentDeveloper?.publisher?.name),
developerStatsProvider(currentDeveloper?.publisher?.name), );
); },
}, ),
),
), ),
); );
} }
@@ -335,29 +321,26 @@ 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) => .map(
data (item) => DropdownMenuItem<SnDeveloper>(
.map( value: item,
(item) => DropdownMenuItem<SnDeveloper>( child: ListTile(
value: item, minTileHeight: 48,
child: ListTile( leading: ProfilePictureWidget(
minTileHeight: 48, radius: 16,
leading: ProfilePictureWidget( fileId: item.publisher?.picture?.id,
radius: 16, ),
fileId: item.publisher?.picture?.id, title: Text(item.publisher!.nick),
), subtitle: Text('@${item.publisher!.name}'),
title: Text(item.publisher!.nick), trailing: currentDeveloper?.id == item.id
subtitle: Text('@${item.publisher!.name}'), ? const Icon(Icons.check)
trailing: : null,
currentDeveloper?.id == item.id contentPadding: EdgeInsets.symmetric(horizontal: 8),
? const Icon(Icons.check) ),
: null, ),
contentPadding: EdgeInsets.symmetric(horizontal: 8), )
), .toList(),
),
)
.toList(),
loading: () => [], loading: () => [],
error: (_, _) => [], error: (_, _) => [],
); );
@@ -446,38 +429,36 @@ 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, child: ListTile(
child: ListTile( minTileHeight: 48,
minTileHeight: 48, leading: CircleAvatar(
leading: CircleAvatar( radius: 16,
radius: 16, backgroundColor: Theme.of(context).colorScheme.primary,
backgroundColor: Theme.of(context).colorScheme.primary, child: Text(
child: Text( item.name.isNotEmpty ? item.name[0].toUpperCase() : '?',
item.name.isNotEmpty ? item.name[0].toUpperCase() : '?', style: TextStyle(
style: TextStyle( color: Theme.of(context).colorScheme.onPrimary,
color: Theme.of(context).colorScheme.onPrimary,
),
),
), ),
title: Text(item.name),
subtitle: Text(
item.description ?? '',
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
trailing:
currentProject?.id == item.id
? const Icon(Icons.check)
: null,
contentPadding: EdgeInsets.symmetric(horizontal: 8),
), ),
), ),
) title: Text(item.name),
.toList(); subtitle: Text(
item.description ?? '',
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
trailing: currentProject?.id == item.id
? const Icon(Icons.check)
: null,
contentPadding: EdgeInsets.symmetric(horizontal: 8),
),
),
)
.toList();
return DropdownButtonHideUnderline( return DropdownButtonHideUnderline(
child: DropdownButton2<DevProject>( child: DropdownButton2<DevProject>(
@@ -496,50 +477,47 @@ 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: Theme.of(
backgroundColor: context,
Theme.of(context).colorScheme.primary, ).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(
color:
Theme.of(context).colorScheme.onPrimary,
),
),
),
const Gap(8),
Text(
e.value?.name ?? '?',
style: TextStyle( style: TextStyle(
color: color: Theme.of(context).colorScheme.onPrimary,
Theme.of(
context,
).appBarTheme.foregroundColor,
), ),
), ),
],
).padding(right: 8)
: CircleAvatar(
radius: 16,
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,
),
), ),
).center().padding(right: 8), const Gap(8),
Text(
e.value?.name ?? '?',
style: TextStyle(
color: Theme.of(
context,
).appBarTheme.foregroundColor,
),
),
],
).padding(right: 8)
: CircleAvatar(
radius: 16,
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,
),
),
).center().padding(right: 8),
) )
.toList(); .toList();
}, },
@@ -590,45 +568,40 @@ 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( children: [
children: [ const Icon(Symbols.edit),
const Icon(Symbols.edit), const SizedBox(width: 12),
const SizedBox(width: 12), Text('edit').tr(),
Text('edit').tr(), ],
], ),
), ),
), PopupMenuItem(
PopupMenuItem( value: 'delete',
value: 'delete', child: Row(
child: Row( 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('delete', style: const TextStyle(color: Colors.red)).tr(),
Text( ],
'delete', ),
style: const TextStyle(color: Colors.red), ),
).tr(), ],
],
),
),
],
onSelected: (value) { onSelected: (value) {
if (value == 'edit') { if (value == 'edit') {
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, project: project,
project: project, ),
), ),
),
).then((value) { ).then((value) {
if (value != null) { if (value != null) {
ref.invalidate(devProjectsProvider(publisherName)); ref.invalidate(devProjectsProvider(publisherName));
@@ -735,16 +708,24 @@ class _DeveloperUnselectedWidget extends HookConsumerWidget {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
if (!hasDevelopers) ...[ if (!hasDevelopers) ...[
const Icon( if (developers.isLoading)
Symbols.info, Padding(
fill: 1, padding: const EdgeInsets.all(8),
size: 32, child: const CircularProgressIndicator(),
).padding(bottom: 6, top: 24), )
Text( else
'developerHubUnselectedHint', ...([
textAlign: TextAlign.center, const Icon(
style: Theme.of(context).textTheme.bodyLarge, Symbols.info,
).tr(), fill: 1,
size: 32,
).padding(bottom: 6, top: 24),
Text(
'developerHubUnselectedHint',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyLarge,
).tr(),
]),
const Gap(24), const Gap(24),
], ],
if (hasDevelopers) if (hasDevelopers)
@@ -818,16 +799,15 @@ 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, )
) : await client.post(
: await client.post( '/develop/developers/$publisherName/projects',
'/develop/developers/$publisherName/projects', data: data,
data: data, );
);
if (!context.mounted) return; if (!context.mounted) return;
Navigator.of(context).pop(DevProject.fromJson(resp.data)); Navigator.of(context).pop(DevProject.fromJson(resp.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,38 +914,34 @@ 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) => ? Center(
items.isEmpty child: Text(
? Center( 'noDevelopersToEnroll',
child: textAlign: TextAlign.center,
Text( ).tr(),
'noDevelopersToEnroll', )
textAlign: TextAlign.center, : ListView.builder(
).tr(), shrinkWrap: true,
) itemCount: items.length,
: ListView.builder( itemBuilder: (context, index) {
shrinkWrap: true, final publisher = items[index];
itemCount: items.length, return ListTile(
itemBuilder: (context, index) { leading: ProfilePictureWidget(
final publisher = items[index]; fileId: publisher.picture?.id,
return ListTile( fallbackIcon: Symbols.group,
leading: ProfilePictureWidget(
fileId: publisher.picture?.id,
fallbackIcon: Symbols.group,
),
title: Text(publisher.nick),
subtitle: Text('@${publisher.name}'),
onTap: () => enroll(publisher),
);
},
), ),
title: Text(publisher.nick),
subtitle: Text('@${publisher.name}'),
onTap: () => enroll(publisher),
);
},
),
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), ),
),
), ),
); );
} }