Compare commits

..

2 commits

Author SHA1 Message Date
4f6986aa56
[strings] Update translations
Co-authored-by: Adolfo Jayme Barrientos <fitojb@ubuntu.com>
Co-authored-by: Armando Bastoni <weblate@sabato.anonaddy.com>
Co-authored-by: Babos Gábor <gabor.babos@gmail.com>
Co-authored-by: DB <denisbatsa@gmail.com>
Co-authored-by: Fjuro <fjuro@users.noreply.hosted.weblate.org>
Co-authored-by: Gari Araolaza <garaolaza@codesyntax.com>
Co-authored-by: Ghost of Sparta <makesocialfoss32@keemail.me>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Jules <jules@j3s.fr>
Co-authored-by: Mads Rumle Nordstrøm <madsrumlenordstrom@icloud.com>
Co-authored-by: Ophiushi <41908476+ishi-sama@users.noreply.github.com>
Co-authored-by: Software In Interlingua <softinterlingua@gmail.com>
Co-authored-by: Sveinn í Felli <sv1@fellsnet.is>
Co-authored-by: Weblate Translation Memory <noreply-mt-weblate-translation-memory@weblate.org>
Co-authored-by: ZeljkoBG <zeljko.velickovic@gmail.com>
Co-authored-by: map-per <map-per@gmx.de>
Co-authored-by: soshial <soshial@gmail.com>
Co-authored-by: summoner001 <summoner@vivaldi.net>
Signed-off-by: Adolfo Jayme Barrientos <fitojb@ubuntu.com>
Signed-off-by: Armando Bastoni <weblate@sabato.anonaddy.com>
Signed-off-by: Babos Gábor <gabor.babos@gmail.com>
Signed-off-by: DB <denisbatsa@gmail.com>
Signed-off-by: Fjuro <fjuro@users.noreply.hosted.weblate.org>
Signed-off-by: Gari Araolaza <garaolaza@codesyntax.com>
Signed-off-by: Ghost of Sparta <makesocialfoss32@keemail.me>
Signed-off-by: Jules <jules@j3s.fr>
Signed-off-by: Mads Rumle Nordstrøm <madsrumlenordstrom@icloud.com>
Signed-off-by: Ophiushi <41908476+ishi-sama@users.noreply.github.com>
Signed-off-by: Roman Tsisyk <roman@tsisyk.com>
Signed-off-by: Software In Interlingua <softinterlingua@gmail.com>
Signed-off-by: Sveinn í Felli <sv1@fellsnet.is>
Signed-off-by: Weblate <hosted@weblate.org>
Signed-off-by: Weblate Translation Memory <noreply-mt-weblate-translation-memory@weblate.org>
Signed-off-by: ZeljkoBG <zeljko.velickovic@gmail.com>
Signed-off-by: map-per <map-per@gmx.de>
Signed-off-by: soshial <soshial@gmail.com>
Signed-off-by: summoner001 <summoner@vivaldi.net>
Translate-URL: https://hosted.weblate.org/projects/organicmaps/android-typestrings/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/android-typestrings/ca/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/android-typestrings/cs/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/android-typestrings/da/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/android-typestrings/de/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/android-typestrings/es/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/android-typestrings/eu/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/android-typestrings/fr/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/android-typestrings/hu/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/android-typestrings/is/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/android/ast/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/android/ca/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/android/de/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/android/es/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/android/fr/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/android/hu/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/android/is/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/android/it/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/android/lv/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/android/ru/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/android/sr/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/countries/ast/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/countries/ia/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/countries/lt/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/ios-plist/ca/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/ios-plist/hu/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/ios-plist/ia/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/ios-plurals/af/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/ios-plurals/ar/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/ios-plurals/ca/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/ios-plurals/da/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/ios-plurals/el/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/ios-plurals/fr/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/ios-plurals/hi/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/ios-plurals/ia/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/ios-plurals/ja/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/ios-plurals/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/ios-plurals/pt/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/ios-plurals/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/ios-plurals/ro/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/ios-plurals/sv/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/ios-plurals/tr/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/ios-plurals/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/ios-typestrings/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/ios-typestrings/ca/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/ios-typestrings/cs/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/ios-typestrings/da/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/ios-typestrings/de/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/ios-typestrings/es/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/ios-typestrings/eu/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/ios-typestrings/fr/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/ios-typestrings/hu/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/ios-typestrings/ia/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/ios-typestrings/is/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/ios/ast/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/ios/ca/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/ios/de/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/ios/es/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/ios/fr/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/ios/hu/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/ios/ia/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/ios/it/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/ios/lv/
Translate-URL: https://hosted.weblate.org/projects/organicmaps/ios/sr/
2025-03-29 03:40:49 +01:00
80d772c2e2 [docs] Some readme fixes 2025-03-27 17:24:40 +00:00
90 changed files with 2685 additions and 1282 deletions

View file

