clean: remove pre-filled search bar clutter and unify direct d10 dice input on home screen
This commit is contained in:
@@ -17,72 +17,21 @@ class HomeSearchScreen extends StatefulWidget {
|
|||||||
|
|
||||||
class _HomeSearchScreenState extends State<HomeSearchScreen> {
|
class _HomeSearchScreenState extends State<HomeSearchScreen> {
|
||||||
final _repo = WordsRepository();
|
final _repo = WordsRepository();
|
||||||
final _searchController = TextEditingController();
|
|
||||||
final _questionController = TextEditingController();
|
final _questionController = TextEditingController();
|
||||||
final _focusNode = FocusNode();
|
|
||||||
final _diceRollerKey = GlobalKey<D10DiceRollerWidgetState>();
|
final _diceRollerKey = GlobalKey<D10DiceRollerWidgetState>();
|
||||||
|
|
||||||
WordItem? _currentWord;
|
WordItem? _currentWord;
|
||||||
String? _activeHistoryId;
|
String? _activeHistoryId;
|
||||||
List<WordItem> _searchResults = [];
|
|
||||||
bool _showKeypad = true;
|
bool _showKeypad = true;
|
||||||
String _currentDigits = ''; // Tracks 0..3 digits input for d10 dice cleromancy
|
String _currentDigits = ''; // Tracks 0..3 digits input: e.g. "", "7", "74", "742"
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_searchController.addListener(_onSearchChanged);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_searchController.removeListener(_onSearchChanged);
|
|
||||||
_searchController.dispose();
|
|
||||||
_questionController.dispose();
|
_questionController.dispose();
|
||||||
_focusNode.dispose();
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onSearchChanged() {
|
Future<void> _setWord(WordItem word, {bool recordHistory = true}) async {
|
||||||
final query = _searchController.text.trim();
|
|
||||||
if (query.isEmpty) {
|
|
||||||
setState(() {
|
|
||||||
_searchResults = [];
|
|
||||||
_currentDigits = '';
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final isNumeric = int.tryParse(query) != null;
|
|
||||||
|
|
||||||
if (isNumeric) {
|
|
||||||
setState(() {
|
|
||||||
_currentDigits = query;
|
|
||||||
});
|
|
||||||
// In Cleromancy, exactly 3 digits (d10x3) are required to resolve a number 000..999
|
|
||||||
if (query.length >= 3) {
|
|
||||||
final num = int.tryParse(query);
|
|
||||||
if (num != null && num <= 999) {
|
|
||||||
final word = _repo.getWordBySessionNumber(num);
|
|
||||||
if (word != null) {
|
|
||||||
_setWord(word, recordHistory: true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Text search by word
|
|
||||||
final results = _repo.search(query);
|
|
||||||
setState(() {
|
|
||||||
_searchResults = results;
|
|
||||||
_currentDigits = '';
|
|
||||||
if (results.isNotEmpty) {
|
|
||||||
_setWord(results.first, recordHistory: true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _setWord(WordItem word, {bool updateText = false, bool recordHistory = true}) async {
|
|
||||||
final question = _questionController.text.trim();
|
final question = _questionController.text.trim();
|
||||||
String? newHistId;
|
String? newHistId;
|
||||||
|
|
||||||
@@ -105,71 +54,78 @@ class _HomeSearchScreenState extends State<HomeSearchScreen> {
|
|||||||
_currentWord = word;
|
_currentWord = word;
|
||||||
_activeHistoryId = newHistId ?? _activeHistoryId;
|
_activeHistoryId = newHistId ?? _activeHistoryId;
|
||||||
_currentDigits = word.formattedSessionNumber;
|
_currentDigits = word.formattedSessionNumber;
|
||||||
if (updateText) {
|
|
||||||
_searchController.text = word.formattedSessionNumber;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
HapticFeedback.selectionClick();
|
HapticFeedback.selectionClick();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onKeypadTap(String val) {
|
void _onKeypadTap(String val) {
|
||||||
HapticFeedback.lightImpact();
|
HapticFeedback.lightImpact();
|
||||||
String currentText = _searchController.text;
|
|
||||||
|
|
||||||
if (val == 'C') {
|
if (val == 'C') {
|
||||||
_searchController.clear();
|
setState(() {
|
||||||
setState(() => _currentDigits = '');
|
_currentDigits = '';
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (val == '⌫') {
|
if (val == '⌫') {
|
||||||
if (currentText.isNotEmpty) {
|
if (_currentDigits.isNotEmpty) {
|
||||||
_searchController.text = currentText.substring(0, currentText.length - 1);
|
setState(() {
|
||||||
_searchController.selection = TextSelection.fromPosition(
|
_currentDigits = _currentDigits.substring(0, _currentDigits.length - 1);
|
||||||
TextPosition(offset: _searchController.text.length),
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// In Cleromancy, append digit up to 3 digits
|
// Append digit up to 3 digits (1st d10, 2nd d10, 3rd d10)
|
||||||
if (currentText.length >= 3) {
|
String newDigits;
|
||||||
_searchController.text = val;
|
if (_currentDigits.length >= 3) {
|
||||||
|
newDigits = val;
|
||||||
} else {
|
} else {
|
||||||
_searchController.text = currentText + val;
|
newDigits = _currentDigits + val;
|
||||||
}
|
}
|
||||||
|
|
||||||
_searchController.selection = TextSelection.fromPosition(
|
setState(() {
|
||||||
TextPosition(offset: _searchController.text.length),
|
_currentDigits = newDigits;
|
||||||
);
|
});
|
||||||
|
|
||||||
|
// Only resolve word when all 3 digits are provided!
|
||||||
|
if (newDigits.length == 3) {
|
||||||
|
final num = int.tryParse(newDigits);
|
||||||
|
if (num != null && num <= 999) {
|
||||||
|
final word = _repo.getWordBySessionNumber(num);
|
||||||
|
if (word != null) {
|
||||||
|
_setWord(word, recordHistory: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onVirtualRollCompleted(int finalNumber) {
|
void _onVirtualRollCompleted(int finalNumber) {
|
||||||
final word = _repo.getWordBySessionNumber(finalNumber);
|
final word = _repo.getWordBySessionNumber(finalNumber);
|
||||||
if (word != null) {
|
if (word != null) {
|
||||||
_setWord(word, updateText: true, recordHistory: true);
|
_setWord(word, recordHistory: true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _resetToWelcome() {
|
void _resetToWelcome() {
|
||||||
setState(() {
|
setState(() {
|
||||||
_currentWord = null;
|
_currentWord = null;
|
||||||
_searchController.clear();
|
|
||||||
_searchResults = [];
|
|
||||||
_currentDigits = '';
|
_currentDigits = '';
|
||||||
|
_activeHistoryId = null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _goToNext() {
|
void _goToNext() {
|
||||||
if (_currentWord == null) return;
|
if (_currentWord == null) return;
|
||||||
final next = _repo.getNextWord(_currentWord!.sessionNumber);
|
final next = _repo.getNextWord(_currentWord!.sessionNumber);
|
||||||
_setWord(next, updateText: true, recordHistory: false);
|
_setWord(next, recordHistory: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _goToPrevious() {
|
void _goToPrevious() {
|
||||||
if (_currentWord == null) return;
|
if (_currentWord == null) return;
|
||||||
final prev = _repo.getPreviousWord(_currentWord!.sessionNumber);
|
final prev = _repo.getPreviousWord(_currentWord!.sessionNumber);
|
||||||
_setWord(prev, updateText: true, recordHistory: false);
|
_setWord(prev, recordHistory: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _reshuffleSession() {
|
void _reshuffleSession() {
|
||||||
@@ -289,7 +245,7 @@ class _HomeSearchScreenState extends State<HomeSearchScreen> {
|
|||||||
|
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
|
|
||||||
// D10 Dice Roller Slots Widget
|
// D10 Dice Roller Slots Widget (Clean & without prefilled junk)
|
||||||
D10DiceRollerWidget(
|
D10DiceRollerWidget(
|
||||||
key: _diceRollerKey,
|
key: _diceRollerKey,
|
||||||
digits: _currentDigits,
|
digits: _currentDigits,
|
||||||
@@ -298,109 +254,6 @@ class _HomeSearchScreenState extends State<HomeSearchScreen> {
|
|||||||
onRollCompleted: _onVirtualRollCompleted,
|
onRollCompleted: _onVirtualRollCompleted,
|
||||||
),
|
),
|
||||||
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
|
|
||||||
// Search & Direct Number Input Bar
|
|
||||||
TextField(
|
|
||||||
controller: _searchController,
|
|
||||||
focusNode: _focusNode,
|
|
||||||
keyboardType: TextInputType.text,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'Введите число (#000..#999) или слово',
|
|
||||||
hintText: 'Например: 777 или огонь',
|
|
||||||
prefixIcon: const Padding(
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: 12),
|
|
||||||
child: GradientStar(size: 18),
|
|
||||||
),
|
|
||||||
suffixIcon: _searchController.text.isNotEmpty
|
|
||||||
? IconButton(
|
|
||||||
icon: const Icon(Icons.clear_rounded),
|
|
||||||
onPressed: () {
|
|
||||||
_searchController.clear();
|
|
||||||
_focusNode.unfocus();
|
|
||||||
setState(() => _currentDigits = '');
|
|
||||||
},
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
filled: true,
|
|
||||||
fillColor: isDark
|
|
||||||
? const Color(0xFF1B1D30)
|
|
||||||
: theme.colorScheme.surfaceContainerHighest.withValues(alpha: 0.4),
|
|
||||||
border: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.circular(18),
|
|
||||||
borderSide: BorderSide(
|
|
||||||
color: const Color(0xFFFFD54F).withValues(alpha: 0.3),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
enabledBorder: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.circular(18),
|
|
||||||
borderSide: BorderSide(
|
|
||||||
color: const Color(0xFFFFD54F).withValues(alpha: 0.25),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
focusedBorder: OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.circular(18),
|
|
||||||
borderSide: const BorderSide(
|
|
||||||
color: Color(0xFFFFD54F),
|
|
||||||
width: 1.8,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
// Search matches dropdown list (for text search)
|
|
||||||
if (_searchResults.length > 1 && _searchController.text.isNotEmpty && int.tryParse(_searchController.text) == null) ...[
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Container(
|
|
||||||
constraints: const BoxConstraints(maxHeight: 140),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: isDark ? const Color(0xFF161828) : theme.colorScheme.surface,
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
|
||||||
border: Border.all(
|
|
||||||
color: const Color(0xFFFFD54F).withValues(alpha: 0.3),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: ListView.separated(
|
|
||||||
shrinkWrap: true,
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
|
||||||
itemCount: _searchResults.length > 10 ? 10 : _searchResults.length,
|
|
||||||
separatorBuilder: (_, _) => const Divider(height: 1),
|
|
||||||
itemBuilder: (context, idx) {
|
|
||||||
final item = _searchResults[idx];
|
|
||||||
final isSelected = item.sessionNumber == _currentWord?.sessionNumber;
|
|
||||||
return ListTile(
|
|
||||||
dense: true,
|
|
||||||
selected: isSelected,
|
|
||||||
leading: GradientStarBadge(
|
|
||||||
text: '#${item.formattedSessionNumber}',
|
|
||||||
size: 14,
|
|
||||||
),
|
|
||||||
title: Text(
|
|
||||||
item.word.toUpperCase(),
|
|
||||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
subtitle: Text(
|
|
||||||
item.description,
|
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
style: const TextStyle(fontSize: 11),
|
|
||||||
),
|
|
||||||
trailing: Text(
|
|
||||||
item.partOfSpeech,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 11,
|
|
||||||
color: theme.colorScheme.outline,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
onTap: () {
|
|
||||||
_setWord(item, updateText: true, recordHistory: true);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
|
|
||||||
const SizedBox(height: 14),
|
const SizedBox(height: 14),
|
||||||
|
|
||||||
// Welcome Idle State OR Active Word Card
|
// Welcome Idle State OR Active Word Card
|
||||||
@@ -439,7 +292,7 @@ class _HomeSearchScreenState extends State<HomeSearchScreen> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(
|
Text(
|
||||||
'Сформулируйте вопрос, затем совершите 3 броска кубика d10 кнопкой выше или введите 3 выпавших числа (Сотни, Десятки, Единицы).',
|
'Сформулируйте вопрос, затем совершите 3 броска кубика d10 кнопкой выше или наберите 3 выпавших грани на панели.',
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
@@ -501,7 +354,7 @@ class _HomeSearchScreenState extends State<HomeSearchScreen> {
|
|||||||
|
|
||||||
const SizedBox(height: 14),
|
const SizedBox(height: 14),
|
||||||
|
|
||||||
// Interactive Numeric Keypad
|
// Interactive Numeric Keypad (Directly maps to 3 d10 dice slots)
|
||||||
if (_showKeypad) ...[
|
if (_showKeypad) ...[
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
@@ -524,7 +377,7 @@ class _HomeSearchScreenState extends State<HomeSearchScreen> {
|
|||||||
Icon(Icons.casino_outlined, size: 15, color: Color(0xFFFFD54F)),
|
Icon(Icons.casino_outlined, size: 15, color: Color(0xFFFFD54F)),
|
||||||
SizedBox(width: 6),
|
SizedBox(width: 6),
|
||||||
Text(
|
Text(
|
||||||
'Ввод граней d10 (3 цифры):',
|
'Ввод граней d10:',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.w700,
|
fontWeight: FontWeight.w700,
|
||||||
@@ -534,9 +387,11 @@ class _HomeSearchScreenState extends State<HomeSearchScreen> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
_currentDigits.length == 3
|
_currentDigits.isEmpty
|
||||||
|
? 'Ожидание 3 цифр'
|
||||||
|
: (_currentDigits.length == 3
|
||||||
? 'Код: #$_currentDigits'
|
? 'Код: #$_currentDigits'
|
||||||
: 'Введено: ${_currentDigits.length} из 3',
|
: 'Введено: ${_currentDigits.length} из 3'),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
|
|||||||
Reference in New Issue
Block a user