💄 Hub now shows loading stautus of publishers / developers
This commit is contained in:
@@ -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),
|
),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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),
|
),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user