@ -167,9 +167,8 @@ 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 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.
- **Star us on Forgejo**.
- Report bugs or issues to [the issue tracker](https://git.omaps.dev/organicmaps/organicmaps/issues).
- 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) для обратной связи и помощи.
@ -179,7 +178,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),
[Twitter](https://twitter.com/OrganicMapsApp),
[X (Twitter)](https://x.com/OrganicMapsApp),
[Instagram](https://instagram.com/organicmaps.app/).
- Güncellemelerimizi [Instagram](https://instagram.com/organicmapstr/) üzerinden takip edin.

View file

@ -144,4 +144,8 @@
<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 s\'ha pogut determinar la vostra geolocalització</string>
<string name="unknown_current_position">Encara no sha determinat la vostra ubicació</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 teu historial d\'edició</string>
<string name="editor_osm_history">El vostre historial dedicions</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 l\'Organic Maps</string>
<string name="traffic_update_app">Actualitzeu lOrganic 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">Continua al telèfon</string>
<string name="car_continue_on_the_phone">Passa 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,4 +903,7 @@
<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">Equipament Social</string>
<string name="type.amenity.social_facility">Servei 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,4 +850,66 @@
<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">Anhalten ohne zu speichern</string>
<string name="stop_without_saving">Ohne Speichern beenden</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">Amerikanisch</string>
<string name="type.cuisine.american">US-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">Mi historial de edición</string>
<string name="editor_osm_history">Su historial de ediciones</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">Continuar en el teléfono</string>
<string name="car_continue_on_the_phone">Pasar al 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">Psicoterapia</string>
<string name="type.healthcare.psychotherapist">Psicoterapeuta</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">Acantilado</string>
<string name="type.natural.earth_bank">Talud de tierra</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">Cámara de bloqueo</string>
<string name="type.natural.water.lock">Esclusa</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,7 +117,6 @@
<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>
@ -165,7 +164,6 @@
<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>
@ -370,10 +368,9 @@
<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>
@ -400,7 +397,6 @@
<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>
@ -543,7 +539,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>
@ -621,9 +617,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). 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">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_link">Pour plus dinformations, veuillez consulter ce guide.</string>
<string name="transliteration_title">Translittération en latin</string>
<string name="transliteration_title">Translittératisé en alphabet 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>
@ -638,7 +634,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>
@ -756,7 +752,6 @@
<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>
@ -849,7 +844,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 d\'une autorisation de localisation pour fonctionner efficacement</string>
<string name="aa_request_permission_activity_text">Organic Maps dans Android Auto a besoin des permissions 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 -->
@ -900,4 +895,20 @@
<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,4 +1307,7 @@
<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

@ -0,0 +1,348 @@
<?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ētas kartes</string>
<string name="downloader_no_downloaded_maps_title">Ierīcē nav lejupielādētu karšu</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

@ -79,4 +79,3 @@
#import "MWMSearchSuggestionCell.h"
#import "MWMSearch.h"
#import "SearchResult.h"
#import "RoutePreview.h"

View file

@ -1,18 +1,12 @@
extension CALayer {
func setCornerRadius(_ cornerRadius: CornerRadius,
maskedCorners: CACornerMask? = nil) {
self.cornerRadius = cornerRadius.value
if let maskedCorners {
self.maskedCorners = maskedCorners
func setCorner(radius: CGFloat,
corners: CACornerMask? = nil) {
cornerRadius = radius
if let corners {
maskedCorners = corners
}
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.setCornerRadius(.custom(layer.cornerRadius))
snapshot.layer.setCorner(radius: 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.setCornerRadius(.modalSheet)
presentedViewController.view.layer.setCorner(radius: 12)
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.setCornerRadius(.buttonDefault)
presentedViewController.view.layer.setCorner(radius: 8)
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.setCornerRadius(.buttonDefault)
presentedViewController.view.layer.setCorner(radius: 8)
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.setCornerRadius(.buttonDefault)
blurView.layer.setCorner(radius: 8)
blurView.clipsToBounds = true
blurView.alpha = 0

View file

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

View file

@ -58,6 +58,8 @@ 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,5 +8,6 @@
- (void)setHidden:(BOOL)hidden animated:(BOOL)animated;
- (void)updateAvailableArea:(CGRect)frame;
- (CGRect)getAvailableArea;
@end

View file

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

View file

@ -195,8 +195,6 @@ 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

@ -7,8 +7,6 @@ typedef NS_ENUM(NSUInteger, MWMNavigationDashboardState) {
MWMNavigationDashboardStateNavigation
};
@class NavigationDashboardView;
@interface MWMNavigationDashboardManager : NSObject
+ (nonnull MWMNavigationDashboardManager *)sharedManager;

View file

@ -1,24 +1,40 @@
#import "MWMNavigationDashboardManager.h"
#import "MWMMapViewControlsManager.h"
#import "MWMNavigationInfoView.h"
#import "MWMRoutePreview.h"
#import "MWMSearch.h"
#import "MapViewController.h"
#import "NavigationDashboardView.h"
#import "SwiftBridge.h"
namespace {
NSString *const kRoutePreviewIPhoneXibName = @"MWMiPhoneRoutePreview";
NSString *const kNavigationInfoViewXibName = @"MWMNavigationInfoView";
NSString *const kNavigationControlViewXibName = @"NavigationControlView";
} // namespace
@interface MWMMapViewControlsManager ()
@property(nonatomic) MWMNavigationDashboardManager * navigationManager;
@property(nonatomic) MWMNavigationDashboardManager *navigationManager;
@end
@interface MWMNavigationDashboardManager () <SearchOnMapManagerObserver, MWMRoutePreviewDelegate>
@property(copy, nonatomic) NSDictionary * etaAttributes;
@property(copy, nonatomic) NSDictionary * etaSecondaryAttributes;
@property(copy, nonatomic) NSString * errorMessage;
@property(copy, nonatomic) MWMNavigationDashboardEntity * entity;
@property(nonatomic, readonly) NavigationDashboardView * _Nonnull navigationDashboardView;
@property(weak, nonatomic) UIView * ownerView;
@property(copy, nonatomic) NSDictionary *etaAttributes;
@property(copy, nonatomic) NSDictionary *etaSecondaryAttributes;
@property(copy, nonatomic) NSString *errorMessage;
@property(nonatomic) IBOutlet MWMBaseRoutePreviewStatus *baseRoutePreviewStatus;
@property(nonatomic) IBOutlet MWMNavigationControlView *navigationControlView;
@property(nonatomic) IBOutlet MWMNavigationInfoView *navigationInfoView;
@property(nonatomic) IBOutlet MWMRoutePreview *routePreview;
@property(nonatomic) IBOutlet MWMTransportRoutePreviewStatus *transportRoutePreviewStatus;
@property(nonatomic) IBOutletCollection(MWMRouteStartButton) NSArray *goButtons;
@property(nonatomic) MWMNavigationDashboardEntity *entity;
@property(nonatomic) MWMRouteManagerTransitioningManager *routeManagerTransitioningManager;
@property(weak, nonatomic) IBOutlet UIButton *showRouteManagerButton;
@property(weak, nonatomic) IBOutlet UIView *goButtonsContainer;
@property(weak, nonatomic) UIView *ownerView;
@end
@ -32,8 +48,6 @@
self = [super init];
if (self) {
_ownerView = view;
_navigationDashboardView = [[NavigationDashboardView alloc] initWithOwnerView:view];
_navigationDashboardView.delegate = self;
}
return self;
}
@ -42,51 +56,215 @@
return [[MapViewController sharedController] searchManager];
}
- (void)setRouteBuilderProgress:(CGFloat)progress {
[self.navigationDashboardView setRouteBuilderProgress:[MWMRouter type] progress:progress / 100.];
- (void)loadPreviewWithStatusBoxes {
[NSBundle.mainBundle loadNibNamed:kRoutePreviewIPhoneXibName owner:self options:nil];
auto ownerView = self.ownerView;
_baseRoutePreviewStatus.ownerView = ownerView;
_transportRoutePreviewStatus.ownerView = ownerView;
}
#pragma mark - On route updates
#pragma mark - MWMRoutePreview
- (void)setRouteBuilderProgress:(CGFloat)progress {
[self.routePreview router:[MWMRouter type] setProgress:progress / 100.];
}
#pragma mark - MWMNavigationGo
- (IBAction)routingStartTouchUpInside {
[MWMRouter startRouting];
}
- (void)updateGoButtonTitle {
NSString *title = L(@"p2p_start");
for (MWMRouteStartButton *button in self.goButtons)
[button setTitle:title forState:UIControlStateNormal];
}
- (void)onNavigationInfoUpdated {
auto entity = self.entity;
if (!entity.isValid)
return;
[self.navigationDashboardView onNavigationInfoUpdated:entity];
[_navigationInfoView onNavigationInfoUpdated:entity];
bool const isPublicTransport = [MWMRouter type] == MWMRouterTypePublicTransport;
bool const isRuler = [MWMRouter type] == MWMRouterTypeRuler;
if (isPublicTransport || isRuler)
[_transportRoutePreviewStatus onNavigationInfoUpdated:entity prependDistance:isRuler];
else
[_baseRoutePreviewStatus onNavigationInfoUpdated:entity];
[_navigationControlView onNavigationInfoUpdated:entity];
}
#pragma mark - On route updates
- (void)onRoutePrepare {
self.state = MWMNavigationDashboardStatePrepare;
[self.navigationDashboardView setDrivingOptionState:MWMDrivingOptionsStateNone];
self.routePreview.drivingOptionsState = MWMDrivingOptionsStateNone;
}
- (void)onRoutePlanning {
self.state = MWMNavigationDashboardStatePlanning;
[self.navigationDashboardView setDrivingOptionState:MWMDrivingOptionsStateNone];
self.routePreview.drivingOptionsState = MWMDrivingOptionsStateNone;
}
- (void)onRouteError:(NSString *)error {
self.errorMessage = error;
self.state = MWMNavigationDashboardStateError;
[self.navigationDashboardView setDrivingOptionState:[MWMRouter hasActiveDrivingOptions] ? MWMDrivingOptionsStateChange : MWMDrivingOptionsStateNone];
self.routePreview.drivingOptionsState =
[MWMRouter hasActiveDrivingOptions] ? MWMDrivingOptionsStateChange : MWMDrivingOptionsStateNone;
}
- (void)onRouteReady:(BOOL)hasWarnings {
if (self.state != MWMNavigationDashboardStateNavigation)
self.state = MWMNavigationDashboardStateReady;
if ([MWMRouter hasActiveDrivingOptions]) {
[self.navigationDashboardView setDrivingOptionState:MWMDrivingOptionsStateChange];
self.routePreview.drivingOptionsState = MWMDrivingOptionsStateChange;
} else {
[self.navigationDashboardView setDrivingOptionState:hasWarnings ? MWMDrivingOptionsStateDefine : MWMDrivingOptionsStateNone];
self.routePreview.drivingOptionsState = hasWarnings ? MWMDrivingOptionsStateDefine : MWMDrivingOptionsStateNone;
}
}
- (void)onRoutePointsUpdated {
if (self.state == MWMNavigationDashboardStateHidden)
self.state = MWMNavigationDashboardStatePrepare;
[self.navigationDashboardView onRoutePointsUpdated];
[self.navigationInfoView updateToastView];
}
#pragma mark - State changes
- (void)stateHidden {
self.routePreview = nil;
self.navigationInfoView.state = MWMNavigationInfoViewStateHidden;
self.navigationInfoView = nil;
_navigationControlView.isVisible = NO;
_navigationControlView = nil;
[self.baseRoutePreviewStatus hide];
[_transportRoutePreviewStatus hide];
_transportRoutePreviewStatus = nil;
}
- (void)statePrepare {
self.navigationInfoView.state = MWMNavigationInfoViewStatePrepare;
auto routePreview = self.routePreview;
[routePreview addToView:self.ownerView];
[routePreview statePrepare];
[routePreview selectRouter:[MWMRouter type]];
[self updateGoButtonTitle];
[self.baseRoutePreviewStatus hide];
[_transportRoutePreviewStatus hide];
for (MWMRouteStartButton *button in self.goButtons)
[button statePrepare];
}
- (void)statePlanning {
[self statePrepare];
[self.routePreview router:[MWMRouter type] setState:MWMCircularProgressStateSpinner];
[self setRouteBuilderProgress:0.];
}
- (void)stateError {
if (_state == MWMNavigationDashboardStateReady)
return;
NSAssert(_state == MWMNavigationDashboardStatePlanning, @"Invalid state change (error)");
auto routePreview = self.routePreview;
[routePreview router:[MWMRouter type] setState:MWMCircularProgressStateFailed];
[self updateGoButtonTitle];
[self.baseRoutePreviewStatus showErrorWithMessage:self.errorMessage];
for (MWMRouteStartButton *button in self.goButtons)
[button stateError];
}
- (void)stateReady {
// TODO: Here assert sometimes fires with _state = MWMNavigationDashboardStateReady, if app was stopped while navigating and then restarted.
// Also in ruler mode when new point is added by single tap on the map state MWMNavigationDashboardStatePlanning is skipped and we get _state = MWMNavigationDashboardStateReady.
NSAssert(_state == MWMNavigationDashboardStatePlanning || _state == MWMNavigationDashboardStateReady, @"Invalid state change (ready)");
[self setRouteBuilderProgress:100.];
[self updateGoButtonTitle];
bool const isTransport = ([MWMRouter type] == MWMRouterTypePublicTransport);
bool const isRuler = ([MWMRouter type] == MWMRouterTypeRuler);
if (isTransport || isRuler)
[self.transportRoutePreviewStatus showReady];
else
[self.baseRoutePreviewStatus showReady];
self.goButtonsContainer.hidden = isTransport || isRuler;
for (MWMRouteStartButton *button in self.goButtons)
{
if (isRuler)
[button stateHidden];
else
[button stateReady];
}
}
- (void)onRouteStart {
self.state = MWMNavigationDashboardStateNavigation;
}
- (void)onRouteStop {
self.state = MWMNavigationDashboardStateHidden;
}
- (void)stateNavigation {
self.routePreview = nil;
self.navigationInfoView.state = MWMNavigationInfoViewStateNavigation;
self.navigationControlView.isVisible = YES;
[self.baseRoutePreviewStatus hide];
[_transportRoutePreviewStatus hide];
_transportRoutePreviewStatus = nil;
[self onNavigationInfoUpdated];
}
#pragma mark - MWMRoutePreviewStatus
- (IBAction)showRouteManager {
auto routeManagerViewModel = [[MWMRouteManagerViewModel alloc] init];
auto routeManager = [[MWMRouteManagerViewController alloc] initWithViewModel:routeManagerViewModel];
routeManager.modalPresentationStyle = UIModalPresentationCustom;
self.routeManagerTransitioningManager = [[MWMRouteManagerTransitioningManager alloc] init];
routeManager.transitioningDelegate = self.routeManagerTransitioningManager;
[[MapViewController sharedController] presentViewController:routeManager animated:YES completion:nil];
}
#pragma mark - MWMNavigationControlView
- (IBAction)ttsButtonAction {
BOOL const isEnabled = [MWMTextToSpeech tts].active;
[MWMTextToSpeech tts].active = !isEnabled;
}
- (IBAction)settingsButtonAction {
[[MapViewController sharedController] openSettings];
}
- (IBAction)stopRoutingButtonAction {
[MWMSearch clear];
[MWMRouter stopRouting];
[self.searchManager close];
}
#pragma mark - SearchOnMapManagerObserver
- (void)searchManagerWithDidChangeState:(SearchOnMapState)state {
switch (state) {
case SearchOnMapStateClosed:
[self.navigationInfoView setSearchState:NavigationSearchState::MinimizedNormal animated:YES];
break;
case SearchOnMapStateHidden:
case SearchOnMapStateSearching:
[self setMapSearch];
}
}
#pragma mark - Available area
+ (void)updateNavigationInfoAvailableArea:(CGRect)frame {
[[self sharedManager] updateNavigationInfoAvailableArea:frame];
}
- (void)updateNavigationInfoAvailableArea:(CGRect)frame {
_navigationInfoView.availableArea = frame;
}
#pragma mark - Properties
- (void)setState:(MWMNavigationDashboardState)state {
@ -121,91 +299,64 @@
BottomTabBarViewController.controller.isHidden = state != MWMNavigationDashboardStateHidden;
}
@synthesize routePreview = _routePreview;
- (MWMRoutePreview *)routePreview {
if (!_routePreview)
[self loadPreviewWithStatusBoxes];
return _routePreview;
}
- (void)setRoutePreview:(MWMRoutePreview *)routePreview {
if (routePreview == _routePreview)
return;
[_routePreview remove];
_routePreview = routePreview;
_routePreview.delegate = self;
}
- (MWMBaseRoutePreviewStatus *)baseRoutePreviewStatus {
if (!_baseRoutePreviewStatus)
[self loadPreviewWithStatusBoxes];
return _baseRoutePreviewStatus;
}
- (MWMTransportRoutePreviewStatus *)transportRoutePreviewStatus {
if (!_transportRoutePreviewStatus)
[self loadPreviewWithStatusBoxes];
return _transportRoutePreviewStatus;
}
- (MWMNavigationInfoView *)navigationInfoView {
if (!_navigationInfoView) {
[NSBundle.mainBundle loadNibNamed:kNavigationInfoViewXibName owner:self options:nil];
_navigationInfoView.state = MWMNavigationInfoViewStateHidden;
_navigationInfoView.ownerView = self.ownerView;
}
return _navigationInfoView;
}
- (MWMNavigationControlView *)navigationControlView {
if (!_navigationControlView) {
[NSBundle.mainBundle loadNibNamed:kNavigationControlViewXibName owner:self options:nil];
_navigationControlView.ownerView = self.ownerView;
}
return _navigationControlView;
}
- (MWMNavigationDashboardEntity *)entity {
if (!_entity)
_entity = [[MWMNavigationDashboardEntity alloc] init];
return _entity;
}
#pragma mark - State changes
- (void)stateHidden {
[self.navigationDashboardView setHidden];
}
- (void)statePrepare {
[self.navigationDashboardView statePrepare];
}
- (void)statePlanning {
[self statePrepare];
[self.navigationDashboardView statePlanning];
[self setRouteBuilderProgress:0.];
}
- (void)stateError {
if (_state == MWMNavigationDashboardStateReady)
return;
NSAssert(_state == MWMNavigationDashboardStatePlanning, @"Invalid state change (error)");
[self.navigationDashboardView stateError:self.errorMessage];
}
- (void)stateReady {
// TODO: Here assert sometimes fires with _state = MWMNavigationDashboardStateReady, if app was stopped while navigating and then restarted.
// Also in ruler mode when new point is added by single tap on the map state MWMNavigationDashboardStatePlanning is skipped and we get _state = MWMNavigationDashboardStateReady.
NSAssert(_state == MWMNavigationDashboardStatePlanning || _state == MWMNavigationDashboardStateReady, @"Invalid state change (ready)");
[self setRouteBuilderProgress:100.];
[self.navigationDashboardView stateReady];
}
- (void)onRouteStart {
self.state = MWMNavigationDashboardStateNavigation;
}
- (void)onRouteStop {
self.state = MWMNavigationDashboardStateHidden;
}
- (void)stateNavigation {
[self.navigationDashboardView stateNavigation];
[self onNavigationInfoUpdated];
- (void)setMapSearch {
[_navigationInfoView setMapSearch];
}
#pragma mark - MWMRoutePreviewDelegate
- (void)routingStartButtonDidTap {
[MWMRouter startRouting];
}
- (void)routePreviewDidPressDrivingOptions:(MWMRoutePreview *)routePreview {
[[MapViewController sharedController] openDrivingOptions];
}
- (void)ttsButtonDidTap {
BOOL const isEnabled = [MWMTextToSpeech tts].active;
[MWMTextToSpeech tts].active = !isEnabled;
}
- (void)settingsButtonDidTap {
[[MapViewController sharedController] openSettings];
}
- (void)stopRoutingButtonDidTap {
[MWMSearch clear];
[MWMRouter stopRouting];
[self.searchManager close];
}
#pragma mark - SearchOnMapManagerObserver
- (void)searchManagerWithDidChangeState:(SearchOnMapState)state {
[self.navigationDashboardView searchManagerWithDidChangeState:state];
}
#pragma mark - Available area
+ (void)updateNavigationInfoAvailableArea:(CGRect)frame {
[[self sharedManager].navigationDashboardView updateNavigationInfoAvailableArea:frame];
}
@end

View file

@ -1,8 +1,31 @@
#import "RoutePreviewView.h"
#import "MWMCircularProgressState.h"
#import "MWMRouterType.h"
@interface MWMRoutePreview : UIView <RoutePreviewView>
typedef NS_ENUM(NSInteger, MWMDrivingOptionsState) {
MWMDrivingOptionsStateNone,
MWMDrivingOptionsStateDefine,
MWMDrivingOptionsStateChange
};
@class MWMRoutePreview;
@protocol MWMRoutePreviewDelegate
- (void)routePreviewDidPressDrivingOptions:(MWMRoutePreview *)routePreview;
@end
@interface MWMRoutePreview : UIView
@property(nonatomic) MWMDrivingOptionsState drivingOptionsState;
@property(weak, nonatomic) id<MWMRoutePreviewDelegate> delegate;
- (void)addToView:(UIView *)superview;
- (void)remove;
- (void)statePrepare;
- (void)selectRouter:(MWMRouterType)routerType;
- (void)router:(MWMRouterType)routerType setState:(MWMCircularProgressState)state;
- (void)router:(MWMRouterType)routerType setProgress:(CGFloat)progress;
@end

View file

@ -1,33 +0,0 @@
#import "RoutePreviewView.h"
#import "SwiftBridge.h"
NS_ASSUME_NONNULL_BEGIN
@class MWMNavigationDashboardEntity;
@interface NavigationDashboardView : NSObject
@property(weak, nonatomic) id<MWMRoutePreviewDelegate> delegate;
- (instancetype)initWithOwnerView:(UIView *)ownerView;
- (void)loadPreview;
- (void)onNavigationInfoUpdated:(MWMNavigationDashboardEntity *)entity;
- (void)setDrivingOptionState:(MWMDrivingOptionsState)state;
- (void)searchManagerWithDidChangeState:(SearchOnMapState)state;
- (void)updateNavigationInfoAvailableArea:(CGRect)frame;
- (void)setRouteBuilderProgress:(MWMRouterType)router progress:(CGFloat)progress;
- (void)setHidden;
- (void)statePrepare;
- (void)statePlanning;
- (void)stateError:(NSString *_Nonnull)errorMessage;
- (void)stateReady;
- (void)onRouteStart;
- (void)onRouteStop;
- (void)onRoutePointsUpdated;
- (void)stateNavigation;
@end
NS_ASSUME_NONNULL_END

View file

@ -1,245 +0,0 @@
#import "NavigationDashboardView.h"
#import "MWMNavigationDashboardManager.h"
#import "MWMMapViewControlsManager.h"
#import "MWMNavigationInfoView.h"
#import "MWMRoutePreview.h"
#import "MWMSearch.h"
#import "MapViewController.h"
#import "SwiftBridge.h"
NSString *kRoutePreviewIPhoneXibName = @"MWMiPhoneRoutePreview";
NSString *kNavigationInfoViewXibName = @"MWMNavigationInfoView";
NSString *kNavigationControlViewXibName = @"NavigationControlView";
@interface NavigationDashboardView()
@property(nonatomic) IBOutlet MWMBaseRoutePreviewStatus *baseRoutePreviewStatus;
@property(nonatomic) IBOutlet MWMNavigationControlView *navigationControlView;
@property(nonatomic) IBOutlet MWMNavigationInfoView *navigationInfoView;
@property(nonatomic) IBOutlet MWMRoutePreview *routePreview;
@property(nonatomic) IBOutlet MWMTransportRoutePreviewStatus *transportRoutePreviewStatus;
@property(nonatomic) IBOutletCollection(MWMRouteStartButton) NSArray *goButtons;
@property(nonatomic) MWMRouteManagerTransitioningManager *routeManagerTransitioningManager;
@property(weak, nonatomic) IBOutlet UIButton *showRouteManagerButton;
@property(weak, nonatomic) IBOutlet UIView *goButtonsContainer;
@property(weak, nonatomic) UIView *ownerView;
@end
@implementation NavigationDashboardView
- (instancetype)initWithOwnerView:(UIView *)ownerView {
self = [super init];
if (self) {
self.ownerView = ownerView;
[self loadPreview];
}
return self;
}
- (void)loadPreview {
[NSBundle.mainBundle loadNibNamed:kRoutePreviewIPhoneXibName owner:self options:nil];
auto const ownerView = self.ownerView;
self.baseRoutePreviewStatus.ownerView = ownerView;
self.transportRoutePreviewStatus.ownerView = ownerView;
}
- (void)setRouteBuilderProgress:(MWMRouterType)router progress:(CGFloat)progress {
[self.routePreview router:router setProgress:progress];
}
- (void)onNavigationInfoUpdated:(MWMNavigationDashboardEntity *)entity {
[_navigationInfoView onNavigationInfoUpdated:entity];
bool const isPublicTransport = ([MWMRouter type] == MWMRouterTypePublicTransport);
bool const isRuler = ([MWMRouter type] == MWMRouterTypeRuler);
if (isPublicTransport || isRuler)
[_transportRoutePreviewStatus onNavigationInfoUpdated:entity prependDistance:isRuler];
else
[_baseRoutePreviewStatus onNavigationInfoUpdated:entity];
[_navigationControlView onNavigationInfoUpdated:entity];
}
- (void)setDrivingOptionState:(MWMDrivingOptionsState)state {
self.routePreview.drivingOptionsState = state;
}
- (void)onRoutePointsUpdated {
[self.navigationInfoView updateToastView];
}
- (void)updateGoButtonTitle {
NSString *title = L(@"p2p_start");
for (MWMRouteStartButton *button in self.goButtons)
[button setTitle:title forState:UIControlStateNormal];
}
#pragma mark - State changes
- (void)setHidden {
self.routePreview = nil;
self.navigationInfoView.state = MWMNavigationInfoViewStateHidden;
self.navigationInfoView = nil;
_navigationControlView.isVisible = NO;
_navigationControlView = nil;
[self.baseRoutePreviewStatus hide];
[_transportRoutePreviewStatus hide];
_transportRoutePreviewStatus = nil;
}
- (void)statePrepare {
self.navigationInfoView.state = MWMNavigationInfoViewStatePrepare;
[self.routePreview addToView:self.ownerView];
[self.routePreview statePrepare];
[self.routePreview selectRouter:[MWMRouter type]];
[self updateGoButtonTitle];
[self.baseRoutePreviewStatus hide];
[_transportRoutePreviewStatus hide];
for (MWMRouteStartButton *button in self.goButtons)
[button statePrepare];
}
- (void)statePlanning {
// [self statePrepare];
[self.routePreview router:[MWMRouter type] setState:MWMCircularProgressStateSpinner];
}
- (void)stateError:(NSString *_Nonnull)errorMessage {
[self.routePreview router:[MWMRouter type] setState:MWMCircularProgressStateFailed];
[self updateGoButtonTitle];
[self.baseRoutePreviewStatus showErrorWithMessage:errorMessage];
for (MWMRouteStartButton *button in self.goButtons)
[button stateError];
}
- (void)stateReady {
[self updateGoButtonTitle];
bool const isTransport = ([MWMRouter type] == MWMRouterTypePublicTransport);
bool const isRuler = ([MWMRouter type] == MWMRouterTypeRuler);
if (isTransport || isRuler)
[self.transportRoutePreviewStatus showReady];
else
[self.baseRoutePreviewStatus showReady];
self.goButtonsContainer.hidden = isTransport || isRuler;
for (MWMRouteStartButton *button in self.goButtons)
{
if (isRuler)
[button stateHidden];
else
[button stateReady];
}
}
- (void)onRouteStart {
}
- (void)onRouteStop {
}
- (void)stateNavigation {
self.routePreview = nil;
self.navigationInfoView.state = MWMNavigationInfoViewStateNavigation;
self.navigationControlView.isVisible = YES;
[self.baseRoutePreviewStatus hide];
[_transportRoutePreviewStatus hide];
_transportRoutePreviewStatus = nil;
}
#pragma mark - MWMRoutePreviewStatus
- (IBAction)showRouteManager {
MWMRouteManagerViewModel * routeManagerViewModel = [[MWMRouteManagerViewModel alloc] init];
MWMRouteManagerViewController * routeManager = [[MWMRouteManagerViewController alloc] initWithViewModel:routeManagerViewModel];
routeManager.modalPresentationStyle = UIModalPresentationCustom;
self.routeManagerTransitioningManager = [[MWMRouteManagerTransitioningManager alloc] init];
routeManager.transitioningDelegate = self.routeManagerTransitioningManager;
[[MapViewController sharedController] presentViewController:routeManager animated:YES completion:nil];
}
#pragma mark - Properties
@synthesize routePreview = _routePreview;
- (MWMRoutePreview *)routePreview {
if (!_routePreview)
[self loadPreview];
return _routePreview;
}
- (void)setRoutePreview:(MWMRoutePreview *)routePreview {
if (routePreview == _routePreview)
return;
[_routePreview remove];
_routePreview = routePreview;
_routePreview.delegate = self.delegate;
}
- (MWMBaseRoutePreviewStatus *)baseRoutePreviewStatus {
if (!_baseRoutePreviewStatus)
[self loadPreview];
return _baseRoutePreviewStatus;
}
- (MWMTransportRoutePreviewStatus *)transportRoutePreviewStatus {
if (!_transportRoutePreviewStatus)
[self loadPreview];
return _transportRoutePreviewStatus;
}
- (MWMNavigationInfoView *)navigationInfoView {
if (!_navigationInfoView) {
[NSBundle.mainBundle loadNibNamed:kNavigationInfoViewXibName owner:self options:nil];
_navigationInfoView.state = MWMNavigationInfoViewStateHidden;
_navigationInfoView.ownerView = self.ownerView;
}
return _navigationInfoView;
}
- (MWMNavigationControlView *)navigationControlView {
if (!_navigationControlView) {
[NSBundle.mainBundle loadNibNamed:kNavigationControlViewXibName owner:self options:nil];
_navigationControlView.ownerView = self.ownerView;
}
return _navigationControlView;
}
#pragma mark - Button tap Actions
- (IBAction)ttsButtonAction {
[self.delegate ttsButtonDidTap];
}
- (IBAction)settingsButtonAction {
[self.delegate settingsButtonDidTap];
}
- (IBAction)stopRoutingButtonAction {
[self.delegate stopRoutingButtonDidTap];
}
- (IBAction)routingStartTouchUpInside {
[self.delegate routingStartButtonDidTap];
}
#pragma mark - SearchOnMapManagerObserver
- (void)searchManagerWithDidChangeState:(SearchOnMapState)state {
switch (state) {
case SearchOnMapStateClosed:
[self.navigationInfoView setSearchState:NavigationSearchState::MinimizedNormal animated:YES];
break;
case SearchOnMapStateHidden:
case SearchOnMapStateSearching:
[self.navigationInfoView setMapSearch];
}
}
#pragma mark - Available area
- (void)updateNavigationInfoAvailableArea:(CGRect)frame {
_navigationInfoView.availableArea = frame;
}
@end

View file

@ -1,5 +1,6 @@
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 }
@ -9,7 +10,7 @@ final class TransportRuler: TransportTransitCell {
@IBOutlet private weak var background: UIView! {
didSet {
background.layer.setCornerRadius(.buttonSmall)
background.layer.setCorner(radius: Config.backgroundCornerRadius)
background.backgroundColor = Config.backgroundColor
}
}

View file

@ -1,19 +1,20 @@
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.setCornerRadius(.buttonSmall)
background.layer.setCorner(radius: Config.backgroundCornerRadius)
background.backgroundColor = Config.backgroundColor
}
}
@IBOutlet private weak var image: UIImageView! {
didSet {
image.image = UIImage(resource: .icWalk)
image.image = #imageLiteral(resourceName: "ic_walk")
image.tintColor = Config.imageColor
image.contentMode = .scaleAspectFit
}

View file

@ -1,5 +1,6 @@
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
@ -7,7 +8,7 @@ final class TransportTransitTrain: TransportTransitCell {
@IBOutlet private weak var background: UIView! {
didSet {
background.layer.setCornerRadius(.buttonSmall)
background.layer.setCorner(radius: Config.backgroundCornerRadius)
}
}

View file

@ -1,39 +0,0 @@
#import "MWMCircularProgressState.h"
#import "MWMRouterType.h"
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSInteger, MWMDrivingOptionsState) {
MWMDrivingOptionsStateNone,
MWMDrivingOptionsStateDefine,
MWMDrivingOptionsStateChange
};
@protocol MWMRoutePreviewDelegate;
@protocol RoutePreviewView <NSObject>
@property(nonatomic) MWMDrivingOptionsState drivingOptionsState;
@property(weak, nonatomic) id<MWMRoutePreviewDelegate> delegate;
- (void)addToView:(UIView *)superview;
- (void)remove;
- (void)statePrepare;
- (void)selectRouter:(MWMRouterType)routerType;
- (void)router:(MWMRouterType)routerType setState:(MWMCircularProgressState)state;
- (void)router:(MWMRouterType)routerType setProgress:(CGFloat)progress;
@end
@protocol MWMRoutePreviewDelegate <NSObject>
- (void)routePreviewDidPressDrivingOptions:(id<RoutePreviewView>)routePreview;
- (void)ttsButtonDidTap;
- (void)settingsButtonDidTap;
- (void)stopRoutingButtonDidTap;
- (void)routingStartButtonDidTap;
@end
NS_ASSUME_NONNULL_END

View file

@ -7,11 +7,6 @@
@class MWMMapDownloadDialog;
@class BookmarksCoordinator;
@class SearchOnMapManager;
@class SideButtonsArea;
@class WidgetsArea;
@class TrafficButtonArea;
@class PlacePageArea;
@protocol MWMLocationModeListener;
@interface MapViewController : MWMViewController
@ -57,11 +52,5 @@
@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,48 +148,34 @@ 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];
if (IPAD)
self.placePageLeadingConstraint.priority = UILayoutPriorityDefaultLow;
self.placePageLeadingConstraint.active = YES;
self.placePageWidthConstraint = [self.placePageContainer.widthAnchor constraintEqualToConstant:kPlacePageCompactWidth];
self.placePageWidthConstraint = [self.placePageContainer.widthAnchor constraintEqualToConstant:0];
self.placePageTrailingConstraint = [self.placePageContainer.trailingAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.trailingAnchor];
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.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;
}
[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.searchViewAvailableArea != nil) {
NSLayoutConstraint * leadingToSearchConstraint = [self.placePageContainer.leadingAnchor constraintEqualToAnchor:self.searchViewAvailableArea.trailingAnchor constant:kPlacePageLeadingOffset];
if (IPAD && self.searchViewContainer != nil) {
NSLayoutConstraint * leadingToSearchConstraint = [self.placePageContainer.leadingAnchor constraintEqualToAnchor:self.searchViewContainer.trailingAnchor constant:kPlacePageLeadingOffset];
leadingToSearchConstraint.priority = UILayoutPriorityDefaultHigh;
leadingToSearchConstraint.active = isLimitedWidth;
}
@ -273,6 +259,9 @@ 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;
@ -284,10 +273,6 @@ 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;
@ -387,7 +372,6 @@ NSString *const kSettingsSegue = @"Map2Settings";
- (void)viewDidLoad {
[super viewDidLoad];
[self setupPlacePageContainer];
[self setupSearchContainer];
if (@available(iOS 14.0, *))
[self setupTrackPadGestureRecognizers];
@ -742,12 +726,12 @@ NSString *const kSettingsSegue = @"Map2Settings";
- (SearchOnMapManager *)searchManager {
if (!_searchManager)
_searchManager = [[SearchOnMapManager alloc] init];
_searchManager = [[SearchOnMapManager alloc] initWithNavigationController:self.navigationController];
return _searchManager;
}
- (UIView * _Nullable)searchViewAvailableArea {
return self.searchManager.viewController.availableAreaView;
- (UIView * _Nullable)searchViewContainer {
return self.searchManager.viewController.view;
}
- (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,7 +156,6 @@ using Observers = NSHashTable<Observer>;
+ (void)showResultAtIndex:(NSUInteger)index {
auto const & result = [MWMSearch manager]->m_everywhereResults[index];
GetFramework().StopLocationFollow();
GetFramework().SelectSearchResult(result, true);
}
@ -169,13 +168,8 @@ using Observers = NSHashTable<Observer>;
+ (void)showEverywhereSearchResultsOnMap {
MWMSearch * manager = [MWMSearch manager];
if (![MWMRouter isRoutingActive]) {
auto const & results = manager->m_everywhereResults;
if (results.GetCount() == 1)
[self showResultAtIndex:0];
else
GetFramework().ShowSearchResults(manager->m_everywhereResults);
}
if (![MWMRouter isRoutingActive])
GetFramework().ShowSearchResults(manager->m_everywhereResults);
}
+ (void)showViewportSearchResultsOnMap {

View file

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

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

View file

@ -31,7 +31,7 @@ class UISearchBarRenderer: UIViewRenderer {
} else {
control.setSearchFieldBackgroundImage(UIImage(), for: .normal)
}
searchTextField.layer.setCornerRadius(.buttonDefault)
searchTextField.layer.setCorner(radius: 8)
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.setCornerRadius(cornerRadius)
control.layer.setCorner(radius: cornerRadius)
control.clipsToBounds = true
}
control.borderStyle = .none

View file

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

View file

@ -1,27 +1,107 @@
enum SearchStyleSheet: String, CaseIterable {
case searchCancelButton
case searchHeader
case searchInstallButton = "SearchInstallButton"
case searchBanner = "SearchBanner"
case searchClosedBackground = "SearchClosedBackground"
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 = .custom(10)
s.cornerRadius = 10
s.backgroundColor = colors.linkBlueHighlighted
}
case .searchSideAvailableMarker:
return .add { s in
s.backgroundColor = colors.ratingGreen
}
case .searchCancelButton:
case .searchBarView:
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.fontColorHighlighted = colors.whitePrimaryTextHighlighted
s.font = fonts.regular17
s.backgroundColor = .clear
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
}
}
}

View file

@ -82,3 +82,5 @@
"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 s'ha pogut determinar la vostra geolocalització";
"unknown_current_position" = "Encara no sha determinat la vostra ubicació";
/* 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" = "Continua al telèfon";
"car_continue_on_the_phone" = "Passa 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" = "Condoms Dispenser";
"type.amenity.vending_machine.condoms" = "Dispensador de preservatius";
"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" = "National Border";
"type.boundary.administrative.2" = "Frontera nacional";
/* 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" = "Balkan";
"type.cuisine.balkan" = "Balcànica";
"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" = "Filipino";
"type.cuisine.filipino" = "Filipina";
"type.cuisine.fine_dining" = "Fine Dining";
"type.cuisine.fish" = "Fish";
"type.cuisine.fish_and_chips" = "Fish and Chips";
"type.cuisine.french" = "French";
"type.cuisine.french" = "Francesa";
"type.cuisine.friture" = "Friture";
"type.cuisine.georgian" = "Georgian";
"type.cuisine.german" = "German";
"type.cuisine.greek" = "Greek";
"type.cuisine.grill" = "Grill";
"type.cuisine.georgian" = "Georgiana";
"type.cuisine.german" = "Alemanya";
"type.cuisine.greek" = "Grega";
"type.cuisine.grill" = "Graellada";
"type.cuisine.heuriger" = "Heuriger";
"type.cuisine.hotdog" = "Hotdog";
"type.cuisine.hungarian" = "Hungarian";
"type.cuisine.ice_cream" = "Ice Cream";
"type.cuisine.indian" = "Indian";
"type.cuisine.hungarian" = "Hongaresa";
"type.cuisine.ice_cream" = "Gelat";
"type.cuisine.indian" = "Índia";
"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" = "Japanese";
"type.cuisine.japanese" = "Japonesa";
"type.cuisine.kebab" = "Kebab";
"type.cuisine.korean" = "Korean";
"type.cuisine.lao" = "Lao";
"type.cuisine.lebanese" = "Lebanese";
"type.cuisine.korean" = "Coreana";
"type.cuisine.lao" = "Laosiana";
"type.cuisine.lebanese" = "Libanesa";
"type.cuisine.local" = "Local";
"type.cuisine.malagasy" = "Malagasy";
"type.cuisine.malaysian" = "Malaysian";
"type.cuisine.mediterranean" = "Mediterranean";
"type.cuisine.mexican" = "Mexicana";
"type.cuisine.moroccan" = "Moroccan";
"type.cuisine.moroccan" = "Marroquina";
"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" = "Polish";
"type.cuisine.portuguese" = "Portuguese";
"type.cuisine.polish" = "Polonesa";
"type.cuisine.portuguese" = "Portuguesa";
"type.cuisine.ramen" = "Ramen";
"type.cuisine.regional" = "Regional";
"type.cuisine.russian" = "Russian";
"type.cuisine.russian" = "Rusa";
"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" = "Turkish";
"type.cuisine.turkish" = "Turca";
"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" = "Blood Donation Center";
"type.healthcare.blood_donation" = "Centre de donació de sang";
"type.healthcare.optometrist" = "Optometrist";
"type.healthcare.podiatrist" = "Podiatrist";
"type.healthcare.psychotherapist" = "Psychotherapist";
"type.healthcare.podiatrist" = "Podòleg";
"type.healthcare.psychotherapist" = "Psicoterapeuta";
"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" = "Pedestrian Street";
"type.highway.pedestrian.area" = "Pedestrian Street";
"type.highway.pedestrian" = "Carrer de vianants";
"type.highway.pedestrian.area" = "Àrea de vianants";
/* These translations are used for all type.highway.*.bridge. */
"type.highway.pedestrian.bridge" = "Pedestrian Bridge";
"type.highway.pedestrian.bridge" = "Pont de vianants";
/* These translations are used for all type.highway.*.tunnel. */
"type.highway.pedestrian.tunnel" = "Pedestrian Tunnel";
"type.highway.pedestrian.tunnel" = "Túnel de vianants";
"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" = "Field";
"type.landuse.field" = "Camp";
"type.landuse.flowerbed" = "Llit de flors";
"type.landuse.forest" = "Bosc";
"type.landuse.forest.coniferous" = "Coniferous Forest";
"type.landuse.forest.deciduous" = "Deciduous Forest";
"type.landuse.forest.coniferous" = "Bosc de coníferes";
"type.landuse.forest.deciduous" = "Bosc de caducifòlies";
"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" = "Meadow";
"type.landuse.meadow" = "Prat de dall";
"type.landuse.military" = "Military Area";
"type.landuse.orchard" = "Orchard";
"type.landuse.orchard" = "Verger";
"type.landuse.quarry" = "Pedrera";
"type.landuse.railway" = "Railway Premises";
"type.landuse.recreation_ground" = "Recreation Ground";
"type.landuse.reservoir" = "Reservoir";
"type.landuse.reservoir" = "Embassament";
"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" = "Industrial Works";
"type.man_made.works" = "Fàbrica";
"type.mapswithme" = "mapswithme";
"type.mapswithme.grid" = "mapswithme-grid";
"type.military" = "Military";
"type.military.bunker" = "Bunker";
"type.military.bunker" = "Búnquer";
"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" = "Cliff";
"type.natural.earth_bank" = "Earth Bank";
"type.natural.cliff" = "Penya-segat";
"type.natural.earth_bank" = "Talús de terra";
"type.man_made.embankment" = "Terraplè";
"type.natural.coastline" = "Coastline";
"type.natural.coastline" = "Costa";
"type.natural.desert" = "Desert";
"type.natural.geyser" = "Guèiser";
"type.natural.glacier" = "Glacier";
"type.natural.grassland" = "Grassland";
"type.natural.heath" = "Heath";
"type.natural.grassland" = "Herbassar";
"type.natural.heath" = "Landa";
"type.natural.hot_spring" = "Font termal";
"type.natural.water.lake" = "Lake";
"type.natural.water.lock" = "Lock Chamber";
"type.natural.water.pond" = "Pond";
"type.natural.water.reservoir" = "Reservoir";
"type.natural.water.lock" = "Resclosa";
"type.natural.water.pond" = "Estany";
"type.natural.water.reservoir" = "Embassament";
"type.natural.water.basin" = "Basin";
"type.natural.water.river" = "Riu";
"type.natural.land" = "Land";
"type.natural.meadow" = "Meadow";
"type.natural.orchard" = "Orchard";
"type.natural.land" = "Terreny";
"type.natural.meadow" = "Prat de dall";
"type.natural.orchard" = "Verger";
"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" = "Strait";
"type.natural.tree_row" = "Tree Row";
"type.natural.vineyard" = "Vineyard";
"type.natural.strait" = "Estret";
"type.natural.tree_row" = "Fila darbres";
"type.natural.vineyard" = "Vinyar";
"type.natural.volcano" = "Volcà";
"type.natural.water" = "Aigua";
"type.natural.wetland" = "Wetland";
"type.natural.wetland.bog" = "Bog";
"type.natural.wetland.marsh" = "Marsh";
"type.noexit" = "Dead End";
"type.natural.wetland" = "Zona humida";
"type.natural.wetland.bog" = "Torbera";
"type.natural.wetland.marsh" = "Aiguamoll";
"type.noexit" = "Atzucac";
"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" = "Substation";
"type.power.substation" = "Subestació";
/* 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" = "Baseball";
"type.sport.basketball" = "Basketball";
"type.sport.beachvolleyball" = "Beach Volleyball";
"type.sport.baseball" = "Beisbol";
"type.sport.basketball" = "Basquetbol";
"type.sport.beachvolleyball" = "Voleibol de platja";
"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" = "Gymnastics";
"type.sport.gymnastics" = "Gimnàstica";
"type.sport.handball" = "Handball";
"type.sport.multi" = "Various Sports";
"type.sport.multi" = "Diversos esports";
/* 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" = "Equipament Social";
"type.amenity.social_facility" = "Servei 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" = "Anhalten ohne zu speichern";
"stop_without_saving" = "Ohne Speichern beenden";
/* 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" = "Amerikanisch";
"type.cuisine.american" = "US-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" = "Continuar en el teléfono";
"car_continue_on_the_phone" = "Pasar al 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" = "Psicoterapia";
"type.healthcare.psychotherapist" = "Psicoterapeuta";
"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" = "Acantilado";
"type.natural.earth_bank" = "Talud de tierra";
"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" = "Cámara de bloqueo";
"type.natural.water.lock" = "Esclusa";
"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ération en latin";
"transliteration_title" = "Translittératisé en alphabet latin";
/* Subway exits for public transport marks on the map */
"core_exit" = "Sortie";

View file

@ -12,10 +12,14 @@
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>zero</key>
<string>marque-page</string>
<key>one</key>
<string>%d signet</string>
<string>marque-page</string>
<key>many</key>
<string>marque-pages</string>
<key>other</key>
<string>%d signets</string>
<string>marque-pages</string>
</dict>
</dict>
<key>bookmarks_detect_message</key>
@ -28,8 +32,12 @@
<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>
@ -44,8 +52,14 @@
<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 trace</string>
<string>%d voies</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" = "Power Pole";
"type.power.pole" = "Pylône électrique";
/* A single pole supporting various public utilities, such as lighting or telephony. */
"type.man_made.utility_pole" = "Utility Pole";
"type.man_made.utility_pole" = "Pylônes (Télécommunication, lampadaire)";
"type.psurface" = "psurface";
"type.psurface.paved_bad" = "psurface-paved_bad";
"type.psurface.paved_good" = "psurface-paved_good";

View file

@ -0,0 +1 @@

View file

@ -0,0 +1 @@

View file

@ -0,0 +1,18 @@
<?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

@ -0,0 +1,107 @@
"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ētas kartes";
"downloader_no_downloaded_maps_title" = "Ierīcē nav lejupielādētu karšu";
"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,17 +491,20 @@
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 */; };
ED5E02522D92E33300A5CC7B /* NavigationDashboardView.mm in Sources */ = {isa = PBXBuildFile; fileRef = ED5E02512D92E33300A5CC7B /* NavigationDashboardView.mm */; };
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 /* ModalPresentationStep.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED70D57C2D539A2500738C1E /* ModalPresentationStep.swift */; };
ED70D58D2D539A2500738C1E /* ModalScreenPresentationStep.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED70D57C2D539A2500738C1E /* ModalScreenPresentationStep.swift */; };
ED70D58E2D539A2500738C1E /* SideMenuPresentationAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED70D5802D539A2500738C1E /* SideMenuPresentationAnimator.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 */; };
@ -523,9 +526,6 @@
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 */; };
@ -1390,10 +1390,6 @@
A630D205207CAA3A00976DEA /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "zh-Hant"; path = "zh-Hant.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
A630D206207CAA5800976DEA /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
AA1C7E3D269A2DD600BAADF2 /* EditTrackViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditTrackViewController.swift; sourceTree = "<group>"; };
AC4209FF2D79BCEC00A64AA9 /* af */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = af; path = af.lproj/InfoPlist.strings; sourceTree = "<group>"; };
AC420A002D79BCED00A64AA9 /* af */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = af; path = af.lproj/Localizable.strings; sourceTree = "<group>"; };
AC420A012D79BCED00A64AA9 /* af */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = af; path = af.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
AC420A022D79BCEE00A64AA9 /* af */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = af; path = af.lproj/LocalizableTypes.strings; sourceTree = "<group>"; };
AC420A082D79BDDA00A64AA9 /* lt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = lt; path = lt.lproj/InfoPlist.strings; sourceTree = "<group>"; };
AC420A092D79BDDA00A64AA9 /* lt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = lt; path = lt.lproj/Localizable.strings; sourceTree = "<group>"; };
AC420A0A2D79BDDB00A64AA9 /* lt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = lt; path = lt.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
@ -1402,6 +1398,10 @@
AC420A142D79C2EC00A64AA9 /* mt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = mt; path = mt.lproj/Localizable.strings; sourceTree = "<group>"; };
AC420A152D79C2EC00A64AA9 /* mt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = mt; path = mt.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
AC420A162D79C2ED00A64AA9 /* mt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = mt; path = mt.lproj/LocalizableTypes.strings; sourceTree = "<group>"; };
AC4209FF2D79BCEC00A64AA9 /* af */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = af; path = af.lproj/InfoPlist.strings; sourceTree = "<group>"; };
AC420A002D79BCED00A64AA9 /* af */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = af; path = af.lproj/Localizable.strings; sourceTree = "<group>"; };
AC420A012D79BCED00A64AA9 /* af */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = af; path = af.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
AC420A022D79BCEE00A64AA9 /* af */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = af; path = af.lproj/LocalizableTypes.strings; sourceTree = "<group>"; };
AC79C8912A65AB9500594C24 /* UIColor+hexString.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+hexString.swift"; sourceTree = "<group>"; };
B33D21AE20DAF9F000BAD749 /* Toast.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Toast.swift; sourceTree = "<group>"; };
B3E3B4FC20D463B700DA8C13 /* BMCCategoriesHeader.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = BMCCategoriesHeader.xib; sourceTree = "<group>"; };
@ -1467,16 +1467,17 @@
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>"; };
ED5E024F2D92AC9300A5CC7B /* RoutePreviewView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RoutePreviewView.h; sourceTree = "<group>"; };
ED5E02502D92E33300A5CC7B /* NavigationDashboardView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NavigationDashboardView.h; sourceTree = "<group>"; };
ED5E02512D92E33300A5CC7B /* NavigationDashboardView.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = NavigationDashboardView.mm; 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>"; };
ED70D57C2D539A2500738C1E /* ModalPresentationStep.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModalPresentationStep.swift; 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>"; };
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>"; };
@ -1547,9 +1548,6 @@
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>"; };
@ -2554,12 +2552,9 @@
34AB65FE1FC5AA320078E451 /* MWMiPhoneRoutePreview.xib */,
34AB65FF1FC5AA320078E451 /* MWMRoutePreview.h */,
34AB65FD1FC5AA320078E451 /* MWMRoutePreview.mm */,
ED5E024F2D92AC9300A5CC7B /* RoutePreviewView.h */,
34AB65D71FC5AA320078E451 /* RouteManager */,
34AB65EC1FC5AA320078E451 /* RoutePreviewStatus */,
34AB65D51FC5AA320078E451 /* RouteStartButton.swift */,
ED5E02502D92E33300A5CC7B /* NavigationDashboardView.h */,
ED5E02512D92E33300A5CC7B /* NavigationDashboardView.mm */,
);
path = RoutePreview;
sourceTree = "<group>";
@ -3013,7 +3008,6 @@
993DF0CE23F6BDB000AC231A /* MainTheme.swift */,
ED914AB72D351DF000973C45 /* StyleApplicable.swift */,
EDCA7CDE2D317DF9003366CE /* StyleSheet.swift */,
EDB71D8B2D8474A0004A6A7F /* CornerRadius.swift */,
993DF10123F6BDB100AC231A /* GlobalStyleSheet.swift */,
99A906F223FA95AB0005872B /* PlacePageStyleSheet.swift */,
99F8B4C523B644A6009FF0B4 /* MapStyleSheet.swift */,
@ -3297,9 +3291,12 @@
ED70D5812D539A2500738C1E /* Presentation */ = {
isa = PBXGroup;
children = (
ED5E02132D8B17B600A5CC7B /* ModalPresentationStepsController.swift */,
EDB71DFF2D8B0338004A6A7F /* ModalPresentationAnimator.swift */,
ED70D57C2D539A2500738C1E /* ModalPresentationStep.swift */,
ED70D57B2D539A2500738C1E /* MapPassthroughView.swift */,
ED70D57C2D539A2500738C1E /* ModalScreenPresentationStep.swift */,
ED70D57D2D539A2500738C1E /* SearchOnMapModalPresentationController.swift */,
ED70D57E2D539A2500738C1E /* SearchOnMapModalTransitionManager.swift */,
ED70D57F2D539A2500738C1E /* SideMenuDismissalAnimator.swift */,
ED70D5802D539A2500738C1E /* SideMenuPresentationAnimator.swift */,
);
path = Presentation;
sourceTree = "<group>";
@ -3315,7 +3312,6 @@
ED70D5862D539A2500738C1E /* SearchOnMapPresenter.swift */,
ED70D5872D539A2500738C1E /* SearchOnMapViewController.swift */,
ED5BAF4A2D688F5A0088D7B1 /* SearchOnMapHeaderView.swift */,
EDB71E032D8B0943004A6A7F /* SearchOnMapAreaView.swift */,
);
path = SearchOnMap;
sourceTree = "<group>";
@ -4589,7 +4585,6 @@
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 */,
@ -4639,7 +4634,6 @@
993DF12223F6BDB100AC231A /* UINavigationItemRenderer.swift in Sources */,
993DF12B23F6BDB100AC231A /* StyleManager.swift in Sources */,
ED43B8BD2C12063500D07BAA /* DocumentPicker.swift in Sources */,
ED5E02522D92E33300A5CC7B /* NavigationDashboardView.mm in Sources */,
470E1674252AD7F2002D201A /* BookmarksListInfoViewController.swift in Sources */,
47B9065521C7FA400079C85E /* NSString+MD5.m in Sources */,
ED5BAF4B2D688F5B0088D7B1 /* SearchOnMapHeaderView.swift in Sources */,
@ -4670,7 +4664,6 @@
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 */,
@ -4736,7 +4729,6 @@
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 */,
@ -4825,11 +4817,16 @@
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 /* ModalPresentationStep.swift in Sources */,
ED70D58D2D539A2500738C1E /* ModalScreenPresentationStep.swift in Sources */,
ED70D58E2D539A2500738C1E /* SideMenuPresentationAnimator.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 */,
@ -4849,7 +4846,6 @@
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,7 +12,8 @@ final class SearchOnMapTests: XCTestCase {
override func setUp() {
super.setUp()
searchManager = SearchManagerMock.self
presenter = SearchOnMapPresenter(isRouting: false,
presenter = SearchOnMapPresenter(transitionManager: SearchOnMapModalTransitionManager(),
isRouting: false,
didChangeState: { [weak self] in self?.currentState = $0 })
interactor = SearchOnMapInteractor(presenter: presenter, searchManager: searchManager)
view = SearchOnMapViewMock()
@ -130,13 +131,8 @@ final class SearchOnMapTests: XCTestCase {
searchManager.results = results
interactor.handle(.didSelectResult(results[0], withSearchText: searchText))
if isIPad {
XCTAssertEqual(currentState, .searching)
XCTAssertEqual(view.viewModel.presentationStep, .fullScreen)
} else {
XCTAssertEqual(currentState, .hidden)
XCTAssertEqual(view.viewModel.presentationStep, .hidden)
}
XCTAssertEqual(currentState, .hidden)
XCTAssertEqual(view.viewModel.presentationStep, .hidden)
}
func test_GivenSearchIsActive_WhenSelectPlaceOnMap_ThenHideSearch() {
@ -163,13 +159,8 @@ final class SearchOnMapTests: XCTestCase {
searchManager.results = results
interactor.handle(.didSelectResult(results[0], withSearchText: searchText))
if isIPad {
XCTAssertEqual(currentState, .searching)
XCTAssertEqual(view.viewModel.presentationStep, .fullScreen)
} else {
XCTAssertEqual(currentState, .hidden)
XCTAssertEqual(view.viewModel.presentationStep, .hidden)
}
XCTAssertEqual(currentState, .hidden)
XCTAssertEqual(view.viewModel.presentationStep, .hidden)
interactor.handle(.didDeselectPlaceOnMap)
XCTAssertEqual(currentState, .searching)
@ -231,10 +222,6 @@ private class SearchOnMapViewMock: SearchOnMapView {
func render(_ viewModel: SearchOnMap.ViewModel) {
self.viewModel = viewModel
}
func close() {
}
func show() {
}
}
private class SearchManagerMock: SearchManager {

View file

@ -101,10 +101,7 @@ class AvailableArea: UIView {
}
func addConstraints(otherView: UIView, directions: MWMAvailableAreaAffectDirections) {
guard !directions.isEmpty else {
LOG(.warning, "Attempt to add empty affecting directions from \(otherView) to \(self)")
return
}
precondition(!directions.isEmpty)
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.setCornerRadius(.buttonDefault, maskedCorners: [.layerMinXMinYCorner, .layerMaxXMinYCorner])
tableView.layer.setCorner(radius: 8, corners: [.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.setCornerRadius(.buttonDefaultSmall)
layer.setCorner(radius: 5.0)
layer.masksToBounds = true
addTarget(self, action: #selector(buttonDidTap), for: .touchUpInside)
}

View file

@ -153,6 +153,7 @@ final class PlacePageScrollView: UIScrollView {
private func setupView() {
let bgView = UIView()
bgView.setStyle(.ppBackgroundView)
stackView.insertSubview(bgView, at: 0)
bgView.alignToSuperview()
@ -162,7 +163,7 @@ final class PlacePageScrollView: UIScrollView {
stackView.backgroundColor = .clear
let cornersToMask: CACornerMask = alternativeSizeClass(iPhone: [], iPad: [.layerMinXMaxYCorner, .layerMaxXMaxYCorner])
actionBarContainerView.layer.setCornerRadius(.modalSheet, maskedCorners: cornersToMask)
actionBarContainerView.layer.setCorner(radius: 16, corners: 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.setCornerRadius(.custom(bulletSize.height / 2))
view.layer.setCorner(radius: bulletSize.height / 2)
views.append(view)
}
}

View file

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

View file

@ -0,0 +1,30 @@
/// 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

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

View file

@ -0,0 +1,226 @@
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

@ -0,0 +1,21 @@
@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

@ -0,0 +1,22 @@
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

@ -0,0 +1,25 @@
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

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

View file

@ -1,6 +1,5 @@
protocol SearchOnMapHeaderViewDelegate: UISearchBarDelegate {
func cancelButtonDidTap()
func grabberDidTap()
}
final class SearchOnMapHeaderView: UIView {
@ -11,19 +10,15 @@ 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: 16)
static let cancelButtonInsets: UIEdgeInsets = UIEdgeInsets(top: 0, left: 6, bottom: 0, right: 8)
}
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)
@ -37,29 +32,22 @@ final class SearchOnMapHeaderView: UIView {
}
private func setupView() {
setStyle(.primaryBackground)
setStyle(.searchHeader)
layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
setupGrabberView()
setupGrabberTapHandlerView()
setupSearchBar()
setupCancelButton()
}
private func setupGrabberView() {
grabberView.setStyle(.grabber)
grabberView.setStyle(.background)
grabberView.layer.setCorner(radius: Constants.grabberHeight / 2)
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
@ -71,24 +59,19 @@ final class SearchOnMapHeaderView: UIView {
}
private func setupCancelButton() {
cancelContainer.setStyle(.primaryBackground)
cancelButton.setStyle(.searchCancelButton)
cancelButton.tintColor = .whitePrimaryText()
cancelButton.setStyle(.clearBackground)
cancelButton.setTitle(L("cancel"), for: .normal)
cancelButton.addTarget(self, action: #selector(cancelButtonDidTap), for: .touchUpInside)
cancelButton.addTarget(self, action: #selector(cancelButtonTapped), for: .touchUpInside)
}
private func layoutView() {
addSubview(grabberView)
addSubview(grabberTapHandlerView)
addSubview(searchBar)
addSubview(cancelContainer)
cancelContainer.addSubview(cancelButton)
addSubview(cancelButton)
grabberView.translatesAutoresizingMaskIntoConstraints = false
grabberTapHandlerView.translatesAutoresizingMaskIntoConstraints = false
grabberTapHandlerView.setContentHuggingPriority(.defaultLow, for: .vertical)
searchBar.translatesAutoresizingMaskIntoConstraints = false
cancelContainer.translatesAutoresizingMaskIntoConstraints = false
cancelButton.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
@ -97,33 +80,18 @@ final class SearchOnMapHeaderView: UIView {
grabberView.widthAnchor.constraint(equalToConstant: Constants.grabberWidth),
grabberView.heightAnchor.constraint(equalToConstant: Constants.grabberHeight),
grabberTapHandlerView.topAnchor.constraint(equalTo: grabberView.bottomAnchor),
grabberTapHandlerView.leadingAnchor.constraint(equalTo: leadingAnchor),
grabberTapHandlerView.trailingAnchor.constraint(equalTo: trailingAnchor),
grabberTapHandlerView.bottomAnchor.constraint(equalTo: searchBar.topAnchor),
searchBar.topAnchor.constraint(equalTo: grabberView.bottomAnchor),
searchBar.leadingAnchor.constraint(equalTo: leadingAnchor),
searchBar.trailingAnchor.constraint(equalTo: cancelButton.leadingAnchor, constant: -Constants.cancelButtonInsets.left),
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),
cancelButton.centerYAnchor.constraint(equalTo: searchBar.centerYAnchor),
cancelButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -Constants.cancelButtonInsets.right),
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),
bottomAnchor.constraint(equalTo: searchBar.bottomAnchor)
])
}
@objc private func grabberDidTap() {
delegate?.grabberDidTap()
}
@objc private func cancelButtonDidTap() {
@objc private func cancelButtonTapped() {
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,14 +19,16 @@ protocol SearchOnMapManagerObserver: AnyObject {
@objcMembers
final class SearchOnMapManager: NSObject {
private var interactor: SearchOnMapInteractor? { viewController?.interactor }
private let navigationController: UINavigationController
private weak var interactor: SearchOnMapInteractor?
private let observers = ListenerContainer<SearchOnMapManagerObserver>()
weak var viewController: SearchOnMapViewController?
// MARK: - Public properties
weak var viewController: UIViewController?
var isSearching: Bool { viewController != nil }
override init() {
super.init()
init(navigationController: UINavigationController = MapViewController.shared()!.navigationController!) {
self.navigationController = navigationController
}
// MARK: - Public methods
@ -36,9 +38,10 @@ final class SearchOnMapManager: NSObject {
return
}
FrameworkHelper.deactivateMapSelection()
let viewController = SearchOnMapViewControllerBuilder.build(isRouting: isRouting,
didChangeState: notifyObservers)
let viewController = buildViewController(isRouting: isRouting)
self.viewController = viewController
self.interactor = viewController.interactor
navigationController.present(viewController, animated: true)
}
func hide() {
@ -74,20 +77,20 @@ final class SearchOnMapManager: NSObject {
observers.removeListener(observer)
}
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)
// 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) }
})
let interactor = SearchOnMapInteractor(presenter: presenter)
let viewController = SearchOnMapViewController(interactor: interactor)
presenter.view = viewController
viewController.interactor = interactor
viewController.show()
viewController.modalPresentationStyle = .custom
viewController.transitioningDelegate = transitioningManager
return viewController
}
}

View file

@ -1,6 +1,6 @@
enum SearchOnMap {
struct ViewModel: Equatable {
enum Content: Equatable {
enum ContentState: 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: Content
var presentationStep: ModalPresentationStep
var contentState: ContentState
var presentationStep: ModalScreenPresentationStep
}
struct SearchResults: Equatable {
@ -54,7 +54,7 @@ enum SearchOnMap {
case clearButtonDidTap
case didSelectPlaceOnMap
case didDeselectPlaceOnMap
case didUpdatePresentationStep(ModalPresentationStep)
case didUpdatePresentationStep(ModalScreenPresentationStep)
}
enum Response: Equatable {
@ -67,7 +67,7 @@ enum SearchOnMap {
case clearSearch
case setSearchScreenHidden(Bool)
case setSearchScreenCompact
case updatePresentationStep(ModalPresentationStep)
case updatePresentationStep(ModalScreenPresentationStep)
case close
case none
}

View file

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

View file

@ -1,73 +1,52 @@
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 Content = SearchOnMap.ViewModel.Content
typealias ContentState = SearchOnMap.ViewModel.ContentState
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
}
var interactor: SearchOnMapInteractor?
let interactor: SearchOnMapInteractor
weak var scrollViewDelegate: SearchOnMapScrollViewDelegate?
@objc let availableAreaView = SearchOnMapAreaView()
private let contentView = UIView()
private let headerView = SearchOnMapHeaderView()
private let searchResultsView = UIView()
private let resultsTableView = UITableView()
private let historyAndCategoryTabViewController = SearchTabViewController()
private var searchingActivityView = PlaceholderView(hasActivityIndicator: true)
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: - UI Elements
private let headerView = SearchOnMapHeaderView()
private let containerView = 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"))
// MARK: - Init
init() {
init(interactor: SearchOnMapInteractor) {
self.interactor = interactor
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)
@ -75,16 +54,16 @@ final class SearchOnMapViewController: UIViewController {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Lifecycle
override func loadView() {
view = TouchTransparentView()
deinit {
NotificationCenter.default.removeObserver(self)
}
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
setupViews()
layoutViews()
presentationStepsController.setInitialState()
interactor.handle(.openSearch)
}
override func viewWillDisappear(_ animated: Bool) {
@ -92,58 +71,30 @@ final class SearchOnMapViewController: UIViewController {
headerView.setIsSearching(false)
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
updateFrameOfPresentedViewInContainerView()
updateDimView(for: availableAreaView.frame)
}
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()
view.setStyle(.clearBackground)
setupTapGestureRecognizer()
setupHeaderView()
setupSearchResultsView()
setupContainerView()
setupResultsTableView()
setupHistoryAndCategoryTabView()
setupResultsTableView()
setupFiltersCollectionView()
}
private func setupDimView() {
iPhoneSpecific {
dimView = UIView()
dimView?.backgroundColor = .black
dimView?.frame = view.bounds
}
}
private func setupGestureRecognizers() {
private func setupTapGestureRecognizer() {
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTapOutside))
tapGesture.cancelsTouchesInView = false
contentView.addGestureRecognizer(tapGesture)
iPhoneSpecific {
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
panGestureRecognizer.delegate = self
contentView.addGestureRecognizer(panGestureRecognizer)
}
view.addGestureRecognizer(tapGesture)
}
private func setupHeaderView() {
headerView.delegate = self
}
private func setupSearchResultsView() {
searchResultsView.setStyle(.background)
private func setupContainerView() {
containerView.setStyle(.background)
}
private func setupResultsTableView() {
@ -161,141 +112,91 @@ final class SearchOnMapViewController: UIViewController {
historyAndCategoryTabViewController.delegate = self
}
private func layoutViews() {
if let dimView {
view.addSubview(dimView)
dimView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
}
view.addSubview(availableAreaView)
availableAreaView.addSubview(contentView)
contentView.addSubview(headerView)
contentView.addSubview(searchResultsView)
// TODO: (KK) Implement filters collection viewe
private func setupFiltersCollectionView() {
filtersCollectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "FilterCell")
filtersCollectionView.dataSource = self
}
contentView.translatesAutoresizingMaskIntoConstraints = false
private func layoutViews() {
view.addSubview(headerView)
view.addSubview(containerView)
headerView.translatesAutoresizingMaskIntoConstraints = false
searchResultsView.translatesAutoresizingMaskIntoConstraints = false
containerView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
contentView.topAnchor.constraint(equalTo: availableAreaView.topAnchor),
contentView.leadingAnchor.constraint(equalTo: availableAreaView.leadingAnchor),
contentView.trailingAnchor.constraint(equalTo: availableAreaView.trailingAnchor),
contentView.bottomAnchor.constraint(equalTo: availableAreaView.bottomAnchor),
headerView.topAnchor.constraint(equalTo: view.topAnchor),
headerView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
headerView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
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),
containerView.topAnchor.constraint(equalTo: headerView.bottomAnchor),
containerView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
containerView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
containerView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
layoutResultsView()
layoutHistoryAndCategoryTabView()
layoutSearchNoResultsView()
layoutSearchingView()
updateFrameOfPresentedViewInContainerView()
}
private func layoutResultsView() {
searchResultsView.addSubview(resultsTableView)
containerView.addSubview(resultsTableView)
resultsTableView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
resultsTableView.topAnchor.constraint(equalTo: searchResultsView.topAnchor),
resultsTableView.leadingAnchor.constraint(equalTo: searchResultsView.leadingAnchor),
resultsTableView.trailingAnchor.constraint(equalTo: searchResultsView.trailingAnchor),
resultsTableView.bottomAnchor.constraint(equalTo: searchResultsView.bottomAnchor)
resultsTableView.topAnchor.constraint(equalTo: containerView.topAnchor),
resultsTableView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
resultsTableView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
resultsTableView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor)
])
}
private func layoutHistoryAndCategoryTabView() {
searchResultsView.addSubview(historyAndCategoryTabViewController.view)
containerView.addSubview(historyAndCategoryTabViewController.view)
historyAndCategoryTabViewController.view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
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)
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)
])
}
private func layoutSearchNoResultsView() {
searchNoResultsView.translatesAutoresizingMaskIntoConstraints = false
searchResultsView.addSubview(searchNoResultsView)
containerView.addSubview(searchNoResultsView)
NSLayoutConstraint.activate([
searchNoResultsView.topAnchor.constraint(equalTo: searchResultsView.topAnchor),
searchNoResultsView.leadingAnchor.constraint(equalTo: searchResultsView.leadingAnchor),
searchNoResultsView.trailingAnchor.constraint(equalTo: searchResultsView.trailingAnchor),
searchNoResultsView.bottomAnchor.constraint(equalTo: searchResultsView.bottomAnchor)
searchNoResultsView.topAnchor.constraint(equalTo: containerView.topAnchor),
searchNoResultsView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
searchNoResultsView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
searchNoResultsView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor)
])
}
private func layoutSearchingView() {
searchResultsView.insertSubview(searchingActivityView, at: 0)
containerView.insertSubview(searchingActivityView, at: 0)
searchingActivityView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
searchingActivityView.leadingAnchor.constraint(equalTo: searchResultsView.leadingAnchor),
searchingActivityView.trailingAnchor.constraint(equalTo: searchResultsView.trailingAnchor),
searchingActivityView.topAnchor.constraint(equalTo: searchResultsView.topAnchor),
searchingActivityView.bottomAnchor.constraint(equalTo: searchResultsView.bottomAnchor)
searchingActivityView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
searchingActivityView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
searchingActivityView.topAnchor.constraint(equalTo: containerView.topAnchor),
searchingActivityView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor)
])
}
// MARK: - Handle Presentation Steps
private func updateFrameOfPresentedViewInContainerView() {
presentationStepsController.updateMaxAvailableFrame()
availableAreaView.frame = presentationStepsController.currentFrame
view.layoutIfNeeded()
}
@objc
private func handleTapOutside(_ gesture: UITapGestureRecognizer) {
// MARK: - Handle Button Actions
@objc private func handleTapOutside(_ gesture: UITapGestureRecognizer) {
let location = gesture.location(in: view)
if resultsTableView.frame.contains(location) && searchResults.isEmpty {
headerView.setIsSearching(false)
}
}
@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) {
// MARK: - Handle State Updates
private func setContent(_ content: ContentState) {
switch content {
case .historyAndCategory:
historyAndCategoryTabViewController.reloadSearchHistory()
@ -313,7 +214,7 @@ final class SearchOnMapViewController: UIViewController {
showView(viewToShow(for: content))
}
private func viewToShow(for content: Content) -> UIView {
private func viewToShow(for content: ContentState) -> UIView {
switch content {
case .historyAndCategory:
return historyAndCategoryTabViewController.view
@ -331,26 +232,24 @@ final class SearchOnMapViewController: UIViewController {
historyAndCategoryTabViewController.view,
searchNoResultsView,
searchingActivityView].filter { $0 != view }
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
}
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
}
})
}
private func setIsSearching(_ isSearching: Bool) {
headerView.setIsSearching(isSearching)
}
private func setSearchText(_ text: String?) {
if let text {
headerView.setSearchText(text)
}
private func replaceSearchText(with text: String) {
headerView.setSearchText(text)
}
}
@ -359,33 +258,20 @@ extension SearchOnMapViewController: SearchOnMapView {
func render(_ viewModel: ViewModel) {
setContent(viewModel.contentState)
setIsSearching(viewModel.isTyping)
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()
if let searchingText = viewModel.searchingText {
replaceSearchText(with: searchingText)
}
}
}
// MARK: - ModallyPresentedViewController
extension SearchOnMapViewController: ModallyPresentedViewController {
func presentationFrameDidChange(_ frame: CGRect) {
let translationY = frame.origin.y
func translationYDidUpdate(_ translationY: CGFloat) {
self.containerModalYTranslation = translationY
resultsTableView.contentInset.bottom = translationY
historyAndCategoryTabViewController.presentationFrameDidChange(frame)
searchNoResultsView.presentationFrameDidChange(frame)
searchingActivityView.presentationFrameDidChange(frame)
historyAndCategoryTabViewController.translationYDidUpdate(translationY)
searchNoResultsView.translationYDidUpdate(translationY)
searchingActivityView.translationYDidUpdate(translationY)
}
}
@ -417,82 +303,60 @@ 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)
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
}
}
// 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)
}
func grabberDidTap() {
interactor?.handle(.didUpdatePresentationStep(.fullScreen))
interactor.handle(.closeSearch)
}
}
// 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,10 +45,6 @@ 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]
@ -56,8 +52,8 @@ final class SearchCategoriesViewController: MWMTableViewController {
}
extension SearchCategoriesViewController: ModallyPresentedViewController {
func presentationFrameDidChange(_ frame: CGRect) {
func translationYDidUpdate(_ translationY: CGFloat) {
guard isViewLoaded else { return }
tableView.contentInset.bottom = frame.origin.y + view.safeAreaInsets.bottom
tableView.contentInset.bottom = translationY + view.safeAreaInsets.bottom
}
}

View file

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

View file

@ -54,8 +54,8 @@ final class SearchTabViewController: TabViewController {
}
extension SearchTabViewController: ModallyPresentedViewController {
func presentationFrameDidChange(_ frame: CGRect) {
viewControllers.forEach { ($0 as? ModallyPresentedViewController)?.presentationFrameDidChange(frame) }
func translationYDidUpdate(_ translationY: CGFloat) {
viewControllers.forEach { ($0 as? ModallyPresentedViewController)?.translationYDidUpdate(translationY) }
}
}
@ -63,10 +63,6 @@ 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,15 +194,11 @@
<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"/>