guidesoutenance

This commit is contained in:
2026-09-30 08:24:20 +02:00
parent 4de305bbf4
commit 07f88198c0
21 changed files with 1067 additions and 331 deletions

View File

@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import '../services/intra_api_exception.dart';
import '../services/intra_profile_service.dart';
import '../theme/swifty_ui.dart';
/// Vue 2 — profil complet (étape 4).
class ProfileScreen extends StatefulWidget {
@@ -79,18 +80,18 @@ class _ProfileScreenState extends State<ProfileScreen> {
final imageUrl = _imageUrl(user);
return Scaffold(
appBar: AppBar(
title: Text(login),
backgroundColor: const Color(0xFF00BABC),
foregroundColor: Colors.black,
),
appBar: SwiftyUi.appBar(title: login, showBack: true),
body: _loading
? const Center(child: CircularProgressIndicator())
: _error != null
? _ErrorBody(message: _error!, onRetry: _load)
? ResponsiveScrollBody(
maxWidth: SwiftyUi.searchMaxWidth,
child: _ErrorBody(message: _error!, onRetry: _load),
)
: LayoutBuilder(
builder: (context, constraints) {
final wide = constraints.maxWidth >= 720;
final wide =
constraints.maxWidth >= SwiftyUi.wideBreakpoint;
final profile = _profile!;
final header = _ProfileHeader(
login: login,
@@ -104,51 +105,65 @@ class _ProfileScreenState extends State<ProfileScreen> {
final skills = _SkillsCard(skills: profile.skills);
final projects = _ProjectsCard(projects: profile.projects);
final column = Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
header,
const SizedBox(height: 16),
details,
const SizedBox(height: 16),
skills,
const SizedBox(height: 16),
projects,
],
);
if (wide) {
return SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
flex: 2,
child: Column(
children: [
header,
const SizedBox(height: 16),
details,
],
),
padding: SwiftyUi.pagePadding(constraints),
child: Align(
alignment: Alignment.topCenter,
child: ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: SwiftyUi.contentMaxWidth,
),
const SizedBox(width: 24),
Expanded(
flex: 3,
child: Column(
children: [
skills,
const SizedBox(height: 16),
projects,
],
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
flex: 2,
child: Column(
children: [header, const SizedBox(height: 16), details],
),
),
const SizedBox(width: 24),
Expanded(
flex: 3,
child: Column(
children: [
skills,
const SizedBox(height: 16),
projects,
],
),
),
],
),
],
),
),
);
}
return SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
header,
const SizedBox(height: 16),
details,
const SizedBox(height: 16),
skills,
const SizedBox(height: 16),
projects,
],
padding: SwiftyUi.pagePadding(constraints),
child: Align(
alignment: Alignment.topCenter,
child: ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: SwiftyUi.contentMaxWidth,
),
child: column,
),
),
);
},
@@ -165,20 +180,18 @@ class _ErrorBody extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
message,
textAlign: TextAlign.center,
style: TextStyle(color: Theme.of(context).colorScheme.error),
),
const SizedBox(height: 16),
FilledButton(onPressed: onRetry, child: const Text('Réessayer')),
],
),
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(height: 48),
ErrorMessage(message: message),
const SizedBox(height: 20),
LoadingFilledButton(
label: 'Réessayer',
loading: false,
onPressed: onRetry,
),
],
);
}
}
@@ -269,21 +282,39 @@ class _DetailRow extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 120,
child: Text(
label,
style: const TextStyle(fontWeight: FontWeight.w600),
return LayoutBuilder(
builder: (context, constraints) {
final stacked = constraints.maxWidth < 400;
if (stacked) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 6),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(label, style: const TextStyle(fontWeight: FontWeight.w600)),
const SizedBox(height: 2),
Text(value),
],
),
);
}
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 120,
child: Text(
label,
style: const TextStyle(fontWeight: FontWeight.w600),
),
),
Expanded(child: Text(value)),
],
),
Expanded(child: Text(value)),
],
),
);
},
);
}
}
@@ -318,7 +349,7 @@ class _SkillsCard extends StatelessWidget {
value: (s.level + s.percentage / 100).clamp(0, 15) / 15,
minHeight: 6,
backgroundColor: Colors.grey.shade300,
color: const Color(0xFF00BABC),
color: SwiftyUi.brand,
),
],
),
@@ -356,18 +387,25 @@ class _ProjectsCard extends StatelessWidget {
final statusLabel =
p.status == 'failed' ? 'Échec' : 'Validé';
final mark = p.finalMark != null ? ' · ${p.finalMark}' : '';
return ListTile(
dense: true,
contentPadding: EdgeInsets.zero,
title: Text(p.name),
trailing: Text(
'$statusLabel$mark',
style: TextStyle(
color: p.status == 'failed'
? Theme.of(context).colorScheme.error
: Colors.green.shade700,
fontWeight: FontWeight.w600,
),
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Text(p.name),
),
const SizedBox(width: 8),
Text(
'$statusLabel$mark',
style: TextStyle(
color: p.status == 'failed'
? Theme.of(context).colorScheme.error
: Colors.green.shade700,
fontWeight: FontWeight.w600,
),
),
],
),
);
}),