mirror of
https://github.com/FoggedLens/deflock-app.git
synced 2026-02-12 16:52:51 +00:00
log in button, submission guide cancel, filter nsi by popularity and entered text
This commit is contained in:
@@ -96,11 +96,16 @@ class _AddNodeSheetState extends State<AddNodeSheet> {
|
||||
|
||||
if (!hasSeenGuide) {
|
||||
// Show submission guide dialog first
|
||||
await showDialog<void>(
|
||||
final shouldProceed = await showDialog<bool>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (context) => const SubmissionGuideDialog(),
|
||||
);
|
||||
|
||||
// If user canceled the submission guide, don't proceed with submission
|
||||
if (shouldProceed != true) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Now proceed with proximity check
|
||||
@@ -296,6 +301,10 @@ class _AddNodeSheetState extends State<AddNodeSheet> {
|
||||
session.profile != null &&
|
||||
session.profile!.isSubmittable;
|
||||
|
||||
void _navigateToLogin() {
|
||||
Navigator.pushNamed(context, '/settings/osm-account');
|
||||
}
|
||||
|
||||
void _openRefineTags() async {
|
||||
final result = await Navigator.push<RefineTagsResult?>(
|
||||
context,
|
||||
@@ -439,8 +448,8 @@ class _AddNodeSheetState extends State<AddNodeSheet> {
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: allowSubmit ? _commit : null,
|
||||
child: Text(locService.t('actions.submit')),
|
||||
onPressed: !appState.isLoggedIn ? _navigateToLogin : (allowSubmit ? _commit : null),
|
||||
child: Text(!appState.isLoggedIn ? locService.t('actions.logIn') : locService.t('actions.submit')),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -81,11 +81,16 @@ class _EditNodeSheetState extends State<EditNodeSheet> {
|
||||
|
||||
if (!hasSeenGuide) {
|
||||
// Show submission guide dialog first
|
||||
await showDialog<void>(
|
||||
final shouldProceed = await showDialog<bool>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (context) => const SubmissionGuideDialog(),
|
||||
);
|
||||
|
||||
// If user canceled the submission guide, don't proceed with submission
|
||||
if (shouldProceed != true) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Now proceed with proximity check
|
||||
@@ -278,6 +283,10 @@ class _EditNodeSheetState extends State<EditNodeSheet> {
|
||||
session.profile != null &&
|
||||
session.profile!.isSubmittable;
|
||||
|
||||
void _navigateToLogin() {
|
||||
Navigator.pushNamed(context, '/settings/osm-account');
|
||||
}
|
||||
|
||||
void _openRefineTags() async {
|
||||
final result = await Navigator.push<RefineTagsResult?>(
|
||||
context,
|
||||
@@ -495,8 +504,8 @@ class _EditNodeSheetState extends State<EditNodeSheet> {
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: allowSubmit ? _commit : null,
|
||||
child: Text(locService.t('actions.saveEdit')),
|
||||
onPressed: !appState.isLoggedIn ? _navigateToLogin : (allowSubmit ? _commit : null),
|
||||
child: Text(!appState.isLoggedIn ? locService.t('actions.logIn') : locService.t('actions.saveEdit')),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -38,6 +38,7 @@ class _NSITagValueFieldState extends State<NSITagValueField> {
|
||||
_loadSuggestions();
|
||||
|
||||
_focusNode.addListener(_onFocusChanged);
|
||||
_controller.addListener(_onTextChanged);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -65,6 +66,26 @@ class _NSITagValueFieldState extends State<NSITagValueField> {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/// Get filtered suggestions based on current text input (case-sensitive)
|
||||
List<String> _getFilteredSuggestions() {
|
||||
final currentText = _controller.text;
|
||||
if (currentText.isEmpty) {
|
||||
return _suggestions;
|
||||
}
|
||||
|
||||
return _suggestions
|
||||
.where((suggestion) => suggestion.contains(currentText))
|
||||
.toList();
|
||||
}
|
||||
|
||||
/// Handle text changes to update suggestion filtering
|
||||
void _onTextChanged() {
|
||||
if (_showingSuggestions) {
|
||||
// Update the overlay with filtered suggestions
|
||||
_updateSuggestionsOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
void _loadSuggestions() async {
|
||||
if (widget.tagKey.trim().isEmpty) return;
|
||||
|
||||
@@ -86,7 +107,8 @@ class _NSITagValueFieldState extends State<NSITagValueField> {
|
||||
}
|
||||
|
||||
void _onFocusChanged() {
|
||||
if (_focusNode.hasFocus && _suggestions.isNotEmpty && !widget.readOnly) {
|
||||
final filteredSuggestions = _getFilteredSuggestions();
|
||||
if (_focusNode.hasFocus && filteredSuggestions.isNotEmpty && !widget.readOnly) {
|
||||
_showSuggestions();
|
||||
} else {
|
||||
_hideSuggestions();
|
||||
@@ -94,11 +116,38 @@ class _NSITagValueFieldState extends State<NSITagValueField> {
|
||||
}
|
||||
|
||||
void _showSuggestions() {
|
||||
if (_showingSuggestions || _suggestions.isEmpty) return;
|
||||
final filteredSuggestions = _getFilteredSuggestions();
|
||||
if (_showingSuggestions || filteredSuggestions.isEmpty) return;
|
||||
|
||||
_overlayEntry = OverlayEntry(
|
||||
_overlayEntry = _buildSuggestionsOverlay(filteredSuggestions);
|
||||
Overlay.of(context).insert(_overlayEntry);
|
||||
setState(() {
|
||||
_showingSuggestions = true;
|
||||
});
|
||||
}
|
||||
|
||||
/// Update the suggestions overlay with current filtered suggestions
|
||||
void _updateSuggestionsOverlay() {
|
||||
final filteredSuggestions = _getFilteredSuggestions();
|
||||
|
||||
if (filteredSuggestions.isEmpty) {
|
||||
_hideSuggestions();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_showingSuggestions) {
|
||||
// Remove current overlay and create new one with filtered suggestions
|
||||
_overlayEntry.remove();
|
||||
_overlayEntry = _buildSuggestionsOverlay(filteredSuggestions);
|
||||
Overlay.of(context).insert(_overlayEntry);
|
||||
}
|
||||
}
|
||||
|
||||
/// Build the suggestions overlay with the given suggestions list
|
||||
OverlayEntry _buildSuggestionsOverlay(List<String> suggestions) {
|
||||
return OverlayEntry(
|
||||
builder: (context) => Positioned(
|
||||
width: 200, // Fixed width for suggestions
|
||||
width: 250, // Slightly wider to fit more content in refine tags
|
||||
child: CompositedTransformFollower(
|
||||
link: _layerLink,
|
||||
showWhenUnlinked: false,
|
||||
@@ -111,9 +160,9 @@ class _NSITagValueFieldState extends State<NSITagValueField> {
|
||||
child: ListView.builder(
|
||||
padding: EdgeInsets.zero,
|
||||
shrinkWrap: true,
|
||||
itemCount: _suggestions.length,
|
||||
itemCount: suggestions.length,
|
||||
itemBuilder: (context, index) {
|
||||
final suggestion = _suggestions[index];
|
||||
final suggestion = suggestions[index];
|
||||
return ListTile(
|
||||
dense: true,
|
||||
title: Text(suggestion, style: const TextStyle(fontSize: 14)),
|
||||
@@ -126,11 +175,6 @@ class _NSITagValueFieldState extends State<NSITagValueField> {
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
Overlay.of(context).insert(_overlayEntry);
|
||||
setState(() {
|
||||
_showingSuggestions = true;
|
||||
});
|
||||
}
|
||||
|
||||
void _hideSuggestions() {
|
||||
@@ -150,6 +194,8 @@ class _NSITagValueFieldState extends State<NSITagValueField> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final filteredSuggestions = _getFilteredSuggestions();
|
||||
|
||||
return CompositedTransformTarget(
|
||||
link: _layerLink,
|
||||
child: TextField(
|
||||
@@ -171,7 +217,7 @@ class _NSITagValueFieldState extends State<NSITagValueField> {
|
||||
widget.onChanged(value);
|
||||
},
|
||||
onTap: () {
|
||||
if (!widget.readOnly && _suggestions.isNotEmpty) {
|
||||
if (!widget.readOnly && filteredSuggestions.isNotEmpty) {
|
||||
_showSuggestions();
|
||||
}
|
||||
},
|
||||
|
||||
@@ -5,7 +5,7 @@ import '../app_state.dart';
|
||||
import '../models/operator_profile.dart';
|
||||
import '../models/node_profile.dart';
|
||||
import '../services/localization_service.dart';
|
||||
import '../services/nsi_service.dart';
|
||||
import 'nsi_tag_value_field.dart';
|
||||
|
||||
/// Result returned from RefineTagsSheet
|
||||
class RefineTagsResult {
|
||||
@@ -37,47 +37,12 @@ class RefineTagsSheet extends StatefulWidget {
|
||||
class _RefineTagsSheetState extends State<RefineTagsSheet> {
|
||||
OperatorProfile? _selectedOperatorProfile;
|
||||
Map<String, String> _refinedTags = {};
|
||||
Map<String, List<String>> _tagSuggestions = {};
|
||||
Map<String, bool> _loadingSuggestions = {};
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_selectedOperatorProfile = widget.selectedOperatorProfile;
|
||||
_refinedTags = Map<String, String>.from(widget.currentRefinedTags ?? {});
|
||||
_loadTagSuggestions();
|
||||
}
|
||||
|
||||
/// Load suggestions for all empty-value tags in the selected profile
|
||||
void _loadTagSuggestions() async {
|
||||
if (widget.selectedProfile == null) return;
|
||||
|
||||
final refinableTags = _getRefinableTags();
|
||||
|
||||
for (final tagKey in refinableTags) {
|
||||
if (_tagSuggestions.containsKey(tagKey)) continue;
|
||||
|
||||
setState(() {
|
||||
_loadingSuggestions[tagKey] = true;
|
||||
});
|
||||
|
||||
try {
|
||||
final suggestions = await NSIService().getAllSuggestions(tagKey);
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_tagSuggestions[tagKey] = suggestions;
|
||||
_loadingSuggestions[tagKey] = false;
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_tagSuggestions[tagKey] = [];
|
||||
_loadingSuggestions[tagKey] = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get list of tag keys that have empty values and can be refined
|
||||
@@ -262,11 +227,9 @@ class _RefineTagsSheetState extends State<RefineTagsSheet> {
|
||||
];
|
||||
}
|
||||
|
||||
/// Build a dropdown for a single refineable tag
|
||||
/// Build a text field for a single refineable tag (similar to profile editor)
|
||||
Widget _buildTagDropdown(String tagKey, LocalizationService locService) {
|
||||
final suggestions = _tagSuggestions[tagKey] ?? [];
|
||||
final isLoading = _loadingSuggestions[tagKey] ?? false;
|
||||
final currentValue = _refinedTags[tagKey];
|
||||
final currentValue = _refinedTags[tagKey] ?? '';
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 16.0),
|
||||
@@ -278,58 +241,21 @@ class _RefineTagsSheetState extends State<RefineTagsSheet> {
|
||||
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 14),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
if (isLoading)
|
||||
const Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 16,
|
||||
height: 16,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
Text('Loading suggestions...', style: TextStyle(color: Colors.grey)),
|
||||
],
|
||||
)
|
||||
else if (suggestions.isEmpty)
|
||||
DropdownButtonFormField<String>(
|
||||
value: currentValue?.isNotEmpty == true ? currentValue : null,
|
||||
decoration: InputDecoration(
|
||||
hintText: locService.t('refineTagsSheet.noSuggestions'),
|
||||
border: const OutlineInputBorder(),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
),
|
||||
items: const [],
|
||||
onChanged: null, // Disabled when no suggestions
|
||||
)
|
||||
else
|
||||
DropdownButtonFormField<String>(
|
||||
value: currentValue?.isNotEmpty == true ? currentValue : null,
|
||||
decoration: InputDecoration(
|
||||
hintText: locService.t('refineTagsSheet.selectValue'),
|
||||
border: const OutlineInputBorder(),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
),
|
||||
items: [
|
||||
DropdownMenuItem<String>(
|
||||
value: null,
|
||||
child: Text(locService.t('refineTagsSheet.noValue'),
|
||||
style: const TextStyle(color: Colors.grey)),
|
||||
),
|
||||
...suggestions.map((suggestion) => DropdownMenuItem<String>(
|
||||
value: suggestion,
|
||||
child: Text(suggestion),
|
||||
)),
|
||||
],
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
if (value == null) {
|
||||
_refinedTags.remove(tagKey);
|
||||
} else {
|
||||
_refinedTags[tagKey] = value;
|
||||
}
|
||||
});
|
||||
},
|
||||
),
|
||||
NSITagValueField(
|
||||
key: ValueKey('${tagKey}_refine'),
|
||||
tagKey: tagKey,
|
||||
initialValue: currentValue,
|
||||
hintText: locService.t('refineTagsSheet.selectValue'),
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
if (value.trim().isEmpty) {
|
||||
_refinedTags.remove(tagKey);
|
||||
} else {
|
||||
_refinedTags[tagKey] = value.trim();
|
||||
}
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -50,7 +50,7 @@ class _SubmissionGuideDialogState extends State<SubmissionGuideDialog> {
|
||||
}
|
||||
|
||||
if (mounted) {
|
||||
Navigator.of(context).pop();
|
||||
Navigator.of(context).pop(true); // Return true to indicate "proceed with submission"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,6 +148,13 @@ class _SubmissionGuideDialogState extends State<SubmissionGuideDialog> {
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
// Cancel - just close dialog without marking as seen, return false to cancel submission
|
||||
Navigator.of(context).pop(false);
|
||||
},
|
||||
child: Text(locService.cancel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: _onClose,
|
||||
child: Text(locService.t('submissionGuide.gotIt')),
|
||||
|
||||
Reference in New Issue
Block a user