Add pricing entry flow and platform admin foundations
This commit is contained in:
@@ -8,8 +8,12 @@ import '../../../core/providers/auth_provider.dart';
|
||||
import '../../../core/providers/locale_provider.dart';
|
||||
import '../../../core/router/app_router.dart';
|
||||
import '../../../core/theme/app_theme.dart';
|
||||
import '../../../models/job.dart';
|
||||
import '../../../models/tenant.dart';
|
||||
import '../../shared/location_completion_banner.dart';
|
||||
import '../../shared/tenant_team_screen.dart';
|
||||
import '../../shared/location_picker_sheet.dart';
|
||||
import '../../shared/tenant_location_data.dart';
|
||||
import '../connections/lab_connections_screen.dart';
|
||||
|
||||
class LabSettingsScreen extends ConsumerWidget {
|
||||
@@ -29,6 +33,17 @@ class LabSettingsScreen extends ConsumerWidget {
|
||||
body: ListView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
if (tenant?.hasLocation != true) ...[
|
||||
LocationCompletionBanner(
|
||||
title: 'Konum eksik',
|
||||
description:
|
||||
'Laboratuvarınızın haritada görünmesi ve kliniklerin sizi yakın sonuçlarda bulabilmesi için koordinat kaydı tamamlanmalı.',
|
||||
buttonLabel: 'Konumu Düzenle',
|
||||
onTap: () => _showEditSheet(context, ref, tenant, s),
|
||||
compact: true,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
// User card
|
||||
_SectionHeader(title: s.userInfo),
|
||||
_UserCard(profile: profile),
|
||||
@@ -60,7 +75,9 @@ class LabSettingsScreen extends ConsumerWidget {
|
||||
_InfoTileBadge(
|
||||
icon: Icons.circle_outlined,
|
||||
label: s.status,
|
||||
value: tenant?.status == 'active' ? s.active : (tenant?.status ?? '-'),
|
||||
value: tenant?.status == 'active'
|
||||
? s.active
|
||||
: (tenant?.status ?? '-'),
|
||||
badgeColor: AppColors.success,
|
||||
badgeBg: AppColors.successBg,
|
||||
),
|
||||
@@ -69,9 +86,42 @@ class LabSettingsScreen extends ConsumerWidget {
|
||||
label: s.role,
|
||||
value: _roleLabel(membership?.role, s),
|
||||
),
|
||||
_InfoTile(
|
||||
icon: Icons.place_outlined,
|
||||
label: 'Konum',
|
||||
value: tenant?.locationLabel.isNotEmpty == true
|
||||
? tenant!.locationLabel
|
||||
: '-',
|
||||
),
|
||||
]),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
if (tenant != null && tenant.isLab) ...[
|
||||
_SectionHeader(
|
||||
title: 'İş Akışı',
|
||||
action: canEdit
|
||||
? IconButton(
|
||||
icon: const Icon(Icons.tune_rounded,
|
||||
size: 18, color: AppColors.accent),
|
||||
tooltip: 'Akışı Düzenle',
|
||||
onPressed: () => _showWorkflowSheet(context, ref, tenant),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
_InfoCard(
|
||||
children: [
|
||||
_WorkflowPreviewTile(
|
||||
enabledSteps: tenant.workflowOverrideSteps,
|
||||
canEdit: canEdit,
|
||||
onTap: canEdit
|
||||
? () => _showWorkflowSheet(context, ref, tenant)
|
||||
: null,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
|
||||
// Connections
|
||||
if (membership?.showConnections ?? false) ...[
|
||||
_SectionHeader(title: s.connections),
|
||||
@@ -107,7 +157,9 @@ class LabSettingsScreen extends ConsumerWidget {
|
||||
onTap: () {
|
||||
ref.read(authProvider.notifier).setActiveTenant(m);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(s.tenantSelected(m.tenant.companyName))),
|
||||
SnackBar(
|
||||
content:
|
||||
Text(s.tenantSelected(m.tenant.companyName))),
|
||||
);
|
||||
},
|
||||
),
|
||||
@@ -127,8 +179,7 @@ class LabSettingsScreen extends ConsumerWidget {
|
||||
subtitle: s.teamSub,
|
||||
onTap: () => Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => const TenantTeamScreen()),
|
||||
MaterialPageRoute(builder: (_) => const TenantTeamScreen()),
|
||||
),
|
||||
),
|
||||
_NavTile(
|
||||
@@ -155,6 +206,14 @@ class LabSettingsScreen extends ConsumerWidget {
|
||||
subtitle: s.aiAssistantSub,
|
||||
onTap: () => context.push(routeLabAi),
|
||||
),
|
||||
_NavTile(
|
||||
icon: Icons.workspace_premium_outlined,
|
||||
iconColor: AppColors.primary,
|
||||
iconBg: const Color(0xFFEFF6FF),
|
||||
title: 'Paketler ve AI Kredileri',
|
||||
subtitle: 'Trial ve paket görünümünü incele',
|
||||
onTap: () => context.push(routeWelcome),
|
||||
),
|
||||
]),
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
@@ -167,7 +226,8 @@ class LabSettingsScreen extends ConsumerWidget {
|
||||
iconColor: AppColors.accent,
|
||||
iconBg: AppColors.inProgressBg,
|
||||
title: s.appLanguage,
|
||||
subtitle: _currentLanguageLabel(ref.watch(localeProvider).languageCode, s),
|
||||
subtitle: _currentLanguageLabel(
|
||||
ref.watch(localeProvider).languageCode, s),
|
||||
onTap: () => _showLanguagePicker(context, ref, s),
|
||||
),
|
||||
]),
|
||||
@@ -191,7 +251,8 @@ class LabSettingsScreen extends ConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
void _showEditSheet(BuildContext context, WidgetRef ref, Tenant? tenant, AppStrings s) {
|
||||
void _showEditSheet(
|
||||
BuildContext context, WidgetRef ref, Tenant? tenant, AppStrings s) {
|
||||
if (tenant == null) return;
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
@@ -200,11 +261,12 @@ class LabSettingsScreen extends ConsumerWidget {
|
||||
builder: (_) => _EditTenantSheet(
|
||||
tenant: tenant,
|
||||
s: s,
|
||||
onSave: (name, currency) async {
|
||||
onSave: (name, currency, location) async {
|
||||
await ref.read(authProvider.notifier).updateTenantInfo(
|
||||
tenantId: tenant.id,
|
||||
companyName: name,
|
||||
defaultCurrency: currency,
|
||||
location: location,
|
||||
);
|
||||
},
|
||||
),
|
||||
@@ -219,6 +281,29 @@ class LabSettingsScreen extends ConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
void _showWorkflowSheet(
|
||||
BuildContext context,
|
||||
WidgetRef ref,
|
||||
Tenant tenant,
|
||||
) {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (_) => _WorkflowSettingsSheet(
|
||||
tenant: tenant,
|
||||
onSave: (steps) async {
|
||||
await ref.read(authProvider.notifier).updateTenantInfo(
|
||||
tenantId: tenant.id,
|
||||
companyName: tenant.companyName,
|
||||
defaultCurrency: tenant.defaultCurrency,
|
||||
workflowOverrides: steps.map((step) => step.value).toList(),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static String _tenantKindLabel(TenantKind? kind, AppStrings s) =>
|
||||
switch (kind) {
|
||||
TenantKind.clinic => s.tenantKindClinic,
|
||||
@@ -226,7 +311,8 @@ class LabSettingsScreen extends ConsumerWidget {
|
||||
null => '-',
|
||||
};
|
||||
|
||||
static String _currentLanguageLabel(String code, AppStrings s) => switch (code) {
|
||||
static String _currentLanguageLabel(String code, AppStrings s) =>
|
||||
switch (code) {
|
||||
'en' => s.languageEnglish,
|
||||
'ru' => s.languageRussian,
|
||||
'ar' => s.languageArabic,
|
||||
@@ -334,7 +420,11 @@ class _EditTenantSheet extends StatefulWidget {
|
||||
});
|
||||
final Tenant tenant;
|
||||
final AppStrings s;
|
||||
final Future<void> Function(String companyName, String currency) onSave;
|
||||
final Future<void> Function(
|
||||
String companyName,
|
||||
String currency,
|
||||
TenantLocationData location,
|
||||
) onSave;
|
||||
|
||||
@override
|
||||
State<_EditTenantSheet> createState() => _EditTenantSheetState();
|
||||
@@ -342,7 +432,11 @@ class _EditTenantSheet extends StatefulWidget {
|
||||
|
||||
class _EditTenantSheetState extends State<_EditTenantSheet> {
|
||||
late final TextEditingController _nameController;
|
||||
late final TextEditingController _addressController;
|
||||
late final TextEditingController _cityController;
|
||||
late final TextEditingController _districtController;
|
||||
late String _selectedCurrency;
|
||||
late TenantLocationData _location;
|
||||
bool _saving = false;
|
||||
|
||||
static const _currencies = [
|
||||
@@ -358,26 +452,39 @@ class _EditTenantSheetState extends State<_EditTenantSheet> {
|
||||
super.initState();
|
||||
_nameController = TextEditingController(text: widget.tenant.companyName);
|
||||
_selectedCurrency = widget.tenant.defaultCurrency;
|
||||
_location = TenantLocationData.fromTenant(widget.tenant);
|
||||
_addressController = TextEditingController(text: _location.address ?? '');
|
||||
_cityController = TextEditingController(text: _location.city ?? '');
|
||||
_districtController = TextEditingController(text: _location.district ?? '');
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_nameController.dispose();
|
||||
_addressController.dispose();
|
||||
_cityController.dispose();
|
||||
_districtController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _submit() async {
|
||||
final name = _nameController.text.trim();
|
||||
if (name.isEmpty) return;
|
||||
final location = _location.copyWith(
|
||||
address: _addressController.text.trim(),
|
||||
city: _cityController.text.trim(),
|
||||
district: _districtController.text.trim(),
|
||||
);
|
||||
if (!location.hasDetails) return;
|
||||
setState(() => _saving = true);
|
||||
final navigator = Navigator.of(context);
|
||||
final messenger = ScaffoldMessenger.of(context);
|
||||
try {
|
||||
await widget.onSave(name, _selectedCurrency);
|
||||
await widget.onSave(name, _selectedCurrency, location);
|
||||
navigator.pop();
|
||||
} catch (e) {
|
||||
messenger.showSnackBar(
|
||||
SnackBar(content: Text('${widget.s.errorPrefix}: $e')));
|
||||
messenger
|
||||
.showSnackBar(SnackBar(content: Text('${widget.s.errorPrefix}: $e')));
|
||||
} finally {
|
||||
if (mounted) setState(() => _saving = false);
|
||||
}
|
||||
@@ -431,7 +538,7 @@ class _EditTenantSheetState extends State<_EditTenantSheet> {
|
||||
color: AppColors.textSecondary)),
|
||||
const SizedBox(height: 8),
|
||||
DropdownButtonFormField<String>(
|
||||
value: _selectedCurrency,
|
||||
initialValue: _selectedCurrency,
|
||||
decoration: InputDecoration(
|
||||
contentPadding:
|
||||
const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
||||
@@ -454,13 +561,91 @@ class _EditTenantSheetState extends State<_EditTenantSheet> {
|
||||
if (v != null) setState(() => _selectedCurrency = v);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(14),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: AppColors.border),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Konum',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
_location.fullLabel.isNotEmpty
|
||||
? _location.fullLabel
|
||||
: 'Henüz konum veya adres bilgisi girilmedi.',
|
||||
style: const TextStyle(color: AppColors.textSecondary),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
OutlinedButton.icon(
|
||||
onPressed: () async {
|
||||
final picked = await showLocationPickerSheet(
|
||||
context,
|
||||
initialLocation: _location,
|
||||
title: 'Laboratuvar Konumu',
|
||||
);
|
||||
if (picked != null) {
|
||||
setState(() => _location = picked);
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.map_outlined),
|
||||
label: const Text('Haritadan Konum Seç'),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
TextFormField(
|
||||
controller: _addressController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Açık Adres',
|
||||
hintText: 'Cadde, sokak, mahalle bilgisi',
|
||||
),
|
||||
maxLines: 2,
|
||||
textCapitalization: TextCapitalization.sentences,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
controller: _cityController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Şehir',
|
||||
),
|
||||
textCapitalization: TextCapitalization.words,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
controller: _districtController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'İlçe',
|
||||
),
|
||||
textCapitalization: TextCapitalization.words,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
if (_saving)
|
||||
const Center(
|
||||
child: CircularProgressIndicator(color: AppColors.accent))
|
||||
else
|
||||
FilledButton(
|
||||
onPressed: _submit,
|
||||
onPressed: _saving ? null : _submit,
|
||||
style: FilledButton.styleFrom(
|
||||
minimumSize: const Size(double.infinity, 48)),
|
||||
child: Text(s.save),
|
||||
@@ -593,7 +778,10 @@ class _InfoCard extends StatelessWidget {
|
||||
children[i],
|
||||
if (i < children.length - 1)
|
||||
const Divider(
|
||||
height: 1, indent: 16, endIndent: 16, color: AppColors.border),
|
||||
height: 1,
|
||||
indent: 16,
|
||||
endIndent: 16,
|
||||
color: AppColors.border),
|
||||
],
|
||||
],
|
||||
),
|
||||
@@ -662,12 +850,11 @@ class _InfoTileBadge extends StatelessWidget {
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(label,
|
||||
style: const TextStyle(
|
||||
fontSize: 11, color: AppColors.textMuted)),
|
||||
style:
|
||||
const TextStyle(fontSize: 11, color: AppColors.textMuted)),
|
||||
),
|
||||
Container(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: badgeBg,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
@@ -704,8 +891,7 @@ class _NavTile extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListTile(
|
||||
contentPadding:
|
||||
const EdgeInsets.symmetric(horizontal: 16, vertical: 2),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 2),
|
||||
leading: Container(
|
||||
width: 36,
|
||||
height: 36,
|
||||
@@ -720,13 +906,170 @@ class _NavTile extends StatelessWidget {
|
||||
? Text(subtitle!,
|
||||
style: const TextStyle(color: AppColors.textSecondary))
|
||||
: null,
|
||||
trailing:
|
||||
const Icon(Icons.chevron_right, color: AppColors.textSecondary),
|
||||
trailing: const Icon(Icons.chevron_right, color: AppColors.textSecondary),
|
||||
onTap: onTap,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _WorkflowPreviewTile extends StatelessWidget {
|
||||
const _WorkflowPreviewTile({
|
||||
required this.enabledSteps,
|
||||
required this.canEdit,
|
||||
this.onTap,
|
||||
});
|
||||
|
||||
final List<JobStep> enabledSteps;
|
||||
final bool canEdit;
|
||||
final VoidCallback? onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final summary = enabledSteps.isEmpty
|
||||
? 'Varsayılan preset akışı kullanılıyor.'
|
||||
: 'Ekstra adımlar: ${enabledSteps.map((step) => step.label).join(', ')}';
|
||||
|
||||
return ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||
leading: Container(
|
||||
width: 36,
|
||||
height: 36,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.inProgressBg,
|
||||
borderRadius: BorderRadius.circular(9),
|
||||
),
|
||||
child:
|
||||
const Icon(Icons.route_outlined, color: AppColors.accent, size: 18),
|
||||
),
|
||||
title: const Text(
|
||||
'Ekstra Laboratuvar Adımları',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
subtitle: Text(
|
||||
summary,
|
||||
style: const TextStyle(color: AppColors.textSecondary),
|
||||
),
|
||||
trailing: canEdit
|
||||
? const Icon(Icons.chevron_right, color: AppColors.textSecondary)
|
||||
: null,
|
||||
onTap: onTap,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _WorkflowSettingsSheet extends StatefulWidget {
|
||||
const _WorkflowSettingsSheet({
|
||||
required this.tenant,
|
||||
required this.onSave,
|
||||
});
|
||||
|
||||
final Tenant tenant;
|
||||
final Future<void> Function(List<JobStep> steps) onSave;
|
||||
|
||||
@override
|
||||
State<_WorkflowSettingsSheet> createState() => _WorkflowSettingsSheetState();
|
||||
}
|
||||
|
||||
class _WorkflowSettingsSheetState extends State<_WorkflowSettingsSheet> {
|
||||
late final Set<JobStep> _selected =
|
||||
widget.tenant.workflowOverrideSteps.toSet();
|
||||
bool _saving = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: const BoxDecoration(
|
||||
color: AppColors.surface,
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
|
||||
),
|
||||
padding: EdgeInsets.only(
|
||||
left: 20,
|
||||
right: 20,
|
||||
top: 24,
|
||||
bottom: MediaQuery.of(context).viewInsets.bottom + 24,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Ekstra İş Adımları',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const Text(
|
||||
'Bunlar preset akışın üstüne eklenir. Bazı adımlar klinik onayı ister, bazıları laboratuvar içidir.',
|
||||
style: TextStyle(color: AppColors.textSecondary),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Flexible(
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: optionalLabStepCatalog.map((step) {
|
||||
final selected = _selected.contains(step);
|
||||
return CheckboxListTile(
|
||||
value: selected,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
if (value == true) {
|
||||
_selected.add(step);
|
||||
} else {
|
||||
_selected.remove(step);
|
||||
}
|
||||
});
|
||||
},
|
||||
title: Text(step.label),
|
||||
subtitle: Text(
|
||||
'${step.description} · ${step.requiresClinicApproval ? "Klinik onayı gerekir" : "Laboratuvar iç adımı"}',
|
||||
),
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
FilledButton(
|
||||
onPressed: _saving
|
||||
? null
|
||||
: () async {
|
||||
final navigator = Navigator.of(context);
|
||||
setState(() => _saving = true);
|
||||
try {
|
||||
await widget.onSave(_selected.toList());
|
||||
if (mounted) navigator.pop();
|
||||
} finally {
|
||||
if (mounted) setState(() => _saving = false);
|
||||
}
|
||||
},
|
||||
style: FilledButton.styleFrom(
|
||||
minimumSize: const Size.fromHeight(48),
|
||||
),
|
||||
child: _saving
|
||||
? const SizedBox(
|
||||
width: 18,
|
||||
height: 18,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
color: Colors.white,
|
||||
),
|
||||
)
|
||||
: const Text('Kaydet'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SignOutCard extends StatelessWidget {
|
||||
const _SignOutCard({required this.ref, required this.s});
|
||||
final WidgetRef ref;
|
||||
@@ -747,16 +1090,14 @@ class _SignOutCard extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
child: ListTile(
|
||||
contentPadding:
|
||||
const EdgeInsets.symmetric(horizontal: 16, vertical: 2),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 2),
|
||||
leading: Container(
|
||||
width: 36,
|
||||
height: 36,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.cancelledBg,
|
||||
borderRadius: BorderRadius.circular(9)),
|
||||
child: const Icon(Icons.logout,
|
||||
color: AppColors.cancelled, size: 18),
|
||||
child: const Icon(Icons.logout, color: AppColors.cancelled, size: 18),
|
||||
),
|
||||
title: Text(s.signOut,
|
||||
style: const TextStyle(
|
||||
|
||||
Reference in New Issue
Block a user