Compare commits

..

11 commits

Author SHA1 Message Date
38c6689255
[ios] add tap on the grabber that opens the modal screen
Signed-off-by: Kiryl Kaveryn <kirylkaveryn@gmail.com>
2025-03-27 13:41:05 +04:00
6ec24e7a11
[ios] fix internal scroll scrolling to the top
When the user starts scrolling to the top from the halfscreen position the internal scroll continues scrolling the table even if the content offset is zero. This commit fixes it.

Signed-off-by: Kiryl Kaveryn <kirylkaveryn@gmail.com>
2025-03-27 13:41:05 +04:00
587bd00650
[ios] add dim view to the search
Signed-off-by: Kiryl Kaveryn <kirylkaveryn@gmail.com>
2025-03-27 13:41:05 +04:00
f76cc7edb7
[ios] move presentation logic to the ModalPresentationStepsController
Signed-off-by: Kiryl Kaveryn <kirylkaveryn@gmail.com>
2025-03-27 13:41:05 +04:00
fe172c49cf
[ios] fix PlacePage screen drop shadow
Signed-off-by: Kiryl Kaveryn <kirylkaveryn@gmail.com>
2025-03-27 13:41:05 +04:00
77891282c8
[ios] add CornerRadius enum for easily setting up the property
1. new typed corner radius
2. updated styles where the corner radius is used

Signed-off-by: Kiryl Kaveryn <kirylkaveryn@gmail.com>
2025-03-27 13:41:05 +04:00
1171c4f27d
[ios] fix modal screen corner radius and shadows
Signed-off-by: Kiryl Kaveryn <kirylkaveryn@gmail.com>
2025-03-27 13:41:05 +04:00
692a92cb90
[ios] fix the side and traffic buttons animation according to search position
1. the available areas are added to the MapViewController. It add an ability to set the affeced view programmatically rather than only in the main storyboard
2. the search screen view now affects on the side button position by setting the affecting direction
3. fixed animation of the Traffic button when its covered by the other view (out of available area)
4. removed unused `getAvailableArea`

Signed-off-by: Kiryl Kaveryn <kirylkaveryn@gmail.com>
2025-03-27 13:41:05 +04:00
9b5b085526
[ios] fix layout crash on the ios 12 on ipad
The exception was rised by the NSLayoutConstrait when the priority is changed (on ipad) after constraint activation. The all of the constraints set up shood be done before activation.

Signed-off-by: Kiryl Kaveryn <kirylkaveryn@gmail.com>
2025-03-27 13:41:05 +04:00
c4481e69ac
[ios] fix pasting coords to the search
Signed-off-by: Kiryl Kaveryn <kirylkaveryn@gmail.com>
2025-03-27 13:41:05 +04:00
1f21215391
[ios] replace the modally presented search vc presentation with child
Signed-off-by: Kiryl Kaveryn <kirylkaveryn@gmail.com>
2025-03-27 13:41:04 +04:00
85 changed files with 889 additions and 2446 deletions

68
.gitmodules vendored
View file

@ -1,67 +1,67 @@
[submodule "tools/osmctools"]
path = tools/osmctools
url = https://git.omaps.dev/organicmaps/osmctools.git
url = ../osmctools.git
[submodule "tools/kothic"]
path = tools/kothic
url = https://git.omaps.dev/organicmaps/kothic.git
url = ../kothic.git
[submodule "3party/protobuf/protobuf"]
path = 3party/protobuf/protobuf
url = https://git.omaps.dev/organicmaps/protobuf.git
url = ../protobuf.git
[submodule "3party/Vulkan-Headers"]
path = 3party/Vulkan-Headers
url = https://github.com/KhronosGroup/Vulkan-Headers.git
url = ../Vulkan-Headers.git
[submodule "3party/boost"]
path = 3party/boost
url = https://github.com/boostorg/boost.git
branch = boost-1.85.0
url = ../boost.git
branch = boost-1.76.0
ignore = dirty
[submodule "3party/just_gtfs"]
path = 3party/just_gtfs
url = https://git.omaps.dev/organicmaps/just_gtfs.git
url = ../just_gtfs.git
branch = for-usage-as-submodule
[submodule "3party/expat"]
path = 3party/expat
url = https://github.com/libexpat/libexpat.git
url = ../libexpat.git
branch = R_2_2_9
[submodule "3party/glm"]
path = 3party/glm
url = https://github.com/g-truc/glm.git
url = ../glm.git
[submodule "3party/icu/icu"]
path = 3party/icu/icu
url = https://github.com/unicode-org/icu.git
url = ../icu.git
[submodule "3party/freetype/freetype"]
path = 3party/freetype/freetype
url = https://git.omaps.dev/organicmaps/freetype.git
path = 3party/freetype/freetype
url = ../freetype.git
[submodule "3party/googletest"]
path = 3party/googletest
url = https://github.com/google/googletest.git
path = 3party/googletest
url = ../googletest.git
[submodule "3party/fast_double_parser"]
path = 3party/fast_double_parser
url = https://github.com/lemire/fast_double_parser.git
path = 3party/fast_double_parser
url = ../fast_double_parser.git
[submodule "3party/pugixml/pugixml"]
path = 3party/pugixml/pugixml
url = https://github.com/zeux/pugixml.git
path = 3party/pugixml/pugixml
url = ../pugixml.git
[submodule "3party/jansson/jansson"]
path = 3party/jansson/jansson
url = https://github.com/akheron/jansson.git
path = 3party/jansson/jansson
url = ../jansson.git
[submodule "3party/gflags"]
path = 3party/gflags
url = https://github.com/gflags/gflags
path = 3party/gflags
url = ../gflags
[submodule "3party/fast_obj"]
path = 3party/fast_obj
url = https://github.com/thisistherk/fast_obj
path = 3party/fast_obj
url = ../fast_obj
[submodule "3party/harfbuzz/harfbuzz"]
path = 3party/harfbuzz/harfbuzz
url = https://github.com/harfbuzz/harfbuzz.git
path = 3party/harfbuzz/harfbuzz
url = ../harfbuzz.git
[submodule "3party/utfcpp"]
path = 3party/utfcpp
url = https://github.com/nemtrif/utfcpp.git
path = 3party/utfcpp
url = ../utfcpp.git
[submodule "3party/glfw"]
path = 3party/glfw
url = https://github.com/glfw/glfw.git
path = 3party/glfw
url = ../glfw.git
[submodule "3party/CMake-MetalShaderSupport"]
path = 3party/CMake-MetalShaderSupport
url = https://github.com/dpogue/CMake-MetalShaderSupport.git
path = 3party/CMake-MetalShaderSupport
url = ../CMake-MetalShaderSupport.git
[submodule "3party/imgui/imgui"]
path = 3party/imgui/imgui
url = https://github.com/ocornut/imgui
path = 3party/imgui/imgui
url = ../imgui.git

@ -1 +1 @@
Subproject commit caade5a28aad86b92a4b5337a9dc70c4ba73c5eb
Subproject commit 30cc354fe37114ec7a0a4ed2192951690357c2ed

View file

