2026-05-12 18:21:25 +02:00
import ' package:flutter/material.dart ' ;
import ' package:provider/provider.dart ' ;
import ' package:sighej/services/session_store.dart ' ;
2026-05-17 15:52:58 +02:00
const kAvailableInterests = [
' Tech ' , ' Musik ' , ' Filosofi ' , ' Design ' , ' DevOps ' , ' Bøger ' , ' Gaming ' ,
' Fitness ' , ' Kunst ' , ' Mad ' , ' Rejser ' , ' Videnskab ' , ' Iværksætteri ' ,
' Film ' , ' Natur ' , ' Kodning ' , ' Podcast ' , ' Arkitektur ' , ' Klima ' , ' Sport ' ,
2026-05-12 18:21:25 +02:00
] ;
class ProfileScreen extends StatefulWidget {
2026-05-17 15:52:58 +02:00
/// [isSetup] = true → first-run onboarding (no back button)
/// [isSetup] = false → edit mode from HomeScreen
2026-05-12 18:21:25 +02:00
final bool isSetup ;
const ProfileScreen ( { super . key , this . isSetup = false } ) ;
@ override
State < ProfileScreen > createState ( ) = > _ProfileScreenState ( ) ;
}
class _ProfileScreenState extends State < ProfileScreen > {
2026-05-17 15:52:58 +02:00
final _nameCtrl = TextEditingController ( ) ;
final _taglineCtrl = TextEditingController ( ) ;
final Set < String > _selected = { } ;
bool _triedSubmit = false ;
bool _loaded = false ;
2026-05-12 18:21:25 +02:00
@ override
2026-05-17 15:52:58 +02:00
void didChangeDependencies ( ) {
super . didChangeDependencies ( ) ;
if ( ! _loaded ) {
final store = context . read < SessionStore > ( ) ;
_nameCtrl . text = store . name ;
_taglineCtrl . text = store . tagline ;
_selected . addAll ( store . interests ) ;
_loaded = true ;
}
2026-05-12 18:21:25 +02:00
}
@ override
void dispose ( ) {
_nameCtrl . dispose ( ) ;
_taglineCtrl . dispose ( ) ;
super . dispose ( ) ;
}
Future < void > _save ( ) async {
2026-05-17 15:52:58 +02:00
setState ( ( ) = > _triedSubmit = true ) ;
if ( _selected . isEmpty ) return ;
final store = context . read < SessionStore > ( ) ;
await store . saveProfile (
name: _nameCtrl . text . trim ( ) ,
tagline: _taglineCtrl . text . trim ( ) ,
interests: _selected . toList ( ) ,
) ;
if ( ! mounted ) return ;
if ( widget . isSetup ) {
Navigator . of ( context ) . pushReplacement (
MaterialPageRoute ( builder: ( _ ) = > const _HomeRedirect ( ) ) ,
) ;
} else {
Navigator . of ( context ) . pop ( ) ;
}
2026-05-12 18:21:25 +02:00
}
@ override
Widget build ( BuildContext context ) {
2026-05-17 15:52:58 +02:00
final cs = Theme . of ( context ) . colorScheme ;
2026-05-12 18:21:25 +02:00
return Scaffold (
2026-05-17 15:52:58 +02:00
backgroundColor: cs . surface ,
body: Center (
child: ConstrainedBox (
constraints: const BoxConstraints ( maxWidth: 440 ) ,
child: Column (
children: [
// ── Gradient header ──────────────────────────────────
_Header ( isSetup: widget . isSetup ) ,
// ── Scrollable form body ─────────────────────────────
Expanded (
child: SingleChildScrollView (
padding: const EdgeInsets . fromLTRB ( 24 , 24 , 24 , 0 ) ,
child: Column (
crossAxisAlignment: CrossAxisAlignment . start ,
children: [
_buildTextField (
context ,
label: ' Kaldenavn ' ,
hint: ' F.eks. "Henrik" eller "Tech-nerd" ' ,
sublabel: ' Valgfrit ' ,
controller: _nameCtrl ,
maxLength: 40 ,
2026-05-12 18:21:25 +02:00
) ,
2026-05-17 15:52:58 +02:00
const SizedBox ( height: 20 ) ,
_buildTextField (
context ,
label: ' Hvad er du op til i dag? ' ,
hint: ' "Åben for en kaffesnak" eller "Klar til at netværke" ' ,
sublabel: ' Valgfrit — sæt tonen ' ,
controller: _taglineCtrl ,
maxLength: 80 ,
2026-05-12 18:21:25 +02:00
) ,
2026-05-17 15:52:58 +02:00
const SizedBox ( height: 28 ) ,
Row ( children: [
Text ( ' Interesser ' ,
style: Theme . of ( context )
. textTheme
. titleMedium
? . copyWith (
fontWeight: FontWeight . w700 ,
color: cs . onSurface ) ) ,
const SizedBox ( width: 8 ) ,
Container (
padding: const EdgeInsets . symmetric (
horizontal: 8 , vertical: 2 ) ,
decoration: BoxDecoration (
color: _selected . isEmpty & & _triedSubmit
? cs . errorContainer
: cs . primaryContainer ,
borderRadius: BorderRadius . circular ( 20 ) ,
) ,
child: Text (
' ${ _selected . length } valgt ' ,
style: TextStyle (
fontSize: 11 ,
fontWeight: FontWeight . w600 ,
color: _selected . isEmpty & & _triedSubmit
? cs . onErrorContainer
: cs . onPrimaryContainer ,
) ,
2026-05-12 18:21:25 +02:00
) ,
) ,
2026-05-17 15:52:58 +02:00
] ) ,
const SizedBox ( height: 4 ) ,
Text ( ' Vælg mindst én ' ,
style: Theme . of ( context )
. textTheme
. bodySmall
? . copyWith ( color: cs . onSurfaceVariant ) ) ,
const SizedBox ( height: 12 ) ,
Wrap (
spacing: 8 ,
runSpacing: 8 ,
children: kAvailableInterests . map ( ( tag ) {
final on = _selected . contains ( tag ) ;
return _InterestChip (
label: tag ,
selected: on ,
onTap: ( ) = > setState ( ( ) = >
on ? _selected . remove ( tag ) : _selected . add ( tag ) ) ,
) ;
} ) . toList ( ) ,
2026-05-12 18:21:25 +02:00
) ,
2026-05-17 15:52:58 +02:00
if ( _triedSubmit & & _selected . isEmpty ) . . . [
const SizedBox ( height: 8 ) ,
Text ( ' Vælg mindst ét emne for at bruge SigHej. ' ,
style: TextStyle (
color: cs . error , fontSize: 12 ) ) ,
] ,
const SizedBox ( height: 32 ) ,
] ,
) ,
2026-05-12 18:21:25 +02:00
) ,
) ,
2026-05-17 15:52:58 +02:00
// ── Save button ──────────────────────────────────────
Padding (
padding: const EdgeInsets . fromLTRB ( 24 , 12 , 24 , 32 ) ,
child: SizedBox (
width: double . infinity ,
height: 54 ,
child: FilledButton (
onPressed: _save ,
style: FilledButton . styleFrom (
backgroundColor: cs . primary ,
foregroundColor: cs . onPrimary ,
shape: RoundedRectangleBorder (
borderRadius: BorderRadius . circular ( 16 ) ) ,
) ,
child: Text (
widget . isSetup ? ' Kom i gang → ' : ' Gem profil ' ,
style: const TextStyle (
fontSize: 16 , fontWeight: FontWeight . w700 ) ,
) ,
) ,
) ,
) ,
] ,
) ,
) ,
) ,
) ;
}
Widget _buildTextField (
BuildContext context , {
required String label ,
required String hint ,
required String sublabel ,
required TextEditingController controller ,
required int maxLength ,
} ) {
final cs = Theme . of ( context ) . colorScheme ;
return Column (
crossAxisAlignment: CrossAxisAlignment . start ,
children: [
Text ( label ,
style: Theme . of ( context ) . textTheme . titleMedium ? . copyWith (
fontWeight: FontWeight . w700 , color: cs . onSurface ) ) ,
const SizedBox ( height: 2 ) ,
Text ( sublabel ,
style: Theme . of ( context )
. textTheme
. bodySmall
? . copyWith ( color: cs . onSurfaceVariant ) ) ,
const SizedBox ( height: 8 ) ,
TextField (
controller: controller ,
maxLength: maxLength ,
textCapitalization: TextCapitalization . sentences ,
style: TextStyle ( color: cs . onSurface ) ,
decoration: InputDecoration (
hintText: hint ,
hintStyle: TextStyle ( color: cs . onSurfaceVariant . withAlpha ( 160 ) ) ,
counterText: ' ' ,
filled: true ,
fillColor: cs . surfaceContainerHighest . withAlpha ( 120 ) ,
border: OutlineInputBorder (
borderRadius: BorderRadius . circular ( 12 ) ,
borderSide: BorderSide ( color: cs . outline . withAlpha ( 80 ) ) ,
) ,
enabledBorder: OutlineInputBorder (
borderRadius: BorderRadius . circular ( 12 ) ,
borderSide: BorderSide ( color: cs . outline . withAlpha ( 80 ) ) ,
) ,
focusedBorder: OutlineInputBorder (
borderRadius: BorderRadius . circular ( 12 ) ,
borderSide: BorderSide ( color: cs . primary , width: 2 ) ,
) ,
contentPadding:
const EdgeInsets . symmetric ( horizontal: 16 , vertical: 14 ) ,
) ,
) ,
] ,
) ;
}
}
class _Header extends StatelessWidget {
final bool isSetup ;
const _Header ( { required this . isSetup } ) ;
@ override
Widget build ( BuildContext context ) {
final isDark = Theme . of ( context ) . brightness = = Brightness . dark ;
return Container (
width: double . infinity ,
padding: EdgeInsets . fromLTRB (
24 , MediaQuery . of ( context ) . padding . top + 32 , 24 , 32 ) ,
decoration: BoxDecoration (
gradient: LinearGradient (
begin: Alignment . topLeft ,
end: Alignment . bottomRight ,
colors: isDark
? [ const Color ( 0xFF1A3A40 ) , const Color ( 0xFF0F2930 ) ]
: [ const Color ( 0xFF2A9D8F ) , const Color ( 0xFF264653 ) ] ,
) ,
) ,
child: Column (
crossAxisAlignment: CrossAxisAlignment . start ,
children: [
if ( ! isSetup )
IconButton (
icon: const Icon ( Icons . arrow_back , color: Colors . white ) ,
onPressed: ( ) = > Navigator . of ( context ) . pop ( ) ,
padding: EdgeInsets . zero ,
2026-05-12 18:21:25 +02:00
) ,
2026-05-17 15:52:58 +02:00
Row (
children: [
Container (
width: 48 ,
height: 48 ,
decoration: BoxDecoration (
color: Colors . white . withAlpha ( 30 ) ,
borderRadius: BorderRadius . circular ( 14 ) ,
2026-05-12 18:21:25 +02:00
) ,
2026-05-17 15:52:58 +02:00
child: const Icon ( Icons . waving_hand ,
color: Colors . white , size: 26 ) ,
) ,
const SizedBox ( width: 12 ) ,
Column (
crossAxisAlignment: CrossAxisAlignment . start ,
children: [
const Text ( ' SigHej ' ,
style: TextStyle (
color: Colors . white ,
fontSize: 22 ,
fontWeight: FontWeight . w800 ,
letterSpacing: - 0.5 ) ) ,
Text (
isSetup ? ' Fortæl lidt om dig selv ' : ' Rediger profil ' ,
style: TextStyle (
color: Colors . white . withAlpha ( 180 ) , fontSize: 13 ) ,
) ,
] ,
) ,
] ,
) ,
if ( isSetup ) . . . [
const SizedBox ( height: 20 ) ,
Container (
padding: const EdgeInsets . all ( 14 ) ,
decoration: BoxDecoration (
color: Colors . white . withAlpha ( 20 ) ,
borderRadius: BorderRadius . circular ( 12 ) ,
) ,
child: Text (
' SigHej sender en diskret notifikation, når nogen i nærheden deler dine interesser. Ingen profiler at swipe — bare et lille vink om at starte en samtale. ' ,
style: TextStyle (
color: Colors . white . withAlpha ( 210 ) ,
fontSize: 13 ,
height: 1.5 ) ,
2026-05-12 18:21:25 +02:00
) ,
) ,
] ,
2026-05-17 15:52:58 +02:00
] ,
2026-05-12 18:21:25 +02:00
) ,
) ;
}
}
2026-05-17 15:52:58 +02:00
class _InterestChip extends StatelessWidget {
2026-05-12 18:21:25 +02:00
final String label ;
2026-05-17 15:52:58 +02:00
final bool selected ;
final VoidCallback onTap ;
const _InterestChip (
{ required this . label , required this . selected , required this . onTap } ) ;
2026-05-12 18:21:25 +02:00
@ override
Widget build ( BuildContext context ) {
2026-05-17 15:52:58 +02:00
final cs = Theme . of ( context ) . colorScheme ;
return GestureDetector (
onTap: onTap ,
child: AnimatedContainer (
duration: const Duration ( milliseconds: 180 ) ,
padding: const EdgeInsets . symmetric ( horizontal: 14 , vertical: 8 ) ,
decoration: BoxDecoration (
color: selected ? cs . primary : cs . surfaceContainerHighest ,
borderRadius: BorderRadius . circular ( 20 ) ,
border: Border . all (
color: selected ? cs . primary : cs . outline . withAlpha ( 60 ) ,
width: selected ? 0 : 1 ,
2026-05-12 18:21:25 +02:00
) ,
2026-05-17 15:52:58 +02:00
) ,
child: Text (
label ,
style: TextStyle (
fontSize: 13 ,
fontWeight: selected ? FontWeight . w600 : FontWeight . w400 ,
color: selected ? cs . onPrimary : cs . onSurface ,
) ,
) ,
) ,
2026-05-12 18:21:25 +02:00
) ;
}
}
2026-05-17 15:52:58 +02:00
// Redirects to HomeScreen after profile save — avoids circular import.
class _HomeRedirect extends StatelessWidget {
const _HomeRedirect ( ) ;
@ override
Widget build ( BuildContext context ) {
// Defer to main.dart routing which will now see hasProfile=true.
WidgetsBinding . instance . addPostFrameCallback ( ( _ ) {
Navigator . of ( context ) . pushNamedAndRemoveUntil ( ' / ' , ( _ ) = > false ) ;
} ) ;
return const Scaffold ( body: Center ( child: CircularProgressIndicator ( ) ) ) ;
}
}