Improved MacOS support and organized screens and utils
This commit is contained in:
263
lib/utils/user_id_lookup_screen.dart
Normal file
263
lib/utils/user_id_lookup_screen.dart
Normal file
@@ -0,0 +1,263 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:gwid/api/api_service.dart';
|
||||
import 'package:gwid/models/contact.dart';
|
||||
|
||||
class UserIdLookupScreen extends StatefulWidget {
|
||||
const UserIdLookupScreen({super.key});
|
||||
|
||||
@override
|
||||
State<UserIdLookupScreen> createState() => _UserIdLookupScreenState();
|
||||
}
|
||||
|
||||
class _UserIdLookupScreenState extends State<UserIdLookupScreen> {
|
||||
final TextEditingController _idController = TextEditingController();
|
||||
final FocusNode _idFocusNode = FocusNode();
|
||||
bool _isLoading = false;
|
||||
Contact? _foundContact;
|
||||
bool _searchAttempted = false;
|
||||
|
||||
Future<void> _searchById() async {
|
||||
final String idText = _idController.text.trim();
|
||||
if (idText.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
final int? contactId = int.tryParse(idText);
|
||||
if (contactId == null) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Пожалуйста, введите корректный ID (только цифры)'),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
_idFocusNode.unfocus();
|
||||
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
_searchAttempted = true;
|
||||
_foundContact = null;
|
||||
});
|
||||
|
||||
try {
|
||||
final List<Contact> contacts = await ApiService.instance
|
||||
.fetchContactsByIds([contactId]);
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_foundContact = contacts.isNotEmpty ? contacts.first : null;
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text('Ошибка при поиске: $e')));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_idFocusNode.requestFocus();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Поиск по ID')),
|
||||
body: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
child: Column(
|
||||
children: [
|
||||
TextField(
|
||||
controller: _idController,
|
||||
focusNode: _idFocusNode,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Введите ID пользователя',
|
||||
filled: true,
|
||||
fillColor: colors.surfaceContainerHighest,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
prefixIcon: const Icon(Icons.person_search_outlined),
|
||||
suffixIcon: _isLoading
|
||||
? const Padding(
|
||||
padding: EdgeInsets.all(12.0),
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
: IconButton(
|
||||
icon: const Icon(Icons.search),
|
||||
onPressed: _searchById,
|
||||
),
|
||||
),
|
||||
keyboardType: TextInputType.number,
|
||||
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
||||
onSubmitted: (_) => _searchById(),
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
child: _isLoading
|
||||
? const Center(
|
||||
key: ValueKey('loading'),
|
||||
child: CircularProgressIndicator(),
|
||||
)
|
||||
: _searchAttempted
|
||||
? _foundContact != null
|
||||
? _buildContactCard(_foundContact!, colors)
|
||||
: _buildEmptyState(
|
||||
key: const ValueKey('not_found'),
|
||||
colors: colors,
|
||||
icon: Icons.search_off_rounded,
|
||||
title: 'Пользователь не найден',
|
||||
subtitle:
|
||||
'Аккаунт с ID "${_idController.text}" не существует или скрыт.',
|
||||
)
|
||||
: _buildEmptyState(
|
||||
key: const ValueKey('initial'),
|
||||
colors: colors,
|
||||
icon: Icons.person_search_rounded,
|
||||
title: 'Введите ID для поиска',
|
||||
subtitle: 'Найдем пользователя в системе по его ID',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildContactCard(Contact contact, ColorScheme colors) {
|
||||
return Column(
|
||||
key: const ValueKey('contact_card'),
|
||||
children: [
|
||||
CircleAvatar(
|
||||
radius: 56,
|
||||
backgroundColor: colors.primaryContainer,
|
||||
backgroundImage: contact.photoBaseUrl != null
|
||||
? NetworkImage(contact.photoBaseUrl!)
|
||||
: null,
|
||||
child: contact.photoBaseUrl == null
|
||||
? Text(
|
||||
contact.name.isNotEmpty ? contact.name[0].toUpperCase() : '?',
|
||||
style: Theme.of(context).textTheme.headlineLarge?.copyWith(
|
||||
color: colors.onPrimaryContainer,
|
||||
),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
contact.name,
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'ID: ${contact.id}',
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.bodyMedium?.copyWith(color: colors.onSurfaceVariant),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: colors.surfaceContainer,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
_buildInfoTile(
|
||||
colors: colors,
|
||||
icon: Icons.person_outlined,
|
||||
title: 'Имя',
|
||||
subtitle: contact.firstName,
|
||||
),
|
||||
const Divider(height: 1, indent: 16, endIndent: 16),
|
||||
_buildInfoTile(
|
||||
colors: colors,
|
||||
icon: Icons.badge_outlined,
|
||||
title: 'Фамилия',
|
||||
subtitle: contact.lastName,
|
||||
),
|
||||
const Divider(height: 1, indent: 16, endIndent: 16),
|
||||
_buildInfoTile(
|
||||
colors: colors,
|
||||
icon: Icons.notes_rounded,
|
||||
title: 'Описание',
|
||||
subtitle: contact.description,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildInfoTile({
|
||||
required ColorScheme colors,
|
||||
required IconData icon,
|
||||
required String title,
|
||||
String? subtitle,
|
||||
}) {
|
||||
final bool hasData = subtitle != null && subtitle.isNotEmpty;
|
||||
|
||||
return ListTile(
|
||||
leading: Icon(icon, color: colors.primary),
|
||||
title: Text(title),
|
||||
subtitle: Text(
|
||||
hasData ? subtitle : '(не указано)',
|
||||
style: TextStyle(
|
||||
color: hasData
|
||||
? colors.onSurfaceVariant
|
||||
: colors.onSurfaceVariant.withOpacity(0.7),
|
||||
fontStyle: hasData ? FontStyle.normal : FontStyle.italic,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildEmptyState({
|
||||
required Key key,
|
||||
required ColorScheme colors,
|
||||
required IconData icon,
|
||||
required String title,
|
||||
required String subtitle,
|
||||
}) {
|
||||
return Column(
|
||||
key: key,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(icon, size: 64, color: colors.onSurfaceVariant.withOpacity(0.5)),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
title,
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.titleLarge?.copyWith(color: colors.onSurfaceVariant),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
subtitle,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: colors.onSurfaceVariant.withOpacity(0.7),
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user