@ -167,8 +167,9 @@ Please join our beta program, suggest your features, and report bugs:
- **Rate us on the [App Store](https://apps.apple.com/app/organic-maps/id1567437057)
and [Google Play](https://play.google.com/store/apps/details?id=app.organicmaps)**.
- **Star us on Forgejo**.
- Report bugs or issues to [the issue tracker](https://git.omaps.dev/organicmaps/organicmaps/issues).
- **Star us on GitHub**.
- Report bugs or issues to [the issue tracker](https://github.com/organicmaps/organicmaps/issues).
- [Discuss](https://github.com/organicmaps/organicmaps/discussions/categories/ideas) ideas or propose feature requests.
- Subscribe to our [Telegram Channel](https://t.me/OrganicMapsApp) or to the [[matrix] space](https://matrix.to/#/#organicmaps:matrix.org) for updates.
- Join our [Telegram Group](https://t.me/OrganicMaps) to discuss with other users.
- Присоединяйтесь к нашей [русскоязычной группе в Telegram](https://t.me/OrganicMapsRu) для обратной связи и помощи.
@ -178,7 +179,7 @@ and [Google Play](https://play.google.com/store/apps/details?id=app.organicmaps)
- Follow our updates in
[Mastodon](https://fosstodon.org/@organicmaps),
[Facebook](https://facebook.com/OrganicMaps),
[X (Twitter)](https://x.com/OrganicMapsApp),
[Twitter](https://twitter.com/OrganicMapsApp),
[Instagram](https://instagram.com/organicmaps.app/).
- Güncellemelerimizi [Instagram](https://instagram.com/organicmapstr/) üzerinden takip edin.

View file

@ -144,8 +144,4 @@
<string name="stop_without_saving">Parar ensin guardar</string>
<string name="continue_recording">Siguir grabando</string>
<string name="stop_track_recording">Parar grabación del trayeutu</string>
<string name="downloader_no_space_message">Desanicia los datos imprecisos</string>
<string name="editor_osm_history">El to historial dediciones</string>
<string name="m">m</string>
<string name="download_resources_continue">Dir al mapa</string>
</resources>

View file

@ -186,7 +186,7 @@
<!-- resource for context menu -->
<string name="edit">Edita</string>
<!-- Warning message when doing search around current position -->
<string name="unknown_current_position">Encara no sha determinat la vostra ubicació</string>
<string name="unknown_current_position">Encara no s\'ha pogut determinar la vostra geolocalització</string>
<!-- Alert message that we can't run Map Storage settings due to some reasons. -->
<string name="cant_change_this_setting">La configuració de l\'emmagatzematge del mapes està desactivada.</string>
<!-- Alert message that downloading is in progress. -->
@ -528,7 +528,7 @@
<!-- Information about OSM at the top of the editing page -->
<string name="editor_about_osm">Les vostres edicions es pengen a la base de dades pública <a href="https://wiki.openstreetmap.org/wiki/Ca:About">OpenStreetMap</a>. Si us plau, no afegiu informació personal o amb drets d\'autor.</string>
<string name="editor_more_about_osm">Més sobre l\'OpenStreetMap</string>
<string name="editor_osm_history">El vostre historial dedicions</string>
<string name="editor_osm_history">El teu historial d\'edició</string>
<string name="editor_osm_notes">Notes de les vostres dades del mapa</string>
<string name="editor_operator">Operador</string>
<!-- To indicate the operator of ATMs, bicycle rentals, electric vehicle charging stations... -->
@ -604,7 +604,7 @@
<string name="mobile_data_option_ask">Demana-ho sempre</string>
<string name="traffic_update_maps_text">Per a mostrar les dades de trànsit, els maps han d\'estar actualitzats.</string>
<string name="big_font">Augmenta la mida de lletra del mapa</string>
<string name="traffic_update_app">Actualitzeu lOrganic Maps</string>
<string name="traffic_update_app">Actualitzeu l\'Organic Maps</string>
<!-- "traffic" as in "road congestion" -->
<string name="traffic_data_unavailable">No hi ha dades de trànsit disponibles</string>
<string name="enable_logging">Activa el registre</string>
@ -832,7 +832,7 @@
<!-- Displayed on the phone screen. Android Auto connected -->
<string name="aa_connected_title">Us heu connectat a Android Auto</string>
<!-- Displayed on the phone screen. Button to display maps on the phone screen instead of a car -->
<string name="car_continue_on_the_phone">Passa al telèfon</string>
<string name="car_continue_on_the_phone">Continua al telèfon</string>
<!-- Displayed on the Android Auto or CarPlay screen. Button to display maps on the car screen instead of a phone. Must be no more than 18 symbols! -->
<string name="car_continue_in_the_car">A la pantalla del cotxe</string>
<!-- Ask user to grant location permissions -->
@ -903,7 +903,4 @@
<string name="mb">MB</string>
<string name="gb">GB</string>
<string name="login_osm">Inicia sessió a lOpenStreetMap</string>
<string name="twitter">X (Twitter)</string>
<string name="wikimedia_commons">Wikimedia Commons</string>
<string name="matrix">[Matrix]</string>
</resources>

View file

@ -444,7 +444,7 @@
<string name="type.self_service.partially">Autoservei parcial</string>
<string name="type.self_service.no">Sense autoservei</string>
<!-- https://wiki.openstreetmap.org/wiki/Key:social_facility -->
<string name="type.amenity.social_facility">Servei social</string>
<string name="type.amenity.social_facility">Equipament Social</string>
<!-- https://wiki.openstreetmap.org/wiki/Tag:emergency=emergency_ward_entrance -->
<string name="type.emergency.emergency_ward_entrance">Entrada d\'urgències</string>
<!-- https://wiki.openstreetmap.org/wiki/Tag:amenity=dojo -->
@ -850,66 +850,4 @@
<string name="type.leisure.track">Sender</string>
<string name="type.leisure.track.area">Sender</string>
<string name="type.leisure.water_park">Parc aquàtic</string>
<string name="type.sport.baseball">Beisbol</string>
<string name="type.sport.basketball">Basquetbol</string>
<string name="type.sport.beachvolleyball">Voleibol de platja</string>
<string name="type.sport.gymnastics">Gimnàstica</string>
<string name="type.sport.multi">Diversos esports</string>
<string name="type.cuisine.local">Local</string>
<string name="type.boundary.administrative.2">Frontera nacional</string>
<string name="type.cuisine.filipino">Filipina</string>
<string name="type.cuisine.hungarian">Hongaresa</string>
<string name="type.cuisine.japanese">Japonesa</string>
<string name="type.cuisine.lebanese">Libanesa</string>
<string name="type.cuisine.lao">Laosiana</string>
<string name="type.cuisine.korean">Coreana</string>
<string name="type.cuisine.moroccan">Marroquina</string>
<string name="type.cuisine.regional">Regional</string>
<string name="type.landuse.forest.deciduous">Bosc de caducifòlies</string>
<string name="type.power.substation">Subestació</string>
<string name="type.cuisine.portuguese">Portuguesa</string>
<string name="type.cuisine.polish">Polonesa</string>
<string name="type.cuisine.french">Francesa</string>
<string name="type.cuisine.russian">Rusa</string>
<string name="type.landuse.forest.coniferous">Bosc de coníferes</string>
<string name="type.landuse.field">Camp</string>
<string name="type.man_made.works">Fàbrica</string>
<string name="type.cuisine.georgian">Georgiana</string>
<string name="type.cuisine.german">Alemanya</string>
<string name="type.cuisine.greek">Grega</string>
<string name="type.healthcare.blood_donation">Centre de donació de sang</string>
<string name="type.military.bunker">Búnquer</string>
<string name="type.natural.grassland">Herbassar</string>
<string name="type.natural.cliff">Penya-segat</string>
<string name="type.natural.earth_bank">Talús de terra</string>
<string name="type.natural.land">Terreny</string>
<string name="type.natural.meadow">Prat de dall</string>
<string name="type.natural.heath">Landa</string>
<string name="type.amenity.vending_machine.condoms">Dispensador de preservatius</string>
<string name="type.cuisine.balkan">Balcànica</string>
<string name="type.cuisine.ice_cream">Gelat</string>
<string name="type.cuisine.indian">Índia</string>
<string name="type.cuisine.grill">Graellada</string>
<string name="type.healthcare.psychotherapist">Psicoterapeuta</string>
<string name="type.healthcare.podiatrist">Podòleg</string>
<string name="type.natural.coastline">Costa</string>
<string name="type.natural.water.lock">Resclosa</string>
<string name="type.natural.water.pond">Estany</string>
<string name="type.natural.water.reservoir">Embassament</string>
<string name="type.natural.strait">Estret</string>
<string name="type.natural.orchard">Verger</string>
<string name="type.noexit">Atzucac</string>
<string name="type.natural.wetland">Zona humida</string>
<string name="type.natural.wetland.bog">Torbera</string>
<string name="type.natural.wetland.marsh">Aiguamoll</string>
<string name="type.natural.tree_row">Fila darbres</string>
<string name="type.natural.vineyard">Vinyar</string>
<string name="type.highway.pedestrian">Carrer de vianants</string>
<string name="type.highway.pedestrian.area">Àrea de vianants</string>
<string name="type.landuse.reservoir">Embassament</string>
<string name="type.landuse.orchard">Verger</string>
<string name="type.landuse.meadow">Prat de dall</string>
<string name="type.highway.pedestrian.tunnel">Túnel de vianants</string>
<string name="type.cuisine.turkish">Turca</string>
<string name="type.highway.pedestrian.bridge">Pont de vianants</string>
</resources>

View file

@ -875,7 +875,7 @@
<!-- Prompt for stopping a track recording. -->
<string name="stop_track_recording">Aufnahme des Tracks stoppen</string>
<!-- Title for the "Stop Without Saving" action for the alert when saving a track recording. -->
<string name="stop_without_saving">Ohne Speichern beenden</string>
<string name="stop_without_saving">Anhalten ohne zu speichern</string>
<!-- Title for the "Stop Without Saving" action for the alert when saving a track recording. -->
<string name="continue_recording">Aufnahme fortsetzen</string>
<!-- Title for the alert when saving a track recording. -->

View file

@ -248,7 +248,7 @@
<string name="type.craft.winery">Kellerei</string>
<string name="type.craft.tailor">Schneider</string>
<string name="type.cuisine.african">Afrikanisch</string>
<string name="type.cuisine.american">US-amerikanisch</string>
<string name="type.cuisine.american">Amerikanisch</string>
<string name="type.cuisine.arab">Arabisch</string>
<string name="type.cuisine.argentinian">Argentinisch</string>
<string name="type.cuisine.asian">Asiatisch</string>

View file

@ -523,7 +523,7 @@
<!-- Information about OSM at the top of the editing page -->
<string name="editor_about_osm">Sus ediciones se cargan en la base de datos pública <a href="https://wiki.openstreetmap.org/wiki/ES:Acerca_de_OpenStreetMap">OpenStreetMap</a>. Por favor, no añada información personal o protegida por derechos de autor.</string>
<string name="editor_more_about_osm">Más acerca de OpenStreetMap</string>
<string name="editor_osm_history">Su historial de ediciones</string>
<string name="editor_osm_history">Mi historial de edición</string>
<string name="editor_osm_notes">Mis notas de datos cartográficos</string>
<string name="editor_operator">Operador</string>
<!-- To indicate the operator of ATMs, bicycle rentals, electric vehicle charging stations... -->
@ -830,7 +830,7 @@
<!-- Displayed on the phone screen. Android Auto connected -->
<string name="aa_connected_title">Se ha conectado a Android Auto</string>
<!-- Displayed on the phone screen. Button to display maps on the phone screen instead of a car -->
<string name="car_continue_on_the_phone">Pasar al teléfono</string>
<string name="car_continue_on_the_phone">Continuar en el teléfono</string>
<!-- Displayed on the Android Auto or CarPlay screen. Button to display maps on the car screen instead of a phone. Must be no more than 18 symbols! -->
<string name="car_continue_in_the_car">A la pantalla del coche</string>
<!-- Ask user to grant location permissions -->

View file

@ -368,7 +368,7 @@
<string name="type.healthcare.blood_donation">Centro de donación de sangre</string>
<string name="type.healthcare.optometrist">Optometría</string>
<string name="type.healthcare.podiatrist">Podología</string>
<string name="type.healthcare.psychotherapist">Psicoterapeuta</string>
<string name="type.healthcare.psychotherapist">Psicoterapia</string>
<string name="type.healthcare.sample_collection">Muestreo</string>
<string name="type.healthcare.speech_therapist">Logopedia</string>
<!-- SECTION: Types: Roads -->
@ -708,7 +708,7 @@
<string name="type.natural.cape">Cabo</string>
<string name="type.natural.cave_entrance">Cueva</string>
<string name="type.natural.cliff">Acantilado</string>
<string name="type.natural.earth_bank">Talud de tierra</string>
<string name="type.natural.earth_bank">Acantilado</string>
<string name="type.man_made.embankment">Terraplén</string>
<string name="type.natural.coastline">Costa</string>
<string name="type.natural.desert">Desierto</string>
@ -718,7 +718,7 @@
<string name="type.natural.heath">Brezal</string>
<string name="type.natural.hot_spring">Aguas termales</string>
<string name="type.natural.water.lake">Lago</string>
<string name="type.natural.water.lock">Esclusa</string>
<string name="type.natural.water.lock">Cámara de bloqueo</string>
<string name="type.natural.water.pond">Estanque</string>
<string name="type.natural.water.reservoir">Embalse</string>
<string name="type.natural.water.basin">Cuenca</string>

View file

@ -117,6 +117,7 @@
<string name="measurement_units">Unités de mesure</string>
<!-- Detailed description of Measurement Units settings button -->
<string name="measurement_units_summary">Choisir entre miles et kilomètres</string>
<!-- SECTION: Search categories -->
<!-- Search category for cafes, bars, restaurants; any changes should be duplicated in categories.txt @category_eat! -->
<string name="category_eat">Un endroit pour manger</string>
@ -164,6 +165,7 @@
<string name="category_water">Eau</string>
<!-- Search category for RV facilities; any changes should be duplicated in categories.txt @category_rv! -->
<string name="category_rv">Aménagements pour camping-car</string>
<!-- SECTION: Other translations -->
<!-- Notes field in Bookmarks view -->
<string name="description">Notes</string>
@ -368,9 +370,10 @@
<string name="gray">Gris</string>
<!-- blue gray color -->
<string name="blue_gray">Gris-bleu</string>
<!-- SECTION: Routing dialogs strings -->
<string name="dialog_routing_disclaimer_title">Lorsque vous suivez l\'itinéraire, gardez à l\'esprit les points suivants :</string>
<string name="dialog_routing_disclaimer_priority">— Les conditions de circulation, le code de la route et les panneaux de signalisation ont la priorité sur l\'appareil de navigation;</string>
<string name="dialog_routing_disclaimer_priority">— Les conditions de circulation, le code de la route et les panneaux de signalisation ont la priorité sur l\'appareil de navigation ;</string>
<string name="dialog_routing_disclaimer_precision">— La carte peut être imprécise et l\'itinéraire proposé n\'est pas forcément le plus direct pour arriver à destination ;</string>
<string name="dialog_routing_disclaimer_recommendations">— L\'itinéraire proposé doit être considéré comme une simple recommandation ;</string>
<string name="dialog_routing_disclaimer_borders">— Faites attention aux itinéraires traversant des zones frontalières : les itinéraires générés par l\'application peuvent parfois franchir des frontières étatiques dans des zones interdites ;</string>
@ -397,6 +400,7 @@
<string name="not_now">Pas maintenant</string>
<string name="dialog_routing_download_and_build_cross_route">Voulez-vous télécharger la carte et créer un itinéraire plus direct s\'étendant sur plus d\'une carte ?</string>
<string name="dialog_routing_download_cross_route">Téléchargez des cartes pour créer un itinéraire plus direct sortant des limites de cette carte.</string>
<!-- SECTION: Strings for downloading map from search -->
<string name="search_without_internet_advertisement">Pour commencer à rechercher et à créer des itinéraires, veuillez télécharger la carte. Après cela, vous n\'aurez plus besoin d\'une connexion Internet.</string>
<string name="search_select_map">Sélectionner la carte</string>
@ -539,7 +543,7 @@
<string name="editor_osm_notes">Vos notes sur les données cartographiques</string>
<string name="editor_operator">Opérateur</string>
<!-- To indicate the operator of ATMs, bicycle rentals, electric vehicle charging stations... -->
<string name="operator">Opérateur : %s</string>
<string name="operator">Opérateur : %s</string>
<string name="editor_category_unsuitable_title">Tu ne trouves pas de catégorie appropriée ?</string>
<string name="editor_category_unsuitable_text">Organic Maps ne permet d\'ajouter que des catégories de points simples, c\'est-à-dire pas de villes, de routes, de lacs, de contours de bâtiments, etc. Merci d\'ajouter ces catégories directement sur <a href="https://www.openstreetmap.org">OpenStreetMap.org</a>. Consulte notre <a href="https://organicmaps.app/faq/editing/advanced-map-editing">guide</a> pour obtenir des instructions détaillées étape par étape.</string>
<string name="downloader_no_downloaded_maps_title">Vous n\'avez téléchargé aucune carte</string>
@ -617,9 +621,9 @@
<!-- Settings: "Send general feedback" button -->
<string name="feedback_general">Feedback général</string>
<string name="prefs_languages_information">Nous utilisons le système TTS pour les instructions vocales. De nombreux appareils Android utilisent Google TTS, vous pouvez le télécharger ou le mettre à jour depuis Google Play (https://play.google.com/store/apps/details?id=com.google.android.tts)</string>
<string name="prefs_languages_information_off">Pour certaines langues, il vous faudra installer un autre logiciel de synthèse vocale ou un pack de langue supplémentaire depuis lapp store (Google Play, Galaxy Store, App Gallery, FDroid). \nOuvrez les paramètres de votre appareil → Langue et saisie → Reconnaissance vocale → Saisie vocale. \nIci, vous pouvez gérer les paramètres pour la synthèse vocale (par exemple, télécharger un pack de langue pour une utilisation en mode hors ligne) et sélectionner un autre moteur de saisie vocale.</string>
<string name="prefs_languages_information_off">Pour certaines langues, il vous faudra installer un autre logiciel de synthèse vocale ou un pack de langue supplémentaire depuis lapp store (Google Play, Galaxy Store, App Gallery, FDroid). Ouvrez les paramètres de votre appareil → Langue et saisie → Reconnaissance vocale → Saisie vocale. Ici, vous pouvez gérer les paramètres pour la synthèse vocale (par exemple, télécharger un pack de langue pour une utilisation en mode hors ligne) et sélectionner un autre moteur de saisie vocale.</string>
<string name="prefs_languages_information_off_link">Pour plus dinformations, veuillez consulter ce guide.</string>
<string name="transliteration_title">Translittératisé en alphabet latin</string>
<string name="transliteration_title">Translittération en latin</string>
<string name="learn_more">En savoir plus</string>
<!-- Subway exits for public transport marks on the map -->
<string name="core_exit">Sortie</string>
@ -634,7 +638,7 @@
<!-- Alert to ask user relogin to OpenStreetMap with OAuth2 flow after OAuth1 authentication is deprecated. -->
<string name="alert_reauth_message">Connecte-toi à OpenStreetMap pour télécharger automatiquement toutes tes modifications de cartes. En savoir plus <a href="https://github.com/organicmaps/organicmaps/issues/6144">ici</a>.</string>
<string name="dialog_error_storage_title">Problème d\'accès au stockage</string>
<string name="dialog_error_storage_message">Le stockage externe n\'est pas disponible. La carte SD a probablement été enlevée, endommagée ou le système est en lecture seule. Veuillez vérifier votre carte SD ou nous contacter via support@organicmaps.app</string>
<string name="dialog_error_storage_message">Le stockage externe n\'est pas disponible. La carte SD a probablement été enlevée, endommagée ou le système est en lecture seule. Veuillez vérifier votre carte SD ou nous contacter via support\@organicmaps.app</string>
<string name="setting_emulate_bad_storage">Émuler le mauvais stockage</string>
<string name="core_entrance">Entrée</string>
<string name="error_enter_correct_name">Veuillez entrer un nom correct</string>
@ -752,6 +756,7 @@
<string name="moreyear_ago_sorttype">Il y a plus d\'un an</string>
<string name="near_me_sorttype">Près de moi</string>
<string name="others_sorttype">Autres</string>
<!-- SECTION: Bookmark types used for sorting -->
<string name="food_places">Aliments</string>
<string name="tourist_places">Choses à voir</string>
@ -844,7 +849,7 @@
<!-- Notification title for permission request from AA. -->
<string name="aa_request_permission_notification">Cette application a besoin de ta permission</string>
<!-- The text in the activity for location permission request. -->
<string name="aa_request_permission_activity_text">Organic Maps dans Android Auto a besoin des permissions de localisation pour fonctionner efficacement</string>
<string name="aa_request_permission_activity_text">Organic Maps dans Android Auto a besoin d\'une autorisation de localisation pour fonctionner efficacement</string>
<!-- Grant Permissions button. -->
<string name="aa_grant_permissions">Accorder l\'accès</string>
<!-- Outdoors/hiking map style (activity) name in the Styles and Layers dialog -->
@ -895,20 +900,4 @@
<string name="uri_open_location_failed">Aucune application installée ne permet d\'ouvrir l\'emplacement</string>
<!-- preference string for using auto theme only in navigation mode -->
<string name="nav_auto">Auto dans la navigation</string>
<string name="telegram">Telegram</string>
<string name="twitter">X (Twitter)</string>
<string name="instagram">Instagram</string>
<string name="editor_line_social_network">LINE</string>
<string name="vk">VK</string>
<string name="github">GitHub</string>
<string name="mastodon">Mastodon</string>
<string name="facebook">Facebook</string>
<string name="matrix">[Matrix]</string>
<string name="tts_info_link">https://organicmaps.app/fr/faq/voice/synth%C3%A8se-vocale-tts-sur-android/</string>
<string name="telegram_url">https://t.me/OrganicMapsApp</string>
<string name="wikimedia_commons">Wikimedia Commons</string>
<string name="comma_separated_pair">%1$s, %2$s</string>
<string name="instagram_url">https://www.instagram.com/organicmaps.app</string>
<string name="openstreetmap">OpenStreetMap</string>
<string name="navigation_stop_button">Stop</string>
</resources>

View file

@ -1307,7 +1307,4 @@
<string name="type.amenity.dojo">Dojo</string>
<!-- https://wiki.openstreetmap.org/wiki/Tag:leisure=sports_hall -->
<string name="type.leisure.sports_hall">Salle de sport</string>
<string name="type.aeroway.terminal">Terminal</string>
<string name="type.power.pole">Pylône électrique</string>
<string name="type.man_made.utility_pole">Pylônes (Télécommunication, lampadaire)</string>
</resources>

View file

@ -1,348 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="name">Nafn</string>
<string name="address">Heimilisfang</string>
<string name="list">Listi</string>
<string name="settings">Stillingar</string>
<string name="category_fuel">Gas</string>
<string name="category_bank">Banki</string>
<string name="category_pharmacy">Lyfjabúð</string>
<string name="category_hospital">Sjúkrahús</string>
<string name="category_police">Lögreglustöð</string>
<string name="category_wifi">WiFi</string>
<string name="category_recycling">Endurvinnsla</string>
<string name="category_water">Vatn</string>
<string name="edit">Breyta</string>
<string name="prefs_group_general">Almennar stillingar</string>
<string name="prefs_group_information">Upplýsingar</string>
<string name="off">Slökkt</string>
<string name="on">Kveikt</string>
<string name="instagram">Instagram</string>
<string name="openstreetmap">OpenStreetMap</string>
<string name="feedback">Umsagnir</string>
<string name="help">Hjálp</string>
<string name="copyright">Höfundarréttur</string>
<string name="downloader_downloaded_subtitle">Niðurhalað</string>
<string name="downloader_status_maps">Landakort</string>
<string name="downloader_delete_map">Eyða korti</string>
<string name="downloader_update_map">Uppfæra kort</string>
<string name="save">Vista</string>
<string name="red">Rautt</string>
<string name="yellow">Gult</string>
<string name="blue">Blátt</string>
<string name="purple">Purpurablátt</string>
<string name="orange">Appelsínugult</string>
<string name="pink">Bleikt</string>
<string name="hide">Fela</string>
<string name="categories">Flokkar</string>
<string name="history">Breytingaskrá</string>
<string name="read_in_wikipedia">Wikipedia</string>
<string name="p2p_start">Upphaf</string>
<string name="next_button">Næsta</string>
<string name="editor_time_from">Frá</string>
<string name="editor_time_to">Til</string>
<string name="editor_time_open">Opið</string>
<string name="editor_report_problem_send_button">Senda</string>
<string name="daily">Daglega</string>
<string name="twentyfour_seven">24/7</string>
<string name="day_off_today">Lokað í dag</string>
<string name="today">Í dag</string>
<string name="opens_in">Opnar eftir %s</string>
<string name="closes_in">Lokar eftir %s</string>
<string name="closed">Lokað</string>
<string name="login">Skrá inn</string>
<string name="login_osm">Skrá inn á OpenStreetMap</string>
<string name="password">Lykilorð</string>
<string name="street">Gata</string>
<string name="details">Nánar</string>
<string name="building">Bygging</string>
<string name="cuisine">Eldhús</string>
<string name="editor_edit_place_category_title">Flokkur</string>
<string name="downloader_of">%1$d af %2$d</string>
<string name="editor_operator">Rekstraraðili</string>
<string name="m">m</string>
<string name="km">km</string>
<string name="kilometers_per_hour">km/klst</string>
<string name="mi">míl</string>
<string name="ft">ft</string>
<string name="miles_per_hour">mi/klst</string>
<string name="hour">klst</string>
<string name="minute">mín</string>
<string name="placepage_more_button">Meira</string>
<string name="editor_remove_place_button">Eyða</string>
<string name="navigation_stop_button">Stöðva</string>
<string name="learn_more">Kanna nánar</string>
<string name="restore">Endurheimta</string>
<plurals name="tracks">
<item quantity="one">%d ferill</item>
<item quantity="other">%d ferlar</item>
</plurals>
<string name="privacy">Gagnaleynd</string>
<string name="speedcams_alert_title">Hraðamyndavélar</string>
<string name="power_managment_setting_never">Aldrei</string>
<string name="power_managment_setting_manual_max">Alltaf</string>
<string name="no_available">Nei</string>
<string name="comma_separated_pair">%1$s, %2$s</string>
<string name="volume">Hljóðstyrkur</string>
<string name="ok">Í lagi</string>
<string name="by_default">Sjálfgefið</string>
<string name="food_places">Matur</string>
<string name="buildings">Byggingar</string>
<string name="parkings">Bílastæði</string>
<string name="medicine">Lækningar</string>
<string name="elevation_profile_ascent">Hækkun</string>
<string name="elevation_profile_descent">Lækkun</string>
<string name="elevation_profile_time">Tími:</string>
<string name="change_map_locale">Tungumál korts</string>
<string name="outdoor_seating">Sæti utandyra</string>
<string name="gb">GB</string>
<string name="miles">Mílur</string>
<string name="core_my_position">Staðsetning mín</string>
<string name="delete">Eyða</string>
<string name="later">Seinna</string>
<string name="downloading">Sæki…</string>
<string name="kilometres">Kílómetrar</string>
<string name="mb">MB</string>
<string name="close">Loka</string>
<string name="download">Sækja</string>
<string name="pause">Bið</string>
<string name="continue_button">Halda áfram</string>
<string name="core_my_places">Staðirnir mínir</string>
<string name="back">Til baka</string>
<string name="cancel">Hætta við</string>
<string name="search">Leita</string>
<string name="measurement_units">Mælieiningar</string>
<string name="category_transport">Samgöngur</string>
<string name="category_hotel">Hótel</string>
<string name="category_entertainment">Afþreying</string>
<string name="category_atm">Hraðbanki</string>
<string name="share">Deila</string>
<string name="email">Netfang</string>
<string name="done">Lokið</string>
<string name="tracks_title">Ferlar</string>
<string name="length">Lengd</string>
<string name="prefs_group_route">Leiðsögn</string>
<string name="placepage_distance">Vegalengd</string>
<string name="menu">Valmynd</string>
<string name="website">Vefsvæði</string>
<string name="facebook">Facebook</string>
<string name="downloader_download_map">Sækja kort</string>
<string name="downloader_retry">Reyna aftur</string>
<string name="green">Grænt</string>
<string name="brown">Brúnt</string>
<string name="show">Sýna</string>
<string name="day">d</string>
<string name="core_exit">Hætta</string>
<string name="core_entrance">Inngangur</string>
<string name="bookmark_lists_hide_all">Fela allt</string>
<string name="bookmark_lists_show_all">Sýna allt</string>
<string name="button_layer_traffic">Umferð</string>
<string name="button_layer_subway">Neðanjarðarlest</string>
<string name="not_shared">Einka</string>
<string name="yes_available"></string>
<string name="by_type">Eftir gerð</string>
<string name="by_date">Eftir dagsetningu</string>
<string name="others_sorttype">Annað</string>
<string name="elevation_profile_difficulty">Erfiðleikastig</string>
<string name="zoom_in">Renna að</string>
<string name="zoom_out">Renna frá</string>
<string name="copied_to_clipboard">Afritað á klippispjald: %s</string>
<string name="share_my_location">Deila staðsetningu minni</string>
<string name="data_version">OpenStreetMap-gögn: %s</string>
<string name="pref_zoom_title">Aðdráttarhnappar</string>
<string name="pref_map_style_title">Næturhamur</string>
<string name="pref_zoom_summary">Birta á kortinu</string>
<string name="auto">Sjálfvirkt</string>
<string name="pref_map_3d_title">Fjarvíddarsýn</string>
<string name="search_show_on_map">Skoða á korti</string>
<string name="news">Fréttir</string>
<string name="github">GitHub</string>
<string name="matrix">[Matrix]</string>
<string name="mastodon">Mastodon</string>
<string name="twitter">X (Twitter)</string>
<string name="vk">VK</string>
<string name="editor_line_social_network">LINE</string>
<string name="rate_the_app">Gefðu forritinu einkunn</string>
<string name="downloader_update_all_button">Uppfæra allt</string>
<string name="downloader_cancel_all">Hætta við allt</string>
<string name="downloader_download_all_button">Sækja allt</string>
<string name="pref_display_kayak_title">Hóteltenglar frá Kayak.com</string>
<string name="routing_not_enough_space">Ekki nægilegt pláss</string>
<string name="enable_location_services">Virkjaðu staðsetningarþjónustur</string>
<string name="deep_purple">Dökkpurpurablátt</string>
<string name="light_blue">Ljósblátt</string>
<string name="cyan">Grænblátt (cyan)</string>
<string name="teal">Blágrænt</string>
<string name="dialog_routing_system_error">Kerfisvilla</string>
<string name="dialog_routing_try_again">Endilega reyndu aftur</string>
<string name="not_now">Ekki núna</string>
<string name="search_select_map">Veldu kort</string>
<string name="search_not_found">Úbbs, engar niðurstöður fundust.</string>
<string name="search_history_title">Leitarferill</string>
<string name="wikimedia_commons">Wikimedia Commons</string>
<string name="p2p_your_location">Staðsetning þín</string>
<string name="p2p_from_here">Leið frá</string>
<string name="editor_time_close">Lokað</string>
<string name="editor_time_advanced">Ítarlegri hamur</string>
<string name="editor_time_simple">Einfaldur hamur</string>
<string name="editor_correct_mistake">Leiðrétta mistök</string>
<string name="editor_add_select_location">Veldu staðsetningu</string>
<string name="editor_report_problem_title">Vandamál</string>
<string name="editor_report_problem_no_place_title">Þessi staður er ekki til</string>
<string name="day_off">Lokað</string>
<string name="no_osm_account">Ertu ekki með OpenStreetMap-aðgang?</string>
<string name="not_signed_in">Ekki skráð/ur inn</string>
<string name="forgot_password">Gleymdirðu lykilorðinu?</string>
<string name="logout">Skrá út</string>
<string name="edit_place">Breyta stað</string>
<string name="postal_code">Póstnúmer</string>
<string name="email_or_username">Tölvupóstur eða notandanafn</string>
<string name="editor_add_phone">Bæta við símanúmeri</string>
<string name="level">Hæð</string>
<string name="editor_edit_place_name_hint">Nafn staðarins</string>
<string name="editor_report_problem_other_title">Annað vandamál</string>
<string name="editor_zip_code">Póstnúmer</string>
<string name="error_enter_correct_zip_code">Settu inn gilt póstnúmer</string>
<string name="operator">Rekstraraðili: %s</string>
<string name="editor_comment_hint">Athugasemd…</string>
<string name="editor_reset_edits_button">Henda</string>
<string name="mobile_data_option_always">Alltaf nota</string>
<string name="bookmark_lists">Listar</string>
<plurals name="bookmarks_places">
<item quantity="one">%d bókamerki</item>
<item quantity="other">%d bókamerki</item>
</plurals>
<string name="bookmarks_create_new_group">Búa til nýjan lista</string>
<string name="bookmarks_new_list_hint">Nýr listi</string>
<string name="phone_number">Símanúmer</string>
<string name="privacy_policy">Meðferð persónuupplýsinga</string>
<string name="terms_of_use">Notkunarskilmálar</string>
<string name="layers_title">Stílar landakorta og þekjur</string>
<string name="export_file">Flytja út KMZ</string>
<string name="export_file_gpx">Flytja út GPX</string>
<string name="avoid_tolls">Forðast gjaldskyldu</string>
<string name="avoid_unpaved">Forðast vegi með óbundnu slitlagi</string>
<string name="avoid_ferry">Forðast ferjur</string>
<string name="avoid_motorways">Forðast hraðbrautir</string>
<string name="unpaved_road">Óbundið slitlag</string>
<string name="ferry_crossing">Þverun með ferju</string>
<string name="yes"></string>
<string name="no">Nei</string>
<string name="trip_finished">Þú hefur náð á áfangastað!</string>
<string name="sort">Raða…</string>
<string name="sort_bookmarks">Raða bókamerkjum</string>
<string name="by_distance">Eftir vegalengd</string>
<string name="by_name">Eftir nafni</string>
<string name="week_ago_sorttype">Fyrir viku síðan</string>
<string name="month_ago_sorttype">Fyrir mánuði síðan</string>
<string name="moremonth_ago_sorttype">Fyrir meira en mánuði síðan</string>
<string name="moreyear_ago_sorttype">Fyrir meira en ári síðan</string>
<string name="tourist_places">Skoðunarverðir staðir</string>
<string name="animals">Dýr</string>
<string name="shops">Verslanir</string>
<string name="fuel_places">Bensínstöðvar</string>
<string name="button_layer_isolines">Hæðarlínur</string>
<string name="elevation_profile_distance">Fjarl.:</string>
<string name="downloader_loading_ios">Sæki</string>
<string name="bookmarks_error_title_list_name_already_taken">Þetta nafn er þegar í notkun</string>
<string name="bookmarks_error_message_list_name_already_taken">Veldu eitthvað annað heiti</string>
<string name="please_wait">Bíddu aðeins…</string>
<string name="category_desc_more">…meira</string>
<string name="delete_list">Eyða lista</string>
<string name="public_access">Opinber aðgangur</string>
<string name="limited_access">Takmarkaður aðgangur</string>
<string name="place_description_title">Lýsing á stað</string>
<string name="unable_to_calc_alert_title">Tókst ekki að reikna leið</string>
<string name="near_me_sorttype">Nálægt mér</string>
<string name="museums">Söfn</string>
<string name="parks">Almenningsgarðar</string>
<string name="swim_places">Sund</string>
<string name="mountains">Fjöll</string>
<string name="hotels">Hótel</string>
<string name="money">Peningar</string>
<string name="search_in_the_list">Leita í listanum</string>
<string name="religious_places">Trúarlegir staðir</string>
<string name="download_map_title">Sækja heims-yfirlitskortið</string>
<string name="already_donated">Hef þegar styrkt verkefnið</string>
<string name="remind_me_later">Áminna mig síðar</string>
<string name="support_organic_maps">Styddu við Organic Maps</string>
<string name="how_to_support_us">Styddu við verkefnið</string>
<string name="osm_log_out_confirmation">Ertu viss um að þú viljir skrá þig út af OpenStreetMap-aðgangnum þínum?</string>
<string name="telegram">Telegram</string>
<string name="faq">Algengar spurningar (FAQ)</string>
<string name="donate">Styrkja</string>
<string name="report_a_bug">Tilkynna um villu</string>
<string name="downloader_queued">Í biðröð</string>
<string name="downloader_near_me_subtitle">Nálægt mér</string>
<string name="downloader_downloading">Sæki:</string>
<string name="downloader_delete_map_while_routing_dialog">Til að eyða korti skaltu stöðva leiðsögn.</string>
<string name="lime">Límónugrænt</string>
<string name="deep_orange">Dimmappelsínugult</string>
<string name="gray">Grátt</string>
<string name="autodownload">Sækja kort sjálfvirkt</string>
<string name="add_language">Bæta við tungumáli</string>
<string name="house_number">Bygging númer</string>
<string name="blue_gray">Blágrátt</string>
<string name="p2p_to_here">Leið til</string>
<string name="editor_report_problem_under_construction_title">Lokað vegna viðhalds</string>
<string name="editor_report_problem_duplicate_place_title">Tvítekinn staður</string>
<string name="social_media">Samfélagsmiðlar</string>
<string name="add_street">Bæta við götu</string>
<string name="empty_street_name_error">Settu inn götuheiti</string>
<string name="choose_language">Veldu tungumál</string>
<string name="choose_street">Veldu götu</string>
<string name="level_value_generic">Hæð: %s</string>
<string name="editor_login_error_dialog">Villa við innskráningu.</string>
<string name="editor_profile_changes">Staðfestar breytingar</string>
<string name="accept">Samþykkja</string>
<string name="decline">Hafna</string>
<string name="mobile_data_option_never">Aldrei nota</string>
<string name="mobile_data_option_ask">Alltaf spyrja</string>
<string name="download_maps">Sækja kort</string>
<string name="download_has_failed">Mistókst að sækja. Ýttu til að reyna aftur.</string>
<string name="search_map">Leita á korti</string>
<string name="try_again">Reyndu aftur</string>
<string name="about_menu_title">Um Organic Maps</string>
<string name="about_headline">Ókeypis fyrir alla, unnið af alúð</string>
<string name="location_settings">Staðsetningarstillingar</string>
<string name="download_resources_continue">Fara á kort</string>
<string name="download_country_ask">Sækja %s?</string>
<string name="update_country_ask">Uppfæra %s?</string>
<string name="add_new_set">Bæta við nýjum lista</string>
<string name="maps_storage">Vista landakort í</string>
<string name="power_managment_setting_auto">Þegar lítil rafhleðsla er eftir</string>
<string name="define_to_avoid_btn">Skilgreindu vegi sem á að forðast</string>
<string name="toll_road">Vegur með gjaldskyldu</string>
<string name="capacity">Afkastageta: %s</string>
<string name="network">Þjónustuaðili: %s</string>
<string name="elevation_profile_min_elevation">Lágm.hæð</string>
<string name="elevation_profile_max_elevation">Hám.hæð</string>
<string name="isolines_toast_zooms_1_10">Renndu að til að skoða hæðarlínur</string>
<string name="splash_subtitle">Kortagögn frá OpenStreetMap</string>
<string name="telegram_url">https://t.me/OrganicMapsApp</string>
<string name="button_layer_outdoor">Utandyra</string>
<string name="website_menu">Tengill í valmynd</string>
<string name="view_menu">Skoða valmynd</string>
<string name="self_service">Sjálfsafgreiðsla</string>
<string name="limited_accuracy">Takmörkuð nákvæmni</string>
<string name="zoom_to_country">Birta á kortinu</string>
<string name="country_status_download_failed">Mistókst að sækja</string>
<string name="pref_tts_speedcams_auto">Áminna ef farið er of hratt</string>
<string name="pref_tts_speedcams_always">Alltaf aðvara</string>
<string name="pref_tts_speedcams_never">Aldrei aðvara</string>
<string name="power_managment_title">Orkusparnaður</string>
<string name="select_list">Veldu lista</string>
<string name="transit_not_found">Leiðsögn í neðanjarðarlestum er ekki tiltæk í augnablikinu á þessu svæði</string>
<string name="disk_error_title">Villa á diski</string>
<string name="connection_failure">Tenging brást</string>
<string name="disconnect_usb_cable_title">Aftengja USB-kapal</string>
<string name="enable_keep_screen_on">Halda kveiktu á skjánum</string>
<string name="enable_show_on_lock_screen">Birta á læsiskjánum</string>
<string name="instagram_url">https://www.instagram.com/organicmaps.app</string>
<string name="tts_info_link">https://organicmaps.app/faq/text-to-speech-android-tts/</string>
<string name="translated_om_site_url">https://organicmaps.app/</string>
<string name="osm_wiki_about_url">https://wiki.openstreetmap.org/wiki/About_OpenStreetMap</string>
<string name="browser_not_available">Vafri er ekki tiltækur</string>
<string name="open_in_app">Opna í öðru forriti</string>
<string name="select_option">Veldu valkost</string>
</resources>

File diff suppressed because it is too large Load diff

View file

@ -525,7 +525,7 @@
<string name="operator">Operators: %s</string>
<string name="editor_category_unsuitable_title">Vai neatrodat atbilstošu kategoriju?</string>
<string name="editor_category_unsuitable_text">„Organic Maps“ ļauj pievienot tikai vienkāršu punktu kategorijas, proti, nevarat pievienot pilsētas, ceļus, ezerus, ēku aprises u.tml. Šādas kategorijas lūdzam pievienot tiešā veidā <a href="https://www.openstreetmap.org">OpenStreetMap.org</a>. Lai uzzinātu, kā to izdarīt, sekojiet norādēm <a href="https://organicmaps.app/faq/editing/advanced-map-editing">rokasgrāmatā</a>.</string>
<string name="downloader_no_downloaded_maps_title">Ierīcē nav lejupielādētu karšu</string>
<string name="downloader_no_downloaded_maps_title">Ierīcē nav lejupielādētas kartes</string>
<string name="downloader_no_downloaded_maps_message">Lai darbotos nesaistes navigācija un meklēšana, lejupielādējiet kartes.</string>
<string name="current_location_unknown_error_title">Pašreizējā vieta nav zināma.</string>
<!-- abbreviation for meters -->

View file

@ -9,7 +9,7 @@
<string name="delete">Удалить</string>
<string name="download_maps">Загрузить карты</string>
<!-- Settings/Downloader - info for country when download fails -->
<string name="download_has_failed">Ошибка загрузки. Нажмите, чтобы повторить попытку.</string>
<string name="download_has_failed">Ошибка загрузки. Нажмите, чтобы повторить попытку</string>
<!-- Settings/Downloader - info for country which started downloading -->
<string name="downloading">Загружается…</string>
<!-- Choose measurement on first launch alert - choose metric system button -->

View file

@ -1,12 +1,18 @@
extension CALayer {
func setCorner(radius: CGFloat,
corners: CACornerMask? = nil) {
cornerRadius = radius
if let corners {
maskedCorners = corners
func setCornerRadius(_ cornerRadius: CornerRadius,
maskedCorners: CACornerMask? = nil) {
self.cornerRadius = cornerRadius.value
if let maskedCorners {
self.maskedCorners = maskedCorners
}
if #available(iOS 13.0, *) {
cornerCurve = .continuous
}
}
}
extension CACornerMask {
static var all: CACornerMask {
return [.layerMinXMinYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMaxYCorner]
}
}

View file

@ -13,7 +13,7 @@ extension UIView {
snapshot.layer.contents = contents
snapshot.layer.bounds = layer.bounds
}
snapshot.layer.setCorner(radius: layer.cornerRadius)
snapshot.layer.setCornerRadius(.custom(layer.cornerRadius))
snapshot.layer.masksToBounds = layer.masksToBounds
snapshot.contentMode = contentMode
snapshot.transform = transform

View file

@ -8,7 +8,7 @@ final class AlertPresentationController: DimmedModalPresentationController {
override func presentationTransitionWillBegin() {
super.presentationTransitionWillBegin()
presentedViewController.view.layer.setCorner(radius: 12)
presentedViewController.view.layer.setCornerRadius(.modalSheet)
presentedViewController.view.clipsToBounds = true
guard let containerView = containerView, let presentedView = presentedView else { return }
containerView.addSubview(presentedView)

View file

@ -17,7 +17,7 @@ override var frameOfPresentedViewInContainerView: CGRect {
override func presentationTransitionWillBegin() {
super.presentationTransitionWillBegin()
presentedViewController.view.layer.setCorner(radius: 8)
presentedViewController.view.layer.setCornerRadius(.buttonDefault)
presentedViewController.view.clipsToBounds = true
guard let containerView = containerView, let presentedView = presentedView else { return }
containerView.addSubview(presentedView)

View file

@ -16,7 +16,7 @@ final class PromoBookingPresentationController: DimmedModalPresentationControlle
override func presentationTransitionWillBegin() {
super.presentationTransitionWillBegin()
presentedViewController.view.layer.setCorner(radius: 8)
presentedViewController.view.layer.setCornerRadius(.buttonDefault)
presentedViewController.view.clipsToBounds = true
guard let containerView = containerView, let presentedView = presentedView else { return }
containerView.addSubview(presentedView)

View file

@ -22,7 +22,7 @@ final class Toast: NSObject {
}
private init(_ text: String) {
blurView.layer.setCorner(radius: 8)
blurView.layer.setCornerRadius(.buttonDefault)
blurView.clipsToBounds = true
blurView.alpha = 0

View file

@ -14,6 +14,5 @@
- (void)processMyPositionStateModeEvent:(MWMMyPositionMode)mode;
+ (void)updateAvailableArea:(CGRect)frame;
+ (CGRect)getAvailableArea;
@end

View file

@ -58,8 +58,6 @@ NSString * const kUDDidShowLongTapToShowSideButtonsToast = @"kUDDidShowLongTapTo
+ (void)updateAvailableArea:(CGRect)frame { [[self buttons].sideView updateAvailableArea:frame]; }
+ (CGRect)getAvailableArea { return [self buttons].sideView.getAvailableArea; }
- (void)zoomIn
{
GetFramework().Scale(Framework::SCALE_MAG, true);

View file

@ -8,6 +8,5 @@
- (void)setHidden:(BOOL)hidden animated:(BOOL)animated;
- (void)updateAvailableArea:(CGRect)frame;
- (CGRect)getAvailableArea;
@end

View file

@ -144,10 +144,6 @@ CGFloat const kButtonsBottomOffset = 6;
[self setNeedsLayout];
}
- (CGRect)getAvailableArea {
return self.availableArea;
}
- (CGFloat)availableHeight {
return self.availableArea.size.height - kButtonsTopOffset - kButtonsBottomOffset;
}

View file

@ -195,6 +195,8 @@ NSArray<UIImage *> *imagesWithName(NSString *name) {
if (CGRectEqualToRect(controller.availableArea, frame))
return;
controller.availableArea = frame;
BOOL isHidden = frame.origin.y + frame.size.height < controller.view.origin.y + controller.view.height + kTopOffset;
[MapViewController.sharedController.controlsManager setTrafficButtonHidden:isHidden];
[controller refreshLayout];
}

View file

@ -1,6 +1,5 @@
final class TransportRuler: TransportTransitCell {
enum Config {
static let backgroundCornerRadius: CGFloat = 4
static var backgroundColor: UIColor { return UIColor.blackOpaque() }
static var imageColor: UIColor { return UIColor.blackSecondaryText() }
static var labelTextColor: UIColor { return .black }
@ -10,7 +9,7 @@ final class TransportRuler: TransportTransitCell {
@IBOutlet private weak var background: UIView! {
didSet {
background.layer.setCorner(radius: Config.backgroundCornerRadius)
background.layer.setCornerRadius(.buttonSmall)
background.backgroundColor = Config.backgroundColor
}
}

View file

@ -1,20 +1,19 @@
final class TransportTransitPedestrian: TransportTransitCell {
enum Config {
static let backgroundCornerRadius: CGFloat = 4
static var backgroundColor: UIColor { return UIColor.blackOpaque() }
static var imageColor: UIColor { return UIColor.blackSecondaryText() }
}
@IBOutlet private weak var background: UIView! {
didSet {
background.layer.setCorner(radius: Config.backgroundCornerRadius)
background.layer.setCornerRadius(.buttonSmall)
background.backgroundColor = Config.backgroundColor
}
}
@IBOutlet private weak var image: UIImageView! {
didSet {
image.image = #imageLiteral(resourceName: "ic_walk")
image.image = UIImage(resource: .icWalk)
image.tintColor = Config.imageColor
image.contentMode = .scaleAspectFit
}

View file

@ -1,6 +1,5 @@
final class TransportTransitTrain: TransportTransitCell {
enum Config {
static let backgroundCornerRadius: CGFloat = 4
static var labelTextColor: UIColor { return .white }
static let labelTextFont = UIFont.bold12()
static let labelTrailing: CGFloat = 4
@ -8,7 +7,7 @@ final class TransportTransitTrain: TransportTransitCell {
@IBOutlet private weak var background: UIView! {
didSet {
background.layer.setCorner(radius: Config.backgroundCornerRadius)
background.layer.setCornerRadius(.buttonSmall)
}
}

View file

@ -7,6 +7,11 @@
@class MWMMapDownloadDialog;
@class BookmarksCoordinator;
@class SearchOnMapManager;
@class SideButtonsArea;
@class WidgetsArea;
@class TrafficButtonArea;
@class PlacePageArea;
@protocol MWMLocationModeListener;
@interface MapViewController : MWMViewController
@ -52,5 +57,11 @@
@property(nonatomic) MWMMyPositionMode currentPositionMode;
@property(strong, nonatomic) IBOutlet EAGLView * _Nonnull mapView;
@property(strong, nonatomic) IBOutlet UIView * _Nonnull controlsView;
@property(nonatomic) UIView * _Nonnull searchContainer;
@property (weak, nonatomic) IBOutlet SideButtonsArea * sideButtonsArea;
@property (weak, nonatomic) IBOutlet WidgetsArea * widgetsArea;
@property (weak, nonatomic) IBOutlet TrafficButtonArea * trafficButtonArea;
@property (weak, nonatomic) IBOutlet PlacePageArea * placePageArea;
@end

View file

@ -148,34 +148,48 @@ NSString *const kSettingsSegue = @"Map2Settings";
- (void)setupPlacePageContainer {
self.placePageContainer = [[TouchTransparentView alloc] initWithFrame:self.view.bounds];
self.placePageContainer.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.placePageContainer];
[self.view bringSubviewToFront:self.placePageContainer];
self.placePageContainer.translatesAutoresizingMaskIntoConstraints = NO;
self.placePageLeadingConstraint = [self.placePageContainer.leadingAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.leadingAnchor constant:kPlacePageLeadingOffset];
self.placePageLeadingConstraint.active = YES;
if (IPAD)
self.placePageLeadingConstraint.priority = UILayoutPriorityDefaultLow;
self.placePageWidthConstraint = [self.placePageContainer.widthAnchor constraintEqualToConstant:0];
self.placePageWidthConstraint = [self.placePageContainer.widthAnchor constraintEqualToConstant:kPlacePageCompactWidth];
self.placePageTrailingConstraint = [self.placePageContainer.trailingAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.trailingAnchor];
[self.placePageContainer.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor].active = YES;
if (IPAD) {
self.placePageLeadingConstraint.priority = UILayoutPriorityDefaultLow;
[self.placePageContainer.bottomAnchor constraintLessThanOrEqualToAnchor:self.view.bottomAnchor].active = YES;
}
else {
[self.placePageContainer.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor].active = YES;
}
NSLayoutConstraint * topConstraint = [self.placePageContainer.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor];
NSLayoutConstraint * bottomConstraint;
if (IPAD)
bottomConstraint = [self.placePageContainer.bottomAnchor constraintLessThanOrEqualToAnchor:self.view.bottomAnchor];
else
bottomConstraint = [self.placePageContainer.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor];
[NSLayoutConstraint activateConstraints:@[
self.placePageLeadingConstraint,
topConstraint,
bottomConstraint,
]];
[self updatePlacePageContainerConstraints];
}
- (void)setupSearchContainer {
if (self.searchContainer != nil)
return;
self.searchContainer = [[TouchTransparentView alloc] initWithFrame:self.view.bounds];
[self.view addSubview:self.searchContainer];
[self.view bringSubviewToFront:self.searchContainer];
self.searchContainer.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
}
- (void)updatePlacePageContainerConstraints {
const BOOL isLimitedWidth = IPAD || self.traitCollection.verticalSizeClass == UIUserInterfaceSizeClassCompact;
[self.placePageWidthConstraint setConstant:kPlacePageCompactWidth];
if (IPAD && self.searchViewContainer != nil) {
NSLayoutConstraint * leadingToSearchConstraint = [self.placePageContainer.leadingAnchor constraintEqualToAnchor:self.searchViewContainer.trailingAnchor constant:kPlacePageLeadingOffset];
if (IPAD && self.searchViewAvailableArea != nil) {
NSLayoutConstraint * leadingToSearchConstraint = [self.placePageContainer.leadingAnchor constraintEqualToAnchor:self.searchViewAvailableArea.trailingAnchor constant:kPlacePageLeadingOffset];
leadingToSearchConstraint.priority = UILayoutPriorityDefaultHigh;
leadingToSearchConstraint.active = isLimitedWidth;
}
@ -259,9 +273,6 @@ NSString *const kSettingsSegue = @"Map2Settings";
return;
}
if (self.searchManager.isSearching && type == df::TouchEvent::TOUCH_MOVE)
[self.searchManager setMapIsDragging];
NSArray *allTouches = [[event allTouches] allObjects];
if ([allTouches count] < 1)
return;
@ -273,6 +284,10 @@ NSString *const kSettingsSegue = @"Map2Settings";
UITouch *touch = [allTouches objectAtIndex:0];
CGPoint const pt = [touch locationInView:v];
// Check if the tap is inside searchView)
if (self.searchManager.isSearching && type == df::TouchEvent::TOUCH_MOVE && !CGRectContainsPoint(self.searchViewAvailableArea.frame, pt))
[self.searchManager setMapIsDragging];
e.SetTouchType(type);
df::Touch t0;
@ -372,6 +387,7 @@ NSString *const kSettingsSegue = @"Map2Settings";
- (void)viewDidLoad {
[super viewDidLoad];
[self setupPlacePageContainer];
[self setupSearchContainer];
if (@available(iOS 14.0, *))
[self setupTrackPadGestureRecognizers];
@ -726,12 +742,12 @@ NSString *const kSettingsSegue = @"Map2Settings";
- (SearchOnMapManager *)searchManager {
if (!_searchManager)
_searchManager = [[SearchOnMapManager alloc] initWithNavigationController:self.navigationController];
_searchManager = [[SearchOnMapManager alloc] init];
return _searchManager;
}
- (UIView * _Nullable)searchViewContainer {
return self.searchManager.viewController.view;
- (UIView * _Nullable)searchViewAvailableArea {
return self.searchManager.viewController.availableAreaView;
}
- (BOOL)hasNavigationBar {

View file

@ -61,7 +61,7 @@ using Observers = NSHashTable<Observer>;
- (void)searchEverywhere {
self.lastSearchTimestamp += 1;
NSUInteger const timestamp = self.lastSearchTimestamp;
search::EverywhereSearchParams params{
m_query, m_locale, {} /* default timeout */, m_isCategory,
// m_onResults
@ -156,6 +156,7 @@ using Observers = NSHashTable<Observer>;
+ (void)showResultAtIndex:(NSUInteger)index {
auto const & result = [MWMSearch manager]->m_everywhereResults[index];
GetFramework().StopLocationFollow();
GetFramework().SelectSearchResult(result, true);
}
@ -168,8 +169,13 @@ using Observers = NSHashTable<Observer>;
+ (void)showEverywhereSearchResultsOnMap {
MWMSearch * manager = [MWMSearch manager];
if (![MWMRouter isRoutingActive])
GetFramework().ShowSearchResults(manager->m_everywhereResults);
if (![MWMRouter isRoutingActive]) {
auto const & results = manager->m_everywhereResults;
if (results.GetCount() == 1)
[self showResultAtIndex:0];
else
GetFramework().ShowSearchResults(manager->m_everywhereResults);
}
}
+ (void)showViewportSearchResultsOnMap {

View file

@ -4,6 +4,7 @@ class Style: ExpressibleByDictionaryLiteral {
case borderColor
case borderWidth
case cornerRadius
case maskedCorners
case shadowColor
case shadowOpacity
case shadowOffset
@ -115,11 +116,16 @@ extension Style {
set { params[.borderWidth] = newValue }
}
var cornerRadius: CGFloat? {
get { return self[.cornerRadius] as? CGFloat }
var cornerRadius: CornerRadius? {
get { return self[.cornerRadius] as? CornerRadius }
set { params[.cornerRadius] = newValue }
}
var maskedCorners: CACornerMask? {
get { return self[.maskedCorners] as? CACornerMask }
set { params[.maskedCorners] = newValue }
}
var shadowColor: UIColor? {
get { return self[.shadowColor] as? UIColor }
set { params[.shadowColor] = newValue }

View file

@ -0,0 +1,21 @@
enum CornerRadius {
case modalSheet
case buttonDefault
case buttonDefaultSmall
case buttonSmall
case grabber
case custom(CGFloat)
}
extension CornerRadius {
var value: CGFloat {
switch self {
case .modalSheet: return 12
case .buttonDefault: return 8
case .buttonDefaultSmall: return 6
case .buttonSmall: return 4
case .grabber: return 2.5
case .custom(let value): return value
}
}
}

View file

@ -59,6 +59,9 @@ enum GlobalStyleSheet: String, CaseIterable {
case white = "MWMWhite"
case datePickerView = "DatePickerView"
case valueStepperView = "ValueStepperView"
case grabber
case modalSheetBackground
case modalSheetContent
}
extension GlobalStyleSheet: IStyleSheet {
@ -176,7 +179,7 @@ extension GlobalStyleSheet: IStyleSheet {
s.backgroundColor = colors.tabBarButtonBackground
s.tintColor = colors.blackSecondaryText
s.coloring = MWMButtonColoring.black
s.cornerRadius = 8
s.cornerRadius = .buttonDefault
s.shadowColor = UIColor(0,0,0,alpha20)
s.shadowOpacity = 1
s.shadowOffset = CGSize(width: 0, height: 1)
@ -184,7 +187,7 @@ extension GlobalStyleSheet: IStyleSheet {
}
case .trackRecordingWidgetButton:
return .addFrom(Self.bottomTabBarButton) { s in
s.cornerRadius = 23
s.cornerRadius = .custom(23)
}
case .blackOpaqueBackground:
return .add { s in
@ -232,7 +235,7 @@ extension GlobalStyleSheet: IStyleSheet {
}
case .dialogView:
return .add { s in
s.cornerRadius = 8
s.cornerRadius = .buttonDefault
s.shadowRadius = 2
s.shadowColor = UIColor(0,0,0,alpha26)
s.shadowOpacity = 1
@ -242,7 +245,7 @@ extension GlobalStyleSheet: IStyleSheet {
}
case .alertView:
return .add { s in
s.cornerRadius = 12
s.cornerRadius = .modalSheet
s.shadowRadius = 6
s.shadowColor = UIColor(0,0,0,alpha20)
s.shadowOpacity = 1
@ -273,7 +276,7 @@ extension GlobalStyleSheet: IStyleSheet {
case .flatNormalButton:
return .add { s in
s.font = fonts.medium14
s.cornerRadius = 8
s.cornerRadius = .buttonDefault
s.clip = true
s.fontColor = colors.whitePrimaryText
s.backgroundColor = colors.linkBlue
@ -288,7 +291,7 @@ extension GlobalStyleSheet: IStyleSheet {
case .flatNormalTransButton:
return .add { s in
s.font = fonts.medium14
s.cornerRadius = 8
s.cornerRadius = .buttonDefault
s.clip = true
s.fontColor = colors.linkBlue
s.backgroundColor = colors.clear
@ -330,7 +333,7 @@ extension GlobalStyleSheet: IStyleSheet {
case .flatRedButton:
return .add { s in
s.font = fonts.medium14
s.cornerRadius = 8
s.cornerRadius = .buttonDefault
s.fontColor = colors.whitePrimaryText
s.backgroundColor = colors.buttonRed
s.fontColorHighlighted = colors.buttonRedHighlighted
@ -346,7 +349,7 @@ extension GlobalStyleSheet: IStyleSheet {
return .add { s in
s.font = fonts.regular14
s.fontColor = colors.linkBlue
s.cornerRadius = 8
s.cornerRadius = .buttonDefault
s.borderColor = colors.linkBlue
s.borderWidth = 1
s.fontColorHighlighted = colors.linkBlueHighlighted
@ -358,7 +361,7 @@ extension GlobalStyleSheet: IStyleSheet {
s.fontColor = colors.linkBlue
s.fontColorHighlighted = colors.white
s.borderColor = colors.linkBlue
s.cornerRadius = 8
s.cornerRadius = .buttonDefault
s.borderWidth = 1
s.backgroundColor = colors.clear
s.backgroundColorHighlighted = colors.linkBlue
@ -429,6 +432,26 @@ extension GlobalStyleSheet: IStyleSheet {
s.fontColor = colors.blackPrimaryText
s.coloring = MWMButtonColoring.blue
}
case .grabber:
return .addFrom(Self.background) { s in
s.cornerRadius = .grabber
}
case .modalSheetBackground:
return .add { s in
s.backgroundColor = colors.white
s.shadowColor = UIColor.black
s.shadowOffset = CGSize(width: 0, height: 1)
s.shadowOpacity = 0.3
s.shadowRadius = 6
s.cornerRadius = .modalSheet
s.clip = false
s.maskedCorners = isIPad ? [] : [.layerMinXMinYCorner, .layerMaxXMinYCorner]
}
case .modalSheetContent:
return .addFrom(Self.modalSheetBackground) { s in
s.backgroundColor = colors.clear
s.clip = true
}
}
}
}

View file

@ -28,7 +28,7 @@ extension MapStyleSheet: IStyleSheet {
s.backgroundColor = colors.clear
s.borderColor = colors.clear
s.borderWidth = 0
s.cornerRadius = 6
s.cornerRadius = .buttonDefaultSmall
}
case .mapMenuButtonEnabled:
return .add { s in
@ -37,7 +37,7 @@ extension MapStyleSheet: IStyleSheet {
s.backgroundColor = colors.linkBlue
s.borderColor = colors.linkBlue
s.borderWidth = 2
s.cornerRadius = 6
s.cornerRadius = .buttonDefaultSmall
}
case .mapStreetNameBackgroundView:
return .add { s in
@ -90,7 +90,7 @@ extension MapStyleSheet: IStyleSheet {
case .mapFirstTurnView:
return .add { s in
s.backgroundColor = colors.linkBlue
s.cornerRadius = 4
s.cornerRadius = .buttonSmall
s.shadowRadius = 2
s.shadowColor = colors.blackHintText
s.shadowOpacity = 1
@ -104,7 +104,7 @@ extension MapStyleSheet: IStyleSheet {
return .add { s in
s.shadowOffset = CGSize(width: 0, height: 3)
s.shadowRadius = 6
s.cornerRadius = 4
s.cornerRadius = .buttonSmall
s.shadowOpacity = 1
s.backgroundColor = colors.white
}

View file

@ -30,7 +30,7 @@ extension PlacePageStyleSheet: IStyleSheet {
case .ppTitlePopularView:
return .add { s in
s.backgroundColor = colors.linkBlueHighlighted
s.cornerRadius = 10
s.cornerRadius = .custom(10)
}
case .ppActionBarTitle:
return .add { s in
@ -45,7 +45,7 @@ extension PlacePageStyleSheet: IStyleSheet {
case .ppElevationProfileDescriptionCell:
return .add { s in
s.backgroundColor = colors.blackOpaque
s.cornerRadius = 6
s.cornerRadius = .buttonDefault
}
case .ppElevationProfileExtendedDifficulty:
return .add { s in
@ -110,7 +110,7 @@ extension PlacePageStyleSheet: IStyleSheet {
case .ppHeaderView:
return .add { s in
s.backgroundColor = colors.white
s.cornerRadius = 10
s.cornerRadius = .modalSheet
s.clip = true
}
case .ppNavigationShadowView:
@ -123,19 +123,15 @@ extension PlacePageStyleSheet: IStyleSheet {
s.clip = false
}
case .ppBackgroundView:
return .add { s in
return .addFrom(GlobalStyleSheet.modalSheetBackground) { s in
s.backgroundColor = colors.pressBackground
s.cornerRadius = 10
s.shadowColor = UIColor.black
s.shadowOffset = CGSize(width: 0, height: 1)
s.shadowOpacity = 0.6
s.shadowRadius = 2
s.maskedCorners = isIPad ? CACornerMask.all : [.layerMinXMinYCorner, .layerMaxXMinYCorner]
s.clip = false
}
case .ppView:
return .add { s in
s.backgroundColor = colors.clear
s.cornerRadius = 10
s.cornerRadius = .modalSheet
s.clip = true
}
case .ppHeaderCircleIcon:

View file

@ -31,7 +31,7 @@ class UISearchBarRenderer: UIViewRenderer {
} else {
control.setSearchFieldBackgroundImage(UIImage(), for: .normal)
}
searchTextField.layer.setCorner(radius: 8)
searchTextField.layer.setCornerRadius(.buttonDefault)
searchTextField.layer.masksToBounds = true
// Placeholder color
if let placeholder = searchTextField.placeholder {

View file

@ -20,7 +20,7 @@ extension UITextField {
class UITextFieldRenderer {
class func render(_ control: UITextField, style: Style) {
if let cornerRadius = style.cornerRadius {
control.layer.setCorner(radius: cornerRadius)
control.layer.setCornerRadius(cornerRadius)
control.clipsToBounds = true
}
control.borderStyle = .none

View file

@ -46,7 +46,10 @@ class UIViewRenderer {
control.layer.borderWidth = borderWidth
}
if let cornerRadius = style.cornerRadius {
control.layer.cornerRadius = cornerRadius
control.layer.cornerRadius = cornerRadius.value
}
if let maskedCorners = style.maskedCorners {
control.layer.maskedCorners = maskedCorners
}
if let clip = style.clip {
control.clipsToBounds = clip

View file

@ -1,107 +1,27 @@
enum SearchStyleSheet: String, CaseIterable {
case searchHeader
case searchInstallButton = "SearchInstallButton"
case searchBanner = "SearchBanner"
case searchClosedBackground = "SearchClosedBackground"
case searchCancelButton
case searchPopularView = "SearchPopularView"
case searchSideAvailableMarker = "SearchSideAvaliableMarker"
case searchBarView = "SearchBarView"
case searchActionBarView = "SearchActionBarView"
case searchActionBarButton = "SearchActionBarButton"
case searchSearchTextField = "SearchSearchTextField"
case searchSearchTextFieldIcon = "SearchSearchTextFieldIcon"
case searchDatePickerField = "SearchDatePickerField"
case searchCellAvailable = "SearchCellAvaliable"
}
extension SearchStyleSheet: IStyleSheet {
func styleResolverFor(colors: IColors, fonts: IFonts) -> Theme.StyleResolver {
switch self {
case .searchHeader:
return .add { s in
s.backgroundColor = colors.primary
iPhoneSpecific {
s.shadowColor = UIColor.black
s.shadowOffset = CGSize(width: 0, height: 1)
s.shadowOpacity = 0.5
s.shadowRadius = 3
s.cornerRadius = 10
}
}
case .searchInstallButton:
return .add { s in
s.cornerRadius = 10
s.clip = true
s.font = fonts.medium12
s.fontColor = colors.blackSecondaryText
s.backgroundColor = colors.searchPromoBackground
}
case .searchBanner:
return .add { s in
s.backgroundColor = colors.searchPromoBackground
}
case .searchClosedBackground:
return .add { s in
s.cornerRadius = 4
s.backgroundColor = colors.blackHintText
}
case .searchPopularView:
return .add { s in
s.cornerRadius = 10
s.cornerRadius = .custom(10)
s.backgroundColor = colors.linkBlueHighlighted
}
case .searchSideAvailableMarker:
return .add { s in
s.backgroundColor = colors.ratingGreen
}
case .searchBarView:
case .searchCancelButton:
return .add { s in
s.backgroundColor = colors.primary
s.shadowRadius = 2
s.shadowColor = UIColor(0, 0, 0, alpha26)
s.shadowOpacity = 1
s.shadowOffset = CGSize.zero
}
case .searchActionBarView:
return .add { s in
s.backgroundColor = colors.linkBlue
s.cornerRadius = 20
s.shadowRadius = 1
s.shadowColor = UIColor(0, 0, 0, 0.24)
s.shadowOffset = CGSize(width: 0, height: 2)
s.shadowOpacity = 1
}
case .searchActionBarButton:
return .add { s in
s.backgroundColor = colors.clear
s.fontColor = colors.whitePrimaryText
s.font = fonts.semibold14
s.coloring = .whiteText
}
case .searchSearchTextField:
return .add { s in
s.fontColor = colors.blackPrimaryText
s.backgroundColor = colors.white
s.tintColor = colors.blackSecondaryText
s.cornerRadius = 8.0
s.barTintColor = colors.primary
}
case .searchSearchTextFieldIcon:
return .add { s in
s.tintColor = colors.blackSecondaryText
s.coloring = MWMButtonColoring.black
s.color = colors.blackSecondaryText
}
case .searchDatePickerField:
return .add { s in
s.backgroundColor = colors.white
s.cornerRadius = 4
s.borderColor = colors.solidDividers
s.borderWidth = 1
}
case .searchCellAvailable:
return .addFrom(GlobalStyleSheet.tableCell) { s in
s.backgroundColor = colors.transparentGreen
s.fontColorHighlighted = colors.whitePrimaryTextHighlighted
s.font = fonts.regular17
s.backgroundColor = .clear
}
}
}

View file

@ -82,5 +82,3 @@
"kilometers_per_hour" = "km/h";
"stop_without_saving" = "Parar ensin guardar";
"location_services_disabled_header" = "Los servicios dubicación tán desactivaos";
"downloader_no_space_message" = "Desanicia los datos imprecisos";
"m" = "m";

View file

@ -169,7 +169,7 @@
"edit" = "Edita";
/* Warning message when doing search around current position */
"unknown_current_position" = "Encara no sha determinat la vostra ubicació";
"unknown_current_position" = "Encara no s'ha pogut determinar la vostra geolocalització";
/* Subject for emailed bookmark */
"bookmark_share_email_subject" = "Ep, mireu el meu marcador a l'Organic Maps!";
@ -973,7 +973,7 @@
"car_used_on_the_car_screen" = "Ara esteu utilitzant els Organic Maps a la pantalla del cotxe";
/* Displayed on the phone screen. Button to display maps on the phone screen instead of a car */
"car_continue_on_the_phone" = "Passa al telèfon";
"car_continue_on_the_phone" = "Continua al telèfon";
/* Displayed on the Android Auto or CarPlay screen. Button to display maps on the car screen instead of a phone. Must be no more than 18 symbols! */
"car_continue_in_the_car" = "A la pantalla del cotxe";

View file

@ -172,7 +172,7 @@
"type.amenity.vending_machine" = "Màquina expenedora";
"type.amenity.vending_machine.cigarettes" = "Dispensador de cigarrets";
"type.amenity.vending_machine.coffee" = "Dispensador de cafè";
"type.amenity.vending_machine.condoms" = "Dispensador de preservatius";
"type.amenity.vending_machine.condoms" = "Condoms Dispenser";
"type.amenity.vending_machine.drinks" = "Dispensador de begudes";
"type.amenity.vending_machine.food" = "Dispensador daliments";
"type.amenity.vending_machine.newspapers" = "Dispensador de periòdics";
@ -215,7 +215,7 @@
"type.boundary.administrative" = "Frontera administrativa";
/* Borders between countries. */
"type.boundary.administrative.2" = "Frontera nacional";
"type.boundary.administrative.2" = "National Border";
/* Country's primary subdivision borders, e.g. between regions, provinces, states.. */
"type.boundary.administrative.3" = "Frontera regional";
@ -274,7 +274,7 @@
"type.cuisine.asian" = "Asiàtica";
"type.cuisine.austrian" = "Austríaca";
"type.cuisine.bagel" = "Bagel";
"type.cuisine.balkan" = "Balcànica";
"type.cuisine.balkan" = "Balkan";
"type.cuisine.barbecue" = "Barbecue";
"type.cuisine.bavarian" = "Bavarian";
"type.cuisine.beef_bowl" = "Beef Bowl";
@ -295,37 +295,37 @@
"type.cuisine.diner" = "Diner";
"type.cuisine.donut" = "Donut";
"type.cuisine.ethiopian" = "Ethiopian";
"type.cuisine.filipino" = "Filipina";
"type.cuisine.filipino" = "Filipino";
"type.cuisine.fine_dining" = "Fine Dining";
"type.cuisine.fish" = "Fish";
"type.cuisine.fish_and_chips" = "Fish and Chips";
"type.cuisine.french" = "Francesa";
"type.cuisine.french" = "French";
"type.cuisine.friture" = "Friture";
"type.cuisine.georgian" = "Georgiana";
"type.cuisine.german" = "Alemanya";
"type.cuisine.greek" = "Grega";
"type.cuisine.grill" = "Graellada";
"type.cuisine.georgian" = "Georgian";
"type.cuisine.german" = "German";
"type.cuisine.greek" = "Greek";
"type.cuisine.grill" = "Grill";
"type.cuisine.heuriger" = "Heuriger";
"type.cuisine.hotdog" = "Hotdog";
"type.cuisine.hungarian" = "Hongaresa";
"type.cuisine.ice_cream" = "Gelat";
"type.cuisine.indian" = "Índia";
"type.cuisine.hungarian" = "Hungarian";
"type.cuisine.ice_cream" = "Ice Cream";
"type.cuisine.indian" = "Indian";
"type.cuisine.indonesian" = "Indonesian";
"type.cuisine.international" = "Internacional";
"type.cuisine.irish" = "Irish";
"type.cuisine.italian" = "Italian";
"type.cuisine.italian_pizza" = "Italian, Pizza";
"type.cuisine.japanese" = "Japonesa";
"type.cuisine.japanese" = "Japanese";
"type.cuisine.kebab" = "Kebab";
"type.cuisine.korean" = "Coreana";
"type.cuisine.lao" = "Laosiana";
"type.cuisine.lebanese" = "Libanesa";
"type.cuisine.korean" = "Korean";
"type.cuisine.lao" = "Lao";
"type.cuisine.lebanese" = "Lebanese";
"type.cuisine.local" = "Local";
"type.cuisine.malagasy" = "Malagasy";
"type.cuisine.malaysian" = "Malaysian";
"type.cuisine.mediterranean" = "Mediterranean";
"type.cuisine.mexican" = "Mexicana";
"type.cuisine.moroccan" = "Marroquina";
"type.cuisine.moroccan" = "Moroccan";
"type.cuisine.noodles" = "Fideus xinesos";
"type.cuisine.oriental" = "East Asian";
"type.cuisine.pancake" = "Pancake";
@ -333,11 +333,11 @@
"type.cuisine.persian" = "Persian";
"type.cuisine.peruvian" = "Peruvian";
"type.cuisine.pizza" = "Pizza";
"type.cuisine.polish" = "Polonesa";
"type.cuisine.portuguese" = "Portuguesa";
"type.cuisine.polish" = "Polish";
"type.cuisine.portuguese" = "Portuguese";
"type.cuisine.ramen" = "Ramen";
"type.cuisine.regional" = "Regional";
"type.cuisine.russian" = "Rusa";
"type.cuisine.russian" = "Russian";
"type.cuisine.sandwich" = "Sandwich";
"type.cuisine.sausage" = "Sausage";
"type.cuisine.savory_pancakes" = "Savory Pancakes";
@ -349,7 +349,7 @@
"type.cuisine.tapas" = "Tapas";
"type.cuisine.tea" = "Te";
"type.cuisine.thai" = "Thai";
"type.cuisine.turkish" = "Turca";
"type.cuisine.turkish" = "Turkish";
"type.cuisine.vegan" = "Vegan";
"type.cuisine.vegetarian" = "Vegetarian";
"type.cuisine.vietnamese" = "Vietnamese";
@ -377,10 +377,10 @@
"type.healthcare.physiotherapist" = "Fisioterapeuta";
"type.healthcare.alternative" = "Alternative Medicine";
"type.healthcare.audiologist" = "Audiologist";
"type.healthcare.blood_donation" = "Centre de donació de sang";
"type.healthcare.blood_donation" = "Blood Donation Center";
"type.healthcare.optometrist" = "Optometrist";
"type.healthcare.podiatrist" = "Podòleg";
"type.healthcare.psychotherapist" = "Psicoterapeuta";
"type.healthcare.podiatrist" = "Podiatrist";
"type.healthcare.psychotherapist" = "Psychotherapist";
"type.healthcare.sample_collection" = "Sample Collection Centre";
"type.healthcare.speech_therapist" = "Logopedics";
"type.highway" = "Highway";
@ -459,14 +459,14 @@
/* These translations are used for all type.highway.*.tunnel. */
"type.highway.path.tunnel" = "Túnel";
"type.highway.pedestrian" = "Carrer de vianants";
"type.highway.pedestrian.area" = "Àrea de vianants";
"type.highway.pedestrian" = "Pedestrian Street";
"type.highway.pedestrian.area" = "Pedestrian Street";
/* These translations are used for all type.highway.*.bridge. */
"type.highway.pedestrian.bridge" = "Pont de vianants";
"type.highway.pedestrian.bridge" = "Pedestrian Bridge";
/* These translations are used for all type.highway.*.tunnel. */
"type.highway.pedestrian.tunnel" = "Túnel de vianants";
"type.highway.pedestrian.tunnel" = "Pedestrian Tunnel";
"type.highway.primary" = "Carretera primària";
/* These translations are used for all type.highway.*.bridge. */
@ -678,11 +678,11 @@
"type.landuse.education" = "Educational Facility";
"type.landuse.farmland" = "Farmland";
"type.landuse.farmyard" = "Farmyard";
"type.landuse.field" = "Camp";
"type.landuse.field" = "Field";
"type.landuse.flowerbed" = "Llit de flors";
"type.landuse.forest" = "Bosc";
"type.landuse.forest.coniferous" = "Bosc de coníferes";
"type.landuse.forest.deciduous" = "Bosc de caducifòlies";
"type.landuse.forest.coniferous" = "Coniferous Forest";
"type.landuse.forest.deciduous" = "Deciduous Forest";
"type.landuse.forest.mixed" = "Mixed-Leaf Forest";
"type.landuse.garages" = "Garatges";
"type.landuse.grass" = "Grass";
@ -690,13 +690,13 @@
"type.landuse.greenhouse_horticulture" = "Hivernacle";
"type.landuse.industrial" = "Àrea industrial";
"type.landuse.landfill" = "Abocador";
"type.landuse.meadow" = "Prat de dall";
"type.landuse.meadow" = "Meadow";
"type.landuse.military" = "Military Area";
"type.landuse.orchard" = "Verger";
"type.landuse.orchard" = "Orchard";
"type.landuse.quarry" = "Pedrera";
"type.landuse.railway" = "Railway Premises";
"type.landuse.recreation_ground" = "Recreation Ground";
"type.landuse.reservoir" = "Embassament";
"type.landuse.reservoir" = "Reservoir";
"type.landuse.residential" = "Residential Area";
"type.landuse.retail" = "Retail Area";
"type.landuse.salt_pond" = "Salt Pond";
@ -772,11 +772,11 @@
"type.man_made.water_well" = "Pou daigua";
"type.man_made.water_well.drinking_water_no" = "Pou daigua";
"type.man_made.windmill" = "Molí de vent";
"type.man_made.works" = "Fàbrica";
"type.man_made.works" = "Industrial Works";
"type.mapswithme" = "mapswithme";
"type.mapswithme.grid" = "mapswithme-grid";
"type.military" = "Military";
"type.military.bunker" = "Búnquer";
"type.military.bunker" = "Bunker";
"type.mountain_pass" = "Mountain Pass";
"type.natural" = "Nature";
@ -794,40 +794,40 @@
"type.natural.beach.gravel" = "Gravel Beach";
"type.natural.cape" = "Cape";
"type.natural.cave_entrance" = "Cave Entrance";
"type.natural.cliff" = "Penya-segat";
"type.natural.earth_bank" = "Talús de terra";
"type.natural.cliff" = "Cliff";
"type.natural.earth_bank" = "Earth Bank";
"type.man_made.embankment" = "Terraplè";
"type.natural.coastline" = "Costa";
"type.natural.coastline" = "Coastline";
"type.natural.desert" = "Desert";
"type.natural.geyser" = "Guèiser";
"type.natural.glacier" = "Glacier";
"type.natural.grassland" = "Herbassar";
"type.natural.heath" = "Landa";
"type.natural.grassland" = "Grassland";
"type.natural.heath" = "Heath";
"type.natural.hot_spring" = "Font termal";
"type.natural.water.lake" = "Lake";
"type.natural.water.lock" = "Resclosa";
"type.natural.water.pond" = "Estany";
"type.natural.water.reservoir" = "Embassament";
"type.natural.water.lock" = "Lock Chamber";
"type.natural.water.pond" = "Pond";
"type.natural.water.reservoir" = "Reservoir";
"type.natural.water.basin" = "Basin";
"type.natural.water.river" = "Riu";
"type.natural.land" = "Terreny";
"type.natural.meadow" = "Prat de dall";
"type.natural.orchard" = "Verger";
"type.natural.land" = "Land";
"type.natural.meadow" = "Meadow";
"type.natural.orchard" = "Orchard";
"type.natural.peak" = "Peak";
"type.natural.saddle" = "Mountain Saddle";
"type.natural.rock" = "Rock";
"type.natural.scrub" = "Scrub";
"type.natural.spring" = "Natural Spring";
"type.natural.spring.drinking_water_no" = "Natural Spring";
"type.natural.strait" = "Estret";
"type.natural.tree_row" = "Fila darbres";
"type.natural.vineyard" = "Vinyar";
"type.natural.strait" = "Strait";
"type.natural.tree_row" = "Tree Row";
"type.natural.vineyard" = "Vineyard";
"type.natural.volcano" = "Volcà";
"type.natural.water" = "Aigua";
"type.natural.wetland" = "Zona humida";
"type.natural.wetland.bog" = "Torbera";
"type.natural.wetland.marsh" = "Aiguamoll";
"type.noexit" = "Atzucac";
"type.natural.wetland" = "Wetland";
"type.natural.wetland.bog" = "Bog";
"type.natural.wetland.marsh" = "Marsh";
"type.noexit" = "Dead End";
"type.office" = "Office";
"type.office.company" = "Company Office";
"type.office.estate_agent" = "Estate Agent";
@ -892,7 +892,7 @@
"type.power.plant.solar" = "Central d'energia solar";
"type.power.plant.wind" = "Central eòlica";
"type.power.station" = "Power Station";
"type.power.substation" = "Subestació";
"type.power.substation" = "Substation";
/* A tower or pylon carrying high voltage electricity cables. */
"type.power.tower" = "Power Tower";
@ -1333,18 +1333,18 @@
"type.sport.archery" = "Archery";
"type.sport.athletics" = "Atletisme";
"type.sport.australian_football" = "Australian Football";
"type.sport.baseball" = "Beisbol";
"type.sport.basketball" = "Basquetbol";
"type.sport.beachvolleyball" = "Voleibol de platja";
"type.sport.baseball" = "Baseball";
"type.sport.basketball" = "Basketball";
"type.sport.beachvolleyball" = "Beach Volleyball";
"type.sport.bowls" = "Bowls";
"type.sport.chess" = "Escacs";
"type.sport.cricket" = "Cricket";
"type.sport.curling" = "Curling";
"type.sport.equestrian" = "Esports eqüestres";
"type.sport.golf" = "Golf";
"type.sport.gymnastics" = "Gimnàstica";
"type.sport.gymnastics" = "Gymnastics";
"type.sport.handball" = "Handball";
"type.sport.multi" = "Diversos esports";
"type.sport.multi" = "Various Sports";
/* Used to tag a scuba diving site. */
"type.sport.scuba_diving" = "Scuba Diving Site";
@ -1474,7 +1474,7 @@
"type.self_service.no" = "Sense autoservei";
/* https://wiki.openstreetmap.org/wiki/Key:social_facility */
"type.amenity.social_facility" = "Servei social";
"type.amenity.social_facility" = "Equipament Social";
/* https://wiki.openstreetmap.org/wiki/Tag:emergency=emergency_ward_entrance */
"type.emergency.emergency_ward_entrance" = "Entrada d'urgències";

View file

@ -1058,7 +1058,7 @@
"stop_track_recording" = "Aufnahme des Tracks stoppen";
/* Title for the "Stop Without Saving" action for the alert when saving a track recording. */
"stop_without_saving" = "Ohne Speichern beenden";
"stop_without_saving" = "Anhalten ohne zu speichern";
/* Title for the "Stop Without Saving" action for the alert when saving a track recording. */
"continue_recording" = "Aufnahme fortsetzen";

View file

@ -268,7 +268,7 @@
"type.craft.winery" = "Kellerei";
"type.craft.tailor" = "Schneider";
"type.cuisine.african" = "Afrikanisch";
"type.cuisine.american" = "US-amerikanisch";
"type.cuisine.american" = "Amerikanisch";
"type.cuisine.arab" = "Arabisch";
"type.cuisine.argentinian" = "Argentinisch";
"type.cuisine.asian" = "Asiatisch";

View file

@ -973,7 +973,7 @@
"car_used_on_the_car_screen" = "Ahora usa Organic Maps en la pantalla del coche";
/* Displayed on the phone screen. Button to display maps on the phone screen instead of a car */
"car_continue_on_the_phone" = "Pasar al teléfono";
"car_continue_on_the_phone" = "Continuar en el teléfono";
/* Displayed on the Android Auto or CarPlay screen. Button to display maps on the car screen instead of a phone. Must be no more than 18 symbols! */
"car_continue_in_the_car" = "A la pantalla del coche";

View file

@ -380,7 +380,7 @@
"type.healthcare.blood_donation" = "Centro de donación de sangre";
"type.healthcare.optometrist" = "Optometría";
"type.healthcare.podiatrist" = "Podología";
"type.healthcare.psychotherapist" = "Psicoterapeuta";
"type.healthcare.psychotherapist" = "Psicoterapia";
"type.healthcare.sample_collection" = "Muestreo";
"type.healthcare.speech_therapist" = "Logopedia";
"type.highway" = "Carretera";
@ -795,7 +795,7 @@
"type.natural.cape" = "Cabo";
"type.natural.cave_entrance" = "Cueva";
"type.natural.cliff" = "Acantilado";
"type.natural.earth_bank" = "Talud de tierra";
"type.natural.earth_bank" = "Acantilado";
"type.man_made.embankment" = "Terraplén";
"type.natural.coastline" = "Costa";
"type.natural.desert" = "Desierto";
@ -805,7 +805,7 @@
"type.natural.heath" = "Brezal";
"type.natural.hot_spring" = "Aguas termales";
"type.natural.water.lake" = "Lago";
"type.natural.water.lock" = "Esclusa";
"type.natural.water.lock" = "Cámara de bloqueo";
"type.natural.water.pond" = "Estanque";
"type.natural.water.reservoir" = "Embalse";
"type.natural.water.basin" = "Cuenca";

View file

@ -411,7 +411,7 @@
/* blue gray color */
"blue_gray" = "Gris-bleu";
"dialog_routing_disclaimer_title" = "Lorsque vous suivez l'itinéraire, gardez à l'esprit les points suivants :";
"dialog_routing_disclaimer_priority" = "— Les conditions de circulation, le code de la route et les panneaux de signalisation ont la priorité sur l'appareil de navigation;";
"dialog_routing_disclaimer_priority" = "— Les conditions de circulation, le code de la route et les panneaux de signalisation ont la priorité sur l'appareil de navigation ;";
"dialog_routing_disclaimer_precision" = "— La carte peut être imprécise et l'itinéraire proposé n'est pas forcément le plus direct pour arriver à destination ;";
"dialog_routing_disclaimer_recommendations" = "— L'itinéraire proposé doit être considéré comme une simple recommandation ;";
"dialog_routing_disclaimer_borders" = "— Faites attention aux itinéraires traversant des zones frontalières : les itinéraires générés par l'application peuvent parfois franchir des frontières étatiques dans des zones interdites ;";
@ -598,7 +598,7 @@
"editor_operator" = "Opérateur";
/* To indicate the operator of ATMs, bicycle rentals, electric vehicle charging stations... */
"operator" = "Opérateur : %@";
"operator" = "Opérateur : %@";
"editor_category_unsuitable_title" = "Tu ne trouves pas de catégorie appropriée ?";
"editor_category_unsuitable_text" = "Organic Maps ne permet d'ajouter que des catégories de points simples, c'est-à-dire pas de villes, de routes, de lacs, de contours de bâtiments, etc. Merci d'ajouter ces catégories directement sur <a href=\"https://www.openstreetmap.org\">OpenStreetMap.org</a>. Consulte notre <a href=\"https://organicmaps.app/faq/editing/advanced-map-editing\">guide</a> pour obtenir des instructions détaillées étape par étape.";
"downloader_no_downloaded_maps_title" = "Vous n'avez téléchargé aucune carte";
@ -709,7 +709,7 @@
"traffic_data_unavailable" = "Les données de circulation ne sont pas disponibles";
"enable_logging" = "Activer le journal";
"log_file_size" = "Taille du fichier journal : %@";
"transliteration_title" = "Translittératisé en alphabet latin";
"transliteration_title" = "Translittération en latin";
/* Subway exits for public transport marks on the map */
"core_exit" = "Sortie";

View file

@ -12,14 +12,10 @@
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>zero</key>
<string>marque-page</string>
<key>one</key>
<string>marque-page</string>
<key>many</key>
<string>marque-pages</string>
<string>%d signet</string>
<key>other</key>
<string>marque-pages</string>
<string>%d signets</string>
</dict>
</dict>
<key>bookmarks_detect_message</key>
@ -32,12 +28,8 @@
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>zero</key>
<string>%d fichier ont été trouvé. Vous n'en verrez pas après la conversion.</string>
<key>one</key>
<string>%d fichier a été trouvé. Vous le verrez après la conversion.</string>
<key>many</key>
<string>%d fichiers ont été trouvés. Vous les verrez après la conversion.</string>
<key>other</key>
<string>%d fichiers ont été trouvés. Vous les verrez après la conversion.</string>
</dict>
@ -52,14 +44,8 @@
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>zero</key>
<string>%d voie</string>
<key>one</key>
<string>%d voie</string>
<key>many</key>
<string>%d voies</string>
<key>other</key>
<string>%d voies</string>
<string>%d trace</string>
</dict>
</dict>
</dict>

View file

@ -898,10 +898,10 @@
"type.power.tower" = "Pylône électrique";
/* A single pole supporting minor power lines. */
"type.power.pole" = "Pylône électrique";
"type.power.pole" = "Power Pole";
/* A single pole supporting various public utilities, such as lighting or telephony. */
"type.man_made.utility_pole" = "Pylônes (Télécommunication, lampadaire)";
"type.man_made.utility_pole" = "Utility Pole";
"type.psurface" = "psurface";
"type.psurface.paved_bad" = "psurface-paved_bad";
"type.psurface.paved_good" = "psurface-paved_good";

View file

@ -1,18 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>tracks</key>
<dict>
<key>value</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>other</key>
<string>%d tracks?</string>
</dict>
</dict>
</dict>
</plist>

View file

@ -1,107 +0,0 @@
"type.healthcare.laboratory" = "Læknisfræðirannsóknastofa";
"type.barrier.kissing_gate" = "Vippuhlið";
"type.waterway.ditch" = "Drenskurður";
"type.boundary.administrative.3" = "Héraðsmörk";
"type.recycling.paper" = "Pappír";
"type.aeroway.aerodrome" = "Flugvöllur";
"type.barrier.swing_gate" = "Sveifluhlið";
"type.aerialway.cable_car" = "Kláfur";
"type.aerialway.chair_lift" = "Stólalyfta";
"type.aerialway.drag_lift" = "Toglyfta";
"type.aerialway.gondola" = "Eggjalyfta";
"type.aerialway.mixed_lift" = "Blönduð gerð víralyftu";
"type.aerialway.station" = "Lyftustöð";
"type.aeroway" = "Stoðkerfisnet flugsamgangna";
"type.aeroway.aerodrome.international" = "Alþjóðaflugvöllur";
"type.aeroway.apron" = "Flughlað";
"type.aeroway.gate" = "Hlið";
"type.amenity.place_of_worship.hindu" = "Hindú-musteri";
"type.amenity.place_of_worship.jewish" = "Sýnagóga";
"type.amenity.place_of_worship.muslim" = "Moska";
"type.amenity.place_of_worship.shinto" = "Shinto-skrín";
"type.amenity.police" = "Lögreglustöð";
"type.amenity.post_box" = "Pósthólf";
"type.amenity.pub" = "Krá";
"type.amenity.public_bookcase" = "Bókaskipti";
"type.amenity.recycling.centre" = "Endurvinnslustöð";
"type.amenity.recycling" = "Endurvinnslugámur";
"type.amenity.recycling.container" = "Endurvinnslugámur";
"type.recycling.batteries" = "Rafgeymar";
"type.recycling.clothes" = "Fatnaður";
"type.recycling.glass_bottles" = "Glerflöskur";
"type.recycling.plastic_bottles" = "Plastflöskur";
"type.recycling.scrap_metal" = "Brotajárn";
"type.recycling.cardboard" = "Bylgjupappi";
"type.recycling.cans" = "Dósir";
"type.recycling.shoes" = "Skór";
"type.recycling.cartons" = "Pappi";
"type.amenity.restaurant" = "Veitingastaður";
"type.amenity.school" = "Skólaföt";
"type.amenity.shelter" = "Skýli";
"type.amenity.shelter.public_transport" = "Skýli";
"type.amenity.university" = "Háskóli";
"type.amenity.vending_machine" = "Sjálfsali";
"type.amenity.vending_machine.cigarettes" = "Tóbakssjálfsali";
"type.amenity.vending_machine.coffee" = "Kaffisjálfsali";
"type.amenity.vending_machine.condoms" = "Smokkasjálfsali";
"type.amenity.vending_machine.drinks" = "Drykkjasjálfsali";
"type.amenity.vending_machine.food" = "Matvörusjálfsali";
"type.amenity.vending_machine.parking_tickets" = "Stöðumælir";
"type.amenity.vending_machine.public_transport_tickets" = "Miðasjálfsali";
"type.amenity.vending_machine.sweets" = "Sælgætissjálfsali";
"type.amenity.vending_machine.excrement_bags" = "Saurpokasjálfsali";
"type.barrier.hedge" = "Limgerði";
"type.barrier.lift_gate" = "Liftuhlið";
"type.barrier.stile" = "Hliðstigi";
"type.barrier.turnstile" = "Snúningshlið";
"type.barrier.toll_booth" = "Tollverðir";
"type.barrier.wall" = "Veggur";
"type.boundary" = "Mörk";
"type.boundary.administrative" = "Stjórnsýslumörk";
"type.boundary.administrative.2" = "Landamæri";
"type.boundary.administrative.4" = "Héraðsmörk";
"type.boundary.national_park" = "Þjóðgarður";
"type.boundary.aboriginal_lands" = "Frumbyggjaland";
"type.boundary.protected_area" = "Verndarsvæði";
"type.boundary.protected_area.1" = "Verndarsvæði";
"type.boundary.protected_area.2" = "Verndarsvæði";
"type.boundary.protected_area.3" = "Verndarsvæði";
"type.boundary.protected_area.4" = "Verndarsvæði";
"type.boundary.protected_area.5" = "Verndarsvæði";
"type.boundary.protected_area.6" = "Verndarsvæði";
"type.building" = "Bygging";
"type.building.address" = "Heimilisfang";
"type.building.has_parts" = "Bygging";
"type.building_part" = "Bygging";
"type.building.garage" = "Bílskúr";
"type.amenity.parcel_locker" = "Bögglaskápur";
"type.amenity.vehicle_inspection" = "Bifreiðaskoðun";
"type.amenity.vending_machine.fuel" = "Eldsneytisdæla";
"type.amenity.veterinary" = "Dýralæknir";
"type.amenity.waste_disposal" = "Ruslgámur";
"type.amenity.waste_transfer_station" = "Millifærslustöð fyrir úrgang";
"type.amenity.water_point" = "Vatnspóstur";
"type.amenity.water_point.drinking_water_no" = "Vatnspóstur";
"type.barrier" = "Hindrun";
"type.barrier.block" = "Reitur";
"type.barrier.bollard" = "Polli";
"type.barrier.border_control" = "Landamæraeftirlit";
"type.barrier.chain" = "Keðja";
"type.barrier.city_wall" = "Borgarmúr";
"type.barrier.cycle_barrier" = "Hjólahindrun";
"type.natural.water.wastewater" = "Óhreinsað vatn";
"type.barrier.entrance" = "Inngangur";
"type.barrier.fence" = "Girðing";
"type.man_made.silo" = "Síló";
"type.tourism.information.visitor_centre" = "Gestastofa";
"type.tourism.viewpoint" = "Útsýnisstaður";
"type.amenity.waste_basket" = "Rusl";
"type.cuisine.noodles" = "Núðlur";
"type.recycling.plastic" = "Plast";
"type.shop.chemist" = "Lyfsali";
"type.barrier.gate" = "Hlið";
"type.amenity.vending_machine.newspapers" = "Dagblaðasjálfsali";
"type.barrier.retaining_wall" = "Stoðveggur";
"type.amenity.post_office" = "Pósthús";
"type.amenity.prison" = "Fangelsi";
"type.aerialway" = "Víralyfta";

View file

@ -601,7 +601,7 @@
"operator" = "Operators: %@";
"editor_category_unsuitable_title" = "Vai neatrodat atbilstošu kategoriju?";
"editor_category_unsuitable_text" = "„Organic Maps“ ļauj pievienot tikai vienkāršu punktu kategorijas, proti, nevarat pievienot pilsētas, ceļus, ezerus, ēku aprises u.tml. Šādas kategorijas lūdzam pievienot tiešā veidā <a href=\"https://www.openstreetmap.org\">OpenStreetMap.org</a>. Lai uzzinātu, kā to izdarīt, sekojiet norādēm <a href=\"https://organicmaps.app/faq/editing/advanced-map-editing\">rokasgrāmatā</a>.";
"downloader_no_downloaded_maps_title" = "Ierīcē nav lejupielādētu karšu";
"downloader_no_downloaded_maps_title" = "Ierīcē nav lejupielādētas kartes";
"downloader_no_downloaded_maps_message" = "Lai darbotos nesaistes navigācija un meklēšana, lejupielādējiet kartes.";
"current_location_unknown_error_title" = "Pašreizējā vieta nav zināma.";
"current_location_unknown_error_message" = "Nosakot jūsu atrašanās vietu, radās kļūda. Pārleicinieties, ka ierīce darbojas korekti un mēģiniet vēlreiz.";

View file

@ -491,20 +491,16 @@
ED4DC7782CAEDECC0029B338 /* ProductButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED4DC7732CAEDECC0029B338 /* ProductButton.swift */; };
ED4DC7792CAEDECC0029B338 /* ProductsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED4DC7742CAEDECC0029B338 /* ProductsViewController.swift */; };
ED5BAF4B2D688F5B0088D7B1 /* SearchOnMapHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED5BAF4A2D688F5A0088D7B1 /* SearchOnMapHeaderView.swift */; };
ED5E02142D8B17B600A5CC7B /* ModalPresentationStepsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED5E02132D8B17B600A5CC7B /* ModalPresentationStepsController.swift */; };
ED63CEB92BDF8F9D006155C4 /* SettingsTableViewiCloudSwitchCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED63CEB62BDF8F9C006155C4 /* SettingsTableViewiCloudSwitchCell.swift */; };
ED70D55C2D5396F300738C1E /* SearchResult.mm in Sources */ = {isa = PBXBuildFile; fileRef = ED70D55A2D5396F300738C1E /* SearchResult.mm */; };
ED70D5892D539A2500738C1E /* SearchOnMapViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED70D5872D539A2500738C1E /* SearchOnMapViewController.swift */; };
ED70D58A2D539A2500738C1E /* SearchOnMapModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED70D5852D539A2500738C1E /* SearchOnMapModels.swift */; };
ED70D58B2D539A2500738C1E /* SearchOnMapModalTransitionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED70D57E2D539A2500738C1E /* SearchOnMapModalTransitionManager.swift */; };
ED70D58C2D539A2500738C1E /* SearchOnMapPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED70D5862D539A2500738C1E /* SearchOnMapPresenter.swift */; };
ED70D58D2D539A2500738C1E /* ModalScreenPresentationStep.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED70D57C2D539A2500738C1E /* ModalScreenPresentationStep.swift */; };
ED70D58E2D539A2500738C1E /* SideMenuPresentationAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED70D5802D539A2500738C1E /* SideMenuPresentationAnimator.swift */; };
ED70D58D2D539A2500738C1E /* ModalPresentationStep.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED70D57C2D539A2500738C1E /* ModalPresentationStep.swift */; };
ED70D58F2D539A2500738C1E /* SearchOnMapInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED70D5832D539A2500738C1E /* SearchOnMapInteractor.swift */; };
ED70D5902D539A2500738C1E /* SearchOnMapModalPresentationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED70D57D2D539A2500738C1E /* SearchOnMapModalPresentationController.swift */; };
ED70D5912D539A2500738C1E /* MapPassthroughView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED70D57B2D539A2500738C1E /* MapPassthroughView.swift */; };
ED70D5922D539A2500738C1E /* PlaceholderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED70D5822D539A2500738C1E /* PlaceholderView.swift */; };
ED70D5932D539A2500738C1E /* SearchOnMapManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED70D5842D539A2500738C1E /* SearchOnMapManager.swift */; };
ED70D5942D539A2500738C1E /* SideMenuDismissalAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED70D57F2D539A2500738C1E /* SideMenuDismissalAnimator.swift */; };
ED77556E2C2C490B0051E656 /* UIAlertController+openInAppActionSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED77556D2C2C490B0051E656 /* UIAlertController+openInAppActionSheet.swift */; };
ED79A5AB2BD7AA9C00952D1F /* LoadingOverlayViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED79A5AA2BD7AA9C00952D1F /* LoadingOverlayViewController.swift */; };
ED79A5AD2BD7BA0F00952D1F /* UIApplication+LoadingOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED79A5AC2BD7BA0F00952D1F /* UIApplication+LoadingOverlay.swift */; };
@ -526,6 +522,9 @@
ED9857082C4ED02D00694F6C /* MailComposer.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED9857072C4ED02D00694F6C /* MailComposer.swift */; };
ED9966802B94FBC20083CE55 /* ColorPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED99667D2B94FBC20083CE55 /* ColorPicker.swift */; };
EDA1EAA42CC7ECAD00DBDCAA /* ElevationProfileFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDA1EAA32CC7ECAD00DBDCAA /* ElevationProfileFormatter.swift */; };
EDB71D8C2D8474A0004A6A7F /* CornerRadius.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDB71D8B2D8474A0004A6A7F /* CornerRadius.swift */; };
EDB71E002D8B0338004A6A7F /* ModalPresentationAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDB71DFF2D8B0338004A6A7F /* ModalPresentationAnimator.swift */; };
EDB71E042D8B0943004A6A7F /* SearchOnMapAreaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDB71E032D8B0943004A6A7F /* SearchOnMapAreaView.swift */; };
EDBD68072B625724005DD151 /* LocationServicesDisabledAlert.xib in Resources */ = {isa = PBXBuildFile; fileRef = EDBD68062B625724005DD151 /* LocationServicesDisabledAlert.xib */; };
EDBD680B2B62572E005DD151 /* LocationServicesDisabledAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDBD680A2B62572E005DD151 /* LocationServicesDisabledAlert.swift */; };
EDC3573B2B7B5029001AE9CA /* CALayer+SetCorner.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDC3573A2B7B5029001AE9CA /* CALayer+SetCorner.swift */; };
@ -1467,17 +1466,13 @@
ED4DC7742CAEDECC0029B338 /* ProductsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductsViewController.swift; sourceTree = "<group>"; };
ED4DC7752CAEDECC0029B338 /* ProductsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductsViewModel.swift; sourceTree = "<group>"; };
ED5BAF4A2D688F5A0088D7B1 /* SearchOnMapHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchOnMapHeaderView.swift; sourceTree = "<group>"; };
ED5E02132D8B17B600A5CC7B /* ModalPresentationStepsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModalPresentationStepsController.swift; sourceTree = "<group>"; };
ED63CEB62BDF8F9C006155C4 /* SettingsTableViewiCloudSwitchCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsTableViewiCloudSwitchCell.swift; sourceTree = "<group>"; };
ED70D5582D5396F300738C1E /* SearchItemType.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SearchItemType.h; sourceTree = "<group>"; };
ED70D5592D5396F300738C1E /* SearchResult.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SearchResult.h; sourceTree = "<group>"; };
ED70D55A2D5396F300738C1E /* SearchResult.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = SearchResult.mm; sourceTree = "<group>"; };
ED70D55B2D5396F300738C1E /* SearchResult+Core.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SearchResult+Core.h"; sourceTree = "<group>"; };
ED70D57B2D539A2500738C1E /* MapPassthroughView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapPassthroughView.swift; sourceTree = "<group>"; };
ED70D57C2D539A2500738C1E /* ModalScreenPresentationStep.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModalScreenPresentationStep.swift; sourceTree = "<group>"; };
ED70D57D2D539A2500738C1E /* SearchOnMapModalPresentationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchOnMapModalPresentationController.swift; sourceTree = "<group>"; };
ED70D57E2D539A2500738C1E /* SearchOnMapModalTransitionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchOnMapModalTransitionManager.swift; sourceTree = "<group>"; };
ED70D57F2D539A2500738C1E /* SideMenuDismissalAnimator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SideMenuDismissalAnimator.swift; sourceTree = "<group>"; };
ED70D5802D539A2500738C1E /* SideMenuPresentationAnimator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SideMenuPresentationAnimator.swift; sourceTree = "<group>"; };
ED70D57C2D539A2500738C1E /* ModalPresentationStep.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModalPresentationStep.swift; sourceTree = "<group>"; };
ED70D5822D539A2500738C1E /* PlaceholderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaceholderView.swift; sourceTree = "<group>"; };
ED70D5832D539A2500738C1E /* SearchOnMapInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchOnMapInteractor.swift; sourceTree = "<group>"; };
ED70D5842D539A2500738C1E /* SearchOnMapManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchOnMapManager.swift; sourceTree = "<group>"; };
@ -1548,6 +1543,9 @@
ED9857072C4ED02D00694F6C /* MailComposer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MailComposer.swift; sourceTree = "<group>"; };
ED99667D2B94FBC20083CE55 /* ColorPicker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColorPicker.swift; sourceTree = "<group>"; };
EDA1EAA32CC7ECAD00DBDCAA /* ElevationProfileFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElevationProfileFormatter.swift; sourceTree = "<group>"; };
EDB71D8B2D8474A0004A6A7F /* CornerRadius.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CornerRadius.swift; sourceTree = "<group>"; };
EDB71DFF2D8B0338004A6A7F /* ModalPresentationAnimator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModalPresentationAnimator.swift; sourceTree = "<group>"; };
EDB71E032D8B0943004A6A7F /* SearchOnMapAreaView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchOnMapAreaView.swift; sourceTree = "<group>"; };
EDBD68062B625724005DD151 /* LocationServicesDisabledAlert.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LocationServicesDisabledAlert.xib; sourceTree = "<group>"; };
EDBD680A2B62572E005DD151 /* LocationServicesDisabledAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationServicesDisabledAlert.swift; sourceTree = "<group>"; };
EDC3573A2B7B5029001AE9CA /* CALayer+SetCorner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CALayer+SetCorner.swift"; sourceTree = "<group>"; };
@ -3008,6 +3006,7 @@
993DF0CE23F6BDB000AC231A /* MainTheme.swift */,
ED914AB72D351DF000973C45 /* StyleApplicable.swift */,
EDCA7CDE2D317DF9003366CE /* StyleSheet.swift */,
EDB71D8B2D8474A0004A6A7F /* CornerRadius.swift */,
993DF10123F6BDB100AC231A /* GlobalStyleSheet.swift */,
99A906F223FA95AB0005872B /* PlacePageStyleSheet.swift */,
99F8B4C523B644A6009FF0B4 /* MapStyleSheet.swift */,
@ -3291,12 +3290,9 @@
ED70D5812D539A2500738C1E /* Presentation */ = {
isa = PBXGroup;
children = (
ED70D57B2D539A2500738C1E /* MapPassthroughView.swift */,
ED70D57C2D539A2500738C1E /* ModalScreenPresentationStep.swift */,
ED70D57D2D539A2500738C1E /* SearchOnMapModalPresentationController.swift */,
ED70D57E2D539A2500738C1E /* SearchOnMapModalTransitionManager.swift */,
ED70D57F2D539A2500738C1E /* SideMenuDismissalAnimator.swift */,
ED70D5802D539A2500738C1E /* SideMenuPresentationAnimator.swift */,
ED5E02132D8B17B600A5CC7B /* ModalPresentationStepsController.swift */,
EDB71DFF2D8B0338004A6A7F /* ModalPresentationAnimator.swift */,
ED70D57C2D539A2500738C1E /* ModalPresentationStep.swift */,
);
path = Presentation;
sourceTree = "<group>";
@ -3312,6 +3308,7 @@
ED70D5862D539A2500738C1E /* SearchOnMapPresenter.swift */,
ED70D5872D539A2500738C1E /* SearchOnMapViewController.swift */,
ED5BAF4A2D688F5A0088D7B1 /* SearchOnMapHeaderView.swift */,
EDB71E032D8B0943004A6A7F /* SearchOnMapAreaView.swift */,
);
path = SearchOnMap;
sourceTree = "<group>";
@ -4585,6 +4582,7 @@
F653CE191C71F62700A453F1 /* MWMAddPlaceNavigationBar.mm in Sources */,
340475621E081A4600C92850 /* MWMNetworkPolicy+UI.m in Sources */,
F6E2FEE51E097BA00083EBEC /* MWMSearchNoResults.m in Sources */,
ED5E02142D8B17B600A5CC7B /* ModalPresentationStepsController.swift in Sources */,
F6E2FF631E097BA00083EBEC /* MWMTTSLanguageViewController.mm in Sources */,
4715273524907F8200E91BBA /* BookmarkColorViewController.swift in Sources */,
47E3C7292111E614008B3B27 /* FadeInAnimatedTransitioning.swift in Sources */,
@ -4664,6 +4662,7 @@
99AAEA74244DA5ED0039D110 /* BottomMenuPresentationController.swift in Sources */,
99514BB823E82B450085D3A7 /* ElevationProfilePresenter.swift in Sources */,
34C9BD031C6DB693000DC38D /* MWMTableViewController.m in Sources */,
EDB71E002D8B0338004A6A7F /* ModalPresentationAnimator.swift in Sources */,
F6E2FD8C1E097BA00083EBEC /* MWMNoMapsView.m in Sources */,
34D3B0361E389D05004100F9 /* MWMEditorSelectTableViewCell.m in Sources */,
990128562449A82500C72B10 /* BottomTabBarView.swift in Sources */,
@ -4729,6 +4728,7 @@
34D3AFEA1E378AF1004100F9 /* UINib+Init.swift in Sources */,
34AB663E1FC5AA330078E451 /* RouteManagerTransitioning.swift in Sources */,
993DF0CB23F6BD0600AC231A /* ElevationDetailsRouter.swift in Sources */,
EDB71D8C2D8474A0004A6A7F /* CornerRadius.swift in Sources */,
47CA68FC250F99E500671019 /* BookmarksListCellStrategy.swift in Sources */,
34AB662F1FC5AA330078E451 /* RouteManagerPresentationController.swift in Sources */,
993F5508237C622700545511 /* DeepLinkRouteStrategyAdapter.mm in Sources */,
@ -4817,16 +4817,11 @@
3404755C1E081A4600C92850 /* MWMLocationManager.mm in Sources */,
ED70D5892D539A2500738C1E /* SearchOnMapViewController.swift in Sources */,
ED70D58A2D539A2500738C1E /* SearchOnMapModels.swift in Sources */,
ED70D58B2D539A2500738C1E /* SearchOnMapModalTransitionManager.swift in Sources */,
ED70D58C2D539A2500738C1E /* SearchOnMapPresenter.swift in Sources */,
ED70D58D2D539A2500738C1E /* ModalScreenPresentationStep.swift in Sources */,
ED70D58E2D539A2500738C1E /* SideMenuPresentationAnimator.swift in Sources */,
ED70D58D2D539A2500738C1E /* ModalPresentationStep.swift in Sources */,
ED70D58F2D539A2500738C1E /* SearchOnMapInteractor.swift in Sources */,
ED70D5902D539A2500738C1E /* SearchOnMapModalPresentationController.swift in Sources */,
ED70D5912D539A2500738C1E /* MapPassthroughView.swift in Sources */,
ED70D5922D539A2500738C1E /* PlaceholderView.swift in Sources */,
ED70D5932D539A2500738C1E /* SearchOnMapManager.swift in Sources */,
ED70D5942D539A2500738C1E /* SideMenuDismissalAnimator.swift in Sources */,
3454D7BC1E07F045004AF2AD /* CLLocation+Mercator.mm in Sources */,
47E3C7272111E5A8008B3B27 /* AlertPresentationController.swift in Sources */,
CDCA27812243F59800167D87 /* CarPlayRouter.swift in Sources */,
@ -4846,6 +4841,7 @@
34AB66381FC5AA330078E451 /* RouteManagerCell.swift in Sources */,
ED1263AB2B6F99F900AD99F3 /* UIView+AddSeparator.swift in Sources */,
CD4A1F132305872700F2A6B6 /* PromoBookingPresentationController.swift in Sources */,
EDB71E042D8B0943004A6A7F /* SearchOnMapAreaView.swift in Sources */,
3472B5D3200F501500DC6CD5 /* BackgroundFetchTaskFrameworkType.swift in Sources */,
47E460AD240D737D00385B45 /* OpeinigHoursLocalization.swift in Sources */,
99F9A0E52462CA0E00AE21E0 /* DownloadAllView.swift in Sources */,

View file

@ -12,8 +12,7 @@ final class SearchOnMapTests: XCTestCase {
override func setUp() {
super.setUp()
searchManager = SearchManagerMock.self
presenter = SearchOnMapPresenter(transitionManager: SearchOnMapModalTransitionManager(),
isRouting: false,
presenter = SearchOnMapPresenter(isRouting: false,
didChangeState: { [weak self] in self?.currentState = $0 })
interactor = SearchOnMapInteractor(presenter: presenter, searchManager: searchManager)
view = SearchOnMapViewMock()
@ -131,8 +130,13 @@ final class SearchOnMapTests: XCTestCase {
searchManager.results = results
interactor.handle(.didSelectResult(results[0], withSearchText: searchText))
XCTAssertEqual(currentState, .hidden)
XCTAssertEqual(view.viewModel.presentationStep, .hidden)
if isIPad {
XCTAssertEqual(currentState, .searching)
XCTAssertEqual(view.viewModel.presentationStep, .fullScreen)
} else {
XCTAssertEqual(currentState, .hidden)
XCTAssertEqual(view.viewModel.presentationStep, .hidden)
}
}
func test_GivenSearchIsActive_WhenSelectPlaceOnMap_ThenHideSearch() {
@ -159,8 +163,13 @@ final class SearchOnMapTests: XCTestCase {
searchManager.results = results
interactor.handle(.didSelectResult(results[0], withSearchText: searchText))
XCTAssertEqual(currentState, .hidden)
XCTAssertEqual(view.viewModel.presentationStep, .hidden)
if isIPad {
XCTAssertEqual(currentState, .searching)
XCTAssertEqual(view.viewModel.presentationStep, .fullScreen)
} else {
XCTAssertEqual(currentState, .hidden)
XCTAssertEqual(view.viewModel.presentationStep, .hidden)
}
interactor.handle(.didDeselectPlaceOnMap)
XCTAssertEqual(currentState, .searching)
@ -222,6 +231,10 @@ private class SearchOnMapViewMock: SearchOnMapView {
func render(_ viewModel: SearchOnMap.ViewModel) {
self.viewModel = viewModel
}
func close() {
}
func show() {
}
}
private class SearchManagerMock: SearchManager {

View file

@ -101,7 +101,10 @@ class AvailableArea: UIView {
}
func addConstraints(otherView: UIView, directions: MWMAvailableAreaAffectDirections) {
precondition(!directions.isEmpty)
guard !directions.isEmpty else {
LOG(.warning, "Attempt to add empty affecting directions from \(otherView) to \(self)")
return
}
let add = { (sa: NSLayoutConstraint.Attribute, oa: NSLayoutConstraint.Attribute, rel: NSLayoutConstraint.Relation) in
let c = NSLayoutConstraint(item: self, attribute: sa, relatedBy: rel, toItem: otherView, attribute: oa, multiplier: 1, constant: 0)
c.priority = UILayoutPriority.defaultHigh

View file

@ -25,7 +25,7 @@ class BottomMenuViewController: MWMViewController {
override func viewDidLoad() {
super.viewDidLoad()
tableView.layer.setCorner(radius: 8, corners: [.layerMinXMinYCorner, .layerMaxXMinYCorner])
tableView.layer.setCornerRadius(.buttonDefault, maskedCorners: [.layerMinXMinYCorner, .layerMaxXMinYCorner])
tableView.sectionFooterHeight = 0
tableView.dataSource = presenter

View file

@ -22,7 +22,7 @@ final class ProductButton: UIButton {
titleLabel?.allowsDefaultTighteningForTruncation = true
titleLabel?.adjustsFontSizeToFitWidth = true
titleLabel?.minimumScaleFactor = 0.5
layer.setCorner(radius: 5.0)
layer.setCornerRadius(.buttonDefaultSmall)
layer.masksToBounds = true
addTarget(self, action: #selector(buttonDidTap), for: .touchUpInside)
}

View file

@ -153,7 +153,6 @@ final class PlacePageScrollView: UIScrollView {
private func setupView() {
let bgView = UIView()
bgView.setStyle(.ppBackgroundView)
stackView.insertSubview(bgView, at: 0)
bgView.alignToSuperview()
@ -163,7 +162,7 @@ final class PlacePageScrollView: UIScrollView {
stackView.backgroundColor = .clear
let cornersToMask: CACornerMask = alternativeSizeClass(iPhone: [], iPad: [.layerMinXMaxYCorner, .layerMaxXMaxYCorner])
actionBarContainerView.layer.setCorner(radius: 16, corners: cornersToMask)
actionBarContainerView.layer.setCornerRadius(.modalSheet, maskedCorners: cornersToMask)
actionBarContainerView.layer.masksToBounds = true
// See https://github.com/organicmaps/organicmaps/issues/6917 for the details.

View file

@ -43,7 +43,7 @@ class DifficultyView: UIView {
for _ in 0..<difficultyLevelCount {
let view = UIView()
stackView.addArrangedSubview(view)
view.layer.setCorner(radius: bulletSize.height / 2)
view.layer.setCornerRadius(.custom(bulletSize.height / 2))
views.append(view)
}
}

View file

@ -115,8 +115,8 @@ final class PlaceholderView: UIView {
// MARK: - ModallyPresentedViewController
extension PlaceholderView: ModallyPresentedViewController {
func translationYDidUpdate(_ translationY: CGFloat) {
self.containerModalYTranslation = translationY
func presentationFrameDidChange(_ frame: CGRect) {
self.containerModalYTranslation = frame.origin.y
reloadConstraints()
}
}

View file

@ -1,30 +0,0 @@
/// A transparent view that allows touch events to pass through to the MapViewController's view.
///
/// This view is used to enable interaction with the underlying map while still maintaining a
/// transparent overlay. It does not block touch events but forwards them to the specified `passingView`.
final class MapPassthroughView: UIView {
private weak var passingView: UIView?
init(passingView: UIView) {
self.passingView = passingView
super.init(frame: passingView.bounds)
self.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.alpha = 0
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
guard let passingView else { return nil }
let pointInPassthroughView = passingView.convert(point, from: self)
super.hitTest(point, with: event)
if passingView.bounds.contains(pointInPassthroughView) {
return MapViewController.shared()?.view.hitTest(point, with: event)
}
return nil
}
}

View file

@ -0,0 +1,38 @@
enum PresentationStepChangeAnimation {
case none
case slide
case slideAndBounce
}
final class ModalPresentationAnimator {
private enum Constants {
static let animationDuration: TimeInterval = kDefaultAnimationDuration
static let springDamping: CGFloat = 0.85
static let springVelocity: CGFloat = 0.2
}
static func animate(with stepAnimation: PresentationStepChangeAnimation = .slide,
animations: @escaping (() -> Void),
completion: ((Bool) -> Void)?) {
switch stepAnimation {
case .none:
animations()
completion?(true)
case .slide:
UIView.animate(withDuration: Constants.animationDuration,
delay: 0,
options: .curveEaseOut,
animations: animations,
completion: completion)
case .slideAndBounce:
UIView.animate(withDuration: Constants.animationDuration,
delay: 0,
usingSpringWithDamping: Constants.springDamping,
initialSpringVelocity: Constants.springVelocity,
options: .curveLinear,
animations: animations,
completion: completion)
}
}
}

View file

@ -1,11 +1,11 @@
enum ModalScreenPresentationStep {
enum ModalPresentationStep: Int, CaseIterable {
case fullScreen
case halfScreen
case compact
case hidden
}
extension ModalScreenPresentationStep {
extension ModalPresentationStep {
private enum Constants {
static let iPadWidth: CGFloat = 350
static let compactHeightOffset: CGFloat = 120
@ -14,7 +14,7 @@ extension ModalScreenPresentationStep {
static let landscapeTopInset: CGFloat = 10
}
var upper: ModalScreenPresentationStep {
var upper: ModalPresentationStep {
switch self {
case .fullScreen:
return .fullScreen
@ -27,7 +27,7 @@ extension ModalScreenPresentationStep {
}
}
var lower: ModalScreenPresentationStep {
var lower: ModalPresentationStep {
switch self {
case .fullScreen:
return .halfScreen
@ -40,18 +40,22 @@ extension ModalScreenPresentationStep {
}
}
var first: ModalScreenPresentationStep {
var first: ModalPresentationStep {
.fullScreen
}
var last: ModalScreenPresentationStep {
var last: ModalPresentationStep {
.compact
}
func frame(for viewController: UIViewController, in containerView: UIView) -> CGRect {
func frame(for presentedView: UIView, in containerViewController: UIViewController) -> CGRect {
let isIPad = UIDevice.current.userInterfaceIdiom == .pad
let containerSize = containerView.bounds.size
let safeAreaInsets = containerView.safeAreaInsets
var containerSize = containerViewController.view.bounds.size
if containerSize == .zero {
containerSize = UIScreen.main.bounds.size
}
let safeAreaInsets = containerViewController.view.safeAreaInsets
let traitCollection = containerViewController.traitCollection
var frame = CGRect(origin: .zero, size: containerSize)
if isIPad {
@ -65,7 +69,7 @@ extension ModalScreenPresentationStep {
return frame
}
let isPortraitOrientation = viewController.traitCollection.verticalSizeClass == .regular
let isPortraitOrientation = traitCollection.verticalSizeClass == .regular
if isPortraitOrientation {
switch self {
case .fullScreen:

View file

@ -0,0 +1,118 @@
final class ModalPresentationStepsController {
enum StepUpdate {
case didClose
case didUpdateFrame(CGRect)
case didUpdateStep(ModalPresentationStep)
}
fileprivate enum Constants {
static let slowSwipeVelocity: CGFloat = 500
static let fastSwipeDownVelocity: CGFloat = 4000
static let fastSwipeUpVelocity: CGFloat = 3000
static let translationThreshold: CGFloat = 50
}
private weak var presentedView: UIView?
private weak var containerViewController: UIViewController?
private var initialTranslationY: CGFloat = .zero
private(set) var currentStep: ModalPresentationStep = .fullScreen
private(set) var maxAvailableFrame: CGRect = .zero
var currentFrame: CGRect { frame(for: currentStep) }
var hiddenFrame: CGRect { frame(for: .hidden) }
var didUpdateHandler: ((StepUpdate) -> Void)?
func set(presentedView: UIView, containerViewController: UIViewController) {
self.presentedView = presentedView
self.containerViewController = containerViewController
}
func setInitialState() {
setStep(.hidden, animation: .none)
}
func close(completion: (() -> Void)? = nil) {
setStep(.hidden, animation: .slide, completion: completion)
}
func updateMaxAvailableFrame() {
maxAvailableFrame = frame(for: .fullScreen)
}
func handlePan(_ gesture: UIPanGestureRecognizer) {
guard let presentedView else { return }
let translation = gesture.translation(in: presentedView)
let velocity = gesture.velocity(in: presentedView)
var currentFrame = presentedView.frame
switch gesture.state {
case .began:
initialTranslationY = presentedView.frame.origin.y
case .changed:
let newY = max(max(initialTranslationY + translation.y, 0), maxAvailableFrame.origin.y)
currentFrame.origin.y = newY
presentedView.frame = currentFrame
didUpdateHandler?(.didUpdateFrame(currentFrame))
case .ended:
let nextStep: ModalPresentationStep
if velocity.y > Constants.fastSwipeDownVelocity {
didUpdateHandler?(.didClose)
return
} else if velocity.y < -Constants.fastSwipeUpVelocity {
nextStep = .fullScreen
} else if velocity.y > Constants.slowSwipeVelocity || translation.y > Constants.translationThreshold {
if currentStep == .compact {
didUpdateHandler?(.didClose)
return
}
nextStep = currentStep.lower
} else if velocity.y < -Constants.slowSwipeVelocity || translation.y < -Constants.translationThreshold {
nextStep = currentStep.upper
} else {
nextStep = currentStep
}
let animation: PresentationStepChangeAnimation = abs(velocity.y) > Constants.slowSwipeVelocity ? .slideAndBounce : .slide
setStep(nextStep, animation: animation, notifyAboutStepUpdate: true)
default:
break
}
}
func setStep(_ step: ModalPresentationStep,
completion: (() -> Void)? = nil) {
guard currentStep != step else { return }
setStep(step, animation: .slide, notifyAboutStepUpdate: false, completion: completion)
}
private func setStep(_ step: ModalPresentationStep,
animation: PresentationStepChangeAnimation,
notifyAboutStepUpdate: Bool = true,
completion: (() -> Void)? = nil) {
guard let presentedView else { return }
currentStep = step
updateMaxAvailableFrame()
let frame = frame(for: step)
didUpdateHandler?(.didUpdateFrame(frame))
ModalPresentationAnimator.animate(with: animation) {
presentedView.frame = frame
} completion: { [weak self] _ in
guard let self else { return }
if notifyAboutStepUpdate {
self.didUpdateHandler?(.didUpdateStep(step))
}
completion?()
}
}
private func frame(for step: ModalPresentationStep) -> CGRect {
guard let presentedView, let containerViewController else { return .zero }
return step.frame(for: presentedView, in: containerViewController)
}
}

View file

@ -1,226 +0,0 @@
protocol ModallyPresentedViewController {
func translationYDidUpdate(_ translationY: CGFloat)
}
protocol SearchOnMapModalPresentationView: AnyObject {
func setPresentationStep(_ step: ModalScreenPresentationStep)
func close()
}
final class SearchOnMapModalPresentationController: UIPresentationController {
private enum StepChangeAnimation {
case slide
case slideAndBounce
}
private enum Constants {
static let animationDuration: TimeInterval = kDefaultAnimationDuration
static let springDamping: CGFloat = 0.85
static let springVelocity: CGFloat = 0.2
static let iPhoneCornerRadius: CGFloat = 10
static let slowSwipeVelocity: CGFloat = 500
static let fastSwipeDownVelocity: CGFloat = 4000
static let fastSwipeUpVelocity: CGFloat = 3000
static let translationThreshold: CGFloat = 50
static let panGestureThreshold: CGFloat = 5
}
private var initialTranslationY: CGFloat = 0
private weak var interactor: SearchOnMapInteractor? {
(presentedViewController as? SearchOnMapViewController)?.interactor
}
// TODO: replace with set of steps passed from the outside
private var presentationStep: ModalScreenPresentationStep = .fullScreen
private var internalScrollViewContentOffset: CGFloat = 0
private var maxAvailableFrameOfPresentedView: CGRect = .zero
// MARK: - Init
override init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?) {
super.init(presentedViewController: presentedViewController, presenting: presentingViewController)
iPhoneSpecific {
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
panGestureRecognizer.delegate = self
presentedViewController.view.addGestureRecognizer(panGestureRecognizer)
if let presentedViewController = presentedViewController as? SearchOnMapView {
presentedViewController.scrollViewDelegate = self
}
}
}
// MARK: - Lifecycle
override func containerViewWillLayoutSubviews() {
super.containerViewWillLayoutSubviews()
presentedView?.frame = frameOfPresentedViewInContainerView
}
override func presentationTransitionWillBegin() {
guard let containerView else { return }
containerView.backgroundColor = .clear
let passThroughView = MapPassthroughView(passingView: containerView)
containerView.addSubview(passThroughView)
}
override func presentationTransitionDidEnd(_ completed: Bool) {
translationYDidUpdate(presentedView?.frame.origin.y ?? 0)
}
override func dismissalTransitionDidEnd(_ completed: Bool) {
super.dismissalTransitionDidEnd(completed)
if completed {
presentedView?.removeFromSuperview()
}
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
updateMaxAvailableFrameOfPresentedView()
}
// MARK: - Layout
override var frameOfPresentedViewInContainerView: CGRect {
guard let containerView else { return .zero }
let frame = presentationStep.frame(for: presentedViewController, in: containerView)
updateMaxAvailableFrameOfPresentedView()
return frame
}
private func updateMaxAvailableFrameOfPresentedView() {
guard let containerView else { return }
maxAvailableFrameOfPresentedView = ModalScreenPresentationStep.fullScreen.frame(for: presentedViewController, in: containerView)
}
private func updateSideButtonsAvailableArea(_ newY: CGFloat) {
iPhoneSpecific {
guard presentedViewController.traitCollection.verticalSizeClass != .compact else { return }
var sideButtonsAvailableArea = MWMSideButtons.getAvailableArea()
sideButtonsAvailableArea.size.height = newY - sideButtonsAvailableArea.origin.y
MWMSideButtons.updateAvailableArea(sideButtonsAvailableArea)
}
}
// MARK: - Pan gesture handling
@objc private func handlePan(_ gesture: UIPanGestureRecognizer) {
guard let presentedView, maxAvailableFrameOfPresentedView != .zero else { return }
interactor?.handle(.didStartDraggingSearch)
let translation = gesture.translation(in: presentedView)
let velocity = gesture.velocity(in: presentedView)
switch gesture.state {
case .began:
initialTranslationY = presentedView.frame.origin.y
case .changed:
let newY = max(max(initialTranslationY + translation.y, 0), maxAvailableFrameOfPresentedView.origin.y)
presentedView.frame.origin.y = newY
updateSideButtonsAvailableArea(newY)
translationYDidUpdate(newY)
case .ended:
let nextStep: ModalScreenPresentationStep
if velocity.y > Constants.fastSwipeDownVelocity {
interactor?.handle(.closeSearch)
return
} else if velocity.y < -Constants.fastSwipeUpVelocity {
nextStep = .fullScreen // fast swipe up
} else if velocity.y > Constants.slowSwipeVelocity || translation.y > Constants.translationThreshold {
if presentationStep == .compact {
interactor?.handle(.closeSearch)
return
}
nextStep = presentationStep.lower // regular swipe down
} else if velocity.y < -Constants.slowSwipeVelocity || translation.y < -Constants.translationThreshold {
nextStep = presentationStep.upper // regular swipe up
} else {
// TODO: swipe to closest step on the big translation
nextStep = presentationStep
}
let animation: StepChangeAnimation = abs(velocity.y) > Constants.slowSwipeVelocity ? .slideAndBounce : .slide
animateTo(nextStep, animation: animation)
default:
break
}
}
private func animateTo(_ presentationStep: ModalScreenPresentationStep, animation: StepChangeAnimation = .slide) {
guard let presentedView, let containerView else { return }
self.presentationStep = presentationStep
interactor?.handle(.didUpdatePresentationStep(presentationStep))
let updatedFrame = presentationStep.frame(for: presentedViewController, in: containerView)
let targetYTranslation = updatedFrame.origin.y
switch animation {
case .slide:
UIView.animate(withDuration: Constants.animationDuration,
delay: 0,
options: .curveEaseOut,
animations: { [weak self] in
presentedView.frame = updatedFrame
self?.translationYDidUpdate(targetYTranslation)
self?.updateSideButtonsAvailableArea(targetYTranslation)
})
case .slideAndBounce:
UIView.animate(withDuration: Constants.animationDuration,
delay: 0,
usingSpringWithDamping: Constants.springDamping,
initialSpringVelocity: Constants.springVelocity,
options: .curveLinear,
animations: { [weak self] in
presentedView.frame = updatedFrame
self?.translationYDidUpdate(targetYTranslation)
self?.updateSideButtonsAvailableArea(targetYTranslation)
})
}
}
}
// MARK: - SearchOnMapModalPresentationView
extension SearchOnMapModalPresentationController: SearchOnMapModalPresentationView {
func setPresentationStep(_ step: ModalScreenPresentationStep) {
guard presentationStep != step else { return }
animateTo(step)
}
func close() {
guard let containerView else { return }
updateSideButtonsAvailableArea(containerView.frame.height)
presentedViewController.dismiss(animated: true)
}
}
// MARK: - ModallyPresentedViewController
extension SearchOnMapModalPresentationController: ModallyPresentedViewController {
func translationYDidUpdate(_ translationY: CGFloat) {
iPhoneSpecific {
(presentedViewController as? SearchOnMapViewController)?.translationYDidUpdate(translationY)
}
}
}
// MARK: - UIGestureRecognizerDelegate
extension SearchOnMapModalPresentationController: UIGestureRecognizerDelegate {
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
true
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
// threshold is used to soften transition from the internal scroll zero content offset
internalScrollViewContentOffset < Constants.panGestureThreshold
}
}
// MARK: - SearchOnMapScrollViewDelegate
extension SearchOnMapModalPresentationController: SearchOnMapScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard let presentedView else { return }
let hasReachedTheTop = Int(presentedView.frame.origin.y) > Int(maxAvailableFrameOfPresentedView.origin.y)
let hasZeroContentOffset = internalScrollViewContentOffset == 0
if hasReachedTheTop && hasZeroContentOffset {
// prevent the internal scroll view scrolling
scrollView.contentOffset.y = internalScrollViewContentOffset
return
}
internalScrollViewContentOffset = scrollView.contentOffset.y
}
}

View file

@ -1,21 +0,0 @@
@objc
final class SearchOnMapModalTransitionManager: NSObject, UIViewControllerTransitioningDelegate {
weak var presentationController: SearchOnMapModalPresentationView?
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> (any UIViewControllerAnimatedTransitioning)? {
isIPad ? SideMenuPresentationAnimator() : nil
}
func animationController(forDismissed dismissed: UIViewController) -> (any UIViewControllerAnimatedTransitioning)? {
isIPad ? SideMenuDismissalAnimator() : nil
}
func presentationController(forPresented presented: UIViewController,
presenting: UIViewController?,
source: UIViewController) -> UIPresentationController? {
let presentationController = SearchOnMapModalPresentationController(presentedViewController: presented, presenting: presenting)
self.presentationController = presentationController
return presentationController
}
}

View file

@ -1,22 +0,0 @@
final class SideMenuDismissalAnimator: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return kDefaultAnimationDuration / 2
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let fromVC = transitionContext.viewController(forKey: .from) else { return }
let initialFrame = transitionContext.initialFrame(for: fromVC)
let targetFrame = initialFrame.offsetBy(dx: -initialFrame.width, dy: 0)
UIView.animate(withDuration: transitionDuration(using: transitionContext),
delay: .zero,
options: .curveEaseIn,
animations: {
fromVC.view.frame = targetFrame
},
completion: {
fromVC.view.removeFromSuperview()
transitionContext.completeTransition($0)
})
}
}

View file

@ -1,25 +0,0 @@
final class SideMenuPresentationAnimator: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return kDefaultAnimationDuration / 2
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let toVC = transitionContext.viewController(forKey: .to) else { return }
let containerView = transitionContext.containerView
let finalFrame = transitionContext.finalFrame(for: toVC)
let originFrame = finalFrame.offsetBy(dx: -finalFrame.width, dy: 0)
containerView.addSubview(toVC.view)
toVC.view.frame = originFrame
toVC.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
UIView.animate(withDuration: transitionDuration(using: transitionContext),
delay: .zero,
options: .curveEaseOut,
animations: {
toVC.view.frame = finalFrame
},
completion: {
transitionContext.completeTransition($0)
})
}
}

View file

@ -0,0 +1,9 @@
final class SearchOnMapAreaView: UIView {
override var sideButtonsAreaAffectDirections: MWMAvailableAreaAffectDirections {
alternative(iPhone: .bottom, iPad: [])
}
override var trafficButtonAreaAffectDirections: MWMAvailableAreaAffectDirections {
alternative(iPhone: .bottom, iPad: [])
}
}

View file

@ -1,5 +1,6 @@
protocol SearchOnMapHeaderViewDelegate: UISearchBarDelegate {
func cancelButtonDidTap()
func grabberDidTap()
}
final class SearchOnMapHeaderView: UIView {
@ -10,15 +11,19 @@ final class SearchOnMapHeaderView: UIView {
}
private enum Constants {
static let searchBarHeight: CGFloat = 36
static let searchBarInsets: UIEdgeInsets = UIEdgeInsets(top: 8, left: 10, bottom: 10, right: 0)
static let grabberHeight: CGFloat = 5
static let grabberWidth: CGFloat = 36
static let grabberTopMargin: CGFloat = 5
static let cancelButtonInsets: UIEdgeInsets = UIEdgeInsets(top: 0, left: 6, bottom: 0, right: 8)
static let cancelButtonInsets: UIEdgeInsets = UIEdgeInsets(top: 0, left: 6, bottom: 0, right: 16)
}
private let grabberView = UIView()
private let grabberTapHandlerView = UIView()
private let searchBar = UISearchBar()
private let cancelButton = UIButton()
private let cancelContainer = UIView()
override init(frame: CGRect) {
super.init(frame: frame)
@ -32,22 +37,29 @@ final class SearchOnMapHeaderView: UIView {
}
private func setupView() {
setStyle(.searchHeader)
layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
setStyle(.primaryBackground)
setupGrabberView()
setupGrabberTapHandlerView()
setupSearchBar()
setupCancelButton()
}
private func setupGrabberView() {
grabberView.setStyle(.background)
grabberView.layer.setCorner(radius: Constants.grabberHeight / 2)
grabberView.setStyle(.grabber)
iPadSpecific { [weak self] in
self?.grabberView.isHidden = true
}
}
private func setupGrabberTapHandlerView() {
grabberTapHandlerView.backgroundColor = .clear
iPhoneSpecific {
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(grabberDidTap))
grabberTapHandlerView.addGestureRecognizer(tapGesture)
}
}
private func setupSearchBar() {
searchBar.placeholder = L("search")
searchBar.showsCancelButton = false
@ -59,19 +71,24 @@ final class SearchOnMapHeaderView: UIView {
}
private func setupCancelButton() {
cancelButton.tintColor = .whitePrimaryText()
cancelButton.setStyle(.clearBackground)
cancelContainer.setStyle(.primaryBackground)
cancelButton.setStyle(.searchCancelButton)
cancelButton.setTitle(L("cancel"), for: .normal)
cancelButton.addTarget(self, action: #selector(cancelButtonTapped), for: .touchUpInside)
cancelButton.addTarget(self, action: #selector(cancelButtonDidTap), for: .touchUpInside)
}
private func layoutView() {
addSubview(grabberView)
addSubview(grabberTapHandlerView)
addSubview(searchBar)
addSubview(cancelButton)
addSubview(cancelContainer)
cancelContainer.addSubview(cancelButton)
grabberView.translatesAutoresizingMaskIntoConstraints = false
grabberTapHandlerView.translatesAutoresizingMaskIntoConstraints = false
grabberTapHandlerView.setContentHuggingPriority(.defaultLow, for: .vertical)
searchBar.translatesAutoresizingMaskIntoConstraints = false
cancelContainer.translatesAutoresizingMaskIntoConstraints = false
cancelButton.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
@ -80,18 +97,33 @@ final class SearchOnMapHeaderView: UIView {
grabberView.widthAnchor.constraint(equalToConstant: Constants.grabberWidth),
grabberView.heightAnchor.constraint(equalToConstant: Constants.grabberHeight),
searchBar.topAnchor.constraint(equalTo: grabberView.bottomAnchor),
searchBar.leadingAnchor.constraint(equalTo: leadingAnchor),
searchBar.trailingAnchor.constraint(equalTo: cancelButton.leadingAnchor, constant: -Constants.cancelButtonInsets.left),
grabberTapHandlerView.topAnchor.constraint(equalTo: grabberView.bottomAnchor),
grabberTapHandlerView.leadingAnchor.constraint(equalTo: leadingAnchor),
grabberTapHandlerView.trailingAnchor.constraint(equalTo: trailingAnchor),
grabberTapHandlerView.bottomAnchor.constraint(equalTo: searchBar.topAnchor),
cancelButton.centerYAnchor.constraint(equalTo: searchBar.centerYAnchor),
cancelButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -Constants.cancelButtonInsets.right),
searchBar.topAnchor.constraint(equalTo: grabberView.bottomAnchor, constant: Constants.searchBarInsets.top),
searchBar.leadingAnchor.constraint(equalTo: leadingAnchor, constant: Constants.searchBarInsets.left),
searchBar.trailingAnchor.constraint(equalTo: cancelContainer.leadingAnchor),
searchBar.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -Constants.searchBarInsets.bottom),
searchBar.heightAnchor.constraint(equalToConstant: Constants.searchBarHeight),
bottomAnchor.constraint(equalTo: searchBar.bottomAnchor)
cancelContainer.trailingAnchor.constraint(equalTo: trailingAnchor),
cancelContainer.topAnchor.constraint(equalTo: searchBar.topAnchor),
cancelContainer.bottomAnchor.constraint(equalTo: searchBar.bottomAnchor),
cancelButton.topAnchor.constraint(equalTo: cancelContainer.topAnchor),
cancelButton.leadingAnchor.constraint(equalTo: cancelContainer.leadingAnchor, constant: Constants.cancelButtonInsets.left),
cancelButton.trailingAnchor.constraint(equalTo: cancelContainer.trailingAnchor, constant: -Constants.cancelButtonInsets.right),
cancelButton.bottomAnchor.constraint(equalTo: cancelContainer.bottomAnchor),
])
}
@objc private func cancelButtonTapped() {
@objc private func grabberDidTap() {
delegate?.grabberDidTap()
}
@objc private func cancelButtonDidTap() {
delegate?.cancelButtonDidTap()
}

View file

@ -70,12 +70,12 @@ final class SearchOnMapInteractor: NSObject {
searchManager.saveQuery(searchText.text,
forInputLocale: searchText.locale)
showResultsOnMap = true
searchManager.showEverywhereSearchResultsOnMap()
return .showOnTheMap
}
private func processTypedText(_ searchText: SearchOnMap.SearchText) -> SearchOnMap.Response {
isUpdatesDisabled = false
showResultsOnMap = true
searchManager.searchQuery(searchText.text,
forInputLocale: searchText.locale,
withCategory: false)

View file

@ -19,16 +19,14 @@ protocol SearchOnMapManagerObserver: AnyObject {
@objcMembers
final class SearchOnMapManager: NSObject {
private let navigationController: UINavigationController
private weak var interactor: SearchOnMapInteractor?
private var interactor: SearchOnMapInteractor? { viewController?.interactor }
private let observers = ListenerContainer<SearchOnMapManagerObserver>()
// MARK: - Public properties
weak var viewController: UIViewController?
weak var viewController: SearchOnMapViewController?
var isSearching: Bool { viewController != nil }
init(navigationController: UINavigationController = MapViewController.shared()!.navigationController!) {
self.navigationController = navigationController
override init() {
super.init()
}
// MARK: - Public methods
@ -38,10 +36,9 @@ final class SearchOnMapManager: NSObject {
return
}
FrameworkHelper.deactivateMapSelection()
let viewController = buildViewController(isRouting: isRouting)
let viewController = SearchOnMapViewControllerBuilder.build(isRouting: isRouting,
didChangeState: notifyObservers)
self.viewController = viewController
self.interactor = viewController.interactor
navigationController.present(viewController, animated: true)
}
func hide() {
@ -77,20 +74,20 @@ final class SearchOnMapManager: NSObject {
observers.removeListener(observer)
}
// MARK: - Private methods
private func buildViewController(isRouting: Bool) -> SearchOnMapViewController {
let transitioningManager = SearchOnMapModalTransitionManager()
let presenter = SearchOnMapPresenter(transitionManager: transitioningManager,
isRouting: isRouting,
didChangeState: { [weak self] state in
guard let self else { return }
self.observers.forEach { observer in observer.searchManager(didChangeState: state) }
})
private func notifyObservers(_ state: SearchOnMapState) {
observers.forEach { observer in observer.searchManager(didChangeState: state) }
}
}
private struct SearchOnMapViewControllerBuilder {
static func build(isRouting: Bool, didChangeState: @escaping ((SearchOnMapState) -> Void)) -> SearchOnMapViewController {
let viewController = SearchOnMapViewController()
let presenter = SearchOnMapPresenter(isRouting: isRouting,
didChangeState: didChangeState)
let interactor = SearchOnMapInteractor(presenter: presenter)
let viewController = SearchOnMapViewController(interactor: interactor)
presenter.view = viewController
viewController.modalPresentationStyle = .custom
viewController.transitioningDelegate = transitioningManager
viewController.interactor = interactor
viewController.show()
return viewController
}
}

View file

@ -1,6 +1,6 @@
enum SearchOnMap {
struct ViewModel: Equatable {
enum ContentState: Equatable {
enum Content: Equatable {
case historyAndCategory
case results(SearchResults)
case noResults
@ -10,8 +10,8 @@ enum SearchOnMap {
var isTyping: Bool
var skipSuggestions: Bool
var searchingText: String?
var contentState: ContentState
var presentationStep: ModalScreenPresentationStep
var contentState: Content
var presentationStep: ModalPresentationStep
}
struct SearchResults: Equatable {
@ -54,7 +54,7 @@ enum SearchOnMap {
case clearButtonDidTap
case didSelectPlaceOnMap
case didDeselectPlaceOnMap
case didUpdatePresentationStep(ModalScreenPresentationStep)
case didUpdatePresentationStep(ModalPresentationStep)
}
enum Response: Equatable {
@ -67,7 +67,7 @@ enum SearchOnMap {
case clearSearch
case setSearchScreenHidden(Bool)
case setSearchScreenCompact
case updatePresentationStep(ModalScreenPresentationStep)
case updatePresentationStep(ModalPresentationStep)
case close
case none
}

View file

@ -3,7 +3,6 @@ final class SearchOnMapPresenter {
typealias ViewModel = SearchOnMap.ViewModel
weak var view: SearchOnMapView?
weak var presentationView: SearchOnMapModalPresentationView? { transitionManager.presentationController }
private var searchState: SearchOnMapState = .searching {
didSet {
@ -12,13 +11,11 @@ final class SearchOnMapPresenter {
}
}
private let transitionManager: SearchOnMapModalTransitionManager
private var viewModel: ViewModel = .initial
private var isRouting: Bool
private var didChangeState: ((SearchOnMapState) -> Void)?
init(transitionManager: SearchOnMapModalTransitionManager, isRouting: Bool, didChangeState: ((SearchOnMapState) -> Void)?) {
self.transitionManager = transitionManager
init(isRouting: Bool, didChangeState: ((SearchOnMapState) -> Void)?) {
self.isRouting = isRouting
self.didChangeState = didChangeState
didChangeState?(searchState)
@ -28,8 +25,8 @@ final class SearchOnMapPresenter {
guard response != .none else { return }
if response == .close {
view?.close()
searchState = .closed
presentationView?.close()
return
}
@ -43,7 +40,6 @@ final class SearchOnMapPresenter {
viewModel = newViewModel
view?.render(newViewModel)
searchState = newViewModel.presentationStep.searchState
presentationView?.setPresentationStep(newViewModel.presentationStep)
}
}
@ -97,6 +93,9 @@ final class SearchOnMapPresenter {
viewModel.isTyping = false
viewModel.presentationStep = .compact
case .updatePresentationStep(let step):
if step == .hidden {
viewModel.isTyping = false
}
viewModel.presentationStep = step
case .close, .none:
break
@ -105,7 +104,7 @@ final class SearchOnMapPresenter {
}
}
private extension ModalScreenPresentationStep {
private extension ModalPresentationStep {
var searchState: SearchOnMapState {
switch self {
case .fullScreen, .halfScreen, .compact:

View file

@ -1,52 +1,73 @@
protocol SearchOnMapView: AnyObject {
var scrollViewDelegate: SearchOnMapScrollViewDelegate? { get set }
func render(_ viewModel: SearchOnMap.ViewModel)
func show()
func close()
}
@objc
protocol SearchOnMapScrollViewDelegate: AnyObject {
func scrollViewDidScroll(_ scrollView: UIScrollView)
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>)
}
@objc
protocol ModallyPresentedViewController: AnyObject {
@objc func presentationFrameDidChange(_ frame: CGRect)
}
final class SearchOnMapViewController: UIViewController {
typealias ViewModel = SearchOnMap.ViewModel
typealias ContentState = SearchOnMap.ViewModel.ContentState
typealias Content = SearchOnMap.ViewModel.Content
typealias SearchText = SearchOnMap.SearchText
fileprivate enum Constants {
static let categoriesHeight: CGFloat = 100
static let filtersHeight: CGFloat = 50
static let keyboardAnimationDuration: CGFloat = 0.3
static let cancelButtonInsets: UIEdgeInsets = UIEdgeInsets(top: 0, left: 6, bottom: 0, right: 8)
static let estimatedRowHeight: CGFloat = 80
static let panGestureThreshold: CGFloat = 5
static let dimAlpha: CGFloat = 0.3
static let dimViewThreshold: CGFloat = 50
}
let interactor: SearchOnMapInteractor
weak var scrollViewDelegate: SearchOnMapScrollViewDelegate?
var interactor: SearchOnMapInteractor?
private var searchResults = SearchOnMap.SearchResults([])
// MARK: - UI Elements
@objc let availableAreaView = SearchOnMapAreaView()
private let contentView = UIView()
private let headerView = SearchOnMapHeaderView()
private let containerView = UIView()
private let searchResultsView = UIView()
private let resultsTableView = UITableView()
private let historyAndCategoryTabViewController = SearchTabViewController()
// TODO: implement filters
private let filtersCollectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
return UICollectionView(frame: .zero, collectionViewLayout: layout)
}()
private var searchingActivityView = PlaceholderView(hasActivityIndicator: true)
private var containerModalYTranslation: CGFloat = 0
private var searchNoResultsView = PlaceholderView(title: L("search_not_found"),
subtitle: L("search_not_found_query"))
private var dimView: UIView?
private var internalScrollViewContentOffset: CGFloat = .zero
private let presentationStepsController = ModalPresentationStepsController()
private var searchResults = SearchOnMap.SearchResults([])
// MARK: - Init
init(interactor: SearchOnMapInteractor) {
self.interactor = interactor
init() {
super.init(nibName: nil, bundle: nil)
configureModalPresentation()
}
private func configureModalPresentation() {
guard let mapViewController = MapViewController.shared() else {
fatalError("MapViewController is not available")
}
presentationStepsController.set(presentedView: availableAreaView, containerViewController: self)
presentationStepsController.didUpdateHandler = presentationUpdateHandler
mapViewController.searchContainer.addSubview(view)
mapViewController.addChild(self)
view.frame = mapViewController.searchContainer.bounds
view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
didMove(toParent: mapViewController)
let affectedAreaViews = [
mapViewController.sideButtonsArea,
mapViewController.trafficButtonArea,
]
affectedAreaViews.forEach { $0?.addAffectingView(availableAreaView) }
}
@available(*, unavailable)
@ -54,16 +75,16 @@ final class SearchOnMapViewController: UIViewController {
fatalError("init(coder:) has not been implemented")
}
deinit {
NotificationCenter.default.removeObserver(self)
// MARK: - Lifecycle
override func loadView() {
view = TouchTransparentView()
}
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
setupViews()
layoutViews()
interactor.handle(.openSearch)
presentationStepsController.setInitialState()
}
override func viewWillDisappear(_ animated: Bool) {
@ -71,30 +92,58 @@ final class SearchOnMapViewController: UIViewController {
headerView.setIsSearching(false)
}
// MARK: - Private methods
private func setupViews() {
view.setStyle(.clearBackground)
setupTapGestureRecognizer()
setupHeaderView()
setupContainerView()
setupResultsTableView()
setupHistoryAndCategoryTabView()
setupResultsTableView()
setupFiltersCollectionView()
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
updateFrameOfPresentedViewInContainerView()
updateDimView(for: availableAreaView.frame)
}
private func setupTapGestureRecognizer() {
override func viewWillTransition(to size: CGSize, with coordinator: any UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
if #available(iOS 14.0, *), ProcessInfo.processInfo.isiOSAppOnMac {
updateFrameOfPresentedViewInContainerView()
}
}
// MARK: - Private methods
private func setupViews() {
availableAreaView.setStyleAndApply(.modalSheetBackground)
contentView.setStyleAndApply(.modalSheetContent)
setupGestureRecognizers()
setupDimView()
setupHeaderView()
setupSearchResultsView()
setupResultsTableView()
setupHistoryAndCategoryTabView()
}
private func setupDimView() {
iPhoneSpecific {
dimView = UIView()
dimView?.backgroundColor = .black
dimView?.frame = view.bounds
}
}
private func setupGestureRecognizers() {
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTapOutside))
tapGesture.cancelsTouchesInView = false
view.addGestureRecognizer(tapGesture)
contentView.addGestureRecognizer(tapGesture)
iPhoneSpecific {
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
panGestureRecognizer.delegate = self
contentView.addGestureRecognizer(panGestureRecognizer)
}
}
private func setupHeaderView() {
headerView.delegate = self
}
private func setupContainerView() {
containerView.setStyle(.background)
private func setupSearchResultsView() {
searchResultsView.setStyle(.background)
}
private func setupResultsTableView() {
@ -112,91 +161,141 @@ final class SearchOnMapViewController: UIViewController {
historyAndCategoryTabViewController.delegate = self
}
// TODO: (KK) Implement filters collection viewe
private func setupFiltersCollectionView() {
filtersCollectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "FilterCell")
filtersCollectionView.dataSource = self
}
private func layoutViews() {
view.addSubview(headerView)
view.addSubview(containerView)
if let dimView {
view.addSubview(dimView)
dimView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
}
view.addSubview(availableAreaView)
availableAreaView.addSubview(contentView)
contentView.addSubview(headerView)
contentView.addSubview(searchResultsView)
contentView.translatesAutoresizingMaskIntoConstraints = false
headerView.translatesAutoresizingMaskIntoConstraints = false
containerView.translatesAutoresizingMaskIntoConstraints = false
searchResultsView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
headerView.topAnchor.constraint(equalTo: view.topAnchor),
headerView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
headerView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
contentView.topAnchor.constraint(equalTo: availableAreaView.topAnchor),
contentView.leadingAnchor.constraint(equalTo: availableAreaView.leadingAnchor),
contentView.trailingAnchor.constraint(equalTo: availableAreaView.trailingAnchor),
contentView.bottomAnchor.constraint(equalTo: availableAreaView.bottomAnchor),
containerView.topAnchor.constraint(equalTo: headerView.bottomAnchor),
containerView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
containerView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
containerView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
headerView.topAnchor.constraint(equalTo: contentView.topAnchor),
headerView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
headerView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
searchResultsView.topAnchor.constraint(equalTo: headerView.bottomAnchor),
searchResultsView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
searchResultsView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
searchResultsView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
])
layoutResultsView()
layoutHistoryAndCategoryTabView()
layoutSearchNoResultsView()
layoutSearchingView()
updateFrameOfPresentedViewInContainerView()
}
private func layoutResultsView() {
containerView.addSubview(resultsTableView)
searchResultsView.addSubview(resultsTableView)
resultsTableView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
resultsTableView.topAnchor.constraint(equalTo: containerView.topAnchor),
resultsTableView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
resultsTableView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
resultsTableView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor)
resultsTableView.topAnchor.constraint(equalTo: searchResultsView.topAnchor),
resultsTableView.leadingAnchor.constraint(equalTo: searchResultsView.leadingAnchor),
resultsTableView.trailingAnchor.constraint(equalTo: searchResultsView.trailingAnchor),
resultsTableView.bottomAnchor.constraint(equalTo: searchResultsView.bottomAnchor)
])
}
private func layoutHistoryAndCategoryTabView() {
containerView.addSubview(historyAndCategoryTabViewController.view)
searchResultsView.addSubview(historyAndCategoryTabViewController.view)
historyAndCategoryTabViewController.view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
historyAndCategoryTabViewController.view.topAnchor.constraint(equalTo: containerView.topAnchor),
historyAndCategoryTabViewController.view.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
historyAndCategoryTabViewController.view.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
historyAndCategoryTabViewController.view.bottomAnchor.constraint(equalTo: containerView.bottomAnchor)
historyAndCategoryTabViewController.view.topAnchor.constraint(equalTo: searchResultsView.topAnchor),
historyAndCategoryTabViewController.view.leadingAnchor.constraint(equalTo: searchResultsView.leadingAnchor),
historyAndCategoryTabViewController.view.trailingAnchor.constraint(equalTo: searchResultsView.trailingAnchor),
historyAndCategoryTabViewController.view.bottomAnchor.constraint(equalTo: searchResultsView.bottomAnchor)
])
}
private func layoutSearchNoResultsView() {
searchNoResultsView.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(searchNoResultsView)
searchResultsView.addSubview(searchNoResultsView)
NSLayoutConstraint.activate([
searchNoResultsView.topAnchor.constraint(equalTo: containerView.topAnchor),
searchNoResultsView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
searchNoResultsView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
searchNoResultsView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor)
searchNoResultsView.topAnchor.constraint(equalTo: searchResultsView.topAnchor),
searchNoResultsView.leadingAnchor.constraint(equalTo: searchResultsView.leadingAnchor),
searchNoResultsView.trailingAnchor.constraint(equalTo: searchResultsView.trailingAnchor),
searchNoResultsView.bottomAnchor.constraint(equalTo: searchResultsView.bottomAnchor)
])
}
private func layoutSearchingView() {
containerView.insertSubview(searchingActivityView, at: 0)
searchResultsView.insertSubview(searchingActivityView, at: 0)
searchingActivityView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
searchingActivityView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
searchingActivityView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
searchingActivityView.topAnchor.constraint(equalTo: containerView.topAnchor),
searchingActivityView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor)
searchingActivityView.leadingAnchor.constraint(equalTo: searchResultsView.leadingAnchor),
searchingActivityView.trailingAnchor.constraint(equalTo: searchResultsView.trailingAnchor),
searchingActivityView.topAnchor.constraint(equalTo: searchResultsView.topAnchor),
searchingActivityView.bottomAnchor.constraint(equalTo: searchResultsView.bottomAnchor)
])
}
// MARK: - Handle Button Actions
@objc private func handleTapOutside(_ gesture: UITapGestureRecognizer) {
// MARK: - Handle Presentation Steps
private func updateFrameOfPresentedViewInContainerView() {
presentationStepsController.updateMaxAvailableFrame()
availableAreaView.frame = presentationStepsController.currentFrame
view.layoutIfNeeded()
}
@objc
private func handleTapOutside(_ gesture: UITapGestureRecognizer) {
let location = gesture.location(in: view)
if resultsTableView.frame.contains(location) && searchResults.isEmpty {
headerView.setIsSearching(false)
}
}
// MARK: - Handle State Updates
private func setContent(_ content: ContentState) {
@objc
private func handlePan(_ gesture: UIPanGestureRecognizer) {
interactor?.handle(.didStartDraggingSearch)
presentationStepsController.handlePan(gesture)
}
private var presentationUpdateHandler: (ModalPresentationStepsController.StepUpdate) -> Void {
{ [weak self] update in
guard let self else { return }
switch update {
case .didClose:
self.interactor?.handle(.closeSearch)
case .didUpdateFrame(let frame):
self.presentationFrameDidChange(frame)
self.updateDimView(for: frame)
case .didUpdateStep(let step):
self.interactor?.handle(.didUpdatePresentationStep(step))
}
}
}
private func updateDimView(for frame: CGRect) {
guard let dimView else { return }
let currentTop = frame.origin.y
let maxTop = presentationStepsController.maxAvailableFrame.origin.y
let alpha = (1 - (currentTop - maxTop) / Constants.dimViewThreshold) * Constants.dimAlpha
let isCloseToTop = currentTop - maxTop < Constants.dimViewThreshold
let isPortrait = UIApplication.shared.statusBarOrientation.isPortrait
let shouldDim = isCloseToTop && isPortrait
UIView.animate(withDuration: kDefaultAnimationDuration / 2) {
dimView.alpha = shouldDim ? alpha : 0
dimView.isHidden = !shouldDim
}
}
// MARK: - Handle Content Updates
private func setContent(_ content: Content) {
switch content {
case .historyAndCategory:
historyAndCategoryTabViewController.reloadSearchHistory()
@ -214,7 +313,7 @@ final class SearchOnMapViewController: UIViewController {
showView(viewToShow(for: content))
}
private func viewToShow(for content: ContentState) -> UIView {
private func viewToShow(for content: Content) -> UIView {
switch content {
case .historyAndCategory:
return historyAndCategoryTabViewController.view
@ -232,24 +331,26 @@ final class SearchOnMapViewController: UIViewController {
historyAndCategoryTabViewController.view,
searchNoResultsView,
searchingActivityView].filter { $0 != view }
UIView.transition(with: containerView,
duration: kDefaultAnimationDuration / 2,
options: [.transitionCrossDissolve, .curveEaseInOut], animations: {
viewsToHide.forEach { viewToHide in
view.isHidden = false
view.alpha = 1
viewToHide.isHidden = true
viewToHide.alpha = 0
}
})
UIView.animate(withDuration: kDefaultAnimationDuration / 2,
delay: 0,
options: .curveEaseInOut,
animations: {
viewsToHide.forEach { $0.alpha = 0 }
view.alpha = 1
}) { _ in
viewsToHide.forEach { $0.isHidden = true }
view.isHidden = false
}
}
private func setIsSearching(_ isSearching: Bool) {
headerView.setIsSearching(isSearching)
}
private func replaceSearchText(with text: String) {
headerView.setSearchText(text)
private func setSearchText(_ text: String?) {
if let text {
headerView.setSearchText(text)
}
}
}
@ -258,20 +359,33 @@ extension SearchOnMapViewController: SearchOnMapView {
func render(_ viewModel: ViewModel) {
setContent(viewModel.contentState)
setIsSearching(viewModel.isTyping)
if let searchingText = viewModel.searchingText {
replaceSearchText(with: searchingText)
setSearchText(viewModel.searchingText)
presentationStepsController.setStep(viewModel.presentationStep)
}
func show() {
interactor?.handle(.openSearch)
}
func close() {
headerView.setIsSearching(false)
updateDimView(for: presentationStepsController.hiddenFrame)
willMove(toParent: nil)
presentationStepsController.close { [weak self] in
self?.view.removeFromSuperview()
self?.removeFromParent()
}
}
}
// MARK: - ModallyPresentedViewController
extension SearchOnMapViewController: ModallyPresentedViewController {
func translationYDidUpdate(_ translationY: CGFloat) {
self.containerModalYTranslation = translationY
func presentationFrameDidChange(_ frame: CGRect) {
let translationY = frame.origin.y
resultsTableView.contentInset.bottom = translationY
historyAndCategoryTabViewController.translationYDidUpdate(translationY)
searchNoResultsView.translationYDidUpdate(translationY)
searchingActivityView.translationYDidUpdate(translationY)
historyAndCategoryTabViewController.presentationFrameDidChange(frame)
searchNoResultsView.presentationFrameDidChange(frame)
searchingActivityView.presentationFrameDidChange(frame)
}
}
@ -303,60 +417,82 @@ extension SearchOnMapViewController: UITableViewDataSource {
extension SearchOnMapViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let result = searchResults[indexPath.row]
interactor.handle(.didSelectResult(result, withSearchText: headerView.searchText))
interactor?.handle(.didSelectResult(result, withSearchText: headerView.searchText))
tableView.deselectRow(at: indexPath, animated: true)
}
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
interactor.handle(.didStartDraggingSearch)
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
scrollViewDelegate?.scrollViewDidScroll(scrollView)
}
}
// MARK: - UICollectionViewDataSource
extension SearchOnMapViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// TODO: remove search from here
Int(Search.resultsCount())
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "FilterCell", for: indexPath)
return cell
interactor?.handle(.didStartDraggingSearch)
}
}
// MARK: - SearchOnMapHeaderViewDelegate
extension SearchOnMapViewController: SearchOnMapHeaderViewDelegate {
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
interactor.handle(.didStartTyping)
interactor?.handle(.didStartTyping)
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
guard !searchText.isEmpty else {
interactor.handle(.clearButtonDidTap)
interactor?.handle(.clearButtonDidTap)
return
}
interactor.handle(.didType(SearchText(searchText, locale: searchBar.textInputMode?.primaryLanguage)))
interactor?.handle(.didType(SearchText(searchText, locale: searchBar.textInputMode?.primaryLanguage)))
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
guard let searchText = searchBar.text, !searchText.isEmpty else { return }
interactor.handle(.searchButtonDidTap(SearchText(searchText, locale: searchBar.textInputMode?.primaryLanguage)))
interactor?.handle(.searchButtonDidTap(SearchText(searchText, locale: searchBar.textInputMode?.primaryLanguage)))
}
func cancelButtonDidTap() {
interactor.handle(.closeSearch)
interactor?.handle(.closeSearch)
}
func grabberDidTap() {
interactor?.handle(.didUpdatePresentationStep(.fullScreen))
}
}
// MARK: - SearchTabViewControllerDelegate
extension SearchOnMapViewController: SearchTabViewControllerDelegate {
func searchTabController(_ viewController: SearchTabViewController, didSearch text: String, withCategory: Bool) {
interactor.handle(.didSelectText(SearchText(text, locale: nil), isCategory: withCategory))
interactor?.handle(.didSelectText(SearchText(text, locale: nil), isCategory: withCategory))
}
}
// MARK: - UIGestureRecognizerDelegate
extension SearchOnMapViewController: UIGestureRecognizerDelegate {
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
true
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer is UIPanGestureRecognizer && otherGestureRecognizer is UIPanGestureRecognizer {
// threshold is used to soften transition from the internal scroll zero content offset
return internalScrollViewContentOffset < Constants.panGestureThreshold
}
return false
}
}
// MARK: - SearchOnMapScrollViewDelegate
extension SearchOnMapViewController: SearchOnMapScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let hasReachedTheTop = Int(availableAreaView.frame.origin.y) > Int(presentationStepsController.maxAvailableFrame.origin.y)
let hasZeroContentOffset = internalScrollViewContentOffset == 0
if hasReachedTheTop && hasZeroContentOffset {
// prevent the internal scroll view scrolling
scrollView.contentOffset.y = internalScrollViewContentOffset
return
}
internalScrollViewContentOffset = scrollView.contentOffset.y
}
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
// lock internal scroll view when the user fast scrolls screen to the top
if internalScrollViewContentOffset == 0 {
targetContentOffset.pointee = .zero
}
}
}

View file

@ -45,6 +45,10 @@ final class SearchCategoriesViewController: MWMTableViewController {
delegate?.scrollViewDidScroll(scrollView)
}
override func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
delegate?.scrollViewWillEndDragging(scrollView, withVelocity: velocity, targetContentOffset: targetContentOffset)
}
func category(at indexPath: IndexPath) -> String {
let index = indexPath.row
return categories[index]
@ -52,8 +56,8 @@ final class SearchCategoriesViewController: MWMTableViewController {
}
extension SearchCategoriesViewController: ModallyPresentedViewController {
func translationYDidUpdate(_ translationY: CGFloat) {
func presentationFrameDidChange(_ frame: CGRect) {
guard isViewLoaded else { return }
tableView.contentInset.bottom = translationY + view.safeAreaInsets.bottom
tableView.contentInset.bottom = frame.origin.y + view.safeAreaInsets.bottom
}
}

View file

@ -122,9 +122,9 @@ extension SearchHistoryViewController: UITableViewDelegate {
}
extension SearchHistoryViewController: ModallyPresentedViewController {
func translationYDidUpdate(_ translationY: CGFloat) {
func presentationFrameDidChange(_ frame: CGRect) {
guard isViewLoaded else { return }
tableView.contentInset.bottom = translationY
emptyHistoryView.translationYDidUpdate(translationY)
tableView.contentInset.bottom = frame.origin.y
emptyHistoryView.presentationFrameDidChange(frame)
}
}

View file

@ -54,8 +54,8 @@ final class SearchTabViewController: TabViewController {
}
extension SearchTabViewController: ModallyPresentedViewController {
func translationYDidUpdate(_ translationY: CGFloat) {
viewControllers.forEach { ($0 as? ModallyPresentedViewController)?.translationYDidUpdate(translationY) }
func presentationFrameDidChange(_ frame: CGRect) {
viewControllers.forEach { ($0 as? ModallyPresentedViewController)?.presentationFrameDidChange(frame) }
}
}
@ -63,6 +63,10 @@ extension SearchTabViewController: SearchOnMapScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
delegate?.scrollViewDidScroll(scrollView)
}
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
delegate?.scrollViewWillEndDragging(scrollView, withVelocity: velocity, targetContentOffset: targetContentOffset)
}
}
extension SearchTabViewController: SearchCategoriesViewControllerDelegate {

View file

@ -194,11 +194,15 @@
<outlet property="carplayPlaceholderView" destination="ixC-IZ-Pvs" id="3rZ-Kn-VBS"/>
<outlet property="controlsView" destination="rL1-9E-4b7" id="sfV-7X-WlR"/>
<outlet property="mapView" destination="aPn-pa-nCx" id="tCi-LW-1ll"/>
<outlet property="placePageArea" destination="awj-9E-eBS" id="nDP-as-zc2"/>
<outlet property="placePageAreaKeyboard" destination="PFs-sL-oVA" id="O3P-ia-ZlX"/>
<outlet property="sideButtonsArea" destination="xJx-UU-IdV" id="Qug-gg-Za8"/>
<outlet property="sideButtonsAreaBottom" destination="VfU-Zk-8IU" id="MvP-Ki-4wP"/>
<outlet property="sideButtonsAreaKeyboard" destination="SDX-4J-Jz5" id="kv9-zX-hbD"/>
<outlet property="trafficButtonArea" destination="QKu-4A-UgP" id="uJI-rT-zGt"/>
<outlet property="visibleAreaBottom" destination="OE7-Qb-J0v" id="isp-aT-LtA"/>
<outlet property="visibleAreaKeyboard" destination="YUs-MJ-9w8" id="UJP-KT-2uK"/>
<outlet property="widgetsArea" destination="NI8-tV-i2B" id="xU3-51-vHe"/>
<segue destination="Lfa-Zp-orR" kind="custom" identifier="Map2EditorSegue" customClass="MWMSegue" id="OEF-kR-jKi"/>
<segue destination="QlF-CJ-cEG" kind="custom" identifier="MapToCategorySelectorSegue" customClass="MWMSegue" id="4Cc-99-mlN"/>
<segue destination="5Wc-fy-NOW" kind="custom" identifier="Map2OsmLogin" customClass="MWMSegue" id="7YC-t5-0WN"/>