first working version

This commit is contained in:
stopflock committed 2026-09-23 14:39:11 -05:00
1 parent c95171a3ad
commit 54ef1b8afc
26 files changed
+745 -27

No files matched your search

+79 -6
View File
@@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart' show LatLngBounds; import 'package:flutter_map/flutter_map.dart' show LatLngBounds;
import 'package:url_launcher/url_launcher.dart';
import 'services/http_client.dart'; import 'services/http_client.dart';
import 'package:latlong2/latlong.dart'; import 'package:latlong2/latlong.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
@@ -23,7 +24,9 @@ import 'services/deep_link_service.dart';
import 'widgets/node_provider_with_cache.dart'; import 'widgets/node_provider_with_cache.dart';
import 'services/profile_service.dart'; import 'services/profile_service.dart';
import 'widgets/reauth_messages_dialog.dart'; import 'widgets/reauth_messages_dialog.dart';
import 'widgets/active_block_dialog.dart';
import 'dev_config.dart'; import 'dev_config.dart';
import 'state/account_block_state.dart';
import 'state/auth_state.dart'; import 'state/auth_state.dart';
import 'state/messages_state.dart'; import 'state/messages_state.dart';
import 'state/navigation_state.dart'; import 'state/navigation_state.dart';
@@ -46,6 +49,7 @@ class AppState extends ChangeNotifier {
static late AppState instance; static late AppState instance;
// State modules // State modules
late final AccountBlockState _accountBlockState;
late final AuthState _authState; late final AuthState _authState;
late final MessagesState _messagesState; late final MessagesState _messagesState;
late final NavigationState _navigationState; late final NavigationState _navigationState;
@@ -72,6 +76,7 @@ class AppState extends ChangeNotifier {
AppState() { AppState() {
instance = this; instance = this;
_accountBlockState = AccountBlockState();
_authState = AuthState(); _authState = AuthState();
_messagesState = MessagesState(); _messagesState = MessagesState();
_navigationState = NavigationState(); _navigationState = NavigationState();
@@ -84,6 +89,7 @@ class AppState extends ChangeNotifier {
_uploadQueueState = UploadQueueState(); _uploadQueueState = UploadQueueState();
// Set up state change listeners // Set up state change listeners
_accountBlockState.addListener(_onStateChanged);
_authState.addListener(_onStateChanged); _authState.addListener(_onStateChanged);
_messagesState.addListener(_onStateChanged); _messagesState.addListener(_onStateChanged);
_navigationState.addListener(_onStateChanged); _navigationState.addListener(_onStateChanged);
@@ -179,6 +185,9 @@ class AppState extends ChangeNotifier {
bool get hasUnreadChangesetComments => _messagesState.hasUnreadChangesetComments; bool get hasUnreadChangesetComments => _messagesState.hasUnreadChangesetComments;
bool get hasUnreadNotifications => _messagesState.hasUnreadNotifications; bool get hasUnreadNotifications => _messagesState.hasUnreadNotifications;
bool get isCheckingMessages => _messagesState.isChecking; bool get isCheckingMessages => _messagesState.isChecking;
// Account block state
bool get hasActiveBlock => _accountBlockState.isBlocked;
// Tile provider state // Tile provider state
List<TileProvider> get tileProviders => _settingsState.tileProviders; List<TileProvider> get tileProviders => _settingsState.tileProviders;
@@ -196,6 +205,10 @@ class AppState extends ChangeNotifier {
/// queue so in-flight submissions can finish first. /// queue so in-flight submissions can finish first.
bool get hasInFlightUploads => pendingUploads.any((u) => u.isActivelyProcessing); bool get hasInFlightUploads => pendingUploads.any((u) => u.isActivelyProcessing);
/// True if queue processing is currently paused because an active-block
/// check found the user's account blocked.
bool get isQueuePausedDueToBlock => _uploadQueueState.isPausedDueToBlock;
// Suspected location state // Suspected location state
SuspectedLocation? get selectedSuspectedLocation => _suspectedLocationState.selectedLocation; SuspectedLocation? get selectedSuspectedLocation => _suspectedLocationState.selectedLocation;
@@ -245,15 +258,17 @@ class AppState extends ChangeNotifier {
await _suspectedLocationState.init(offlineMode: _settingsState.offlineMode); await _suspectedLocationState.init(offlineMode: _settingsState.offlineMode);
await _uploadQueueState.init(); await _uploadQueueState.init();
await _authState.init(_settingsState.uploadMode); await _authState.init(_settingsState.uploadMode);
await _accountBlockState.init(_settingsState.uploadMode);
// Set up callback to repopulate pending nodes after cache clears // Set up callback to repopulate pending nodes after cache clears
NodeProviderWithCache.instance.setOnCacheClearedCallback(() { NodeProviderWithCache.instance.setOnCacheClearedCallback(() {
_uploadQueueState.repopulateCacheFromQueue(); _uploadQueueState.repopulateCacheFromQueue();
}); });
// Check for messages on app launch if user is already logged in // Check for messages and active blocks on app launch if user is already logged in
if (isLoggedIn) { if (isLoggedIn) {
checkMessages(); checkMessages();
checkActiveBlock();
} }
// Note: Re-auth check will be triggered from home screen after init // Note: Re-auth check will be triggered from home screen after init
@@ -300,16 +315,18 @@ class AppState extends ChangeNotifier {
// ---------- Auth Methods ---------- // ---------- Auth Methods ----------
Future<void> login() async { Future<void> login() async {
await _authState.login(); await _authState.login();
// Check for messages after successful login // Check for messages and active blocks after successful login
if (isLoggedIn) { if (isLoggedIn) {
checkMessages(); checkMessages();
checkActiveBlock();
} }
} }
Future<void> logout() async { Future<void> logout() async {
await _authState.logout(); await _authState.logout();
// Clear message state when logging out // Clear message and block state when logging out
clearMessages(); clearMessages();
_accountBlockState.clear();
} }
Future<void> refreshAuthState() async { Future<void> refreshAuthState() async {
@@ -318,14 +335,65 @@ class AppState extends ChangeNotifier {
Future<void> forceLogin() async { Future<void> forceLogin() async {
await _authState.forceLogin(); await _authState.forceLogin();
// Check for messages after successful login // Check for messages and active blocks after successful login
if (isLoggedIn) { if (isLoggedIn) {
checkMessages(); checkMessages();
checkActiveBlock();
} }
} }
Future<bool> validateToken() async { // ---------- Account Block Methods ----------
return await _authState.validateToken();
/// Silently check whether the user currently has an active OSM block.
/// Returns the conclusive result (true/false), or null if the check was
/// inconclusive (offline, error, not logged in, simulate mode).
Future<bool?> checkActiveBlock() async {
final accessToken = await _authState.getAccessToken();
final result = await _accountBlockState.check(
accessToken: accessToken,
uploadMode: uploadMode,
);
// If the block was just lifted, resume queue processing.
if (result == false && isQueuePausedDueToBlock) {
_startUploader();
}
return result;
}
/// Check for an active block and, if found, show [ActiveBlockDialog].
Future<void> checkAndPromptForActiveBlock(BuildContext context) async {
final result = await checkActiveBlock();
if (result == true) {
if (!context.mounted) return;
_showActiveBlockDialog(context);
}
}
void _showActiveBlockDialog(BuildContext context) {
showDialog(
context: context,
builder: (context) => ActiveBlockDialog(
onViewMessages: () async {
final url = Uri.parse(getMessagesUrl());
await launchUrl(url, mode: LaunchMode.externalApplication);
},
onDismiss: () {
// Just dismiss - will show again on the next check that finds a block.
},
),
);
}
/// Test the OSM connection using the same endpoint as the active-block
/// check. A conclusive result (blocked or not) means the credentials are
/// valid; null means the connection/credentials failed. Also surfaces the
/// [ActiveBlockDialog] if the user turns out to be blocked.
Future<bool> testConnection(BuildContext context) async {
final result = await checkActiveBlock();
if (result == true) {
if (context.mounted) _showActiveBlockDialog(context);
}
return result != null;
} }
// ---------- Messages Methods ---------- // ---------- Messages Methods ----------
@@ -783,9 +851,12 @@ class AppState extends ChangeNotifier {
// Clear and re-check messages for new mode // Clear and re-check messages for new mode
clearMessages(); clearMessages();
// Load the persisted block state for the new mode, then re-check it
await _accountBlockState.init(mode);
if (isLoggedIn) { if (isLoggedIn) {
// Don't await - let it run in background // Don't await - let it run in background
checkMessages(); checkMessages();
checkActiveBlock();
// Note: Re-auth check will be triggered from the settings screen after mode change // Note: Re-auth check will be triggered from the settings screen after mode change
} }
@@ -958,12 +1029,14 @@ class AppState extends ChangeNotifier {
pauseQueueProcessing: pauseQueueProcessing, pauseQueueProcessing: pauseQueueProcessing,
uploadMode: uploadMode, uploadMode: uploadMode,
getAccessToken: _authState.getAccessToken, getAccessToken: _authState.getAccessToken,
checkActiveBlock: checkActiveBlock,
); );
} }
@override @override
void dispose() { void dispose() {
_messageCheckTimer?.cancel(); _messageCheckTimer?.cancel();
_accountBlockState.removeListener(_onStateChanged);
_authState.removeListener(_onStateChanged); _authState.removeListener(_onStateChanged);
_messagesState.removeListener(_onStateChanged); _messagesState.removeListener(_onStateChanged);
_navigationState.removeListener(_onStateChanged); _navigationState.removeListener(_onStateChanged);
+8 -1
View File
@@ -12,6 +12,7 @@
"edit": "Bearbeiten", "edit": "Bearbeiten",
"delete": "Löschen", "delete": "Löschen",
"cancel": "Abbrechen", "cancel": "Abbrechen",
"dismiss": "Verwerfen",
"ok": "OK", "ok": "OK",
"close": "Schließen", "close": "Schließen",
"submit": "Senden", "submit": "Senden",
@@ -124,6 +125,7 @@
"deleteReasonCustom": "Benutzerdefiniert..", "deleteReasonCustom": "Benutzerdefiniert..",
"deleteReasonHint": "Bitte Grund beschreiben...", "deleteReasonHint": "Bitte Grund beschreiben...",
"mustBeLoggedInToDelete": "Sie müssen mit einem OpenStreetMap-Konto angemeldet sein, um Kartendaten beizutragen.", "mustBeLoggedInToDelete": "Sie müssen mit einem OpenStreetMap-Konto angemeldet sein, um Kartendaten beizutragen.",
"accountBlockedCannotDelete": "Ihr OpenStreetMap-Konto ist derzeit gesperrt und kann keine Knoten löschen.",
"uploadPending": "Ausstehender Upload", "uploadPending": "Ausstehender Upload",
"uploadCreatingChangeset": "Changeset wird erstellt...", "uploadCreatingChangeset": "Changeset wird erstellt...",
"uploadUploading": "Wird hochgeladen...", "uploadUploading": "Wird hochgeladen...",
@@ -140,6 +142,7 @@
"direction": "Richtung {}°", "direction": "Richtung {}°",
"profileNoDirectionInfo": "Dieses Profil benötigt keine Richtung.", "profileNoDirectionInfo": "Dieses Profil benötigt keine Richtung.",
"mustBeLoggedIn": "Sie müssen angemeldet sein, um neue Knoten zu übertragen. Bitte melden Sie sich über die Einstellungen an.", "mustBeLoggedIn": "Sie müssen angemeldet sein, um neue Knoten zu übertragen. Bitte melden Sie sich über die Einstellungen an.",
"accountBlocked": "Ihr OpenStreetMap-Konto ist derzeit gesperrt und kann keine neuen Knoten einreichen.",
"enableSubmittableProfile": "Aktivieren Sie ein übertragbares Profil in den Einstellungen, um neue Knoten zu übertragen.", "enableSubmittableProfile": "Aktivieren Sie ein übertragbares Profil in den Einstellungen, um neue Knoten zu übertragen.",
"profileViewOnlyWarning": "Dieses Profil ist nur zum Anzeigen der Karte gedacht. Bitte wählen Sie ein übertragbares Profil aus, um neue Knoten zu übertragen.", "profileViewOnlyWarning": "Dieses Profil ist nur zum Anzeigen der Karte gedacht. Bitte wählen Sie ein übertragbares Profil aus, um neue Knoten zu übertragen.",
"loadingAreaData": "Lade Bereichsdaten... Bitte warten Sie vor dem Übertragen.", "loadingAreaData": "Lade Bereichsdaten... Bitte warten Sie vor dem Übertragen.",
@@ -154,6 +157,7 @@
"profileNoDirectionInfo": "Dieses Profil benötigt keine Richtung.", "profileNoDirectionInfo": "Dieses Profil benötigt keine Richtung.",
"temporarilyDisabled": "Bearbeitungen wurden vorübergehend deaktiviert, während wir einen Fehler beheben - Entschuldigung - schauen Sie bald wieder vorbei.", "temporarilyDisabled": "Bearbeitungen wurden vorübergehend deaktiviert, während wir einen Fehler beheben - Entschuldigung - schauen Sie bald wieder vorbei.",
"mustBeLoggedIn": "Sie müssen angemeldet sein, um Knoten zu bearbeiten. Bitte melden Sie sich über die Einstellungen an.", "mustBeLoggedIn": "Sie müssen angemeldet sein, um Knoten zu bearbeiten. Bitte melden Sie sich über die Einstellungen an.",
"accountBlocked": "Ihr OpenStreetMap-Konto ist derzeit gesperrt und kann keine Knoten bearbeiten.",
"sandboxModeWarning": "Bearbeitungen von Produktionsknoten können nicht an die Sandbox übertragen werden. Wechseln Sie in den Produktionsmodus in den Einstellungen, um Knoten zu bearbeiten.", "sandboxModeWarning": "Bearbeitungen von Produktionsknoten können nicht an die Sandbox übertragen werden. Wechseln Sie in den Produktionsmodus in den Einstellungen, um Knoten zu bearbeiten.",
"enableSubmittableProfile": "Aktivieren Sie ein übertragbares Profil in den Einstellungen, um Knoten zu bearbeiten.", "enableSubmittableProfile": "Aktivieren Sie ein übertragbares Profil in den Einstellungen, um Knoten zu bearbeiten.",
"profileViewOnlyWarning": "Dieses Profil ist nur zum Anzeigen der Karte gedacht. Bitte wählen Sie ein übertragbares Profil aus, um Knoten zu bearbeiten.", "profileViewOnlyWarning": "Dieses Profil ist nur zum Anzeigen der Karte gedacht. Bitte wählen Sie ein übertragbares Profil aus, um Knoten zu bearbeiten.",
@@ -241,7 +245,9 @@
"reauthLater": "Später", "reauthLater": "Später",
"unreadNotificationsTitle": "Sie haben ungelesene Benachrichtigungen", "unreadNotificationsTitle": "Sie haben ungelesene Benachrichtigungen",
"unreadNotificationsMessage": "Sie haben ungelesene OSM-Nachrichten und/oder neue Kommentare zu Ihren Changesets.", "unreadNotificationsMessage": "Sie haben ungelesene OSM-Nachrichten und/oder neue Kommentare zu Ihren Changesets.",
"unreadNotificationsView": "Jetzt ansehen" "unreadNotificationsView": "Jetzt ansehen",
"activeBlockTitle": "Ihr OSM-Konto hat eine aktive Sperre",
"activeBlockMessage": "Es scheint, dass Ihr OpenStreetMap-Konto derzeit gesperrt ist und keine Beiträge einreichen kann. Bitte lesen Sie die Nachricht, die Sie erhalten haben sollten, um zu erfahren, warum und was Sie tun können, um das Problem zu beheben."
}, },
"queue": { "queue": {
"title": "Upload-Warteschlange", "title": "Upload-Warteschlange",
@@ -277,6 +283,7 @@
"processingPaused": "Warteschlangenverarbeitung pausiert", "processingPaused": "Warteschlangenverarbeitung pausiert",
"pausedDueToOffline": "Upload-Verarbeitung ist pausiert, da der Offline-Modus aktiviert ist.", "pausedDueToOffline": "Upload-Verarbeitung ist pausiert, da der Offline-Modus aktiviert ist.",
"pausedByUser": "Upload-Verarbeitung ist manuell pausiert.", "pausedByUser": "Upload-Verarbeitung ist manuell pausiert.",
"pausedDueToBlock": "Die Upload-Verarbeitung ist pausiert, da Ihr OSM-Konto derzeit gesperrt ist.",
"inFlightWaitTitle": "Übermittlung wird abgeschlossen", "inFlightWaitTitle": "Übermittlung wird abgeschlossen",
"inFlightWaitMessage": "Eine Übermittlung läuft gerade und muss abgeschlossen werden, bevor diese Einstellung angewendet werden kann. Dies sollte nur einen Moment dauern.", "inFlightWaitMessage": "Eine Übermittlung läuft gerade und muss abgeschlossen werden, bevor diese Einstellung angewendet werden kann. Dies sollte nur einen Moment dauern.",
"inFlightCompleteTitle": "Übermittlung Abgeschlossen", "inFlightCompleteTitle": "Übermittlung Abgeschlossen",
+8 -1
View File
@@ -49,6 +49,7 @@
"edit": "Edit", "edit": "Edit",
"delete": "Delete", "delete": "Delete",
"cancel": "Cancel", "cancel": "Cancel",
"dismiss": "Dismiss",
"ok": "OK", "ok": "OK",
"close": "Close", "close": "Close",
"submit": "Submit", "submit": "Submit",
@@ -161,6 +162,7 @@
"deleteReasonCustom": "Custom..", "deleteReasonCustom": "Custom..",
"deleteReasonHint": "Please describe the reason...", "deleteReasonHint": "Please describe the reason...",
"mustBeLoggedInToDelete": "You must be logged in with an OpenStreetMap account to contribute map data.", "mustBeLoggedInToDelete": "You must be logged in with an OpenStreetMap account to contribute map data.",
"accountBlockedCannotDelete": "Your OpenStreetMap account has an active block and cannot delete nodes.",
"uploadPending": "Pending upload", "uploadPending": "Pending upload",
"uploadCreatingChangeset": "Creating changeset...", "uploadCreatingChangeset": "Creating changeset...",
"uploadUploading": "Uploading...", "uploadUploading": "Uploading...",
@@ -177,6 +179,7 @@
"direction": "Direction {}°", "direction": "Direction {}°",
"profileNoDirectionInfo": "This profile does not require a direction.", "profileNoDirectionInfo": "This profile does not require a direction.",
"mustBeLoggedIn": "You must be logged in to submit new nodes. Please log in via Settings.", "mustBeLoggedIn": "You must be logged in to submit new nodes. Please log in via Settings.",
"accountBlocked": "Your OpenStreetMap account has an active block and cannot submit new nodes.",
"enableSubmittableProfile": "Enable a submittable profile in Settings to submit new nodes.", "enableSubmittableProfile": "Enable a submittable profile in Settings to submit new nodes.",
"profileViewOnlyWarning": "This profile is for map viewing only. Please select a submittable profile to submit new nodes.", "profileViewOnlyWarning": "This profile is for map viewing only. Please select a submittable profile to submit new nodes.",
"loadingAreaData": "Loading area data... Please wait before submitting.", "loadingAreaData": "Loading area data... Please wait before submitting.",
@@ -191,6 +194,7 @@
"profileNoDirectionInfo": "This profile does not require a direction.", "profileNoDirectionInfo": "This profile does not require a direction.",
"temporarilyDisabled": "Edits have been temporarily disabled while we sort out a bug - apologies - check back soon.", "temporarilyDisabled": "Edits have been temporarily disabled while we sort out a bug - apologies - check back soon.",
"mustBeLoggedIn": "You must be logged in to edit nodes. Please log in via Settings.", "mustBeLoggedIn": "You must be logged in to edit nodes. Please log in via Settings.",
"accountBlocked": "Your OpenStreetMap account has an active block and cannot edit nodes.",
"sandboxModeWarning": "Cannot submit edits on production nodes to sandbox. Switch to Production mode in Settings to edit nodes.", "sandboxModeWarning": "Cannot submit edits on production nodes to sandbox. Switch to Production mode in Settings to edit nodes.",
"enableSubmittableProfile": "Enable a submittable profile in Settings to edit nodes.", "enableSubmittableProfile": "Enable a submittable profile in Settings to edit nodes.",
"profileViewOnlyWarning": "This profile is for map viewing only. Please select a submittable profile to edit nodes.", "profileViewOnlyWarning": "This profile is for map viewing only. Please select a submittable profile to edit nodes.",
@@ -278,7 +282,9 @@
"reauthLater": "Later", "reauthLater": "Later",
"unreadNotificationsTitle": "You Have Unread Notifications", "unreadNotificationsTitle": "You Have Unread Notifications",
"unreadNotificationsMessage": "You have unread OSM messages and/or new comments on your changesets.", "unreadNotificationsMessage": "You have unread OSM messages and/or new comments on your changesets.",
"unreadNotificationsView": "View Now" "unreadNotificationsView": "View Now",
"activeBlockTitle": "Your OSM Account Has an Active Block",
"activeBlockMessage": "It seems your OpenStreetMap account has an active block, and is currently restricted from making submissions. Please read the message you should have received to learn why, and what you can do to resolve it."
}, },
"queue": { "queue": {
"title": "Upload Queue", "title": "Upload Queue",
@@ -314,6 +320,7 @@
"processingPaused": "Queue Processing Paused", "processingPaused": "Queue Processing Paused",
"pausedDueToOffline": "Upload processing is paused because offline mode is enabled.", "pausedDueToOffline": "Upload processing is paused because offline mode is enabled.",
"pausedByUser": "Upload processing is manually paused.", "pausedByUser": "Upload processing is manually paused.",
"pausedDueToBlock": "Upload processing is paused because your OSM account has an active block.",
"inFlightWaitTitle": "Finishing Submission", "inFlightWaitTitle": "Finishing Submission",
"inFlightWaitMessage": "A submission is currently in progress and needs to finish before this setting can be applied. This should only take a moment.", "inFlightWaitMessage": "A submission is currently in progress and needs to finish before this setting can be applied. This should only take a moment.",
"inFlightCompleteTitle": "Submission Complete", "inFlightCompleteTitle": "Submission Complete",
+8 -1
View File
@@ -49,6 +49,7 @@
"edit": "Editar", "edit": "Editar",
"delete": "Eliminar", "delete": "Eliminar",
"cancel": "Cancelar", "cancel": "Cancelar",
"dismiss": "Descartar",
"ok": "Aceptar", "ok": "Aceptar",
"close": "Cerrar", "close": "Cerrar",
"submit": "Enviar", "submit": "Enviar",
@@ -161,6 +162,7 @@
"deleteReasonCustom": "Personalizado..", "deleteReasonCustom": "Personalizado..",
"deleteReasonHint": "Por favor describa la razón...", "deleteReasonHint": "Por favor describa la razón...",
"mustBeLoggedInToDelete": "Debes iniciar sesión con una cuenta de OpenStreetMap para contribuir con datos del mapa.", "mustBeLoggedInToDelete": "Debes iniciar sesión con una cuenta de OpenStreetMap para contribuir con datos del mapa.",
"accountBlockedCannotDelete": "Su cuenta de OpenStreetMap tiene un bloqueo activo y no puede eliminar nodos.",
"uploadPending": "Subida pendiente", "uploadPending": "Subida pendiente",
"uploadCreatingChangeset": "Creando changeset...", "uploadCreatingChangeset": "Creando changeset...",
"uploadUploading": "Subiendo...", "uploadUploading": "Subiendo...",
@@ -177,6 +179,7 @@
"direction": "Dirección {}°", "direction": "Dirección {}°",
"profileNoDirectionInfo": "Este perfil no requiere una dirección.", "profileNoDirectionInfo": "Este perfil no requiere una dirección.",
"mustBeLoggedIn": "Debe estar conectado para enviar nuevos nodos. Por favor, inicie sesión a través de Configuración.", "mustBeLoggedIn": "Debe estar conectado para enviar nuevos nodos. Por favor, inicie sesión a través de Configuración.",
"accountBlocked": "Su cuenta de OpenStreetMap tiene un bloqueo activo y no puede enviar nodos nuevos.",
"enableSubmittableProfile": "Habilite un perfil envíable en Configuración para enviar nuevos nodos.", "enableSubmittableProfile": "Habilite un perfil envíable en Configuración para enviar nuevos nodos.",
"profileViewOnlyWarning": "Este perfil es solo para visualización del mapa. Por favor, seleccione un perfil envíable para enviar nuevos nodos.", "profileViewOnlyWarning": "Este perfil es solo para visualización del mapa. Por favor, seleccione un perfil envíable para enviar nuevos nodos.",
"loadingAreaData": "Cargando datos del área... Por favor espere antes de enviar.", "loadingAreaData": "Cargando datos del área... Por favor espere antes de enviar.",
@@ -191,6 +194,7 @@
"profileNoDirectionInfo": "Este perfil no requiere una dirección.", "profileNoDirectionInfo": "Este perfil no requiere una dirección.",
"temporarilyDisabled": "Las ediciones han sido temporalmente deshabilitadas mientras solucionamos un error - disculpas - regrese pronto.", "temporarilyDisabled": "Las ediciones han sido temporalmente deshabilitadas mientras solucionamos un error - disculpas - regrese pronto.",
"mustBeLoggedIn": "Debe estar conectado para editar nodos. Por favor, inicie sesión a través de Configuración.", "mustBeLoggedIn": "Debe estar conectado para editar nodos. Por favor, inicie sesión a través de Configuración.",
"accountBlocked": "Su cuenta de OpenStreetMap tiene un bloqueo activo y no puede editar nodos.",
"sandboxModeWarning": "No se pueden enviar ediciones de nodos de producción al sandbox. Cambie al modo Producción en Configuración para editar nodos.", "sandboxModeWarning": "No se pueden enviar ediciones de nodos de producción al sandbox. Cambie al modo Producción en Configuración para editar nodos.",
"enableSubmittableProfile": "Habilite un perfil envíable en Configuración para editar nodos.", "enableSubmittableProfile": "Habilite un perfil envíable en Configuración para editar nodos.",
"profileViewOnlyWarning": "Este perfil es solo para visualización del mapa. Por favor, seleccione un perfil envíable para editar nodos.", "profileViewOnlyWarning": "Este perfil es solo para visualización del mapa. Por favor, seleccione un perfil envíable para editar nodos.",
@@ -278,7 +282,9 @@
"reauthLater": "Más Tarde", "reauthLater": "Más Tarde",
"unreadNotificationsTitle": "Tienes Notificaciones Sin Leer", "unreadNotificationsTitle": "Tienes Notificaciones Sin Leer",
"unreadNotificationsMessage": "Tienes mensajes de OSM sin leer y/o nuevos comentarios en tus changesets.", "unreadNotificationsMessage": "Tienes mensajes de OSM sin leer y/o nuevos comentarios en tus changesets.",
"unreadNotificationsView": "Ver Ahora" "unreadNotificationsView": "Ver Ahora",
"activeBlockTitle": "Su cuenta de OSM tiene un bloqueo activo",
"activeBlockMessage": "Parece que su cuenta de OpenStreetMap tiene un bloqueo activo y actualmente no puede realizar envíos. Lea el mensaje que debería haber recibido para saber por qué y qué puede hacer para resolverlo."
}, },
"queue": { "queue": {
"title": "Cola de Subida", "title": "Cola de Subida",
@@ -314,6 +320,7 @@
"processingPaused": "Procesamiento de Cola Pausado", "processingPaused": "Procesamiento de Cola Pausado",
"pausedDueToOffline": "El procesamiento de subida está pausado porque el modo sin conexión está habilitado.", "pausedDueToOffline": "El procesamiento de subida está pausado porque el modo sin conexión está habilitado.",
"pausedByUser": "El procesamiento de subida está pausado manualmente.", "pausedByUser": "El procesamiento de subida está pausado manualmente.",
"pausedDueToBlock": "El procesamiento de subidas está pausado porque su cuenta de OSM tiene un bloqueo activo.",
"inFlightWaitTitle": "Finalizando Envío", "inFlightWaitTitle": "Finalizando Envío",
"inFlightWaitMessage": "Actualmente hay un envío en curso que debe finalizar antes de que se pueda aplicar esta configuración. Esto solo debería tomar un momento.", "inFlightWaitMessage": "Actualmente hay un envío en curso que debe finalizar antes de que se pueda aplicar esta configuración. Esto solo debería tomar un momento.",
"inFlightCompleteTitle": "Envío Completado", "inFlightCompleteTitle": "Envío Completado",
+8 -1
View File
@@ -49,6 +49,7 @@
"edit": "Modifier", "edit": "Modifier",
"delete": "Supprimer", "delete": "Supprimer",
"cancel": "Annuler", "cancel": "Annuler",
"dismiss": "Ignorer",
"ok": "OK", "ok": "OK",
"close": "Fermer", "close": "Fermer",
"submit": "Soumettre", "submit": "Soumettre",
@@ -161,6 +162,7 @@
"deleteReasonCustom": "Personnalisé..", "deleteReasonCustom": "Personnalisé..",
"deleteReasonHint": "Veuillez décrire la raison...", "deleteReasonHint": "Veuillez décrire la raison...",
"mustBeLoggedInToDelete": "Vous devez être connecté avec un compte OpenStreetMap pour contribuer aux données cartographiques.", "mustBeLoggedInToDelete": "Vous devez être connecté avec un compte OpenStreetMap pour contribuer aux données cartographiques.",
"accountBlockedCannotDelete": "Votre compte OpenStreetMap a un blocage actif et ne peut pas supprimer de nœuds.",
"uploadPending": "Téléchargement en attente", "uploadPending": "Téléchargement en attente",
"uploadCreatingChangeset": "Création du changeset...", "uploadCreatingChangeset": "Création du changeset...",
"uploadUploading": "Téléchargement en cours...", "uploadUploading": "Téléchargement en cours...",
@@ -177,6 +179,7 @@
"direction": "Direction {}°", "direction": "Direction {}°",
"profileNoDirectionInfo": "Ce profil ne nécessite pas de direction.", "profileNoDirectionInfo": "Ce profil ne nécessite pas de direction.",
"mustBeLoggedIn": "Vous devez être connecté pour soumettre de nouveaux nœuds. Veuillez vous connecter via les Paramètres.", "mustBeLoggedIn": "Vous devez être connecté pour soumettre de nouveaux nœuds. Veuillez vous connecter via les Paramètres.",
"accountBlocked": "Votre compte OpenStreetMap a un blocage actif et ne peut pas soumettre de nouveaux nœuds.",
"enableSubmittableProfile": "Activez un profil soumissible dans les Paramètres pour soumettre de nouveaux nœuds.", "enableSubmittableProfile": "Activez un profil soumissible dans les Paramètres pour soumettre de nouveaux nœuds.",
"profileViewOnlyWarning": "Ce profil est uniquement pour la visualisation de la carte. Veuillez sélectionner un profil soumissible pour soumettre de nouveaux nœuds.", "profileViewOnlyWarning": "Ce profil est uniquement pour la visualisation de la carte. Veuillez sélectionner un profil soumissible pour soumettre de nouveaux nœuds.",
"loadingAreaData": "Chargement des données de zone... Veuillez patienter avant de soumettre.", "loadingAreaData": "Chargement des données de zone... Veuillez patienter avant de soumettre.",
@@ -191,6 +194,7 @@
"profileNoDirectionInfo": "Ce profil ne nécessite pas de direction.", "profileNoDirectionInfo": "Ce profil ne nécessite pas de direction.",
"temporarilyDisabled": "Les modifications ont été temporairement désactivées pendant que nous résolvons un bug - désolés - revenez bientôt.", "temporarilyDisabled": "Les modifications ont été temporairement désactivées pendant que nous résolvons un bug - désolés - revenez bientôt.",
"mustBeLoggedIn": "Vous devez être connecté pour modifier les nœuds. Veuillez vous connecter via les Paramètres.", "mustBeLoggedIn": "Vous devez être connecté pour modifier les nœuds. Veuillez vous connecter via les Paramètres.",
"accountBlocked": "Votre compte OpenStreetMap a un blocage actif et ne peut pas modifier de nœuds.",
"sandboxModeWarning": "Impossible de soumettre des modifications de nœuds de production au sandbox. Passez au mode Production dans les Paramètres pour modifier les nœuds.", "sandboxModeWarning": "Impossible de soumettre des modifications de nœuds de production au sandbox. Passez au mode Production dans les Paramètres pour modifier les nœuds.",
"enableSubmittableProfile": "Activez un profil soumissible dans les Paramètres pour modifier les nœuds.", "enableSubmittableProfile": "Activez un profil soumissible dans les Paramètres pour modifier les nœuds.",
"profileViewOnlyWarning": "Ce profil est uniquement pour la visualisation de la carte. Veuillez sélectionner un profil soumissible pour modifier les nœuds.", "profileViewOnlyWarning": "Ce profil est uniquement pour la visualisation de la carte. Veuillez sélectionner un profil soumissible pour modifier les nœuds.",
@@ -278,7 +282,9 @@
"reauthLater": "Plus Tard", "reauthLater": "Plus Tard",
"unreadNotificationsTitle": "Vous Avez des Notifications Non Lues", "unreadNotificationsTitle": "Vous Avez des Notifications Non Lues",
"unreadNotificationsMessage": "Vous avez des messages OSM non lus et/ou de nouveaux commentaires sur vos changesets.", "unreadNotificationsMessage": "Vous avez des messages OSM non lus et/ou de nouveaux commentaires sur vos changesets.",
"unreadNotificationsView": "Voir Maintenant" "unreadNotificationsView": "Voir Maintenant",
"activeBlockTitle": "Votre compte OSM a un blocage actif",
"activeBlockMessage": "Il semble que votre compte OpenStreetMap ait un blocage actif et ne puisse actuellement pas effectuer de soumissions. Veuillez lire le message que vous devriez avoir reçu pour savoir pourquoi et ce que vous pouvez faire pour y remédier."
}, },
"queue": { "queue": {
"title": "File de Téléchargement", "title": "File de Téléchargement",
@@ -314,6 +320,7 @@
"processingPaused": "Traitement de la File d'Attente Interrompu", "processingPaused": "Traitement de la File d'Attente Interrompu",
"pausedDueToOffline": "Le traitement des téléversements est interrompu car le mode hors ligne est activé.", "pausedDueToOffline": "Le traitement des téléversements est interrompu car le mode hors ligne est activé.",
"pausedByUser": "Le traitement des téléversements est interrompu manuellement.", "pausedByUser": "Le traitement des téléversements est interrompu manuellement.",
"pausedDueToBlock": "Le traitement des envois est en pause car votre compte OSM a un blocage actif.",
"inFlightWaitTitle": "Finalisation de la Soumission", "inFlightWaitTitle": "Finalisation de la Soumission",
"inFlightWaitMessage": "Une soumission est actuellement en cours et doit se terminer avant que ce paramètre puisse être appliqué. Cela ne devrait prendre qu'un instant.", "inFlightWaitMessage": "Une soumission est actuellement en cours et doit se terminer avant que ce paramètre puisse être appliqué. Cela ne devrait prendre qu'un instant.",
"inFlightCompleteTitle": "Soumission Terminée", "inFlightCompleteTitle": "Soumission Terminée",
+8 -1
View File
@@ -49,6 +49,7 @@
"edit": "Modifica", "edit": "Modifica",
"delete": "Elimina", "delete": "Elimina",
"cancel": "Annulla", "cancel": "Annulla",
"dismiss": "Ignora",
"ok": "OK", "ok": "OK",
"close": "Chiudi", "close": "Chiudi",
"submit": "Invia", "submit": "Invia",
@@ -161,6 +162,7 @@
"deleteReasonCustom": "Personalizzato..", "deleteReasonCustom": "Personalizzato..",
"deleteReasonHint": "Descrivi il motivo...", "deleteReasonHint": "Descrivi il motivo...",
"mustBeLoggedInToDelete": "Devi aver effettuato l'accesso con un account OpenStreetMap per contribuire ai dati della mappa.", "mustBeLoggedInToDelete": "Devi aver effettuato l'accesso con un account OpenStreetMap per contribuire ai dati della mappa.",
"accountBlockedCannotDelete": "Il tuo account OpenStreetMap ha un blocco attivo e non può eliminare i nodi.",
"uploadPending": "Caricamento in sospeso", "uploadPending": "Caricamento in sospeso",
"uploadCreatingChangeset": "Creazione changeset...", "uploadCreatingChangeset": "Creazione changeset...",
"uploadUploading": "Caricamento...", "uploadUploading": "Caricamento...",
@@ -177,6 +179,7 @@
"direction": "Direzione {}°", "direction": "Direzione {}°",
"profileNoDirectionInfo": "Questo profilo non richiede una direzione.", "profileNoDirectionInfo": "Questo profilo non richiede una direzione.",
"mustBeLoggedIn": "Devi essere loggato per inviare nuovi nodi. Per favore accedi tramite Impostazioni.", "mustBeLoggedIn": "Devi essere loggato per inviare nuovi nodi. Per favore accedi tramite Impostazioni.",
"accountBlocked": "Il tuo account OpenStreetMap ha un blocco attivo e non può inviare nuovi nodi.",
"enableSubmittableProfile": "Abilita un profilo inviabile nelle Impostazioni per inviare nuovi nodi.", "enableSubmittableProfile": "Abilita un profilo inviabile nelle Impostazioni per inviare nuovi nodi.",
"profileViewOnlyWarning": "Questo profilo è solo per la visualizzazione della mappa. Per favore seleziona un profilo inviabile per inviare nuovi nodi.", "profileViewOnlyWarning": "Questo profilo è solo per la visualizzazione della mappa. Per favore seleziona un profilo inviabile per inviare nuovi nodi.",
"loadingAreaData": "Caricamento dati area... Per favore attendi prima di inviare.", "loadingAreaData": "Caricamento dati area... Per favore attendi prima di inviare.",
@@ -191,6 +194,7 @@
"profileNoDirectionInfo": "Questo profilo non richiede una direzione.", "profileNoDirectionInfo": "Questo profilo non richiede una direzione.",
"temporarilyDisabled": "Le modifiche sono state temporaneamente disabilitate mentre risolviamo un bug - scuse - torna presto.", "temporarilyDisabled": "Le modifiche sono state temporaneamente disabilitate mentre risolviamo un bug - scuse - torna presto.",
"mustBeLoggedIn": "Devi essere loggato per modificare i nodi. Per favore accedi tramite Impostazioni.", "mustBeLoggedIn": "Devi essere loggato per modificare i nodi. Per favore accedi tramite Impostazioni.",
"accountBlocked": "Il tuo account OpenStreetMap ha un blocco attivo e non può modificare i nodi.",
"sandboxModeWarning": "Impossibile inviare modifiche di nodi di produzione alla sandbox. Passa alla modalità Produzione nelle Impostazioni per modificare i nodi.", "sandboxModeWarning": "Impossibile inviare modifiche di nodi di produzione alla sandbox. Passa alla modalità Produzione nelle Impostazioni per modificare i nodi.",
"enableSubmittableProfile": "Abilita un profilo inviabile nelle Impostazioni per modificare i nodi.", "enableSubmittableProfile": "Abilita un profilo inviabile nelle Impostazioni per modificare i nodi.",
"profileViewOnlyWarning": "Questo profilo è solo per la visualizzazione della mappa. Per favore seleziona un profilo inviabile per modificare i nodi.", "profileViewOnlyWarning": "Questo profilo è solo per la visualizzazione della mappa. Per favore seleziona un profilo inviabile per modificare i nodi.",
@@ -278,7 +282,9 @@
"reauthLater": "Più Tardi", "reauthLater": "Più Tardi",
"unreadNotificationsTitle": "Hai Notifiche Non Lette", "unreadNotificationsTitle": "Hai Notifiche Non Lette",
"unreadNotificationsMessage": "Hai messaggi OSM non letti e/o nuovi commenti sui tuoi changeset.", "unreadNotificationsMessage": "Hai messaggi OSM non letti e/o nuovi commenti sui tuoi changeset.",
"unreadNotificationsView": "Visualizza Ora" "unreadNotificationsView": "Visualizza Ora",
"activeBlockTitle": "Il tuo account OSM ha un blocco attivo",
"activeBlockMessage": "Sembra che il tuo account OpenStreetMap abbia un blocco attivo e sia attualmente impossibilitato a effettuare invii. Leggi il messaggio che dovresti aver ricevuto per sapere perché e cosa puoi fare per risolvere il problema."
}, },
"queue": { "queue": {
"title": "Coda di Upload", "title": "Coda di Upload",
@@ -314,6 +320,7 @@
"processingPaused": "Elaborazione Coda Sospesa", "processingPaused": "Elaborazione Coda Sospesa",
"pausedDueToOffline": "L'elaborazione dei caricamenti è sospesa perché la modalità offline è abilitata.", "pausedDueToOffline": "L'elaborazione dei caricamenti è sospesa perché la modalità offline è abilitata.",
"pausedByUser": "L'elaborazione dei caricamenti è sospesa manualmente.", "pausedByUser": "L'elaborazione dei caricamenti è sospesa manualmente.",
"pausedDueToBlock": "L'elaborazione della coda di caricamento è in pausa perché il tuo account OSM ha un blocco attivo.",
"inFlightWaitTitle": "Completamento Invio", "inFlightWaitTitle": "Completamento Invio",
"inFlightWaitMessage": "Un invio è attualmente in corso e deve terminare prima che questa impostazione possa essere applicata. Questo dovrebbe richiedere solo un momento.", "inFlightWaitMessage": "Un invio è attualmente in corso e deve terminare prima che questa impostazione possa essere applicata. Questo dovrebbe richiedere solo un momento.",
"inFlightCompleteTitle": "Invio Completato", "inFlightCompleteTitle": "Invio Completato",
+8 -1
View File
@@ -49,6 +49,7 @@
"edit": "Bewerken", "edit": "Bewerken",
"delete": "Verwijderen", "delete": "Verwijderen",
"cancel": "Annuleren", "cancel": "Annuleren",
"dismiss": "Negeren",
"ok": "OK", "ok": "OK",
"close": "Sluiten", "close": "Sluiten",
"submit": "Indienen", "submit": "Indienen",
@@ -161,6 +162,7 @@
"deleteReasonCustom": "Aangepast..", "deleteReasonCustom": "Aangepast..",
"deleteReasonHint": "Beschrijf de reden...", "deleteReasonHint": "Beschrijf de reden...",
"mustBeLoggedInToDelete": "U moet ingelogd zijn met een OpenStreetMap-account om kaartgegevens bij te dragen.", "mustBeLoggedInToDelete": "U moet ingelogd zijn met een OpenStreetMap-account om kaartgegevens bij te dragen.",
"accountBlockedCannotDelete": "Uw OpenStreetMap-account heeft een actieve blokkade en kan geen knooppunten verwijderen.",
"uploadPending": "Upload in behandeling", "uploadPending": "Upload in behandeling",
"uploadCreatingChangeset": "Changeset maken...", "uploadCreatingChangeset": "Changeset maken...",
"uploadUploading": "Uploaden...", "uploadUploading": "Uploaden...",
@@ -177,6 +179,7 @@
"direction": "Richting {}°", "direction": "Richting {}°",
"profileNoDirectionInfo": "Dit profiel vereist geen richting.", "profileNoDirectionInfo": "Dit profiel vereist geen richting.",
"mustBeLoggedIn": "U moet ingelogd zijn om nieuwe nodes in te dienen. Log in via Instellingen.", "mustBeLoggedIn": "U moet ingelogd zijn om nieuwe nodes in te dienen. Log in via Instellingen.",
"accountBlocked": "Uw OpenStreetMap-account heeft een actieve blokkade en kan geen nieuwe knooppunten indienen.",
"enableSubmittableProfile": "Schakel een indienbaar profiel in via Instellingen om nieuwe nodes in te dienen.", "enableSubmittableProfile": "Schakel een indienbaar profiel in via Instellingen om nieuwe nodes in te dienen.",
"profileViewOnlyWarning": "Dit profiel is alleen voor kaart weergave. Selecteer een indienbaar profiel om nieuwe nodes in te dienen.", "profileViewOnlyWarning": "Dit profiel is alleen voor kaart weergave. Selecteer een indienbaar profiel om nieuwe nodes in te dienen.",
"loadingAreaData": "Gebied gegevens laden... Wacht even voordat u indient.", "loadingAreaData": "Gebied gegevens laden... Wacht even voordat u indient.",
@@ -191,6 +194,7 @@
"profileNoDirectionInfo": "Dit profiel vereist geen richting.", "profileNoDirectionInfo": "Dit profiel vereist geen richting.",
"temporarilyDisabled": "Bewerkingen zijn tijdelijk uitgeschakeld terwijl we een bug oplossen - excuses - controleer binnenkort opnieuw.", "temporarilyDisabled": "Bewerkingen zijn tijdelijk uitgeschakeld terwijl we een bug oplossen - excuses - controleer binnenkort opnieuw.",
"mustBeLoggedIn": "U moet ingelogd zijn om nodes te bewerken. Log in via Instellingen.", "mustBeLoggedIn": "U moet ingelogd zijn om nodes te bewerken. Log in via Instellingen.",
"accountBlocked": "Uw OpenStreetMap-account heeft een actieve blokkade en kan geen knooppunten bewerken.",
"sandboxModeWarning": "Kan geen bewerkingen op productie nodes naar sandbox versturen. Schakel naar Productie modus in Instellingen om nodes te bewerken.", "sandboxModeWarning": "Kan geen bewerkingen op productie nodes naar sandbox versturen. Schakel naar Productie modus in Instellingen om nodes te bewerken.",
"enableSubmittableProfile": "Schakel een indienbaar profiel in via Instellingen om nodes te bewerken.", "enableSubmittableProfile": "Schakel een indienbaar profiel in via Instellingen om nodes te bewerken.",
"profileViewOnlyWarning": "Dit profiel is alleen voor kaart weergave. Selecteer een indienbaar profiel om nodes te bewerken.", "profileViewOnlyWarning": "Dit profiel is alleen voor kaart weergave. Selecteer een indienbaar profiel om nodes te bewerken.",
@@ -278,7 +282,9 @@
"reauthLater": "Later", "reauthLater": "Later",
"unreadNotificationsTitle": "U Heeft Ongelezen Meldingen", "unreadNotificationsTitle": "U Heeft Ongelezen Meldingen",
"unreadNotificationsMessage": "U heeft ongelezen OSM-berichten en/of nieuwe opmerkingen op uw changesets.", "unreadNotificationsMessage": "U heeft ongelezen OSM-berichten en/of nieuwe opmerkingen op uw changesets.",
"unreadNotificationsView": "Nu Bekijken" "unreadNotificationsView": "Nu Bekijken",
"activeBlockTitle": "Uw OSM-account heeft een actieve blokkade",
"activeBlockMessage": "Het lijkt erop dat uw OpenStreetMap-account een actieve blokkade heeft en momenteel geen inzendingen kan doen. Lees het bericht dat u zou moeten hebben ontvangen om te weten waarom en wat u kunt doen om dit op te lossen."
}, },
"queue": { "queue": {
"title": "Upload Wachtrij", "title": "Upload Wachtrij",
@@ -314,6 +320,7 @@
"processingPaused": "Wachtrij Verwerking Gepauzeerd", "processingPaused": "Wachtrij Verwerking Gepauzeerd",
"pausedDueToOffline": "Upload verwerking is gepauzeerd omdat offline modus is ingeschakeld.", "pausedDueToOffline": "Upload verwerking is gepauzeerd omdat offline modus is ingeschakeld.",
"pausedByUser": "Upload verwerking is handmatig gepauzeerd.", "pausedByUser": "Upload verwerking is handmatig gepauzeerd.",
"pausedDueToBlock": "Het uploadproces is gepauzeerd omdat uw OSM-account een actieve blokkade heeft.",
"inFlightWaitTitle": "Indiening Wordt Afgerond", "inFlightWaitTitle": "Indiening Wordt Afgerond",
"inFlightWaitMessage": "Er is momenteel een indiening bezig die moet worden afgerond voordat deze instelling kan worden toegepast. Dit zou maar een moment moeten duren.", "inFlightWaitMessage": "Er is momenteel een indiening bezig die moet worden afgerond voordat deze instelling kan worden toegepast. Dit zou maar een moment moeten duren.",
"inFlightCompleteTitle": "Indiening Voltooid", "inFlightCompleteTitle": "Indiening Voltooid",
+8 -1
View File
@@ -49,6 +49,7 @@
"edit": "Edytuj", "edit": "Edytuj",
"delete": "Usuń", "delete": "Usuń",
"cancel": "Anuluj", "cancel": "Anuluj",
"dismiss": "Odrzuć",
"ok": "OK", "ok": "OK",
"close": "Zamknij", "close": "Zamknij",
"submit": "Zgłoś", "submit": "Zgłoś",
@@ -161,6 +162,7 @@
"deleteReasonCustom": "Niestandardowy..", "deleteReasonCustom": "Niestandardowy..",
"deleteReasonHint": "Opisz powód...", "deleteReasonHint": "Opisz powód...",
"mustBeLoggedInToDelete": "Musisz być zalogowany na konto OpenStreetMap, aby dodawać dane do mapy.", "mustBeLoggedInToDelete": "Musisz być zalogowany na konto OpenStreetMap, aby dodawać dane do mapy.",
"accountBlockedCannotDelete": "Twoje konto OpenStreetMap ma aktywną blokadę i nie może usuwać węzłów.",
"uploadPending": "Przesyłanie oczekujące", "uploadPending": "Przesyłanie oczekujące",
"uploadCreatingChangeset": "Tworzenie zestawu zmian...", "uploadCreatingChangeset": "Tworzenie zestawu zmian...",
"uploadUploading": "Przesyłanie...", "uploadUploading": "Przesyłanie...",
@@ -177,6 +179,7 @@
"direction": "Kierunek {}°", "direction": "Kierunek {}°",
"profileNoDirectionInfo": "Ten profil nie wymaga kierunku.", "profileNoDirectionInfo": "Ten profil nie wymaga kierunku.",
"mustBeLoggedIn": "Musisz być zalogowany, aby zgłaszać nowe węzły. Zaloguj się przez Ustawienia.", "mustBeLoggedIn": "Musisz być zalogowany, aby zgłaszać nowe węzły. Zaloguj się przez Ustawienia.",
"accountBlocked": "Twoje konto OpenStreetMap ma aktywną blokadę i nie może zgłaszać nowych węzłów.",
"enableSubmittableProfile": "Włącz profil możliwy do zgłoszenia w Ustawieniach, aby zgłaszać nowe węzły.", "enableSubmittableProfile": "Włącz profil możliwy do zgłoszenia w Ustawieniach, aby zgłaszać nowe węzły.",
"profileViewOnlyWarning": "Ten profil służy tylko do przeglądania mapy. Proszę wybrać profil możliwy do zgłoszenia, aby zgłaszać nowe węzły.", "profileViewOnlyWarning": "Ten profil służy tylko do przeglądania mapy. Proszę wybrać profil możliwy do zgłoszenia, aby zgłaszać nowe węzły.",
"loadingAreaData": "Ładowanie danych obszaru... Poczekaj przed zgłoszeniem.", "loadingAreaData": "Ładowanie danych obszaru... Poczekaj przed zgłoszeniem.",
@@ -191,6 +194,7 @@
"profileNoDirectionInfo": "Ten profil nie wymaga kierunku.", "profileNoDirectionInfo": "Ten profil nie wymaga kierunku.",
"temporarilyDisabled": "Edycje zostały tymczasowo wyłączone, gdy rozwiązujemy błąd - przepraszamy - sprawdź wkrótce.", "temporarilyDisabled": "Edycje zostały tymczasowo wyłączone, gdy rozwiązujemy błąd - przepraszamy - sprawdź wkrótce.",
"mustBeLoggedIn": "Musisz być zalogowany, aby edytować węzły. Zaloguj się przez Ustawienia.", "mustBeLoggedIn": "Musisz być zalogowany, aby edytować węzły. Zaloguj się przez Ustawienia.",
"accountBlocked": "Twoje konto OpenStreetMap ma aktywną blokadę i nie może edytować węzłów.",
"sandboxModeWarning": "Nie można przesyłać edycji węzłów produkcyjnych do piaskownicy. Przełącz na tryb Produkcyjny w Ustawieniach, aby edytować węzły.", "sandboxModeWarning": "Nie można przesyłać edycji węzłów produkcyjnych do piaskownicy. Przełącz na tryb Produkcyjny w Ustawieniach, aby edytować węzły.",
"enableSubmittableProfile": "Włącz profil możliwy do zgłoszenia w Ustawieniach, aby edytować węzły.", "enableSubmittableProfile": "Włącz profil możliwy do zgłoszenia w Ustawieniach, aby edytować węzły.",
"profileViewOnlyWarning": "Ten profil służy tylko do przeglądania mapy. Proszę wybrać profil możliwy do zgłoszenia, aby edytować węzły.", "profileViewOnlyWarning": "Ten profil służy tylko do przeglądania mapy. Proszę wybrać profil możliwy do zgłoszenia, aby edytować węzły.",
@@ -278,7 +282,9 @@
"reauthLater": "Później", "reauthLater": "Później",
"unreadNotificationsTitle": "Masz Nieprzeczytane Powiadomienia", "unreadNotificationsTitle": "Masz Nieprzeczytane Powiadomienia",
"unreadNotificationsMessage": "Masz nieprzeczytane wiadomości OSM i/lub nowe komentarze do Twoich zestawów zmian.", "unreadNotificationsMessage": "Masz nieprzeczytane wiadomości OSM i/lub nowe komentarze do Twoich zestawów zmian.",
"unreadNotificationsView": "Zobacz Teraz" "unreadNotificationsView": "Zobacz Teraz",
"activeBlockTitle": "Twoje konto OSM ma aktywną blokadę",
"activeBlockMessage": "Wygląda na to, że Twoje konto OpenStreetMap ma aktywną blokadę i obecnie nie może dokonywać zgłoszeń. Przeczytaj wiadomość, którą powinieneś był otrzymać, aby dowiedzieć się dlaczego i co możesz zrobić, aby to rozwiązać."
}, },
"queue": { "queue": {
"title": "Kolejka Przesyłania", "title": "Kolejka Przesyłania",
@@ -314,6 +320,7 @@
"processingPaused": "Przetwarzanie Kolejki Wstrzymane", "processingPaused": "Przetwarzanie Kolejki Wstrzymane",
"pausedDueToOffline": "Przetwarzanie przesyłania jest wstrzymane, ponieważ tryb offline jest włączony.", "pausedDueToOffline": "Przetwarzanie przesyłania jest wstrzymane, ponieważ tryb offline jest włączony.",
"pausedByUser": "Przetwarzanie przesyłania jest ręcznie wstrzymane.", "pausedByUser": "Przetwarzanie przesyłania jest ręcznie wstrzymane.",
"pausedDueToBlock": "Przetwarzanie kolejki przesyłania jest wstrzymane, ponieważ Twoje konto OSM ma aktywną blokadę.",
"inFlightWaitTitle": "Kończenie Przesyłania", "inFlightWaitTitle": "Kończenie Przesyłania",
"inFlightWaitMessage": "Przesyłanie jest obecnie w toku i musi zostać zakończone, zanim to ustawienie będzie mogło zostać zastosowane. To powinno zająć tylko chwilę.", "inFlightWaitMessage": "Przesyłanie jest obecnie w toku i musi zostać zakończone, zanim to ustawienie będzie mogło zostać zastosowane. To powinno zająć tylko chwilę.",
"inFlightCompleteTitle": "Przesyłanie Zakończone", "inFlightCompleteTitle": "Przesyłanie Zakończone",
+8 -1
View File
@@ -49,6 +49,7 @@
"edit": "Editar", "edit": "Editar",
"delete": "Excluir", "delete": "Excluir",
"cancel": "Cancelar", "cancel": "Cancelar",
"dismiss": "Dispensar",
"ok": "OK", "ok": "OK",
"close": "Fechar", "close": "Fechar",
"submit": "Enviar", "submit": "Enviar",
@@ -161,6 +162,7 @@
"deleteReasonCustom": "Personalizado..", "deleteReasonCustom": "Personalizado..",
"deleteReasonHint": "Descreva o motivo...", "deleteReasonHint": "Descreva o motivo...",
"mustBeLoggedInToDelete": "Você precisa estar conectado com uma conta do OpenStreetMap para contribuir com dados do mapa.", "mustBeLoggedInToDelete": "Você precisa estar conectado com uma conta do OpenStreetMap para contribuir com dados do mapa.",
"accountBlockedCannotDelete": "Sua conta OpenStreetMap tem um bloqueio ativo e não pode excluir nós.",
"uploadPending": "Envio pendente", "uploadPending": "Envio pendente",
"uploadCreatingChangeset": "Criando changeset...", "uploadCreatingChangeset": "Criando changeset...",
"uploadUploading": "Enviando...", "uploadUploading": "Enviando...",
@@ -177,6 +179,7 @@
"direction": "Direção {}°", "direction": "Direção {}°",
"profileNoDirectionInfo": "Este perfil não requer uma direção.", "profileNoDirectionInfo": "Este perfil não requer uma direção.",
"mustBeLoggedIn": "Você deve estar logado para enviar novos nós. Por favor, faça login via Configurações.", "mustBeLoggedIn": "Você deve estar logado para enviar novos nós. Por favor, faça login via Configurações.",
"accountBlocked": "Sua conta OpenStreetMap tem um bloqueio ativo e não pode enviar novos nós.",
"enableSubmittableProfile": "Ative um perfil enviável nas Configurações para enviar novos nós.", "enableSubmittableProfile": "Ative um perfil enviável nas Configurações para enviar novos nós.",
"profileViewOnlyWarning": "Este perfil é apenas para visualização do mapa. Por favor, selecione um perfil enviável para enviar novos nós.", "profileViewOnlyWarning": "Este perfil é apenas para visualização do mapa. Por favor, selecione um perfil enviável para enviar novos nós.",
"loadingAreaData": "Carregando dados da área... Por favor aguarde antes de enviar.", "loadingAreaData": "Carregando dados da área... Por favor aguarde antes de enviar.",
@@ -191,6 +194,7 @@
"profileNoDirectionInfo": "Este perfil não requer uma direção.", "profileNoDirectionInfo": "Este perfil não requer uma direção.",
"temporarilyDisabled": "As edições foram temporariamente desabilitadas enquanto resolvemos um bug - desculpe - volte em breve.", "temporarilyDisabled": "As edições foram temporariamente desabilitadas enquanto resolvemos um bug - desculpe - volte em breve.",
"mustBeLoggedIn": "Você deve estar logado para editar nós. Por favor, faça login via Configurações.", "mustBeLoggedIn": "Você deve estar logado para editar nós. Por favor, faça login via Configurações.",
"accountBlocked": "Sua conta OpenStreetMap tem um bloqueio ativo e não pode editar nós.",
"sandboxModeWarning": "Não é possível enviar edições de nós de produção para o sandbox. Mude para o modo Produção nas Configurações para editar nós.", "sandboxModeWarning": "Não é possível enviar edições de nós de produção para o sandbox. Mude para o modo Produção nas Configurações para editar nós.",
"enableSubmittableProfile": "Ative um perfil enviável nas Configurações para editar nós.", "enableSubmittableProfile": "Ative um perfil enviável nas Configurações para editar nós.",
"profileViewOnlyWarning": "Este perfil é apenas para visualização do mapa. Por favor, selecione um perfil enviável para editar nós.", "profileViewOnlyWarning": "Este perfil é apenas para visualização do mapa. Por favor, selecione um perfil enviável para editar nós.",
@@ -278,7 +282,9 @@
"reauthLater": "Mais Tarde", "reauthLater": "Mais Tarde",
"unreadNotificationsTitle": "Você Tem Notificações Não Lidas", "unreadNotificationsTitle": "Você Tem Notificações Não Lidas",
"unreadNotificationsMessage": "Você tem mensagens OSM não lidas e/ou novos comentários nos seus changesets.", "unreadNotificationsMessage": "Você tem mensagens OSM não lidas e/ou novos comentários nos seus changesets.",
"unreadNotificationsView": "Ver Agora" "unreadNotificationsView": "Ver Agora",
"activeBlockTitle": "Sua conta OSM tem um bloqueio ativo",
"activeBlockMessage": "Parece que sua conta OpenStreetMap tem um bloqueio ativo e está impedida de fazer envios no momento. Leia a mensagem que você deve ter recebido para saber o motivo e o que pode fazer para resolver."
}, },
"queue": { "queue": {
"title": "Fila de Upload", "title": "Fila de Upload",
@@ -314,6 +320,7 @@
"processingPaused": "Processamento da Fila Pausado", "processingPaused": "Processamento da Fila Pausado",
"pausedDueToOffline": "O processamento de upload está pausado porque o modo offline está habilitado.", "pausedDueToOffline": "O processamento de upload está pausado porque o modo offline está habilitado.",
"pausedByUser": "O processamento de upload está pausado manualmente.", "pausedByUser": "O processamento de upload está pausado manualmente.",
"pausedDueToBlock": "O processamento da fila de upload está pausado porque sua conta OSM tem um bloqueio ativo.",
"inFlightWaitTitle": "Finalizando Envio", "inFlightWaitTitle": "Finalizando Envio",
"inFlightWaitMessage": "Atualmente há um envio em andamento que precisa terminar antes que esta configuração possa ser aplicada. Isso deve levar apenas um momento.", "inFlightWaitMessage": "Atualmente há um envio em andamento que precisa terminar antes que esta configuração possa ser aplicada. Isso deve levar apenas um momento.",
"inFlightCompleteTitle": "Envio Concluído", "inFlightCompleteTitle": "Envio Concluído",
+8 -1
View File
@@ -49,6 +49,7 @@
"edit": "Düzenle", "edit": "Düzenle",
"delete": "Sil", "delete": "Sil",
"cancel": "İptal", "cancel": "İptal",
"dismiss": "Kapat",
"ok": "Tamam", "ok": "Tamam",
"close": "Kapat", "close": "Kapat",
"submit": "Gönder", "submit": "Gönder",
@@ -161,6 +162,7 @@
"deleteReasonCustom": "Özel..", "deleteReasonCustom": "Özel..",
"deleteReasonHint": "Lütfen nedeni açıklayın...", "deleteReasonHint": "Lütfen nedeni açıklayın...",
"mustBeLoggedInToDelete": "Harita verilerine katkıda bulunmak için bir OpenStreetMap hesabıyla oturum açmış olmanız gerekir.", "mustBeLoggedInToDelete": "Harita verilerine katkıda bulunmak için bir OpenStreetMap hesabıyla oturum açmış olmanız gerekir.",
"accountBlockedCannotDelete": "OpenStreetMap hesabınızda aktif bir engelleme var ve düğüm silemez.",
"uploadPending": "Yükleme bekliyor", "uploadPending": "Yükleme bekliyor",
"uploadCreatingChangeset": "Değişiklik seti oluşturuluyor...", "uploadCreatingChangeset": "Değişiklik seti oluşturuluyor...",
"uploadUploading": "Yükleniyor...", "uploadUploading": "Yükleniyor...",
@@ -177,6 +179,7 @@
"direction": "Yön {}°", "direction": "Yön {}°",
"profileNoDirectionInfo": "Bu profil bir yön gerektirmez.", "profileNoDirectionInfo": "Bu profil bir yön gerektirmez.",
"mustBeLoggedIn": "Yeni düğümler göndermek için giriş yapmalısınız. Lütfen Ayarlar üzerinden giriş yapın.", "mustBeLoggedIn": "Yeni düğümler göndermek için giriş yapmalısınız. Lütfen Ayarlar üzerinden giriş yapın.",
"accountBlocked": "OpenStreetMap hesabınızda aktif bir engelleme var ve yeni düğüm gönderemez.",
"enableSubmittableProfile": "Yeni düğümler göndermek için Ayarlarda gönderilebilir bir profili etkinleştirin.", "enableSubmittableProfile": "Yeni düğümler göndermek için Ayarlarda gönderilebilir bir profili etkinleştirin.",
"profileViewOnlyWarning": "Bu profil sadece harita görüntüleme içindir. Yeni düğümler göndermek için lütfen gönderilebilir bir profil seçin.", "profileViewOnlyWarning": "Bu profil sadece harita görüntüleme içindir. Yeni düğümler göndermek için lütfen gönderilebilir bir profil seçin.",
"loadingAreaData": "Alan verisi yükleniyor... Göndermeden önce lütfen bekleyin.", "loadingAreaData": "Alan verisi yükleniyor... Göndermeden önce lütfen bekleyin.",
@@ -191,6 +194,7 @@
"profileNoDirectionInfo": "Bu profil bir yön gerektirmez.", "profileNoDirectionInfo": "Bu profil bir yön gerektirmez.",
"temporarilyDisabled": "Bir hatayı çözmemiz sırasında düzenlemeler geçici olarak devre dışı bırakıldı - özür dileriz - yakında tekrar kontrol edin.", "temporarilyDisabled": "Bir hatayı çözmemiz sırasında düzenlemeler geçici olarak devre dışı bırakıldı - özür dileriz - yakında tekrar kontrol edin.",
"mustBeLoggedIn": "Düğümleri düzenlemek için giriş yapmalısınız. Lütfen Ayarlar üzerinden giriş yapın.", "mustBeLoggedIn": "Düğümleri düzenlemek için giriş yapmalısınız. Lütfen Ayarlar üzerinden giriş yapın.",
"accountBlocked": "OpenStreetMap hesabınızda aktif bir engelleme var ve düğüm düzenleyemez.",
"sandboxModeWarning": "Üretim düğümlerinde düzenlemeleri sandbox'a gönderemezsiniz. Düğümleri düzenlemek için Ayarlar'da Üretim moduna geçin.", "sandboxModeWarning": "Üretim düğümlerinde düzenlemeleri sandbox'a gönderemezsiniz. Düğümleri düzenlemek için Ayarlar'da Üretim moduna geçin.",
"enableSubmittableProfile": "Düğümleri düzenlemek için Ayarlarda gönderilebilir bir profili etkinleştirin.", "enableSubmittableProfile": "Düğümleri düzenlemek için Ayarlarda gönderilebilir bir profili etkinleştirin.",
"profileViewOnlyWarning": "Bu profil sadece harita görüntüleme içindir. Düğümleri düzenlemek için lütfen gönderilebilir bir profil seçin.", "profileViewOnlyWarning": "Bu profil sadece harita görüntüleme içindir. Düğümleri düzenlemek için lütfen gönderilebilir bir profil seçin.",
@@ -278,7 +282,9 @@
"reauthLater": "Sonra", "reauthLater": "Sonra",
"unreadNotificationsTitle": "Okunmamış Bildirimleriniz Var", "unreadNotificationsTitle": "Okunmamış Bildirimleriniz Var",
"unreadNotificationsMessage": "Okunmamış OSM mesajlarınız ve/veya değişiklik kümelerinize yapılan yeni yorumlar var.", "unreadNotificationsMessage": "Okunmamış OSM mesajlarınız ve/veya değişiklik kümelerinize yapılan yeni yorumlar var.",
"unreadNotificationsView": "Şimdi Görüntüle" "unreadNotificationsView": "Şimdi Görüntüle",
"activeBlockTitle": "OSM Hesabınızda Aktif Bir Engelleme Var",
"activeBlockMessage": "OpenStreetMap hesabınızda aktif bir engelleme olduğu ve şu anda gönderim yapmanızın kısıtlandığı görülüyor. Nedenini ve bunu nasıl çözebileceğinizi öğrenmek için almış olmanız gereken mesajı okuyun."
}, },
"queue": { "queue": {
"title": "Yükleme Kuyruğu", "title": "Yükleme Kuyruğu",
@@ -314,6 +320,7 @@
"processingPaused": "Kuyruk İşleme Duraklatıldı", "processingPaused": "Kuyruk İşleme Duraklatıldı",
"pausedDueToOffline": "Çevrimdışı mod etkin olduğu için yükleme işleme duraklatıldı.", "pausedDueToOffline": "Çevrimdışı mod etkin olduğu için yükleme işleme duraklatıldı.",
"pausedByUser": "Yükleme işleme manuel olarak duraklatıldı.", "pausedByUser": "Yükleme işleme manuel olarak duraklatıldı.",
"pausedDueToBlock": "OSM hesabınızda aktif bir engelleme olduğu için yükleme işlemi duraklatıldı.",
"inFlightWaitTitle": "Gönderim Tamamlanıyor", "inFlightWaitTitle": "Gönderim Tamamlanıyor",
"inFlightWaitMessage": "Şu anda devam eden bir gönderim var ve bu ayar uygulanmadan önce tamamlanması gerekiyor. Bu sadece bir an sürmelidir.", "inFlightWaitMessage": "Şu anda devam eden bir gönderim var ve bu ayar uygulanmadan önce tamamlanması gerekiyor. Bu sadece bir an sürmelidir.",
"inFlightCompleteTitle": "Gönderim Tamamlandı", "inFlightCompleteTitle": "Gönderim Tamamlandı",
+8 -1
View File
@@ -49,6 +49,7 @@
"edit": "Редагувати", "edit": "Редагувати",
"delete": "Видалити", "delete": "Видалити",
"cancel": "Скасувати", "cancel": "Скасувати",
"dismiss": "Відхилити",
"ok": "ОК", "ok": "ОК",
"close": "Закрити", "close": "Закрити",
"submit": "Подати", "submit": "Подати",
@@ -161,6 +162,7 @@
"deleteReasonCustom": "Власний..", "deleteReasonCustom": "Власний..",
"deleteReasonHint": "Опишіть причину...", "deleteReasonHint": "Опишіть причину...",
"mustBeLoggedInToDelete": "Ви повинні увійти в обліковий запис OpenStreetMap, щоб надавати дані карти.", "mustBeLoggedInToDelete": "Ви повинні увійти в обліковий запис OpenStreetMap, щоб надавати дані карти.",
"accountBlockedCannotDelete": "Ваш обліковий запис OpenStreetMap має активне блокування і не може видаляти вузли.",
"uploadPending": "Завантаження очікує", "uploadPending": "Завантаження очікує",
"uploadCreatingChangeset": "Створення набору змін...", "uploadCreatingChangeset": "Створення набору змін...",
"uploadUploading": "Завантаження...", "uploadUploading": "Завантаження...",
@@ -177,6 +179,7 @@
"direction": "Напрямок {}°", "direction": "Напрямок {}°",
"profileNoDirectionInfo": "Цей профіль не потребує напрямку.", "profileNoDirectionInfo": "Цей профіль не потребує напрямку.",
"mustBeLoggedIn": "Ви повинні увійти в систему, щоб подавати нові вузли. Будь ласка, увійдіть через Налаштування.", "mustBeLoggedIn": "Ви повинні увійти в систему, щоб подавати нові вузли. Будь ласка, увійдіть через Налаштування.",
"accountBlocked": "Ваш обліковий запис OpenStreetMap має активне блокування і не може подавати нові вузли.",
"enableSubmittableProfile": "Увімкніть профіль, який можна подавати, в Налаштуваннях для подання нових вузлів.", "enableSubmittableProfile": "Увімкніть профіль, який можна подавати, в Налаштуваннях для подання нових вузлів.",
"profileViewOnlyWarning": "Цей профіль призначений лише для перегляду карти. Будь ласка, оберіть профіль, який можна подавати, для подання нових вузлів.", "profileViewOnlyWarning": "Цей профіль призначений лише для перегляду карти. Будь ласка, оберіть профіль, який можна подавати, для подання нових вузлів.",
"loadingAreaData": "Завантаження даних області... Будь ласка, зачекайте перед поданням.", "loadingAreaData": "Завантаження даних області... Будь ласка, зачекайте перед поданням.",
@@ -191,6 +194,7 @@
"profileNoDirectionInfo": "Цей профіль не потребує напрямку.", "profileNoDirectionInfo": "Цей профіль не потребує напрямку.",
"temporarilyDisabled": "Редагування тимчасово вимкнено, поки ми розбираємося з помилкою - вибачте - перевірте пізніше.", "temporarilyDisabled": "Редагування тимчасово вимкнено, поки ми розбираємося з помилкою - вибачте - перевірте пізніше.",
"mustBeLoggedIn": "Ви повинні увійти в систему, щоб редагувати вузли. Будь ласка, увійдіть через Налаштування.", "mustBeLoggedIn": "Ви повинні увійти в систему, щоб редагувати вузли. Будь ласка, увійдіть через Налаштування.",
"accountBlocked": "Ваш обліковий запис OpenStreetMap має активне блокування і не може редагувати вузли.",
"sandboxModeWarning": "Не можна подавати редагування виробничих вузлів в sandbox. Перейдіть в режим Виробництва в Налаштуваннях для редагування вузлів.", "sandboxModeWarning": "Не можна подавати редагування виробничих вузлів в sandbox. Перейдіть в режим Виробництва в Налаштуваннях для редагування вузлів.",
"enableSubmittableProfile": "Увімкніть профіль, який можна подавати, в Налаштуваннях для редагування вузлів.", "enableSubmittableProfile": "Увімкніть профіль, який можна подавати, в Налаштуваннях для редагування вузлів.",
"profileViewOnlyWarning": "Цей профіль призначений лише для перегляду карти. Будь ласка, оберіть профіль, який можна подавати, для редагування вузлів.", "profileViewOnlyWarning": "Цей профіль призначений лише для перегляду карти. Будь ласка, оберіть профіль, який можна подавати, для редагування вузлів.",
@@ -278,7 +282,9 @@
"reauthLater": "Пізніше", "reauthLater": "Пізніше",
"unreadNotificationsTitle": "У Вас Є Непрочитані Сповіщення", "unreadNotificationsTitle": "У Вас Є Непрочитані Сповіщення",
"unreadNotificationsMessage": "У вас є непрочитані повідомлення OSM та/або нові коментарі до ваших наборів змін.", "unreadNotificationsMessage": "У вас є непрочитані повідомлення OSM та/або нові коментарі до ваших наборів змін.",
"unreadNotificationsView": "Переглянути Зараз" "unreadNotificationsView": "Переглянути Зараз",
"activeBlockTitle": "Ваш обліковий запис OSM має активне блокування",
"activeBlockMessage": "Схоже, що ваш обліковий запис OpenStreetMap має активне блокування і наразі не може надсилати дані. Будь ласка, прочитайте повідомлення, яке ви мали отримати, щоб дізнатися чому та що можна зробити для вирішення цієї проблеми."
}, },
"queue": { "queue": {
"title": "Черга Завантаження", "title": "Черга Завантаження",
@@ -314,6 +320,7 @@
"processingPaused": "Обробка Черги Призупинена", "processingPaused": "Обробка Черги Призупинена",
"pausedDueToOffline": "Обробка завантаження призупинена, оскільки увімкнено офлайн режим.", "pausedDueToOffline": "Обробка завантаження призупинена, оскільки увімкнено офлайн режим.",
"pausedByUser": "Обробка завантаження призупинена вручну.", "pausedByUser": "Обробка завантаження призупинена вручну.",
"pausedDueToBlock": "Обробку черги завантаження призупинено, оскільки ваш обліковий запис OSM має активне блокування.",
"inFlightWaitTitle": "Завершення Подання", "inFlightWaitTitle": "Завершення Подання",
"inFlightWaitMessage": "Наразі триває подання, яке потрібно завершити перед застосуванням цього налаштування. Це має зайняти лише мить.", "inFlightWaitMessage": "Наразі триває подання, яке потрібно завершити перед застосуванням цього налаштування. Це має зайняти лише мить.",
"inFlightCompleteTitle": "Подання Завершено", "inFlightCompleteTitle": "Подання Завершено",
+8 -1
View File
@@ -49,6 +49,7 @@
"edit": "编辑", "edit": "编辑",
"delete": "删除", "delete": "删除",
"cancel": "取消", "cancel": "取消",
"dismiss": "关闭",
"ok": "确定", "ok": "确定",
"close": "关闭", "close": "关闭",
"submit": "提交", "submit": "提交",
@@ -161,6 +162,7 @@
"deleteReasonCustom": "自定义..", "deleteReasonCustom": "自定义..",
"deleteReasonHint": "请描述原因...", "deleteReasonHint": "请描述原因...",
"mustBeLoggedInToDelete": "您必须使用 OpenStreetMap 账户登录才能贡献地图数据。", "mustBeLoggedInToDelete": "您必须使用 OpenStreetMap 账户登录才能贡献地图数据。",
"accountBlockedCannotDelete": "您的 OpenStreetMap 账户有一个有效封禁,无法删除节点。",
"uploadPending": "上传待处理", "uploadPending": "上传待处理",
"uploadCreatingChangeset": "正在创建变更集...", "uploadCreatingChangeset": "正在创建变更集...",
"uploadUploading": "正在上传...", "uploadUploading": "正在上传...",
@@ -177,6 +179,7 @@
"direction": "方向 {}°", "direction": "方向 {}°",
"profileNoDirectionInfo": "此配置文件不需要方向。", "profileNoDirectionInfo": "此配置文件不需要方向。",
"mustBeLoggedIn": "您必须登录才能提交新节点。请通过设置登录。", "mustBeLoggedIn": "您必须登录才能提交新节点。请通过设置登录。",
"accountBlocked": "您的 OpenStreetMap 账户有一个有效封禁,无法提交新节点。",
"enableSubmittableProfile": "在设置中启用可提交的配置文件以提交新节点。", "enableSubmittableProfile": "在设置中启用可提交的配置文件以提交新节点。",
"profileViewOnlyWarning": "此配置文件仅用于地图查看。请选择可提交的配置文件来提交新节点。", "profileViewOnlyWarning": "此配置文件仅用于地图查看。请选择可提交的配置文件来提交新节点。",
"loadingAreaData": "正在加载区域数据...提交前请稍候。", "loadingAreaData": "正在加载区域数据...提交前请稍候。",
@@ -191,6 +194,7 @@
"profileNoDirectionInfo": "此配置文件不需要方向。", "profileNoDirectionInfo": "此配置文件不需要方向。",
"temporarilyDisabled": "编辑功能已暂时禁用,我们正在修复一个错误 - 抱歉 - 请稍后再试。", "temporarilyDisabled": "编辑功能已暂时禁用,我们正在修复一个错误 - 抱歉 - 请稍后再试。",
"mustBeLoggedIn": "您必须登录才能编辑节点。请通过设置登录。", "mustBeLoggedIn": "您必须登录才能编辑节点。请通过设置登录。",
"accountBlocked": "您的 OpenStreetMap 账户有一个有效封禁,无法编辑节点。",
"sandboxModeWarning": "无法将生产节点的编辑提交到沙盒。在设置中切换到生产模式以编辑节点。", "sandboxModeWarning": "无法将生产节点的编辑提交到沙盒。在设置中切换到生产模式以编辑节点。",
"enableSubmittableProfile": "在设置中启用可提交的配置文件以编辑节点。", "enableSubmittableProfile": "在设置中启用可提交的配置文件以编辑节点。",
"profileViewOnlyWarning": "此配置文件仅用于地图查看。请选择可提交的配置文件来编辑节点。", "profileViewOnlyWarning": "此配置文件仅用于地图查看。请选择可提交的配置文件来编辑节点。",
@@ -278,7 +282,9 @@
"reauthLater": "稍后", "reauthLater": "稍后",
"unreadNotificationsTitle": "您有未读通知", "unreadNotificationsTitle": "您有未读通知",
"unreadNotificationsMessage": "您有未读的 OSM 消息和/或对您变更集的新评论。", "unreadNotificationsMessage": "您有未读的 OSM 消息和/或对您变更集的新评论。",
"unreadNotificationsView": "立即查看" "unreadNotificationsView": "立即查看",
"activeBlockTitle": "您的 OSM 账户有一个有效封禁",
"activeBlockMessage": "您的 OpenStreetMap 账户似乎存在有效封禁,目前无法提交内容。请阅读您应该已收到的消息,了解原因以及如何解决。"
}, },
"queue": { "queue": {
"title": "上传队列", "title": "上传队列",
@@ -314,6 +320,7 @@
"processingPaused": "队列处理已暂停", "processingPaused": "队列处理已暂停",
"pausedDueToOffline": "因为离线模式已启用,上传处理已暂停。", "pausedDueToOffline": "因为离线模式已启用,上传处理已暂停。",
"pausedByUser": "上传处理已手动暂停。", "pausedByUser": "上传处理已手动暂停。",
"pausedDueToBlock": "由于您的 OSM 账户有一个有效封禁,上传处理已暂停。",
"inFlightWaitTitle": "正在完成提交", "inFlightWaitTitle": "正在完成提交",
"inFlightWaitMessage": "当前有一个提交正在进行中,需要先完成才能应用此设置。这应该只需要一点时间。", "inFlightWaitMessage": "当前有一个提交正在进行中,需要先完成才能应用此设置。这应该只需要一点时间。",
"inFlightCompleteTitle": "提交完成", "inFlightCompleteTitle": "提交完成",
+22 -2
View File
@@ -37,7 +37,7 @@ class HomeScreen extends StatefulWidget {
State<HomeScreen> createState() => _HomeScreenState(); State<HomeScreen> createState() => _HomeScreenState();
} }
class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin { class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin, WidgetsBindingObserver {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>(); final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
final GlobalKey<MapViewState> _mapViewKey = GlobalKey<MapViewState>(); final GlobalKey<MapViewState> _mapViewKey = GlobalKey<MapViewState>();
late final AnimatedMapController _mapController; late final AnimatedMapController _mapController;
@@ -62,6 +62,7 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
WidgetsBinding.instance.addObserver(this);
_mapController = AnimatedMapController(vsync: this); _mapController = AnimatedMapController(vsync: this);
_sheetCoordinator = SheetCoordinator(); _sheetCoordinator = SheetCoordinator();
_navigationCoordinator = NavigationCoordinator(); _navigationCoordinator = NavigationCoordinator();
@@ -71,11 +72,24 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
@override @override
void dispose() { void dispose() {
WidgetsBinding.instance.removeObserver(this);
DeepLinkService().onNodeDeepLink = null; DeepLinkService().onNodeDeepLink = null;
_mapController.dispose(); _mapController.dispose();
super.dispose(); super.dispose();
} }
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
// Re-check for an active OSM block when the user returns to the app,
// in case they resolved it (or got blocked) while away.
if (state == AppLifecycleState.resumed) {
final appState = context.read<AppState>();
if (appState.isLoggedIn) {
appState.checkAndPromptForActiveBlock(context);
}
}
}
String _getFollowMeTooltip(FollowMeMode mode) { String _getFollowMeTooltip(FollowMeMode mode) {
final locService = LocalizationService.instance; final locService = LocalizationService.instance;
switch (mode) { switch (mode) {
@@ -474,6 +488,10 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
_checkForPopup(); _checkForPopup();
// Check if re-authentication is needed for message notifications // Check if re-authentication is needed for message notifications
appState.checkAndPromptReauthForMessages(context); appState.checkAndPromptReauthForMessages(context);
// Check for an active OSM block on app launch
if (appState.isLoggedIn) {
appState.checkAndPromptForActiveBlock(context);
}
}); });
} }
@@ -544,7 +562,9 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
return IconButton( return IconButton(
tooltip: LocalizationService.instance.settings, tooltip: LocalizationService.instance.settings,
icon: Badge( icon: Badge(
isLabelVisible: appState.hasUnreadNotifications, isLabelVisible: appState.hasActiveBlock || appState.hasUnreadNotifications,
backgroundColor: appState.hasActiveBlock ? Colors.red : null,
label: appState.hasActiveBlock ? const Icon(Icons.block, size: 10, color: Colors.white) : null,
child: const Icon(Icons.settings), child: const Icon(Icons.settings),
), ),
onPressed: () => Navigator.pushNamed(context, '/settings'), onPressed: () => Navigator.pushNamed(context, '/settings'),
+30 -2
View File
@@ -17,11 +17,12 @@ class _OSMAccountScreenState extends State<OSMAccountScreen> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
// Check for messages when screen loads // Check for messages and active blocks when screen loads
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
final appState = context.read<AppState>(); final appState = context.read<AppState>();
if (appState.isLoggedIn) { if (appState.isLoggedIn) {
appState.checkMessages(); appState.checkMessages();
appState.checkAndPromptForActiveBlock(context);
} }
}); });
} }
@@ -83,13 +84,40 @@ class _OSMAccountScreenState extends State<OSMAccountScreen> {
), ),
if (appState.isLoggedIn) ...[ if (appState.isLoggedIn) ...[
// Active block warning - shown whenever a check has
// found the user's OSM account currently blocked.
if (appState.hasActiveBlock) ...[
const Divider(),
ListTile(
leading: Badge(
isLabelVisible: true,
backgroundColor: Colors.red,
child: const Icon(Icons.block, color: Colors.red),
),
title: Text(locService.t('auth.activeBlockTitle')),
subtitle: Text(locService.t('auth.activeBlockMessage')),
trailing: const Icon(Icons.open_in_new),
onTap: () async {
final url = Uri.parse(appState.getMessagesUrl());
if (await canLaunchUrl(url)) {
await launchUrl(url, mode: LaunchMode.externalApplication);
} else {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(locService.t('advancedEdit.couldNotOpenOSMWebsite'))),
);
}
}
},
),
],
const Divider(), const Divider(),
ListTile( ListTile(
leading: const Icon(Icons.wifi_protected_setup), leading: const Icon(Icons.wifi_protected_setup),
title: Text(locService.t('auth.testConnection')), title: Text(locService.t('auth.testConnection')),
subtitle: Text(locService.t('auth.testConnectionSubtitle')), subtitle: Text(locService.t('auth.testConnectionSubtitle')),
onTap: () async { onTap: () async {
final isValid = await appState.validateToken(); final isValid = await appState.testConnection(context);
if (context.mounted) { if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
@@ -53,7 +53,7 @@ class AuthSection extends StatelessWidget {
title: Text(locService.t('auth.testConnection')), title: Text(locService.t('auth.testConnection')),
subtitle: Text(locService.t('auth.testConnectionSubtitle')), subtitle: Text(locService.t('auth.testConnectionSubtitle')),
onTap: () async { onTap: () async {
final isValid = await appState.validateToken(); final isValid = await appState.testConnection(context);
if (context.mounted) { if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
+3 -1
View File
@@ -118,7 +118,9 @@ class SettingsScreen extends StatelessWidget {
return ListTile( return ListTile(
leading: Badge( leading: Badge(
isLabelVisible: appState.hasUnreadNotifications, isLabelVisible: appState.hasActiveBlock || appState.hasUnreadNotifications,
backgroundColor: appState.hasActiveBlock ? Colors.red : null,
label: appState.hasActiveBlock ? const Icon(Icons.block, size: 10, color: Colors.white) : null,
child: const Icon(Icons.account_circle), child: const Icon(Icons.account_circle),
), ),
title: Text(locService.t('auth.osmAccountTitle')), title: Text(locService.t('auth.osmAccountTitle')),
+6 -4
View File
@@ -111,7 +111,7 @@ class UploadQueueScreen extends StatelessWidget {
final appState = context.watch<AppState>(); final appState = context.watch<AppState>();
// Check if queue processing is paused // Check if queue processing is paused
final isQueuePaused = appState.offlineMode || appState.pauseQueueProcessing; final isQueuePaused = appState.offlineMode || appState.pauseQueueProcessing || appState.isQueuePausedDueToBlock;
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
@@ -151,9 +151,11 @@ class UploadQueueScreen extends StatelessWidget {
), ),
), ),
Text( Text(
appState.offlineMode appState.isQueuePausedDueToBlock
? locService.t('queue.pausedDueToOffline') ? locService.t('queue.pausedDueToBlock')
: locService.t('queue.pausedByUser'), : appState.offlineMode
? locService.t('queue.pausedDueToOffline')
: locService.t('queue.pausedByUser'),
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: Colors.orange.shade700, color: Colors.orange.shade700,
+55
View File
@@ -0,0 +1,55 @@
import 'dart:convert';
import 'package:http/http.dart' as http;
import '../state/settings_state.dart';
import 'http_client.dart';
/// Service for checking whether the current user has an active OSM DWG block.
///
/// See: https://wiki.openstreetmap.org/wiki/API_v0.6#List_active_blocks:_GET_/api/0.6/user/blocks/active
class OSMBlockService {
final http.Client _client;
OSMBlockService({http.Client? client}) : _client = client ?? UserAgentClient();
/// Returns whether the user currently has an active block.
///
/// Returns null if not logged in, in simulate mode, or on any error
/// (including no network connection) - callers should treat null as
/// "unknown, assume unchanged" rather than "not blocked".
Future<bool?> checkActiveBlock({
required String? accessToken,
required UploadMode uploadMode,
}) async {
if (uploadMode == UploadMode.simulate) return null;
if (accessToken == null || accessToken.isEmpty) return null;
try {
final apiHost = _getApiHost(uploadMode);
final response = await _client.get(
Uri.parse('$apiHost/api/0.6/user/blocks/active.json'),
headers: {'Authorization': 'Bearer $accessToken'},
);
if (response.statusCode != 200) return null;
final data = jsonDecode(response.body);
final userBlocks = data['user_blocks'];
if (userBlocks is! List) return null;
return userBlocks.isNotEmpty;
} catch (e) {
return null;
}
}
String _getApiHost(UploadMode uploadMode) {
switch (uploadMode) {
case UploadMode.production:
return 'https://api.openstreetmap.org';
case UploadMode.sandbox:
return 'https://api06.dev.openstreetmap.org';
case UploadMode.simulate:
return 'https://api.openstreetmap.org';
}
}
}
+60
View File
@@ -0,0 +1,60 @@
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../services/osm_block_service.dart';
import 'settings_state.dart';
/// Tracks whether the current user has an active OSM DWG block.
///
/// Persisted per upload mode so the last known state survives app restarts
/// (e.g. launching offline) - a failed/inconclusive check leaves the
/// persisted value untouched rather than assuming "not blocked".
class AccountBlockState extends ChangeNotifier {
final OSMBlockService _blockService;
AccountBlockState({OSMBlockService? blockService})
: _blockService = blockService ?? OSMBlockService();
bool _isBlocked = false;
bool get isBlocked => _isBlocked;
/// Load the last known block state for the given upload mode.
Future<void> init(UploadMode uploadMode) async {
final prefs = await SharedPreferences.getInstance();
_isBlocked = prefs.getBool(_prefsKey(uploadMode)) ?? false;
notifyListeners();
}
/// Check for an active block. Returns the conclusive result (true/false),
/// or null if the check was inconclusive (offline, error, not logged in,
/// simulate mode). On null, the previously known state is left unchanged.
Future<bool?> check({
required String? accessToken,
required UploadMode uploadMode,
}) async {
final result = await _blockService.checkActiveBlock(
accessToken: accessToken,
uploadMode: uploadMode,
);
if (result != null && result != _isBlocked) {
_isBlocked = result;
final prefs = await SharedPreferences.getInstance();
await prefs.setBool(_prefsKey(uploadMode), result);
notifyListeners();
}
return result;
}
/// Clear block state (called on logout).
void clear() {
if (_isBlocked) {
_isBlocked = false;
notifyListeners();
}
}
String _prefsKey(UploadMode uploadMode) => 'account_blocked_${uploadMode.name}';
}
+14
View File
@@ -20,11 +20,13 @@ class UploadQueueState extends ChangeNotifier {
final List<PendingUpload> _queue = []; final List<PendingUpload> _queue = [];
Timer? _uploadTimer; Timer? _uploadTimer;
int _activeUploadCount = 0; int _activeUploadCount = 0;
bool _isPausedDueToBlock = false;
// Getters // Getters
int get pendingCount => _queue.length; int get pendingCount => _queue.length;
List<PendingUpload> get pendingUploads => List.unmodifiable(_queue); List<PendingUpload> get pendingUploads => List.unmodifiable(_queue);
int get activeUploadCount => _activeUploadCount; int get activeUploadCount => _activeUploadCount;
bool get isPausedDueToBlock => _isPausedDueToBlock;
// Initialize by loading queue from storage and repopulate cache with pending nodes // Initialize by loading queue from storage and repopulate cache with pending nodes
Future<void> init() async { Future<void> init() async {
@@ -326,8 +328,10 @@ class UploadQueueState extends ChangeNotifier {
required bool pauseQueueProcessing, required bool pauseQueueProcessing,
required UploadMode uploadMode, required UploadMode uploadMode,
required Future<String?> Function() getAccessToken, required Future<String?> Function() getAccessToken,
required Future<bool?> Function() checkActiveBlock,
}) { }) {
_uploadTimer?.cancel(); _uploadTimer?.cancel();
_isPausedDueToBlock = false;
// No uploads if queue is empty, offline mode is enabled, or queue processing is paused // No uploads if queue is empty, offline mode is enabled, or queue processing is paused
if (_queue.isEmpty || offlineMode || pauseQueueProcessing) return; if (_queue.isEmpty || offlineMode || pauseQueueProcessing) return;
@@ -386,6 +390,16 @@ class UploadQueueState extends ChangeNotifier {
final access = await getAccessToken(); final access = await getAccessToken();
if (access == null) return; // not logged in if (access == null) return; // not logged in
// Don't start new uploads if the user has an active OSM block.
// Leave the item pending - it'll be retried next time the uploader starts.
if (await checkActiveBlock() == true) {
debugPrint('[UploadQueue] Active block detected, pausing queue processing');
_isPausedDueToBlock = true;
_uploadTimer?.cancel();
notifyListeners();
return;
}
// Start processing the next pending upload // Start processing the next pending upload
final item = pendingItems.first; final item = pendingItems.first;
debugPrint('[UploadQueue] Starting new upload processing for item at ${item.coord} ($_activeUploadCount/$kMaxConcurrentUploads active)'); debugPrint('[UploadQueue] Starting new upload processing for item at ${item.coord} ($_activeUploadCount/$kMaxConcurrentUploads active)');
+54
View File
@@ -0,0 +1,54 @@
import 'package:flutter/material.dart';
import '../services/localization_service.dart';
/// Dialog shown whenever a check finds that the user's OSM account has an
/// active block. Unlike the unread-notifications dialog, this has no
/// "don't show again" option and is shown every time a check finds a block.
class ActiveBlockDialog extends StatelessWidget {
final VoidCallback onViewMessages;
final VoidCallback onDismiss;
const ActiveBlockDialog({
super.key,
required this.onViewMessages,
required this.onDismiss,
});
@override
Widget build(BuildContext context) {
final locService = LocalizationService.instance;
return AlertDialog(
title: Row(
children: [
Icon(
Icons.block,
color: Theme.of(context).colorScheme.error,
),
const SizedBox(width: 8),
Expanded(
child: Text(locService.t('auth.activeBlockTitle')),
),
],
),
content: Text(locService.t('auth.activeBlockMessage')),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
onDismiss();
},
child: Text(locService.t('actions.dismiss')),
),
FilledButton.icon(
onPressed: () {
Navigator.of(context).pop();
onViewMessages();
},
icon: const Icon(Icons.message, size: 18),
label: Text(locService.t('auth.viewMessages')),
),
],
);
}
}
+17
View File
@@ -421,6 +421,7 @@ class _AddNodeSheetState extends State<AddNodeSheet> {
} }
final allowSubmit = appState.isLoggedIn && final allowSubmit = appState.isLoggedIn &&
!appState.hasActiveBlock &&
submittableProfiles.isNotEmpty && submittableProfiles.isNotEmpty &&
session.profile != null && session.profile != null &&
session.profile!.isSubmittable && session.profile!.isSubmittable &&
@@ -493,6 +494,22 @@ class _AddNodeSheetState extends State<AddNodeSheet> {
], ],
), ),
) )
else if (appState.hasActiveBlock)
Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 12),
child: Row(
children: [
const Icon(Icons.block, color: Colors.red, size: 20),
const SizedBox(width: 6),
Expanded(
child: Text(
locService.t('addNode.accountBlocked'),
style: const TextStyle(color: Colors.red, fontSize: 13),
),
),
],
),
)
else if (submittableProfiles.isEmpty) else if (submittableProfiles.isEmpty)
Padding( Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 12), padding: const EdgeInsets.fromLTRB(16, 0, 16, 12),
+17
View File
@@ -377,6 +377,7 @@ class _EditNodeSheetState extends State<EditNodeSheet> {
final allowSubmit = kEnableNodeEdits && final allowSubmit = kEnableNodeEdits &&
appState.isLoggedIn && appState.isLoggedIn &&
!appState.hasActiveBlock &&
submittableProfiles.isNotEmpty && submittableProfiles.isNotEmpty &&
session.profile != null && session.profile != null &&
session.profile!.isSubmittable && session.profile!.isSubmittable &&
@@ -526,6 +527,22 @@ class _EditNodeSheetState extends State<EditNodeSheet> {
], ],
), ),
) )
else if (appState.hasActiveBlock)
Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 12),
child: Row(
children: [
const Icon(Icons.block, color: Colors.red, size: 20),
const SizedBox(width: 6),
Expanded(
child: Text(
locService.t('editNode.accountBlocked'),
style: const TextStyle(color: Colors.red, fontSize: 13),
),
),
],
),
)
else if (submittableProfiles.isEmpty) else if (submittableProfiles.isEmpty)
Padding( Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 12), padding: const EdgeInsets.fromLTRB(16, 0, 16, 12),
+17
View File
@@ -184,6 +184,23 @@ class _NodeTagSheetState extends State<NodeTagSheet> {
return; return;
} }
if (appState.hasActiveBlock) {
await showDialog<void>(
context: context,
builder: (BuildContext context) => AlertDialog(
title: Text(locService.t('node.confirmDeleteTitle')),
content: Text(locService.t('node.accountBlockedCannotDelete')),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(locService.ok),
),
],
),
);
return;
}
final result = await showDialog<({bool confirmed, String comment})>( final result = await showDialog<({bool confirmed, String comment})>(
context: context, context: context,
builder: (BuildContext context) => _DeleteNodeDialog( builder: (BuildContext context) => _DeleteNodeDialog(
+164
View File
@@ -0,0 +1,164 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart' as http;
import 'package:http/testing.dart';
import 'package:deflockapp/services/osm_block_service.dart';
import 'package:deflockapp/state/settings_state.dart';
void main() {
group('OSMBlockService', () {
test('returns null in simulate mode without making a request', () async {
var requested = false;
final client = MockClient((request) async {
requested = true;
return http.Response('{"user_blocks":[]}', 200);
});
final service = OSMBlockService(client: client);
final result = await service.checkActiveBlock(
accessToken: 'token',
uploadMode: UploadMode.simulate,
);
expect(result, isNull);
expect(requested, isFalse);
});
test('returns null when access token is null', () async {
final client = MockClient((request) async => http.Response('{"user_blocks":[]}', 200));
final service = OSMBlockService(client: client);
final result = await service.checkActiveBlock(
accessToken: null,
uploadMode: UploadMode.production,
);
expect(result, isNull);
});
test('returns null when access token is empty', () async {
final client = MockClient((request) async => http.Response('{"user_blocks":[]}', 200));
final service = OSMBlockService(client: client);
final result = await service.checkActiveBlock(
accessToken: '',
uploadMode: UploadMode.production,
);
expect(result, isNull);
});
test('returns false when user_blocks is empty', () async {
final client = MockClient((request) async => http.Response('{"user_blocks":[]}', 200));
final service = OSMBlockService(client: client);
final result = await service.checkActiveBlock(
accessToken: 'token',
uploadMode: UploadMode.production,
);
expect(result, isFalse);
});
test('returns true when user_blocks is non-empty', () async {
final client = MockClient((request) async => http.Response(
'{"user_blocks":[{"id":101,"needs_view":true}]}',
200,
));
final service = OSMBlockService(client: client);
final result = await service.checkActiveBlock(
accessToken: 'token',
uploadMode: UploadMode.production,
);
expect(result, isTrue);
});
test('sends the request to the correct production host with auth header', () async {
Uri? capturedUri;
String? capturedAuth;
final client = MockClient((request) async {
capturedUri = request.url;
capturedAuth = request.headers['Authorization'];
return http.Response('{"user_blocks":[]}', 200);
});
final service = OSMBlockService(client: client);
await service.checkActiveBlock(
accessToken: 'my-token',
uploadMode: UploadMode.production,
);
expect(capturedUri?.host, 'api.openstreetmap.org');
expect(capturedUri?.path, '/api/0.6/user/blocks/active.json');
expect(capturedAuth, 'Bearer my-token');
});
test('sends the request to the sandbox host in sandbox mode', () async {
Uri? capturedUri;
final client = MockClient((request) async {
capturedUri = request.url;
return http.Response('{"user_blocks":[]}', 200);
});
final service = OSMBlockService(client: client);
await service.checkActiveBlock(
accessToken: 'my-token',
uploadMode: UploadMode.sandbox,
);
expect(capturedUri?.host, 'api06.dev.openstreetmap.org');
});
test('returns null on non-200 response', () async {
final client = MockClient((request) async => http.Response('Unauthorized', 401));
final service = OSMBlockService(client: client);
final result = await service.checkActiveBlock(
accessToken: 'token',
uploadMode: UploadMode.production,
);
expect(result, isNull);
});
test('returns null on malformed JSON response', () async {
final client = MockClient((request) async => http.Response('not json', 200));
final service = OSMBlockService(client: client);
final result = await service.checkActiveBlock(
accessToken: 'token',
uploadMode: UploadMode.production,
);
expect(result, isNull);
});
test('returns null when user_blocks field is missing', () async {
final client = MockClient((request) async => http.Response('{}', 200));
final service = OSMBlockService(client: client);
final result = await service.checkActiveBlock(
accessToken: 'token',
uploadMode: UploadMode.production,
);
expect(result, isNull);
});
test('returns null when the client throws (e.g. no network)', () async {
final client = MockClient((request) async {
throw Exception('network error');
});
final service = OSMBlockService(client: client);
final result = await service.checkActiveBlock(
accessToken: 'token',
uploadMode: UploadMode.production,
);
expect(result, isNull);
});
});
}
+118
View File
@@ -0,0 +1,118 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart' as http;
import 'package:http/testing.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:deflockapp/services/osm_block_service.dart';
import 'package:deflockapp/state/account_block_state.dart';
import 'package:deflockapp/state/settings_state.dart';
void main() {
setUp(() {
SharedPreferences.setMockInitialValues({});
});
group('AccountBlockState', () {
test('defaults to not blocked before init()', () {
final state = AccountBlockState();
expect(state.isBlocked, isFalse);
});
test('init() loads persisted blocked state for the given upload mode', () async {
SharedPreferences.setMockInitialValues({
'account_blocked_production': true,
});
final state = AccountBlockState();
await state.init(UploadMode.production);
expect(state.isBlocked, isTrue);
});
test('init() defaults to false when nothing is persisted', () async {
final state = AccountBlockState();
await state.init(UploadMode.production);
expect(state.isBlocked, isFalse);
});
test('init() is scoped per upload mode', () async {
SharedPreferences.setMockInitialValues({
'account_blocked_production': true,
});
final state = AccountBlockState();
await state.init(UploadMode.sandbox);
expect(state.isBlocked, isFalse);
});
test('check() updates and persists state on a conclusive true result', () async {
final client = MockClient((request) async => http.Response(
'{"user_blocks":[{"id":1}]}',
200,
));
final state = AccountBlockState(blockService: OSMBlockService(client: client));
await state.init(UploadMode.production);
final result = await state.check(accessToken: 'token', uploadMode: UploadMode.production);
expect(result, isTrue);
expect(state.isBlocked, isTrue);
final prefs = await SharedPreferences.getInstance();
expect(prefs.getBool('account_blocked_production'), isTrue);
});
test('check() updates and persists state on a conclusive false result', () async {
SharedPreferences.setMockInitialValues({
'account_blocked_production': true,
});
final client = MockClient((request) async => http.Response('{"user_blocks":[]}', 200));
final state = AccountBlockState(blockService: OSMBlockService(client: client));
await state.init(UploadMode.production);
expect(state.isBlocked, isTrue);
final result = await state.check(accessToken: 'token', uploadMode: UploadMode.production);
expect(result, isFalse);
expect(state.isBlocked, isFalse);
final prefs = await SharedPreferences.getInstance();
expect(prefs.getBool('account_blocked_production'), isFalse);
});
test('check() leaves state unchanged on an inconclusive (null) result', () async {
SharedPreferences.setMockInitialValues({
'account_blocked_production': true,
});
final client = MockClient((request) async {
throw Exception('offline');
});
final state = AccountBlockState(blockService: OSMBlockService(client: client));
await state.init(UploadMode.production);
expect(state.isBlocked, isTrue);
final result = await state.check(accessToken: 'token', uploadMode: UploadMode.production);
expect(result, isNull);
expect(state.isBlocked, isTrue); // unchanged
final prefs = await SharedPreferences.getInstance();
expect(prefs.getBool('account_blocked_production'), isTrue); // unchanged
});
test('clear() resets in-memory blocked state', () async {
SharedPreferences.setMockInitialValues({
'account_blocked_production': true,
});
final state = AccountBlockState();
await state.init(UploadMode.production);
expect(state.isBlocked, isTrue);
state.clear();
expect(state.isBlocked, isFalse);
});
});
}