Compare commits

...
This repository has been archived on 2025-03-22. You can view files and clone it, but cannot push or open issues or pull requests.

19 commits

Author SHA1 Message Date
b05cc564c6 [android] fix enabling and visibility of the Edit and App buttons on the PP
Signed-off-by: Kiryl Kaveryn <kirylkaveryn@gmail.com>
2024-07-25 13:53:39 +01:00
2a3fc64a00 [core] [map] [ios] fix the CanEditMap logic to add places only to the downloaded and updated maps
Signed-off-by: Kiryl Kaveryn <kirylkaveryn@gmail.com>
2024-07-25 13:53:39 +01:00
c77020d43d [ios] prevent from selecting disabled menu cells
Signed-off-by: Kiryl Kaveryn <kirylkaveryn@gmail.com>
2024-07-25 13:53:39 +01:00
94e32b4b42 [strings] regenerate
Signed-off-by: Kiryl Kaveryn <kirylkaveryn@gmail.com>
2024-07-25 13:53:19 +01:00
e6a4d0ff7a [strings] icloud failure alert strings
Signed-off-by: Kiryl Kaveryn <kirylkaveryn@gmail.com>
2024-07-25 13:53:19 +01:00
d28001d723 [ios] refactor icloud sync to prevent syncing when some errors occure
- throws an exeption when the metadata cannot be initialized from the url or nsmetadataitem
- add 2 new sync errors cases to clarify errors reasons
- stop sync on the all errors except ubiquity errors (uploading/downloading)
- subscribe the settings screen on the sync state notification to update the relates cell properly from the cloud manager
- show the alert with an error if cloud sync fails with proposal to the user to send a bugreport

Signed-off-by: Kiryl Kaveryn <kirylkaveryn@gmail.com>
2024-07-25 13:53:19 +01:00
edf982ae7d [ios] move mail composing logic to the MailComposer class
to reuse both in the About screen and the Cloud sync manager

Signed-off-by: Kiryl Kaveryn <kirylkaveryn@gmail.com>
2024-07-25 13:53:19 +01:00
69bc067ca9 [ios] remove unused code from the MetadataItem
Signed-off-by: Kiryl Kaveryn <kirylkaveryn@gmail.com>
2024-07-25 13:53:19 +01:00
aa63781b11 [bookmarks] [tests] unit tests for the recently deleted
Signed-off-by: Kiryl Kaveryn <kirylkaveryn@gmail.com>
2024-07-25 13:53:04 +01:00
fcf985c2b1 [bookmarks] delete category files using the .deleted extension instead of full deleting
Signed-off-by: Kiryl Kaveryn <kirylkaveryn@gmail.com>
2024-07-25 13:53:04 +01:00
48aac0a9eb [ios] refactor the ActivityViewController: set creational methods to nonnull
Signed-off-by: Kiryl Kaveryn <kirylkaveryn@gmail.com>
2024-07-25 13:52:52 +01:00
2cbb5132dc [ios] refactor the PP's header and add the share button
- increase grabber size
- move the share button to the PP's and Layers header trailing-top corner
- add a new color for the close/share icons on the pp
- crate a new button class with rounded image
Signed-off-by: Kiryl Kaveryn <kirylkaveryn@gmail.com>
2024-07-25 13:52:52 +01:00
92647d9691 [ios] add new icons to share and close buttons
Signed-off-by: Kiryl Kaveryn <kirylkaveryn@gmail.com>
2024-07-25 13:52:52 +01:00
0a573f3d69 [strings] Regenerate
Signed-off-by: Kiryl Kaveryn <kirylkaveryn@gmail.com>
2024-07-25 13:52:38 +01:00
eae5eac29c [strings] add open_in string
Signed-off-by: Kiryl Kaveryn <kirylkaveryn@gmail.com>
2024-07-25 13:52:38 +01:00
7a66304806 [ios] implement open_in_app feature
Signed-off-by: Kiryl Kaveryn <kirylkaveryn@gmail.com>
2024-07-25 13:52:38 +01:00
37b493e503 [drape] add GetCurrentZoom()
- add to the drape_engine and framework `GetCurrentZoom`
- make GetCurrentZoom public in the frontend_renderer

Signed-off-by: Kiryl Kaveryn <kirylkaveryn@gmail.com>
2024-07-25 13:52:38 +01:00
edef4d2e7f [ios] add open_in_app icon and fix ic_placepage_wheelchair
Signed-off-by: Kiryl Kaveryn <kirylkaveryn@gmail.com>
2024-07-25 13:52:38 +01:00
233334c4c9 [ios] register app schemes in LSApplicationQueriesSchemes
Signed-off-by: Kiryl Kaveryn <kirylkaveryn@gmail.com>
2024-07-25 13:52:37 +01:00
128 changed files with 1594 additions and 648 deletions

View file

@ -188,13 +188,23 @@ Java_app_organicmaps_editor_Editor_nativeShouldShowAddPlace(JNIEnv *, jclass)
}
JNIEXPORT jboolean JNICALL
Java_app_organicmaps_editor_Editor_nativeShouldShowAddBusiness(JNIEnv *, jclass)
Java_app_organicmaps_editor_Editor_nativeShouldEnableEditPlace(JNIEnv *, jclass)
{
::Framework * frm = g_framework->NativeFramework();
if (!frm->HasPlacePageInfo())
return static_cast<jboolean>(false);
return g_framework->GetPlacePageInfo().ShouldShowAddBusiness();
return g_framework->GetPlacePageInfo().ShouldEnableEditPlace();
}
JNIEXPORT jboolean JNICALL
Java_app_organicmaps_editor_Editor_nativeShouldEnableAddPlace(JNIEnv *, jclass)
{
::Framework * frm = g_framework->NativeFramework();
if (!frm->HasPlacePageInfo())
return static_cast<jboolean>(false);
return g_framework->GetPlacePageInfo().ShouldEnableAddPlace();
}
JNIEXPORT jintArray JNICALL

View file

@ -55,7 +55,8 @@ public final class Editor
public static native boolean nativeShouldShowEditPlace();
public static native boolean nativeShouldShowAddPlace();
public static native boolean nativeShouldShowAddBusiness();
public static native boolean nativeShouldEnableEditPlace();
public static native boolean nativeShouldEnableAddPlace();
@NonNull
public static native int[] nativeGetEditableProperties();

View file

@ -431,8 +431,9 @@ public class PlacePageView extends Fragment implements View.OnClickListener,
else
{
UiUtils.showIf(Editor.nativeShouldShowEditPlace(), mEditPlace);
UiUtils.showIf(Editor.nativeShouldShowAddBusiness(), mAddOrganisation);
UiUtils.showIf(Editor.nativeShouldShowAddPlace(), mAddPlace);
mEditPlace.setEnabled(Editor.nativeShouldEnableEditPlace());
mAddPlace.setEnabled(Editor.nativeShouldEnableAddPlace());
UiUtils.showIf(UiUtils.isVisible(mEditPlace)
|| UiUtils.isVisible(mAddOrganisation)
|| UiUtils.isVisible(mAddPlace), mEditTopSpace);

View file

@ -30537,6 +30537,98 @@
zh-Hans = 备份
zh-Hant = 备份
[icloud_synchronization_error_alert_title]
comment = Title for the "iCloud synchronization failure" alert.
tags = ios
en = iCloud synchronization failure
af = iCloud-sinchronisasie mislukking
ar = فشل مزامنة iCloud
az = iCloud sinxronizasiya uğursuzluğu
be = Збой сінхранізацыі iCloud
bg = Неуспешна синхронизация на iCloud
ca = Error de sincronització d'iCloud
cs = Selhání synchronizace iCloudu
da = Fejl i iCloud-synkronisering
de = iCloud-Synchronisierung fehlgeschlagen
el = Αποτυχία συγχρονισμού iCloud
es = Fallo de sincronización de iCloud
et = iCloudi sünkroniseerimise tõrge
eu = iCloud sinkronizazioaren hutsegitea
fa = همگام سازی iCloud شکست خورده است
fi = iCloud-synkronoinnin epäonnistuminen
fr = Échec de la synchronisation iCloud
he = כשל בסינכרון iCloud
hi = iCloud सिंक्रनाइज़ेशन विफलता
hu = iCloud-szinkronizálási hiba
id = Kegagalan sinkronisasi iCloud
it = Mancata sincronizzazione di iCloud
ja = iCloud同期の失敗
ko = iCloud 동기화 실패
lt = "iCloud" sinchronizavimo sutrikimas
mr = iCloud सिंक्रोनाइझेशन अयशस्वी
nb = iCloud-synkroniseringsfeil
nl = Storing iCloud-synchronisatie
pl = Błąd synchronizacji iCloud
pt = Falha de sincronização do iCloud
pt-BR = Falha na sincronização do iCloud
ro = Eșecul sincronizării iCloud
ru = Сбой синхронизации iCloud
sk = Zlyhanie synchronizácie iCloud
sv = fel i iCloud-synkroniseringen
sw = Imeshindwa kusawazisha iCloud
th = การซิงโครไนซ์ iCloud ล้มเหลว
tr = iCloud senkronizasyon hatası
uk = Збій синхронізації iCloud
vi = Lỗi đồng bộ hóa iCloud
zh-Hans = iCloud 同步失败
zh-Hant = iCloud 同步失敗
[icloud_synchronization_error_alert_message]
comment = Message for the "iCloud synchronization failure" alert.
tags = ios
en = Would you like to send a bug report to the developers?
af = Wil jy 'n foutverslag aan die ontwikkelaars stuur?
ar = هل ترغب في إرسال تقرير عن الأخطاء إلى المطورين؟
az = Tərtibatçılara səhv hesabatı göndərmək istərdinizmi?
be = Хочаце адправіць справаздачу пра памылку распрацоўшчыкам?
bg = Искате ли да изпратите доклад за грешка на разработчиците?
ca = Voleu enviar un informe d'error als desenvolupadors?
cs = Chcete poslat vývojářům hlášení o chybě?
da = Vil du gerne sende en fejlrapport til udviklerne?
de = Willst du einen Fehlerbericht an die Entwickler schicken?
el = Θα θέλατε να στείλετε μια αναφορά σφάλματος στους προγραμματιστές;
es = ¿Quieres enviar un informe de error a los desarrolladores?
et = Kas soovite saata arendajatele veateate?
eu = Akatsen txostena bidali nahi diezu garatzaileei?
fa = آیا می خواهید یک گزارش اشکال برای توسعه دهندگان ارسال کنید؟
fi = Haluatko lähettää vikailmoituksen kehittäjille?
fr = Veux-tu envoyer un rapport de bogue aux développeurs ?
he = האם תרצה לשלוח דוח באג למפתחים?
hi = क्या आप डेवलपर्स को बग रिपोर्ट भेजना चाहेंगे?
hu = Szeretne hibajelentést küldeni a fejlesztőknek?
id = Apakah Anda ingin mengirim laporan bug ke pengembang?
it = Vuoi inviare una segnalazione di bug agli sviluppatori?
ja = 開発者にバグレポートを送りたいか?
ko = 개발자에게 버그 리포트를 보내시겠습니까?
lt = Ar norėtumėte išsiųsti pranešimą apie klaidą kūrėjams?
mr = तुम्ही विकासकांना बग अहवाल पाठवू इच्छिता?
nb = Vil du sende en feilrapport til utviklerne?
nl = Wil je een bugrapport naar de ontwikkelaars sturen?
pl = Chcesz wysłać raport o błędzie do deweloperów?
pt = Gostarias de enviar um relatório de erro para os programadores?
pt-BR = Você gostaria de enviar um relatório de bug para os desenvolvedores?
ro = Doriți să trimiteți un raport de eroare dezvoltatorilor?
ru = Хотите отправить разработчикам сообщение об ошибке?
sk = Chcete poslať vývojárom hlásenie o chybe?
sv = Vill du skicka en felrapport till utvecklarna?
sw = Je, ungependa kutuma ripoti ya hitilafu kwa wasanidi programu?
th = คุณต้องการส่งรายงานข้อผิดพลาดไปยังนักพัฒนาหรือไม่?
tr = Geliştiricilere bir hata raporu göndermek ister misiniz?
uk = Бажаєте надіслати розробникам звіт про помилку?
vi = Bạn có muốn gửi báo cáo lỗi cho nhà phát triển không?
zh-Hans = 您想向开发人员发送错误报告吗?
zh-Hant = 您想向開發人員發送錯誤報告嗎?
[icloud_synchronization_error_connection_error]
comment = iCloud error message: Failed to synchronize due to connection error
tags = ios
@ -30676,3 +30768,49 @@
vi = Lỗi: iCloud không khả dụng
zh-Hans = 错误iCloud 不可用
zh-Hant = 錯誤iCloud 不可用
[open_in_app]
comment = Title for the "Open In Another App" button on the PlacePage.
tags = ios
en = Open In Another App
af = Maak oop in 'n ander toepassing
ar = فتح في تطبيق آخر
az = Başqa Tətbiqdə Açın
be = Адкрыць у іншай прыладзе
bg = Отваряне в друго приложение
ca = Obre en una altra aplicació
cs = Otevřít v jiné aplikaci
da = Åbn i en anden app
de = In einer anderen App öffnen
el = Άνοιγμα σε άλλη εφαρμογή
es = Abrir en otra aplicación
et = Avatud teises rakenduses
eu = Ireki beste aplikazio batean
fa = در یک برنامه دیگر باز کنید
fi = Avaa toisessa sovelluksessa
fr = Ouvrir dans une autre application
he = פתח באפליקציה אחרת
hi = किसी अन्य ऐप में खोलें
hu = Megnyitás egy másik alkalmazásban
id = Buka di Aplikasi Lain
it = Apri in un'altra applicazione
ja = 別のアプリで開く
ko = 다른 앱에서 열기
lt = Atidaryti kitoje programoje
mr = दुसऱ्या ॲपमध्ये उघडा
nb = Åpne i en annen app
nl = Openen in een andere app
pl = Otwórz w innej aplikacji
pt = Abrir noutra aplicação
pt-BR = Abrir em outro aplicativo
ro = Deschidere în altă aplicație
ru = Открыть в другом приложении
sk = Otvoriť v inej aplikácii
sv = Öppna i en annan app
sw = Fungua Katika Programu Nyingine
th = เปิดในแอปอื่น
tr = Başka Bir Uygulamada Aç
uk = Відкрити в іншій програмі
vi = Mở trong ứng dụng khác
zh-Hans = 在另一个应用程序中打开
zh-Hant = 在另一個應用程式中打開

View file

@ -166,6 +166,11 @@ void DrapeEngine::SetVisibleViewport(m2::RectD const & rect) const
MessagePriority::Normal);
}
int DrapeEngine::GetCurrentZoomLevel() const
{
return m_frontend->GetCurrentZoom();
}
void DrapeEngine::Invalidate()
{
m_threadCommutator->PostMessage(ThreadsCommutator::RenderThread,

View file

@ -119,6 +119,8 @@ public:
void SetVisibleViewport(m2::RectD const & rect) const;
int GetCurrentZoomLevel() const;
void AddTouchEvent(TouchEvent const & event);
void Scale(double factor, m2::PointD const & pxPoint, bool isAnim);
void Move(double factorX, double factorY, bool isAnim);

View file

@ -2524,6 +2524,12 @@ void FrontendRenderer::ChangeModelView(double autoScale, m2::PointD const & user
AddUserEvent(make_unique_dp<FollowAndRotateEvent>(userPos, pxZero, azimuth, autoScale, parallelAnimCreator));
}
int FrontendRenderer::GetCurrentZoom() const
{
ASSERT(IsValidCurrentZoom(), ());
return m_currentZoomLevel;
}
void FrontendRenderer::OnEnterBackground()
{
m_myPositionController->OnEnterBackground();

View file

@ -145,6 +145,7 @@ public:
drape_ptr<ScenarioManager> const & GetScenarioManager() const { return m_scenarioManager; }
location::EMyPositionMode GetMyPositionMode() const { return m_myPositionController->GetCurrentMode(); }
int GetCurrentZoom() const;
void OnEnterBackground();
@ -351,12 +352,6 @@ private:
return m_currentZoomLevel >= 0;
}
int GetCurrentZoom() const
{
ASSERT(IsValidCurrentZoom(), ());
return m_currentZoomLevel;
}
int m_currentZoomLevel = -1;
ref_ptr<RequestedTiles> m_requestedTiles;

View file

@ -18,6 +18,7 @@ NS_SWIFT_NAME(GeoUtil)
@interface MWMGeoUtil : NSObject
+ (float)angleAtPoint:(CLLocationCoordinate2D)p1 toPoint:(CLLocationCoordinate2D)p2;
+ (NSString *)formattedOsmLinkForCoordinate:(CLLocationCoordinate2D)coordinate zoomLevel:(int)zoomLevel;
@end

View file

@ -1,4 +1,5 @@
#import "MWMGeoUtil.h"
#import "MWMFrameworkHelper.h"
#include "geometry/mercator.hpp"
#include "geometry/angles.hpp"
@ -7,6 +8,7 @@
#include "platform/localization.hpp"
#include "platform/settings.hpp"
#include "platform/measurement_utils.hpp"
#include "drape_frontend/frontend_renderer.hpp"
@implementation Measure
@ -58,4 +60,9 @@
return ang::AngleTo(mp1, mp2);
}
+ (NSString *)formattedOsmLinkForCoordinate:(CLLocationCoordinate2D)coordinate zoomLevel:(int)zoomLevel {
auto const link = measurement_utils::FormatOsmLink(coordinate.latitude, coordinate.longitude, zoomLevel);
return [NSString stringWithCString:link.c_str() encoding:NSUTF8StringEncoding];
}
@end

View file

@ -34,12 +34,13 @@ NS_SWIFT_NAME(FrameworkHelper)
+ (void)searchInDownloader:(NSString *)query
inputLocale:(NSString *)locale
completion:(SearchInDownloaderCompletions)completion;
+ (BOOL)canEditMap;
+ (BOOL)canEditMapAtViewportCenter;
+ (void)showOnMap:(MWMMarkGroupID)categoryId;
+ (void)showBookmark:(MWMMarkID)bookmarkId;
+ (void)showTrack:(MWMTrackID)trackId;
+ (void)updatePlacePageData;
+ (void)updateAfterDeleteBookmark;
+ (int)currentZoomLevel;
@end

View file

@ -162,8 +162,9 @@
GetFramework().GetSearchAPI().SearchInDownloader(std::move(params));
}
+ (BOOL)canEditMap {
return GetFramework().CanEditMap();
+ (BOOL)canEditMapAtViewportCenter {
auto &f = GetFramework();
return f.CanEditMapForPosition(f.GetViewportCenter());
}
+ (void)showOnMap:(MWMMarkGroupID)categoryId {
@ -190,4 +191,9 @@
buildInfo.m_source = place_page::BuildInfo::Source::Other;
frm.UpdatePlacePageInfoForCurrentSelection(buildInfo);
}
+ (int)currentZoomLevel {
return GetFramework().GetCurrentZoomLevel();
}
@end

View file

@ -6,7 +6,9 @@ NS_ASSUME_NONNULL_BEGIN
@property(nonatomic, readonly) BOOL showAddPlace;
@property(nonatomic, readonly) BOOL showEditPlace;
@property(nonatomic, readonly) BOOL showAddBusiness;
@property(nonatomic, readonly) BOOL enableAddPlace;
@property(nonatomic, readonly) BOOL enableEditPlace;
@end

View file

@ -11,8 +11,9 @@
if (self) {
_showAddPlace = rawData.ShouldShowAddPlace();
_showEditPlace = rawData.ShouldShowEditPlace();
_showAddBusiness = rawData.ShouldShowAddBusiness();
if (_showAddPlace || _showEditPlace || _showAddBusiness)
_enableAddPlace = rawData.ShouldEnableAddPlace();
_enableEditPlace = rawData.ShouldEnableEditPlace();
if (_showAddPlace || _showEditPlace || _enableAddPlace || _enableEditPlace)
return self;
}
return nil;

View file

@ -246,7 +246,7 @@ extension BookmarksListViewController: IBookmarksListView {
message: L("share_bookmarks_email_body")) { (_, _, _, _) in
completion()
}
shareController?.present(inParentViewController: self, anchorView: self.toolBar)
shareController.present(inParentViewController: self, anchorView: self.toolBar)
}
func showError(title: String, message: String) {

View file

@ -87,7 +87,7 @@ final class BMCViewController: MWMViewController {
{ [weak self] _, _, _, _ in
self?.viewModel?.finishShareCategory()
}
shareController?.present(inParentViewController: self, anchorView: anchorView)
shareController.present(inParentViewController: self, anchorView: anchorView)
case .emptyCategory:
MWMAlertViewController.activeAlert().presentInfoAlert(L("bookmarks_error_title_share_empty"),
text: L("bookmarks_error_message_share_empty"))

View file

@ -148,9 +148,7 @@ NSString *const kMapToCategorySelectorSegue = @"MapToCategorySelectorSegue";
applyPosition:hasPoint
position:point
doneBlock:^{
auto &f = GetFramework();
if (IsPointCoveredByDownloadedMaps(f.GetViewportCenter(), f.GetStorage(), f.GetCountryInfoGetter()))
if ([MWMFrameworkHelper canEditMapAtViewportCenter])
[ownerController performSegueWithIdentifier:kMapToCategorySelectorSegue sender:nil];
else
[ownerController.alertController presentIncorrectFeauturePositionAlert];

View file

@ -6,13 +6,13 @@ NS_ASSUME_NONNULL_BEGIN
NS_SWIFT_NAME(ActivityViewController)
@interface MWMActivityViewController : UIActivityViewController
+ (nullable instancetype)shareControllerForEditorViral;
+ (instancetype)shareControllerForEditorViral;
+ (nullable instancetype)shareControllerForMyPosition:(CLLocationCoordinate2D)location;
+ (instancetype)shareControllerForMyPosition:(CLLocationCoordinate2D)location;
+ (nullable instancetype)shareControllerForPlacePage:(PlacePageData *)data;
+ (instancetype)shareControllerForPlacePage:(PlacePageData *)data;
+ (nullable instancetype)shareControllerForURL:(nullable NSURL *)url
+ (instancetype)shareControllerForURL:(nullable NSURL *)url
message:(NSString *)message
completionHandler:(nullable UIActivityViewControllerCompletionWithItemsHandler)completionHandler;

View file

@ -59,6 +59,8 @@ class DayColors: IColors {
var outdoorColor = UIColor(red: 0.235, green: 0.549, blue: 0.235, alpha: 1)
var lonelyPlanetLogoColor = UIColor(red: 0, green: 0.286, blue: 0.565, alpha: 1)
var carplayPlaceholderBackground = UIColor(221, 221, 205, alpha100)
var iconOpaqueGrayTint = UIColor(117, 117, 117, alpha100)
var iconOpaqueGrayBackground = UIColor(231, 231, 231, alpha100)
}
class NightColors: IColors {
@ -122,4 +124,6 @@ class NightColors: IColors {
var outdoorColor = UIColor(147, 191, 57, alpha100)
var lonelyPlanetLogoColor = UIColor(red: 1, green: 1, blue: 1, alpha: 0.7)
var carplayPlaceholderBackground = UIColor(50, 54, 58, alpha100)
var iconOpaqueGrayTint = UIColor(197, 197, 197, alpha100)
var iconOpaqueGrayBackground = UIColor(84, 86, 90, alpha100)
}

View file

@ -69,4 +69,6 @@ let alpha100: CGFloat = 1.0
var outdoorColor: UIColor { get }
var lonelyPlanetLogoColor: UIColor { get }
var carplayPlaceholderBackground: UIColor { get }
var iconOpaqueGrayTint: UIColor { get }
var iconOpaqueGrayBackground: UIColor { get }
}

View file

@ -111,8 +111,9 @@ class PlacePageStyleSheet: IStyleSheet {
s.clip = true
}
theme.add(styleName: "PPCloseButton") { (s) -> (Void) in
s.tintColor = colors.blackDividers
theme.add(styleName: "PPHeaderCircleIcon") { (s) -> (Void) in
s.tintColor = colors.iconOpaqueGrayTint
s.backgroundColor = colors.iconOpaqueGrayBackground
}
theme.add(styleName: "ChartView") { (s) -> (Void) in

View file

@ -18,11 +18,23 @@ private let kBookmarksDirectoryName = "bookmarks"
private let kICloudSynchronizationDidChangeEnabledStateNotificationName = "iCloudSynchronizationDidChangeEnabledStateNotification"
private let kUDDidFinishInitialCloudSynchronization = "kUDDidFinishInitialCloudSynchronization"
final class CloudStorageSynchronizationState: NSObject {
let isAvailable: Bool
let isOn: Bool
let error: NSError?
init(isAvailable: Bool, isOn: Bool, error: NSError?) {
self.isAvailable = isAvailable
self.isOn = isOn
self.error = error
}
}
@objc @objcMembers final class CloudStorageManager: NSObject {
fileprivate struct Observation {
weak var observer: AnyObject?
var onErrorCompletionHandler: ((NSError?) -> Void)?
var onSynchronizationStateDidChangeHandler: ((CloudStorageSynchronizationState) -> Void)?
}
let fileManager: FileManager
@ -33,7 +45,7 @@ private let kUDDidFinishInitialCloudSynchronization = "kUDDidFinishInitialCloudS
private let synchronizationStateManager: SynchronizationStateManager
private var fileWriter: SynchronizationFileWriter?
private var observers = [ObjectIdentifier: CloudStorageManager.Observation]()
private var synchronizationError: SynchronizationError? {
private var synchronizationError: Error? {
didSet { notifyObserversOnSynchronizationError(synchronizationError) }
}
@ -104,13 +116,11 @@ private extension CloudStorageManager {
guard let self else { return }
switch result {
case .failure(let error):
self.stopSynchronization()
self.processError(error)
case .success(let cloudDirectoryUrl):
self.localDirectoryMonitor.start { result in
switch result {
case .failure(let error):
self.stopSynchronization()
self.processError(error)
case .success(let localDirectoryUrl):
self.fileWriter = SynchronizationFileWriter(fileManager: self.fileManager,
@ -124,13 +134,30 @@ private extension CloudStorageManager {
}
}
func stopSynchronization() {
func stopSynchronization(withError error: Error? = nil) {
LOG(.debug, "Stop synchronization")
localDirectoryMonitor.stop()
cloudDirectoryMonitor.stop()
synchronizationError = nil
fileWriter = nil
synchronizationStateManager.resetState()
if let error {
settings.setICLoudSynchronizationEnabled(false)
synchronizationError = error
MWMAlertViewController.activeAlert().presentDefaultAlert(withTitle: L("icloud_synchronization_error_alert_title"),
message: L("icloud_synchronization_error_alert_message"),
rightButtonTitle: L("report_a_bug"),
leftButtonTitle: L("cancel"),
rightButtonAction: {
UIApplication.shared.showLoadingOverlay {
let logFileURL = Logger.getLogFileURL()
UIApplication.shared.hideLoadingOverlay {
MailComposer.default.sendEmailWith(header: "Organic Maps Bugreport",
toRecipients: [AboutInfo.reportABug.link!],
attachmentFileURL: logFileURL)
}
}
})
}
}
func pauseSynchronization() {
@ -228,53 +255,60 @@ private extension CloudStorageManager {
func writingResultHandler(for event: OutgoingEvent) -> WritingResultCompletionHandler {
return { [weak self] result in
guard let self else { return }
DispatchQueue.main.async {
switch result {
case .success:
// Mark that initial synchronization is finished.
if case .didFinishInitialSynchronization = event {
UserDefaults.standard.set(true, forKey: kUDDidFinishInitialCloudSynchronization)
}
case .reloadCategoriesAtURLs(let urls):
urls.forEach { self.bookmarksManager.reloadCategory(atFilePath: $0.path) }
case .deleteCategoriesAtURLs(let urls):
urls.forEach { self.bookmarksManager.deleteCategory(atFilePath: $0.path) }
case .failure(let error):
self.processError(error)
switch result {
case .success:
// Mark that initial synchronization is finished.
if case .didFinishInitialSynchronization = event {
UserDefaults.standard.set(true, forKey: kUDDidFinishInitialCloudSynchronization)
}
case .reloadCategoriesAtURLs(let urls):
DispatchQueue.main.async {
urls.forEach { self.bookmarksManager.reloadCategory(atFilePath: $0.path) }
}
case .deleteCategoriesAtURLs(let urls):
DispatchQueue.main.async {
urls.forEach { self.bookmarksManager.deleteCategory(atFilePath: $0.path) }
}
case .failure(let error):
self.processError(error)
}
}
}
// MARK: - Error handling
func processError(_ error: Error) {
if let synchronizationError = error as? SynchronizationError {
LOG(.debug, "Synchronization error: \(error.localizedDescription)")
switch synchronizationError {
case .fileUnavailable: break
case .fileNotUploadedDueToQuota: break
case .ubiquityServerNotAvailable: break
case .iCloudIsNotAvailable: fallthrough
case .failedToOpenLocalDirectoryFileDescriptor: fallthrough
case .failedToRetrieveLocalDirectoryContent: fallthrough
case .containerNotFound:
switch error {
case let syncError as SynchronizationError:
switch syncError {
case .fileUnavailable,
.fileNotUploadedDueToQuota,
.ubiquityServerNotAvailable:
LOG(.warning, "Synchronization Warning: \(syncError.localizedDescription)")
synchronizationError = syncError
case .iCloudIsNotAvailable:
LOG(.warning, "Synchronization Warning: \(error.localizedDescription)")
stopSynchronization()
case .failedToOpenLocalDirectoryFileDescriptor,
.failedToRetrieveLocalDirectoryContent,
.containerNotFound,
.failedToCreateMetadataItem,
.failedToRetrieveMetadataQueryContent:
LOG(.error, "Synchronization Error: \(error.localizedDescription)")
stopSynchronization(withError: error)
}
self.synchronizationError = synchronizationError
} else {
// TODO: Handle non-synchronization errors
LOG(.debug, "Non-synchronization error: \(error.localizedDescription)")
default:
LOG(.debug, "Non-synchronization Error: \(error.localizedDescription)")
stopSynchronization(withError: error)
}
}
}
// MARK: - CloudStorageManger Observing
extension CloudStorageManager {
func addObserver(_ observer: AnyObject, onErrorCompletionHandler: @escaping (NSError?) -> Void) {
func addObserver(_ observer: AnyObject, synchronizationStateDidChangeHandler: @escaping (CloudStorageSynchronizationState) -> Void) {
let id = ObjectIdentifier(observer)
observers[id] = Observation(observer: observer, onErrorCompletionHandler:onErrorCompletionHandler)
// Notify the new observer immediately to handle initial state.
observers[id]?.onErrorCompletionHandler?(synchronizationError as NSError?)
observers[id] = Observation(observer: observer, onSynchronizationStateDidChangeHandler: synchronizationStateDidChangeHandler)
notifyObserversOnSynchronizationError(synchronizationError)
}
func removeObserver(_ observer: AnyObject) {
@ -282,10 +316,13 @@ extension CloudStorageManager {
observers.removeValue(forKey: id)
}
private func notifyObserversOnSynchronizationError(_ error: SynchronizationError?) {
self.observers.removeUnreachable().forEach { _, observable in
private func notifyObserversOnSynchronizationError(_ error: Error?) {
let state = CloudStorageSynchronizationState(isAvailable: cloudDirectoryMonitor.isCloudAvailable(),
isOn: settings.iCLoudSynchronizationEnabled(),
error: error as? NSError)
observers.removeUnreachable().forEach { _, observable in
DispatchQueue.main.async {
observable.onErrorCompletionHandler?(error as NSError?)
observable.onSynchronizationStateDidChangeHandler?(state)
}
}
}

View file

@ -53,7 +53,9 @@ final class DefaultLocalDirectoryMonitor: LocalDirectoryMonitor {
self.fileManager = fileManager
self.directory = directory
self.fileType = fileType
try fileManager.createDirectoryIfNeeded(at: directory)
if !fileManager.fileExists(atPath: directory.path) {
try fileManager.createDirectory(at: directory, withIntermediateDirectories: true)
}
}
// MARK: - Public methods
@ -143,31 +145,23 @@ final class DefaultLocalDirectoryMonitor: LocalDirectoryMonitor {
dispatchSourceDebounceState = .started(source: source)
do {
let files = try fileManager.contentsOfDirectory(at: directory, includingPropertiesForKeys: [], options: [.skipsHiddenFiles], fileExtension: fileType.fileExtension)
let contents = files.compactMap { url in
do {
let metadataItem = try LocalMetadataItem(fileUrl: url)
return metadataItem
} catch {
delegate?.didReceiveLocalMonitorError(error)
return nil
}
}
let contentMetadataItems = LocalContents(contents)
let files = try fileManager
.contentsOfDirectory(at: directory, includingPropertiesForKeys: [.contentModificationDateKey], options: [.skipsHiddenFiles])
.filter { $0.pathExtension == fileType.fileExtension }
let contents: LocalContents = try files.map { try LocalMetadataItem(fileUrl: $0) }
if !didFinishGatheringIsCalled {
didFinishGatheringIsCalled = true
LOG(.debug, "LocalMonitor: didFinishGathering called.")
LOG(.debug, "LocalMonitor: contentMetadataItems count: \(contentMetadataItems.count)")
delegate?.didFinishGathering(contents: contentMetadataItems)
LOG(.debug, "LocalMonitor: contentMetadataItems count: \(contents.count)")
delegate?.didFinishGathering(contents: contents)
} else {
LOG(.debug, "LocalMonitor: didUpdate called.")
LOG(.debug, "LocalMonitor: contentMetadataItems count: \(contentMetadataItems.count)")
delegate?.didUpdate(contents: contentMetadataItems)
LOG(.debug, "LocalMonitor: contentMetadataItems count: \(contents.count)")
delegate?.didUpdate(contents: contents)
}
} catch {
LOG(.debug, "\(error)")
delegate?.didReceiveLocalMonitorError(SynchronizationError.failedToRetrieveLocalDirectoryContent)
delegate?.didReceiveLocalMonitorError(error)
}
}
@ -202,15 +196,4 @@ private extension FileManager {
}
return dispatchSource
}
func createDirectoryIfNeeded(at url: URL) throws {
if !fileExists(atPath: url.path) {
try createDirectory(at: url, withIntermediateDirectories: true)
}
}
func contentsOfDirectory(at url: URL, includingPropertiesForKeys keys: [URLResourceKey]?, options: FileManager.DirectoryEnumerationOptions, fileExtension: String) throws -> [URL] {
let files = try contentsOfDirectory(at: url, includingPropertiesForKeys: keys, options: options)
return files.filter { $0.pathExtension == fileExtension }
}
}

View file

@ -1,28 +1,19 @@
protocol MetadataItem: Equatable, Hashable {
var fileName: String { get }
var fileUrl: URL { get }
var fileSize: Int { get }
var contentType: String { get }
var creationDate: TimeInterval { get }
var lastModificationDate: TimeInterval { get }
}
struct LocalMetadataItem: MetadataItem {
let fileName: String
let fileUrl: URL
let fileSize: Int
let contentType: String
let creationDate: TimeInterval
let lastModificationDate: TimeInterval
}
struct CloudMetadataItem: MetadataItem {
let fileName: String
let fileUrl: URL
let fileSize: Int
let contentType: String
var isDownloaded: Bool
let creationDate: TimeInterval
var lastModificationDate: TimeInterval
var isRemoved: Bool
let downloadingError: NSError?
@ -31,36 +22,14 @@ struct CloudMetadataItem: MetadataItem {
}
extension LocalMetadataItem {
init(metadataItem: NSMetadataItem) throws {
guard let fileName = metadataItem.value(forAttribute: NSMetadataItemFSNameKey) as? String,
let fileUrl = metadataItem.value(forAttribute: NSMetadataItemURLKey) as? URL,
let fileSize = metadataItem.value(forAttribute: NSMetadataItemFSSizeKey) as? Int,
let contentType = metadataItem.value(forAttribute: NSMetadataItemContentTypeKey) as? String,
let creationDate = (metadataItem.value(forAttribute: NSMetadataItemFSCreationDateKey) as? Date)?.timeIntervalSince1970.rounded(.down),
let lastModificationDate = (metadataItem.value(forAttribute: NSMetadataItemFSContentChangeDateKey) as? Date)?.timeIntervalSince1970.rounded(.down) else {
throw NSError(domain: "LocalMetadataItem", code: 0, userInfo: [NSLocalizedDescriptionKey: "Failed to initialize LocalMetadataItem from NSMetadataItem"])
}
self.fileName = fileName
self.fileUrl = fileUrl
self.fileSize = fileSize
self.contentType = contentType
self.creationDate = creationDate
self.lastModificationDate = lastModificationDate
}
init(fileUrl: URL) throws {
let resources = try fileUrl.resourceValues(forKeys: [.fileSizeKey, .typeIdentifierKey, .contentModificationDateKey, .creationDateKey])
guard let fileSize = resources.fileSize,
let contentType = resources.typeIdentifier,
let creationDate = resources.creationDate?.timeIntervalSince1970.rounded(.down),
let lastModificationDate = resources.contentModificationDate?.timeIntervalSince1970.rounded(.down) else {
throw NSError(domain: "LocalMetadataItem", code: 0, userInfo: [NSLocalizedDescriptionKey: "Failed to initialize LocalMetadataItem from URL"])
let resources = try fileUrl.resourceValues(forKeys: [.contentModificationDateKey])
guard let lastModificationDate = resources.contentModificationDate?.roundedTime else {
LOG(.error, "Failed to initialize LocalMetadataItem from URL's resources: \(resources)")
throw SynchronizationError.failedToCreateMetadataItem
}
self.fileName = fileUrl.lastPathComponent
self.fileUrl = fileUrl
self.fileSize = fileSize
self.contentType = contentType
self.creationDate = creationDate
self.lastModificationDate = lastModificationDate
}
@ -70,48 +39,44 @@ extension LocalMetadataItem {
}
extension CloudMetadataItem {
init(metadataItem: NSMetadataItem) throws {
init(metadataItem: NSMetadataItem, isRemoved: Bool = false) throws {
guard let fileName = metadataItem.value(forAttribute: NSMetadataItemFSNameKey) as? String,
let fileUrl = metadataItem.value(forAttribute: NSMetadataItemURLKey) as? URL,
let fileSize = metadataItem.value(forAttribute: NSMetadataItemFSSizeKey) as? Int,
let contentType = metadataItem.value(forAttribute: NSMetadataItemContentTypeKey) as? String,
let downloadStatus = metadataItem.value(forAttribute: NSMetadataUbiquitousItemDownloadingStatusKey) as? String,
let creationDate = (metadataItem.value(forAttribute: NSMetadataItemFSCreationDateKey) as? Date)?.timeIntervalSince1970.rounded(.down),
let lastModificationDate = (metadataItem.value(forAttribute: NSMetadataItemFSContentChangeDateKey) as? Date)?.timeIntervalSince1970.rounded(.down),
let lastModificationDate = (metadataItem.value(forAttribute: NSMetadataItemFSContentChangeDateKey) as? Date)?.roundedTime,
let hasUnresolvedConflicts = metadataItem.value(forAttribute: NSMetadataUbiquitousItemHasUnresolvedConflictsKey) as? Bool else {
throw NSError(domain: "CloudMetadataItem", code: 0, userInfo: [NSLocalizedDescriptionKey: "Failed to initialize CloudMetadataItem from NSMetadataItem"])
let allAttributes = metadataItem.values(forAttributes: metadataItem.attributes)
LOG(.error, "Failed to initialize CloudMetadataItem from NSMetadataItem: \(allAttributes.debugDescription)")
throw SynchronizationError.failedToCreateMetadataItem
}
self.fileName = fileName
self.fileUrl = fileUrl
self.fileSize = fileSize
self.contentType = contentType
self.isDownloaded = downloadStatus == NSMetadataUbiquitousItemDownloadingStatusCurrent
self.creationDate = creationDate
self.lastModificationDate = lastModificationDate
self.isRemoved = CloudMetadataItem.isInTrash(fileUrl)
self.isRemoved = isRemoved || CloudMetadataItem.isInTrash(fileUrl)
self.hasUnresolvedConflicts = hasUnresolvedConflicts
self.downloadingError = metadataItem.value(forAttribute: NSMetadataUbiquitousItemDownloadingErrorKey) as? NSError
self.uploadingError = metadataItem.value(forAttribute: NSMetadataUbiquitousItemUploadingErrorKey) as? NSError
}
init(fileUrl: URL) throws {
let resources = try fileUrl.resourceValues(forKeys: [.nameKey, .fileSizeKey, .typeIdentifierKey, .contentModificationDateKey, .creationDateKey, .ubiquitousItemDownloadingStatusKey, .ubiquitousItemHasUnresolvedConflictsKey, .ubiquitousItemDownloadingErrorKey, .ubiquitousItemUploadingErrorKey])
guard let fileSize = resources.fileSize,
let contentType = resources.typeIdentifier,
let creationDate = resources.creationDate?.timeIntervalSince1970.rounded(.down),
let downloadStatus = resources.ubiquitousItemDownloadingStatus,
let lastModificationDate = resources.contentModificationDate?.timeIntervalSince1970.rounded(.down),
init(fileUrl: URL, isRemoved: Bool = false) throws {
let resources = try fileUrl.resourceValues(forKeys: [.nameKey,
.contentModificationDateKey,
.ubiquitousItemDownloadingStatusKey,
.ubiquitousItemHasUnresolvedConflictsKey,
.ubiquitousItemDownloadingErrorKey,
.ubiquitousItemUploadingErrorKey])
guard let downloadStatus = resources.ubiquitousItemDownloadingStatus,
let lastModificationDate = resources.contentModificationDate?.roundedTime,
let hasUnresolvedConflicts = resources.ubiquitousItemHasUnresolvedConflicts else {
throw NSError(domain: "CloudMetadataItem", code: 0, userInfo: [NSLocalizedDescriptionKey: "Failed to initialize CloudMetadataItem from NSMetadataItem"])
LOG(.error, "Failed to initialize CloudMetadataItem from \(fileUrl) resources: \(resources.allValues)")
throw SynchronizationError.failedToCreateMetadataItem
}
self.fileName = fileUrl.lastPathComponent
self.fileUrl = fileUrl
self.fileSize = fileSize
self.contentType = contentType
self.isDownloaded = downloadStatus.rawValue == NSMetadataUbiquitousItemDownloadingStatusCurrent
self.creationDate = creationDate
self.lastModificationDate = lastModificationDate
self.isRemoved = CloudMetadataItem.isInTrash(fileUrl)
self.isRemoved = isRemoved || CloudMetadataItem.isInTrash(fileUrl)
self.hasUnresolvedConflicts = hasUnresolvedConflicts
self.downloadingError = resources.ubiquitousItemDownloadingError
self.uploadingError = resources.ubiquitousItemUploadingError
@ -154,3 +119,9 @@ extension Array where Element == CloudMetadataItem {
filter { $0.hasUnresolvedConflicts == hasUnresolvedConflicts }
}
}
fileprivate extension Date {
var roundedTime: TimeInterval {
timeIntervalSince1970.rounded(.down)
}
}

View file

@ -6,6 +6,8 @@
case containerNotFound
case failedToOpenLocalDirectoryFileDescriptor
case failedToRetrieveLocalDirectoryContent
case failedToCreateMetadataItem
case failedToRetrieveMetadataQueryContent
}
extension SynchronizationError: LocalizedError {
@ -21,13 +23,17 @@ extension SynchronizationError: LocalizedError {
return "Failed to open local directory file descriptor"
case .failedToRetrieveLocalDirectoryContent:
return "Failed to retrieve local directory content"
case .failedToCreateMetadataItem:
return "Failed to create metadata item."
case .failedToRetrieveMetadataQueryContent:
return "Failed to retrieve NSMetadataQuery content."
}
}
}
extension SynchronizationError {
static func fromError(_ error: Error) -> SynchronizationError? {
let nsError = error as NSError
extension Error {
var ubiquitousError: SynchronizationError? {
let nsError = self as NSError
switch nsError.code {
// NSURLUbiquitousItemDownloadingErrorKey contains an error with this code when the item has not been uploaded to iCloud by the other devices yet
case NSUbiquitousFileUnavailableError:

View file

@ -215,10 +215,10 @@ final class DefaultSynchronizationStateManager: SynchronizationStateManager {
private static func getItemsWithErrors(_ cloudContents: CloudContents) -> [SynchronizationError] {
cloudContents.reduce(into: [SynchronizationError](), { partialResult, cloudItem in
if let downloadingError = cloudItem.downloadingError, let synchronizationError = SynchronizationError.fromError(downloadingError) {
if let downloadingError = cloudItem.downloadingError, let synchronizationError = downloadingError.ubiquitousError {
partialResult.append(synchronizationError)
}
if let uploadingError = cloudItem.uploadingError, let synchronizationError = SynchronizationError.fromError(uploadingError) {
if let uploadingError = cloudItem.uploadingError, let synchronizationError = uploadingError.ubiquitousError {
partialResult.append(synchronizationError)
}
})

View file

@ -141,65 +141,34 @@ class iCloudDocumentsDirectoryMonitor: NSObject, CloudDirectoryMonitor {
return metadataQuery
}
class func getContentsFromNotification(_ notification: Notification, _ onError: (Error) -> Void) -> CloudContents {
class func getContentsFromNotification(_ notification: Notification) throws -> CloudContents {
guard let metadataQuery = notification.object as? NSMetadataQuery,
let metadataItems = metadataQuery.results as? [NSMetadataItem] else {
return []
throw SynchronizationError.failedToRetrieveMetadataQueryContent
}
let cloudMetadataItems = CloudContents(metadataItems.compactMap { item in
do {
return try CloudMetadataItem(metadataItem: item)
} catch {
onError(error)
return nil
}
})
return cloudMetadataItems
return try metadataItems.map { try CloudMetadataItem(metadataItem: $0) }
}
// There are no ways to retrieve the content of iCloud's .Trash directory on the macOS because it uses different file system and place trashed content in the /Users/<user_name>/.Trash which cannot be observed without access.
// When we get a new notification and retrieve the metadata from the object the actual list of items in iOS contains both current and deleted files (which is in .Trash/ directory now) but on macOS we only have absence of the file. So there are no way to get list of deleted items on macOS on didFinishGathering state.
// Due to didUpdate state we can get the list of deleted items on macOS from the userInfo property but cannot get their new url.
class func getTrashContentsFromNotification(_ notification: Notification, _ onError: (Error) -> Void) -> CloudContents {
guard let removedItems = notification.userInfo?[NSMetadataQueryUpdateRemovedItemsKey] as? [NSMetadataItem] else { return [] }
return CloudContents(removedItems.compactMap { metadataItem in
do {
var item = try CloudMetadataItem(metadataItem: metadataItem)
// on macOS deleted file will not be in the ./Trash directory, but it doesn't mean that it is not removed because it is placed in the NSMetadataQueryUpdateRemovedItems array.
item.isRemoved = true
return item
} catch {
onError(error)
return nil
}
})
class func getTrashContentsFromNotification(_ notification: Notification) throws -> CloudContents {
guard let removedItems = notification.userInfo?[NSMetadataQueryUpdateRemovedItemsKey] as? [NSMetadataItem] else {
return []
}
return try removedItems.map { try CloudMetadataItem(metadataItem: $0, isRemoved: true) }
}
class func getTrashedContentsFromTrashDirectory(fileManager: FileManager, ubiquitousDocumentsDirectory: URL?, onError: (Error) -> Void) -> CloudContents {
class func getTrashedContentsFromTrashDirectory(fileManager: FileManager, ubiquitousDocumentsDirectory: URL) throws -> CloudContents {
// There are no ways to retrieve the content of iCloud's .Trash directory on macOS.
if #available(iOS 14.0, *), ProcessInfo.processInfo.isiOSAppOnMac {
return []
}
// On iOS we can get the list of deleted items from the .Trash directory but only when iCloud is enabled.
guard let ubiquitousDocumentsDirectory,
let trashDirectoryUrl = try? fileManager.trashDirectoryUrl(for: ubiquitousDocumentsDirectory),
let removedItems = try? fileManager.contentsOfDirectory(at: trashDirectoryUrl,
includingPropertiesForKeys: [.isDirectoryKey],
options: [.skipsPackageDescendants, .skipsSubdirectoryDescendants]) else {
return []
}
let removedCloudMetadataItems = CloudContents(removedItems.compactMap { url in
do {
var item = try CloudMetadataItem(fileUrl: url)
item.isRemoved = true
return item
} catch {
onError(error)
return nil
}
})
return removedCloudMetadataItems
let trashDirectoryUrl = try fileManager.trashDirectoryUrl(for: ubiquitousDocumentsDirectory)
let removedItems = try fileManager.contentsOfDirectory(at: trashDirectoryUrl,
includingPropertiesForKeys: [.isDirectoryKey],
options: [.skipsPackageDescendants, .skipsSubdirectoryDescendants])
return try removedItems.map { try CloudMetadataItem(fileUrl: $0, isRemoved: true) }
}
}
@ -236,16 +205,18 @@ private extension iCloudDocumentsDirectoryMonitor {
}
@objc func queryDidFinishGathering(_ notification: Notification) {
guard isCloudAvailable() else { return }
guard isCloudAvailable(), let ubiquitousDocumentsDirectory else { return }
metadataQuery?.disableUpdates()
LOG(.debug, "iCloudMonitor: Query did finish gathering")
let contents = Self.getContentsFromNotification(notification, metadataQueryErrorHandler)
let trashedContents = Self.getTrashedContentsFromTrashDirectory(fileManager: fileManager,
ubiquitousDocumentsDirectory: ubiquitousDocumentsDirectory,
onError: metadataQueryErrorHandler)
LOG(.debug, "iCloudMonitor: Cloud contents count: \(contents.count)")
LOG(.debug, "iCloudMonitor: Trashed contents count: \(trashedContents.count)")
delegate?.didFinishGathering(contents: contents + trashedContents)
do {
let contents = try Self.getContentsFromNotification(notification)
let trashedContents = try Self.getTrashedContentsFromTrashDirectory(fileManager: fileManager, ubiquitousDocumentsDirectory: ubiquitousDocumentsDirectory)
LOG(.debug, "iCloudMonitor: Cloud contents count: \(contents.count)")
LOG(.debug, "iCloudMonitor: Trashed contents count: \(trashedContents.count)")
delegate?.didFinishGathering(contents: contents + trashedContents)
} catch {
delegate?.didReceiveCloudMonitorError(error)
}
metadataQuery?.enableUpdates()
}
@ -253,17 +224,15 @@ private extension iCloudDocumentsDirectoryMonitor {
guard isCloudAvailable() else { return }
metadataQuery?.disableUpdates()
LOG(.debug, "iCloudMonitor: Query did update")
let contents = Self.getContentsFromNotification(notification, metadataQueryErrorHandler)
let trashedContents = Self.getTrashContentsFromNotification(notification, metadataQueryErrorHandler)
LOG(.debug, "iCloudMonitor: Cloud contents count: \(contents.count)")
LOG(.debug, "iCloudMonitor: Trashed contents count: \(trashedContents.count)")
delegate?.didUpdate(contents: contents + trashedContents)
do {
let contents = try Self.getContentsFromNotification(notification)
let trashedContents = try Self.getTrashContentsFromNotification(notification)
LOG(.debug, "iCloudMonitor: Cloud contents count: \(contents.count)")
LOG(.debug, "iCloudMonitor: Trashed contents count: \(trashedContents.count)")
delegate?.didUpdate(contents: contents + trashedContents)
} catch {
delegate?.didReceiveCloudMonitorError(error)
}
metadataQuery?.enableUpdates()
}
private var metadataQueryErrorHandler: (Error) -> Void {
{ [weak self] error in
self?.delegate?.didReceiveCloudMonitorError(error)
}
}
}

View file

@ -1,26 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "ic_nav_bar_close.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "ic_nav_bar_close@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "ic_nav_bar_close@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 391 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 691 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -1,26 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "Vector.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "Vector@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "Vector@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 311 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 674 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "ic_28px_circle_close.svg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}

View file

@ -0,0 +1,3 @@
<svg width="28" height="28" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.049 8c-.305 0-.555.1-.752.297A1.018 1.018 0 0 0 8 9.049c0 .305.1.556.297.754L12.494 14l-4.197 4.197a1.022 1.022 0 0 0-.297.754c0 .305.1.555.297.752.197.197.447.297.752.297s.556-.1.754-.297L14 15.506l4.197 4.197c.198.197.45.297.754.297.305 0 .555-.1.752-.297.197-.197.297-.447.297-.752s-.1-.556-.297-.754L15.506 14l4.197-4.197c.197-.198.297-.45.297-.754 0-.305-.1-.555-.297-.752A1.018 1.018 0 0 0 18.951 8c-.305 0-.556.1-.754.297L14 12.494 9.803 8.297A1.022 1.022 0 0 0 9.049 8Z" style="fill:#000"/>
</svg>

After

Width:  |  Height:  |  Size: 596 B

View file

@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "ic_open_in_app.svg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}

View file

@ -0,0 +1,4 @@
<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg">
<path style="fill:#f0f;stroke:#000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;paint-order:fill markers stroke" d="M20 9V4m-5 0h5m-9 9 9-9"/>
<path style="fill:none;fill-opacity:.5;stroke:#000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;paint-order:fill markers stroke" d="M11 6H6a2 2 135 0 0-2 2v10a2 2 45 0 0 2 2h10a2 2 135 0 0 2-2v-5"/>
</svg>

After

Width:  |  Height:  |  Size: 487 B

View file

@ -1,17 +1,8 @@
{
"images" : [
{
"filename" : "ic_wheelchair_white.svg",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
"filename" : "ic_placepage_wheelchair.svg",
"idiom" : "universal"
}
],
"info" : {

View file

@ -0,0 +1,5 @@
<svg height="24" width="24" fill="#e8eaed" xmlns="http://www.w3.org/2000/svg">
<circle cx="17" cy="5" r="2" fill="#000"/>
<path d="M8.76 7c-.81 0-1.54.47-1.87 1.2l-.28.76c-.21.56.11 1.17.68 1.33.49.14 1-.11 1.2-.58l.3-.71H11l-1.83 4.1c-.6 1.33.39 2.9 1.85 2.9h4.843l-.84 3.783a1 1 0 1 0 1.954.434l1-4.5a1 1 0 0 0 .015-.34A2.004 2.004 0 0 0 16 13.5h-1.86l1.67-3.67C16.42 8.5 15.44 7 13.96 7Z" fill="#000"/>
<path d="M8 22c-1.383 0-2.563-.488-3.537-1.462C3.487 19.562 3 18.383 3 17c0-1.15.358-2.175 1.075-3.075a4.996 4.996 0 0 1 2.75-1.775.948.948 0 0 1 .75.113.963.963 0 0 1 .45.612.948.948 0 0 1-.113.75.963.963 0 0 1-.612.45 2.937 2.937 0 0 0-1.662 1.063A2.934 2.934 0 0 0 5 17c0 .833.292 1.542.875 2.125A2.893 2.893 0 0 0 8 20c.683 0 1.292-.208 1.825-.625.533-.417.892-.95 1.075-1.6.083-.267.242-.47.475-.613a.879.879 0 0 1 .75-.087c.267.083.47.242.612.475.142.233.171.483.088.75a4.905 4.905 0 0 1-1.788 2.675A4.863 4.863 0 0 1 8 22Z" fill="#000"/>
</svg>

After

Width:  |  Height:  |  Size: 964 B

View file

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="48" height="48" version="1.1" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
<path d="m30.327 12c-1.3184 0-2.388 1.0669-2.388 2.3851 0 1.3183 1.0696 2.388 2.388 2.388 1.3182 0 2.388-1.0696 2.388-2.388 0-1.3182-1.0697-2.3851-2.388-2.3851zm-8.1593 1.156c-0.36747-0.01946-0.73901 0.10069-1.0266 0.35721l-3.1895 2.8464c-0.5877 0.5242-0.63955 1.4261-0.1153 2.0138 0.52428 0.58766 1.4262 0.63968 2.0138 0.1153l2.419-2.1572 1.9969 1.1616-3.527 4.0164c-1.4633 0.23866-2.7803 0.91509-3.8139 1.8873l1.8422 1.8422c0.83304-0.75747 1.9408-1.2207 3.1529-1.2207 2.5851 0 4.6857 2.1033 4.6857 4.6887 0 1.212-0.46048 2.3198-1.2178 3.1529l1.8422 1.8422c1.2276-1.3049 1.98-3.0622 1.98-4.9952 0-1.1516-0.26712-2.2407-0.74253-3.2092l1.9239-0.10689-0.46688 5.7433c-0.06402 0.78493 0.52009 1.4716 1.3051 1.5357 0.03929 0.0034 0.07929 0.0057 0.11812 0.0057 0.73497 0 1.3597-0.56526 1.4204-1.3107l0.59909-7.3719c0.03354-0.41084-0.11182-0.81586-0.39939-1.1109-0.2877-0.2952-0.68894-0.45282-1.0997-0.43033l-4.9726 0.27845 2.7367-3.1164c0.38979-0.44394 0.50227-1.0351 0.35159-1.5639-0.07955-0.36233-0.30137-0.69222-0.63846-0.90283-0.01024-0.0075-6.5394-3.8027-6.5394-3.8027-0.19997-0.1161-0.41798-0.17665-0.63846-0.1884zm-5.9909 11.059c-0.97051 1.2386-1.5497 2.7961-1.5497 4.4918 0 4.0275 3.2654 7.293 7.293 7.293 1.6955 0 3.2532-0.57913 4.4918-1.5497l-1.862-1.8647c-0.75056 0.51051-1.6556 0.81004-2.6297 0.81004-2.5851 0-4.6887-2.1033-4.6887-4.6887 0-0.97429 0.29657-1.8792 0.80722-2.6297l-1.862-1.862z" fill="#fff" stroke-width=".92306"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "ic_28px_circle_share.svg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}

View file

@ -0,0 +1,4 @@
<svg width="28" height="28" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9 10c-.55 0-1.02.196-1.412.588A1.925 1.925 0 0 0 7 12v8c0 .55.196 1.02.588 1.412C7.98 21.804 8.45 22 9 22h10c.55 0 1.02-.196 1.412-.588.392-.392.588-.862.588-1.412v-8c0-.55-.196-1.02-.588-1.412A1.925 1.925 0 0 0 19 10h-2a.968.968 0 0 0-.713.287A.968.968 0 0 0 16 11c0 .283.095.521.287.713.192.192.43.287.713.287h2v8H9v-8h2a.968.968 0 0 0 .713-.287A.968.968 0 0 0 12 11a.968.968 0 0 0-.287-.713A.968.968 0 0 0 11 10Z" style="fill:#000"/>
<path d="M14 4.4c-.267 0-.5.1-.7.3l-2.6 2.6c-.2.2-.296.433-.288.7.008.267.104.5.287.7.2.2.435.303.701.312.267.008.5-.087.7-.287l.9-.9V15c0 .283.095.521.287.713.192.192.43.287.713.287a.968.968 0 0 0 .713-.287A.968.968 0 0 0 15 15V7.824l.875.875c.2.2.438.301.713.301.275 0 .513-.1.713-.3.183-.2.273-.437.273-.712a.931.931 0 0 0-.273-.687l-2.602-2.602A.956.956 0 0 0 14 4.4Z" style="fill:#000"/>
</svg>

After

Width:  |  Height:  |  Size: 927 B

View file

@ -1353,6 +1353,12 @@
/* Title for the "Enable iCloud Syncronization" alert's "Backup" action button. */
"backup" = "دعم";
/* Title for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_title" = "فشل مزامنة iCloud";
/* Message for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_message" = "هل ترغب في إرسال تقرير عن الأخطاء إلى المطورين؟";
/* iCloud error message: Failed to synchronize due to connection error */
"icloud_synchronization_error_connection_error" = "خطأ: فشل المزامنة بسبب خطأ في الاتصال";
@ -1362,6 +1368,9 @@
/* iCloud error message: iCloud is not available */
"icloud_synchronization_error_cloud_is_unavailable" = "خطأ: iCloud غير متوفر";
/* Title for the "Open In Another App" buton on the PlacePage. */
"open_in_app" = "فتح في تطبيق آخر";
/********** Types **********/

View file

@ -1353,6 +1353,12 @@
/* Title for the "Enable iCloud Syncronization" alert's "Backup" action button. */
"backup" = "Yedəkləmə";
/* Title for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_title" = "iCloud sinxronizasiya uğursuzluğu";
/* Message for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_message" = "Tərtibatçılara səhv hesabatı göndərmək istərdinizmi?";
/* iCloud error message: Failed to synchronize due to connection error */
"icloud_synchronization_error_connection_error" = "Xəta: Bağlantı xətası səbəbindən sinxronizasiya alınmadı";
@ -1362,6 +1368,9 @@
/* iCloud error message: iCloud is not available */
"icloud_synchronization_error_cloud_is_unavailable" = "Xəta: iCloud mövcud deyil";
/* Title for the "Open In Another App" buton on the PlacePage. */
"open_in_app" = "Başqa Tətbiqdə Açın";
/********** Types **********/

View file

@ -1353,6 +1353,12 @@
/* Title for the "Enable iCloud Syncronization" alert's "Backup" action button. */
"backup" = "Рэзервовае капіраванне";
/* Title for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_title" = "Збой сінхранізацыі iCloud";
/* Message for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_message" = "Хочаце адправіць справаздачу пра памылку распрацоўшчыкам?";
/* iCloud error message: Failed to synchronize due to connection error */
"icloud_synchronization_error_connection_error" = "Памылка: не ўдалося сінхранізаваць з-за памылкі злучэння";
@ -1362,6 +1368,9 @@
/* iCloud error message: iCloud is not available */
"icloud_synchronization_error_cloud_is_unavailable" = "Памылка: iCloud недаступны";
/* Title for the "Open In Another App" buton on the PlacePage. */
"open_in_app" = "Адкрыць у іншай прыладзе";
/********** Types **********/

View file

@ -1353,6 +1353,12 @@
/* Title for the "Enable iCloud Syncronization" alert's "Backup" action button. */
"backup" = "Резервно копие";
/* Title for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_title" = "Неуспешна синхронизация на iCloud";
/* Message for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_message" = "Искате ли да изпратите доклад за грешка на разработчиците?";
/* iCloud error message: Failed to synchronize due to connection error */
"icloud_synchronization_error_connection_error" = "Грешка: Не успя да се синхронизира поради грешка при свързването";
@ -1362,6 +1368,9 @@
/* iCloud error message: iCloud is not available */
"icloud_synchronization_error_cloud_is_unavailable" = "Грешка: iCloud не е наличен";
/* Title for the "Open In Another App" buton on the PlacePage. */
"open_in_app" = "Отваряне в друго приложение";
/********** Types **********/

View file

@ -1353,6 +1353,12 @@
/* Title for the "Enable iCloud Syncronization" alert's "Backup" action button. */
"backup" = "Còpia de seguretat";
/* Title for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_title" = "Error de sincronització d'iCloud";
/* Message for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_message" = "Voleu enviar un informe d'error als desenvolupadors?";
/* iCloud error message: Failed to synchronize due to connection error */
"icloud_synchronization_error_connection_error" = "Error: no s'ha pogut sincronitzar a causa d'un error de connexió";
@ -1362,6 +1368,9 @@
/* iCloud error message: iCloud is not available */
"icloud_synchronization_error_cloud_is_unavailable" = "Error: iCloud no està disponible";
/* Title for the "Open In Another App" buton on the PlacePage. */
"open_in_app" = "Obre en una altra aplicació";
/********** Types **********/

View file

@ -1353,6 +1353,12 @@
/* Title for the "Enable iCloud Syncronization" alert's "Backup" action button. */
"backup" = "Záloha";
/* Title for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_title" = "Selhání synchronizace iCloudu";
/* Message for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_message" = "Chcete poslat vývojářům hlášení o chybě?";
/* iCloud error message: Failed to synchronize due to connection error */
"icloud_synchronization_error_connection_error" = "Chyba: Nepodařilo se synchronizovat z důvodu chyby připojení";
@ -1362,6 +1368,9 @@
/* iCloud error message: iCloud is not available */
"icloud_synchronization_error_cloud_is_unavailable" = "Chyba: iCloud není k dispozici";
/* Title for the "Open In Another App" buton on the PlacePage. */
"open_in_app" = "Otevřít v jiné aplikaci";
/********** Types **********/

View file

@ -1353,6 +1353,12 @@
/* Title for the "Enable iCloud Syncronization" alert's "Backup" action button. */
"backup" = "Sikkerhedskopiering";
/* Title for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_title" = "Fejl i iCloud-synkronisering";
/* Message for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_message" = "Vil du gerne sende en fejlrapport til udviklerne?";
/* iCloud error message: Failed to synchronize due to connection error */
"icloud_synchronization_error_connection_error" = "Fejl: Kunne ikke synkronisere på grund af forbindelsesfejl";
@ -1362,6 +1368,9 @@
/* iCloud error message: iCloud is not available */
"icloud_synchronization_error_cloud_is_unavailable" = "Fejl: iCloud er ikke tilgængelig";
/* Title for the "Open In Another App" buton on the PlacePage. */
"open_in_app" = "Åbn i en anden app";
/********** Types **********/

View file

@ -1353,6 +1353,12 @@
/* Title for the "Enable iCloud Syncronization" alert's "Backup" action button. */
"backup" = "Sicherung";
/* Title for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_title" = "iCloud-Synchronisierung fehlgeschlagen";
/* Message for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_message" = "Willst du einen Fehlerbericht an die Entwickler schicken?";
/* iCloud error message: Failed to synchronize due to connection error */
"icloud_synchronization_error_connection_error" = "Fehler: Synchronisierung aufgrund eines Verbindungsfehlers fehlgeschlagen";
@ -1362,6 +1368,9 @@
/* iCloud error message: iCloud is not available */
"icloud_synchronization_error_cloud_is_unavailable" = "Fehler: iCloud ist nicht verfügbar";
/* Title for the "Open In Another App" buton on the PlacePage. */
"open_in_app" = "In einer anderen App öffnen";
/********** Types **********/

View file

@ -1353,6 +1353,12 @@
/* Title for the "Enable iCloud Syncronization" alert's "Backup" action button. */
"backup" = "Δημιουργία αντιγράφων ασφαλείας";
/* Title for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_title" = "Αποτυχία συγχρονισμού iCloud";
/* Message for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_message" = "Θα θέλατε να στείλετε μια αναφορά σφάλματος στους προγραμματιστές;";
/* iCloud error message: Failed to synchronize due to connection error */
"icloud_synchronization_error_connection_error" = "Σφάλμα: Αποτυχία συγχρονισμού λόγω σφάλματος σύνδεσης";
@ -1362,6 +1368,9 @@
/* iCloud error message: iCloud is not available */
"icloud_synchronization_error_cloud_is_unavailable" = "Σφάλμα: Το iCloud δεν είναι διαθέσιμο";
/* Title for the "Open In Another App" buton on the PlacePage. */
"open_in_app" = "Άνοιγμα σε άλλη εφαρμογή";
/********** Types **********/

View file

@ -1353,6 +1353,12 @@
/* Title for the "Enable iCloud Syncronization" alert's "Backup" action button. */
"backup" = "Backup";
/* Title for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_title" = "iCloud synchronization failure";
/* Message for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_message" = "Would you like to send a bug report to the developers?";
/* iCloud error message: Failed to synchronize due to connection error */
"icloud_synchronization_error_connection_error" = "Error: Failed to synchronize due to connection error";
@ -1362,6 +1368,9 @@
/* iCloud error message: iCloud is not available */
"icloud_synchronization_error_cloud_is_unavailable" = "Error: iCloud is not available";
/* Title for the "Open In Another App" buton on the PlacePage. */
"open_in_app" = "Open In Another App";
/********** Types **********/

View file

@ -1353,6 +1353,12 @@
/* Title for the "Enable iCloud Syncronization" alert's "Backup" action button. */
"backup" = "Backup";
/* Title for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_title" = "iCloud synchronization failure";
/* Message for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_message" = "Would you like to send a bug report to the developers?";
/* iCloud error message: Failed to synchronize due to connection error */
"icloud_synchronization_error_connection_error" = "Error: Failed to synchronize due to connection error";
@ -1362,6 +1368,9 @@
/* iCloud error message: iCloud is not available */
"icloud_synchronization_error_cloud_is_unavailable" = "Error: iCloud is not available";
/* Title for the "Open In Another App" buton on the PlacePage. */
"open_in_app" = "Open In Another App";
/********** Types **********/

View file

@ -1353,6 +1353,12 @@
/* Title for the "Enable iCloud Syncronization" alert's "Backup" action button. */
"backup" = "Respaldo";
/* Title for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_title" = "Fallo de sincronización de iCloud";
/* Message for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_message" = "¿Quieres enviar un informe de error a los desarrolladores?";
/* iCloud error message: Failed to synchronize due to connection error */
"icloud_synchronization_error_connection_error" = "Error: No se ha podido sincronizar debido a un error de conexión";
@ -1362,6 +1368,9 @@
/* iCloud error message: iCloud is not available */
"icloud_synchronization_error_cloud_is_unavailable" = "Error: iCloud no está disponible";
/* Title for the "Open In Another App" buton on the PlacePage. */
"open_in_app" = "Abrir en otra aplicación";
/********** Types **********/

View file

@ -1353,6 +1353,12 @@
/* Title for the "Enable iCloud Syncronization" alert's "Backup" action button. */
"backup" = "Copia de seguridad";
/* Title for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_title" = "Fallo de sincronización de iCloud";
/* Message for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_message" = "¿Quieres enviar un informe de error a los desarrolladores?";
/* iCloud error message: Failed to synchronize due to connection error */
"icloud_synchronization_error_connection_error" = "Error: No se ha podido sincronizar debido a un error de conexión";
@ -1362,6 +1368,9 @@
/* iCloud error message: iCloud is not available */
"icloud_synchronization_error_cloud_is_unavailable" = "Error: iCloud no está disponible";
/* Title for the "Open In Another App" buton on the PlacePage. */
"open_in_app" = "Abrir en otra aplicación";
/********** Types **********/

View file

@ -1353,6 +1353,12 @@
/* Title for the "Enable iCloud Syncronization" alert's "Backup" action button. */
"backup" = "Varukoopia";
/* Title for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_title" = "iCloudi sünkroniseerimise tõrge";
/* Message for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_message" = "Kas soovite saata arendajatele veateate?";
/* iCloud error message: Failed to synchronize due to connection error */
"icloud_synchronization_error_connection_error" = "Viga: Sünkroniseerimine ebaõnnestus ühenduse vea tõttu.";
@ -1362,6 +1368,9 @@
/* iCloud error message: iCloud is not available */
"icloud_synchronization_error_cloud_is_unavailable" = "Viga: iCloud ei ole saadaval";
/* Title for the "Open In Another App" buton on the PlacePage. */
"open_in_app" = "Avatud teises rakenduses";
/********** Types **********/

View file

@ -1353,6 +1353,12 @@
/* Title for the "Enable iCloud Syncronization" alert's "Backup" action button. */
"backup" = "Babeskopia";
/* Title for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_title" = "iCloud sinkronizazioaren hutsegitea";
/* Message for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_message" = "Akatsen txostena bidali nahi diezu garatzaileei?";
/* iCloud error message: Failed to synchronize due to connection error */
"icloud_synchronization_error_connection_error" = "Errorea: Ezin izan da sinkronizatu konexio-errore baten ondorioz";
@ -1362,6 +1368,9 @@
/* iCloud error message: iCloud is not available */
"icloud_synchronization_error_cloud_is_unavailable" = "Errorea: iCloud ez dago erabilgarri";
/* Title for the "Open In Another App" buton on the PlacePage. */
"open_in_app" = "Ireki beste aplikazio batean";
/********** Types **********/

View file

@ -1353,6 +1353,12 @@
/* Title for the "Enable iCloud Syncronization" alert's "Backup" action button. */
"backup" = "پشتیبان گیری";
/* Title for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_title" = "همگام سازی iCloud شکست خورده است";
/* Message for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_message" = "آیا می خواهید یک گزارش اشکال برای توسعه دهندگان ارسال کنید؟";
/* iCloud error message: Failed to synchronize due to connection error */
"icloud_synchronization_error_connection_error" = "خطا: به دلیل خطای اتصال همگام سازی نشد";
@ -1362,6 +1368,9 @@
/* iCloud error message: iCloud is not available */
"icloud_synchronization_error_cloud_is_unavailable" = "خطا: iCloud در دسترس نیست";
/* Title for the "Open In Another App" buton on the PlacePage. */
"open_in_app" = "در یک برنامه دیگر باز کنید";
/********** Types **********/

View file

@ -1353,6 +1353,12 @@
/* Title for the "Enable iCloud Syncronization" alert's "Backup" action button. */
"backup" = "Varmuuskopiointi";
/* Title for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_title" = "iCloud-synkronoinnin epäonnistuminen";
/* Message for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_message" = "Haluatko lähettää vikailmoituksen kehittäjille?";
/* iCloud error message: Failed to synchronize due to connection error */
"icloud_synchronization_error_connection_error" = "Virhe: Synkronointi epäonnistui yhteysvirheen vuoksi.";
@ -1362,6 +1368,9 @@
/* iCloud error message: iCloud is not available */
"icloud_synchronization_error_cloud_is_unavailable" = "Virhe: iCloud ei ole käytettävissä";
/* Title for the "Open In Another App" buton on the PlacePage. */
"open_in_app" = "Avaa toisessa sovelluksessa";
/********** Types **********/

View file

@ -1353,6 +1353,12 @@
/* Title for the "Enable iCloud Syncronization" alert's "Backup" action button. */
"backup" = "Sauvegarde";
/* Title for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_title" = "Échec de la synchronisation iCloud";
/* Message for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_message" = "Veux-tu envoyer un rapport de bogue aux développeurs ?";
/* iCloud error message: Failed to synchronize due to connection error */
"icloud_synchronization_error_connection_error" = "Erreur : La synchronisation a échoué en raison d'une erreur de connexion";
@ -1362,6 +1368,9 @@
/* iCloud error message: iCloud is not available */
"icloud_synchronization_error_cloud_is_unavailable" = "Erreur : iCloud n'est pas disponible";
/* Title for the "Open In Another App" buton on the PlacePage. */
"open_in_app" = "Ouvrir dans une autre application";
/********** Types **********/

View file

@ -1353,6 +1353,12 @@
/* Title for the "Enable iCloud Syncronization" alert's "Backup" action button. */
"backup" = "גיבוי";
/* Title for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_title" = "כשל בסינכרון iCloud";
/* Message for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_message" = "האם תרצה לשלוח דוח באג למפתחים?";
/* iCloud error message: Failed to synchronize due to connection error */
"icloud_synchronization_error_connection_error" = "שגיאה: הסנכרון נכשל עקב שגיאת חיבור";
@ -1362,6 +1368,9 @@
/* iCloud error message: iCloud is not available */
"icloud_synchronization_error_cloud_is_unavailable" = "שגיאה: iCloud אינו זמין";
/* Title for the "Open In Another App" buton on the PlacePage. */
"open_in_app" = "פתח באפליקציה אחרת";
/********** Types **********/

View file

@ -1353,6 +1353,12 @@
/* Title for the "Enable iCloud Syncronization" alert's "Backup" action button. */
"backup" = "बैकअप";
/* Title for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_title" = "iCloud सिंक्रनाइज़ेशन विफलता";
/* Message for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_message" = "क्या आप डेवलपर्स को बग रिपोर्ट भेजना चाहेंगे?";
/* iCloud error message: Failed to synchronize due to connection error */
"icloud_synchronization_error_connection_error" = "त्रुटि: कनेक्शन त्रुटि के कारण सिंक्रनाइज़ करने में विफल";
@ -1362,6 +1368,9 @@
/* iCloud error message: iCloud is not available */
"icloud_synchronization_error_cloud_is_unavailable" = "त्रुटि: iCloud उपलब्ध नहीं है";
/* Title for the "Open In Another App" buton on the PlacePage. */
"open_in_app" = "किसी अन्य ऐप में खोलें";
/********** Types **********/

View file

@ -1353,6 +1353,12 @@
/* Title for the "Enable iCloud Syncronization" alert's "Backup" action button. */
"backup" = "Biztonsági mentés";
/* Title for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_title" = "iCloud-szinkronizálási hiba";
/* Message for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_message" = "Szeretne hibajelentést küldeni a fejlesztőknek?";
/* iCloud error message: Failed to synchronize due to connection error */
"icloud_synchronization_error_connection_error" = "Hiba: A szinkronizálás sikertelen volt a kapcsolat hibája miatt.";
@ -1362,6 +1368,9 @@
/* iCloud error message: iCloud is not available */
"icloud_synchronization_error_cloud_is_unavailable" = "Hiba: Az iCloud nem elérhető";
/* Title for the "Open In Another App" buton on the PlacePage. */
"open_in_app" = "Megnyitás egy másik alkalmazásban";
/********** Types **********/

View file

@ -1353,6 +1353,12 @@
/* Title for the "Enable iCloud Syncronization" alert's "Backup" action button. */
"backup" = "Cadangan";
/* Title for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_title" = "Kegagalan sinkronisasi iCloud";
/* Message for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_message" = "Apakah Anda ingin mengirim laporan bug ke pengembang?";
/* iCloud error message: Failed to synchronize due to connection error */
"icloud_synchronization_error_connection_error" = "Kesalahan: Gagal menyinkronkan karena kesalahan koneksi";
@ -1362,6 +1368,9 @@
/* iCloud error message: iCloud is not available */
"icloud_synchronization_error_cloud_is_unavailable" = "Kesalahan: iCloud tidak tersedia";
/* Title for the "Open In Another App" buton on the PlacePage. */
"open_in_app" = "Buka di Aplikasi Lain";
/********** Types **********/

View file

@ -1353,6 +1353,12 @@
/* Title for the "Enable iCloud Syncronization" alert's "Backup" action button. */
"backup" = "Backup";
/* Title for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_title" = "Mancata sincronizzazione di iCloud";
/* Message for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_message" = "Vuoi inviare una segnalazione di bug agli sviluppatori?";
/* iCloud error message: Failed to synchronize due to connection error */
"icloud_synchronization_error_connection_error" = "Errore: Impossibile sincronizzare a causa di un errore di connessione";
@ -1362,6 +1368,9 @@
/* iCloud error message: iCloud is not available */
"icloud_synchronization_error_cloud_is_unavailable" = "Errore: iCloud non è disponibile";
/* Title for the "Open In Another App" buton on the PlacePage. */
"open_in_app" = "Apri in un'altra applicazione";
/********** Types **********/

View file

@ -1353,6 +1353,12 @@
/* Title for the "Enable iCloud Syncronization" alert's "Backup" action button. */
"backup" = "バックアップ";
/* Title for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_title" = "iCloud同期の失敗";
/* Message for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_message" = "開発者にバグレポートを送りたいか?";
/* iCloud error message: Failed to synchronize due to connection error */
"icloud_synchronization_error_connection_error" = "エラー:接続エラーにより同期に失敗した";
@ -1362,6 +1368,9 @@
/* iCloud error message: iCloud is not available */
"icloud_synchronization_error_cloud_is_unavailable" = "エラーiCloudが利用できない";
/* Title for the "Open In Another App" buton on the PlacePage. */
"open_in_app" = "別のアプリで開く";
/********** Types **********/

View file

@ -1353,6 +1353,12 @@
/* Title for the "Enable iCloud Syncronization" alert's "Backup" action button. */
"backup" = "백업";
/* Title for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_title" = "iCloud 동기화 실패";
/* Message for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_message" = "개발자에게 버그 리포트를 보내시겠습니까?";
/* iCloud error message: Failed to synchronize due to connection error */
"icloud_synchronization_error_connection_error" = "오류입니다: 연결 오류로 인해 동기화하지 못했습니다.";
@ -1362,6 +1368,9 @@
/* iCloud error message: iCloud is not available */
"icloud_synchronization_error_cloud_is_unavailable" = "오류: iCloud를 사용할 수 없습니다.";
/* Title for the "Open In Another App" buton on the PlacePage. */
"open_in_app" = "다른 앱에서 열기";
/********** Types **********/

View file

@ -1353,6 +1353,12 @@
/* Title for the "Enable iCloud Syncronization" alert's "Backup" action button. */
"backup" = "बॅकअप";
/* Title for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_title" = "iCloud सिंक्रोनाइझेशन अयशस्वी";
/* Message for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_message" = "तुम्ही विकासकांना बग अहवाल पाठवू इच्छिता?";
/* iCloud error message: Failed to synchronize due to connection error */
"icloud_synchronization_error_connection_error" = "त्रुटी: कनेक्शन त्रुटीमुळे सिंक्रोनाइझ करण्यात अयशस्वी";
@ -1362,6 +1368,9 @@
/* iCloud error message: iCloud is not available */
"icloud_synchronization_error_cloud_is_unavailable" = "त्रुटी: iCloud उपलब्ध नाही";
/* Title for the "Open In Another App" buton on the PlacePage. */
"open_in_app" = "दुसऱ्या ॲपमध्ये उघडा";
/********** Types **********/

View file

@ -1353,6 +1353,12 @@
/* Title for the "Enable iCloud Syncronization" alert's "Backup" action button. */
"backup" = "Sikkerhetskopiering";
/* Title for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_title" = "iCloud-synkroniseringsfeil";
/* Message for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_message" = "Vil du sende en feilrapport til utviklerne?";
/* iCloud error message: Failed to synchronize due to connection error */
"icloud_synchronization_error_connection_error" = "Feil: Kunne ikke synkronisere på grunn av tilkoblingsfeil";
@ -1362,6 +1368,9 @@
/* iCloud error message: iCloud is not available */
"icloud_synchronization_error_cloud_is_unavailable" = "Feil: iCloud er ikke tilgjengelig";
/* Title for the "Open In Another App" buton on the PlacePage. */
"open_in_app" = "Åpne i en annen app";
/********** Types **********/

View file

@ -1353,6 +1353,12 @@
/* Title for the "Enable iCloud Syncronization" alert's "Backup" action button. */
"backup" = "Back-up";
/* Title for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_title" = "Storing iCloud-synchronisatie";
/* Message for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_message" = "Wil je een bugrapport naar de ontwikkelaars sturen?";
/* iCloud error message: Failed to synchronize due to connection error */
"icloud_synchronization_error_connection_error" = "Fout: Synchronisatie mislukt door verbindingsfout";
@ -1362,6 +1368,9 @@
/* iCloud error message: iCloud is not available */
"icloud_synchronization_error_cloud_is_unavailable" = "Fout: iCloud is niet beschikbaar";
/* Title for the "Open In Another App" buton on the PlacePage. */
"open_in_app" = "Openen in een andere app";
/********** Types **********/

View file

@ -1353,6 +1353,12 @@
/* Title for the "Enable iCloud Syncronization" alert's "Backup" action button. */
"backup" = "Kopia zapasowa";
/* Title for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_title" = "Błąd synchronizacji iCloud";
/* Message for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_message" = "Chcesz wysłać raport o błędzie do deweloperów?";
/* iCloud error message: Failed to synchronize due to connection error */
"icloud_synchronization_error_connection_error" = "Błąd: Nie udało się zsynchronizować z powodu błędu połączenia";
@ -1362,6 +1368,9 @@
/* iCloud error message: iCloud is not available */
"icloud_synchronization_error_cloud_is_unavailable" = "Błąd: usługa iCloud jest niedostępna";
/* Title for the "Open In Another App" buton on the PlacePage. */
"open_in_app" = "Otwórz w innej aplikacji";
/********** Types **********/

View file

@ -1353,6 +1353,12 @@
/* Title for the "Enable iCloud Syncronization" alert's "Backup" action button. */
"backup" = "Backup";
/* Title for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_title" = "Falha na sincronização do iCloud";
/* Message for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_message" = "Você gostaria de enviar um relatório de bug para os desenvolvedores?";
/* iCloud error message: Failed to synchronize due to connection error */
"icloud_synchronization_error_connection_error" = "Erro: Falha na sincronização devido a erro de conexão";
@ -1362,6 +1368,9 @@
/* iCloud error message: iCloud is not available */
"icloud_synchronization_error_cloud_is_unavailable" = "Erro: o iCloud não está disponível";
/* Title for the "Open In Another App" buton on the PlacePage. */
"open_in_app" = "Abrir em outro aplicativo";
/********** Types **********/

View file

@ -1353,6 +1353,12 @@
/* Title for the "Enable iCloud Syncronization" alert's "Backup" action button. */
"backup" = "Cópia de segurança";
/* Title for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_title" = "Falha de sincronização do iCloud";
/* Message for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_message" = "Gostarias de enviar um relatório de erro para os programadores?";
/* iCloud error message: Failed to synchronize due to connection error */
"icloud_synchronization_error_connection_error" = "Erro: Falha na sincronização devido a um erro de ligação";
@ -1362,6 +1368,9 @@
/* iCloud error message: iCloud is not available */
"icloud_synchronization_error_cloud_is_unavailable" = "Erro: o iCloud não está disponível";
/* Title for the "Open In Another App" buton on the PlacePage. */
"open_in_app" = "Abrir noutra aplicação";
/********** Types **********/

View file

@ -1353,6 +1353,12 @@
/* Title for the "Enable iCloud Syncronization" alert's "Backup" action button. */
"backup" = "Backup";
/* Title for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_title" = "Eșecul sincronizării iCloud";
/* Message for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_message" = "Doriți să trimiteți un raport de eroare dezvoltatorilor?";
/* iCloud error message: Failed to synchronize due to connection error */
"icloud_synchronization_error_connection_error" = "Eroare: Nu s-a reușit sincronizarea din cauza unei erori de conexiune";
@ -1362,6 +1368,9 @@
/* iCloud error message: iCloud is not available */
"icloud_synchronization_error_cloud_is_unavailable" = "Eroare: iCloud nu este disponibil";
/* Title for the "Open In Another App" buton on the PlacePage. */
"open_in_app" = "Deschidere în altă aplicație";
/********** Types **********/

View file

@ -1353,6 +1353,12 @@
/* Title for the "Enable iCloud Syncronization" alert's "Backup" action button. */
"backup" = "Резервная копия";
/* Title for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_title" = "Сбой синхронизации iCloud";
/* Message for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_message" = "Хотите отправить разработчикам сообщение об ошибке?";
/* iCloud error message: Failed to synchronize due to connection error */
"icloud_synchronization_error_connection_error" = "Ошибка: Не удалось выполнить синхронизацию из-за ошибки подключения";
@ -1362,6 +1368,9 @@
/* iCloud error message: iCloud is not available */
"icloud_synchronization_error_cloud_is_unavailable" = "Ошибка: iCloud недоступен";
/* Title for the "Open In Another App" buton on the PlacePage. */
"open_in_app" = "Открыть в другом приложении";
/********** Types **********/

View file

@ -1353,6 +1353,12 @@
/* Title for the "Enable iCloud Syncronization" alert's "Backup" action button. */
"backup" = "Zálohovanie";
/* Title for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_title" = "Zlyhanie synchronizácie iCloud";
/* Message for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_message" = "Chcete poslať vývojárom hlásenie o chybe?";
/* iCloud error message: Failed to synchronize due to connection error */
"icloud_synchronization_error_connection_error" = "Chyba: Nepodarilo sa synchronizovať z dôvodu chyby pripojenia";
@ -1362,6 +1368,9 @@
/* iCloud error message: iCloud is not available */
"icloud_synchronization_error_cloud_is_unavailable" = "Chyba: iCloud nie je k dispozícii";
/* Title for the "Open In Another App" buton on the PlacePage. */
"open_in_app" = "Otvoriť v inej aplikácii";
/********** Types **********/

View file

@ -1353,6 +1353,12 @@
/* Title for the "Enable iCloud Syncronization" alert's "Backup" action button. */
"backup" = "Säkerhetskopiering";
/* Title for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_title" = "fel i iCloud-synkroniseringen";
/* Message for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_message" = "Vill du skicka en felrapport till utvecklarna?";
/* iCloud error message: Failed to synchronize due to connection error */
"icloud_synchronization_error_connection_error" = "Fel: Synkronisering misslyckades på grund av anslutningsfel";
@ -1362,6 +1368,9 @@
/* iCloud error message: iCloud is not available */
"icloud_synchronization_error_cloud_is_unavailable" = "Fel: iCloud är inte tillgängligt";
/* Title for the "Open In Another App" buton on the PlacePage. */
"open_in_app" = "Öppna i en annan app";
/********** Types **********/

View file

@ -1353,6 +1353,12 @@
/* Title for the "Enable iCloud Syncronization" alert's "Backup" action button. */
"backup" = "Hifadhi nakala";
/* Title for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_title" = "Imeshindwa kusawazisha iCloud";
/* Message for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_message" = "Je, ungependa kutuma ripoti ya hitilafu kwa wasanidi programu?";
/* iCloud error message: Failed to synchronize due to connection error */
"icloud_synchronization_error_connection_error" = "Hitilafu: Imeshindwa kusawazisha kwa sababu ya hitilafu ya muunganisho";
@ -1362,6 +1368,9 @@
/* iCloud error message: iCloud is not available */
"icloud_synchronization_error_cloud_is_unavailable" = "Hitilafu: iCloud haipatikani";
/* Title for the "Open In Another App" buton on the PlacePage. */
"open_in_app" = "Fungua Katika Programu Nyingine";
/********** Types **********/

View file

@ -1353,6 +1353,12 @@
/* Title for the "Enable iCloud Syncronization" alert's "Backup" action button. */
"backup" = "สำรองข้อมูล";
/* Title for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_title" = "การซิงโครไนซ์ iCloud ล้มเหลว";
/* Message for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_message" = "คุณต้องการส่งรายงานข้อผิดพลาดไปยังนักพัฒนาหรือไม่?";
/* iCloud error message: Failed to synchronize due to connection error */
"icloud_synchronization_error_connection_error" = "ข้อผิดพลาด: ไม่สามารถซิงโครไนซ์ได้เนื่องจากข้อผิดพลาดในการเชื่อมต่อ";
@ -1362,6 +1368,9 @@
/* iCloud error message: iCloud is not available */
"icloud_synchronization_error_cloud_is_unavailable" = "ข้อผิดพลาด: iCloud ไม่พร้อมใช้งาน";
/* Title for the "Open In Another App" buton on the PlacePage. */
"open_in_app" = "เปิดในแอปอื่น";
/********** Types **********/

View file

@ -1353,6 +1353,12 @@
/* Title for the "Enable iCloud Syncronization" alert's "Backup" action button. */
"backup" = "Yedekle";
/* Title for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_title" = "iCloud senkronizasyon hatası";
/* Message for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_message" = "Geliştiricilere bir hata raporu göndermek ister misiniz?";
/* iCloud error message: Failed to synchronize due to connection error */
"icloud_synchronization_error_connection_error" = "Hata oluştu: Bağlantı hatası nedeniyle senkronizasyon başarısız oldu";
@ -1362,6 +1368,9 @@
/* iCloud error message: iCloud is not available */
"icloud_synchronization_error_cloud_is_unavailable" = "Hata: iCloud kullanılamıyor";
/* Title for the "Open In Another App" buton on the PlacePage. */
"open_in_app" = "Başka Bir Uygulamada Aç";
/********** Types **********/

View file

@ -1353,6 +1353,12 @@
/* Title for the "Enable iCloud Syncronization" alert's "Backup" action button. */
"backup" = "Резервне копіювання";
/* Title for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_title" = "Збій синхронізації iCloud";
/* Message for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_message" = "Бажаєте надіслати розробникам звіт про помилку?";
/* iCloud error message: Failed to synchronize due to connection error */
"icloud_synchronization_error_connection_error" = "Помилка: Не вдалося синхронізувати через помилку з'єднання";
@ -1362,6 +1368,9 @@
/* iCloud error message: iCloud is not available */
"icloud_synchronization_error_cloud_is_unavailable" = "Помилка: iCloud недоступний";
/* Title for the "Open In Another App" buton on the PlacePage. */
"open_in_app" = "Відкрити в іншій програмі";
/********** Types **********/

View file

@ -1353,6 +1353,12 @@
/* Title for the "Enable iCloud Syncronization" alert's "Backup" action button. */
"backup" = "Hỗ trợ";
/* Title for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_title" = "Lỗi đồng bộ hóa iCloud";
/* Message for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_message" = "Bạn có muốn gửi báo cáo lỗi cho nhà phát triển không?";
/* iCloud error message: Failed to synchronize due to connection error */
"icloud_synchronization_error_connection_error" = "Lỗi: Không đồng bộ được do lỗi kết nối";
@ -1362,6 +1368,9 @@
/* iCloud error message: iCloud is not available */
"icloud_synchronization_error_cloud_is_unavailable" = "Lỗi: iCloud không khả dụng";
/* Title for the "Open In Another App" buton on the PlacePage. */
"open_in_app" = "Mở trong ứng dụng khác";
/********** Types **********/

View file

@ -1353,6 +1353,12 @@
/* Title for the "Enable iCloud Syncronization" alert's "Backup" action button. */
"backup" = "备份";
/* Title for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_title" = "iCloud 同步失败";
/* Message for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_message" = "您想向开发人员发送错误报告吗?";
/* iCloud error message: Failed to synchronize due to connection error */
"icloud_synchronization_error_connection_error" = "错误:由于连接错误,同步失败";
@ -1362,6 +1368,9 @@
/* iCloud error message: iCloud is not available */
"icloud_synchronization_error_cloud_is_unavailable" = "错误iCloud 不可用";
/* Title for the "Open In Another App" buton on the PlacePage. */
"open_in_app" = "在另一个应用程序中打开";
/********** Types **********/

View file

@ -1353,6 +1353,12 @@
/* Title for the "Enable iCloud Syncronization" alert's "Backup" action button. */
"backup" = "备份";
/* Title for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_title" = "iCloud 同步失敗";
/* Message for the "iCloud synchronization failure" alert. */
"icloud_synchronization_error_alert_message" = "您想向開發人員發送錯誤報告嗎?";
/* iCloud error message: Failed to synchronize due to connection error */
"icloud_synchronization_error_connection_error" = "錯誤:由於連線錯誤而無法同步";
@ -1362,6 +1368,9 @@
/* iCloud error message: iCloud is not available */
"icloud_synchronization_error_cloud_is_unavailable" = "錯誤iCloud 不可用";
/* Title for the "Open In Another App" buton on the PlacePage. */
"open_in_app" = "在另一個應用程式中打開";
/********** Types **********/

View file

@ -475,6 +475,7 @@
ED3EAC202B03C88100220A4A /* BottomTabBarButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED3EAC1F2B03C88100220A4A /* BottomTabBarButton.swift */; };
ED43B8BD2C12063500D07BAA /* DocumentPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED43B8BC2C12063500D07BAA /* DocumentPicker.swift */; };
ED63CEB92BDF8F9D006155C4 /* SettingsTableViewiCloudSwitchCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED63CEB62BDF8F9C006155C4 /* SettingsTableViewiCloudSwitchCell.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 */; };
ED79A5D32BDF8D6100952D1F /* CloudStorageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED79A5CB2BDF8D6100952D1F /* CloudStorageManager.swift */; };
@ -484,7 +485,9 @@
ED79A5D72BDF8D6100952D1F /* SynchronizationStateManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED79A5CF2BDF8D6100952D1F /* SynchronizationStateManager.swift */; };
ED79A5D82BDF8D6100952D1F /* DefaultLocalDirectoryMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED79A5D02BDF8D6100952D1F /* DefaultLocalDirectoryMonitor.swift */; };
ED7CCC4F2C1362E300E2A737 /* FileType.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED7CCC4E2C1362E300E2A737 /* FileType.swift */; };
ED808D0F2C38407800D52585 /* CircleImageButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED808D0E2C38407800D52585 /* CircleImageButton.swift */; };
ED8270F02C2071A3005966DA /* SettingsTableViewDetailedSwitchCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED8270EF2C2071A3005966DA /* SettingsTableViewDetailedSwitchCell.swift */; };
ED9857082C4ED02D00694F6C /* MailComposer.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED9857072C4ED02D00694F6C /* MailComposer.swift */; };
ED9966802B94FBC20083CE55 /* ColorPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED99667D2B94FBC20083CE55 /* ColorPicker.swift */; };
EDBD68072B625724005DD151 /* LocationServicesDisabledAlert.xib in Resources */ = {isa = PBXBuildFile; fileRef = EDBD68062B625724005DD151 /* LocationServicesDisabledAlert.xib */; };
EDBD680B2B62572E005DD151 /* LocationServicesDisabledAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDBD680A2B62572E005DD151 /* LocationServicesDisabledAlert.swift */; };
@ -492,6 +495,7 @@
EDE243DD2B6D2E640057369B /* AboutController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDE243D52B6CF3980057369B /* AboutController.swift */; };
EDE243E52B6D3F400057369B /* OSMView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDE243E42B6D3F400057369B /* OSMView.swift */; };
EDE243E72B6D55610057369B /* InfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDE243E02B6D3EA00057369B /* InfoView.swift */; };
EDE8EAEB2C2DBB99002777F5 /* OpenInApplication.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDE8EAEA2C2DBB99002777F5 /* OpenInApplication.swift */; };
EDF838842C00B640007E4E67 /* SynchronizationFileWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDF838812C00B640007E4E67 /* SynchronizationFileWriter.swift */; };
EDF838BE2C00B9D0007E4E67 /* LocalDirectoryMonitorDelegateMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDF838AD2C00B9C7007E4E67 /* LocalDirectoryMonitorDelegateMock.swift */; };
EDF838BF2C00B9D0007E4E67 /* SynchronizationStateManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDF838AF2C00B9C7007E4E67 /* SynchronizationStateManagerTests.swift */; };
@ -1391,6 +1395,7 @@
ED48BBB817C2B1E2003E7E92 /* CircleView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CircleView.h; sourceTree = "<group>"; };
ED48BBB917C2B1E2003E7E92 /* CircleView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CircleView.m; sourceTree = "<group>"; };
ED63CEB62BDF8F9C006155C4 /* SettingsTableViewiCloudSwitchCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsTableViewiCloudSwitchCell.swift; sourceTree = "<group>"; };
ED77556D2C2C490B0051E656 /* UIAlertController+openInAppActionSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIAlertController+openInAppActionSheet.swift"; sourceTree = "<group>"; };
ED79A5AA2BD7AA9C00952D1F /* LoadingOverlayViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingOverlayViewController.swift; sourceTree = "<group>"; };
ED79A5AC2BD7BA0F00952D1F /* UIApplication+LoadingOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIApplication+LoadingOverlay.swift"; sourceTree = "<group>"; };
ED79A5CB2BDF8D6100952D1F /* CloudStorageManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CloudStorageManager.swift; sourceTree = "<group>"; };
@ -1400,7 +1405,9 @@
ED79A5CF2BDF8D6100952D1F /* SynchronizationStateManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SynchronizationStateManager.swift; sourceTree = "<group>"; };
ED79A5D02BDF8D6100952D1F /* DefaultLocalDirectoryMonitor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DefaultLocalDirectoryMonitor.swift; sourceTree = "<group>"; };
ED7CCC4E2C1362E300E2A737 /* FileType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileType.swift; sourceTree = "<group>"; };
ED808D0E2C38407800D52585 /* CircleImageButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircleImageButton.swift; sourceTree = "<group>"; };
ED8270EF2C2071A3005966DA /* SettingsTableViewDetailedSwitchCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsTableViewDetailedSwitchCell.swift; sourceTree = "<group>"; };
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>"; };
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>"; };
@ -1408,6 +1415,7 @@
EDE243D52B6CF3980057369B /* AboutController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutController.swift; sourceTree = "<group>"; };
EDE243E02B6D3EA00057369B /* InfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoView.swift; sourceTree = "<group>"; };
EDE243E42B6D3F400057369B /* OSMView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSMView.swift; sourceTree = "<group>"; };
EDE8EAEA2C2DBB99002777F5 /* OpenInApplication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenInApplication.swift; sourceTree = "<group>"; };
EDF838812C00B640007E4E67 /* SynchronizationFileWriter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SynchronizationFileWriter.swift; sourceTree = "<group>"; };
EDF838AD2C00B9C7007E4E67 /* LocalDirectoryMonitorDelegateMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalDirectoryMonitorDelegateMock.swift; sourceTree = "<group>"; };
EDF838AE2C00B9C7007E4E67 /* DefaultLocalDirectoryMonitorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DefaultLocalDirectoryMonitorTests.swift; sourceTree = "<group>"; };
@ -2915,6 +2923,7 @@
99C964222428C0D500E41723 /* PlacePageHeader */,
993DF0C223F6BD0600AC231A /* ElevationDetails */,
99DEF9D523E420D2006BFD21 /* ElevationProfile */,
EDE8EAE32C2DB74A002777F5 /* OpenInAppActionSheet */,
);
path = Components;
sourceTree = "<group>";
@ -2936,6 +2945,7 @@
99C964252428C0F700E41723 /* PlacePageHeaderViewController.swift */,
99C964262428C0F700E41723 /* PlacePageHeaderBuilder.swift */,
99C9642F2428C27A00E41723 /* PlacePageHeaderView.swift */,
ED808D0E2C38407800D52585 /* CircleImageButton.swift */,
);
path = PlacePageHeader;
sourceTree = "<group>";
@ -3098,6 +3108,14 @@
path = iCloud;
sourceTree = "<group>";
};
ED9857022C4ECFFC00694F6C /* MailComposer */ = {
isa = PBXGroup;
children = (
ED9857072C4ED02D00694F6C /* MailComposer.swift */,
);
path = MailComposer;
sourceTree = "<group>";
};
ED99667C2B94FBC20083CE55 /* ColorPicker */ = {
isa = PBXGroup;
children = (
@ -3106,6 +3124,15 @@
path = ColorPicker;
sourceTree = "<group>";
};
EDE8EAE32C2DB74A002777F5 /* OpenInAppActionSheet */ = {
isa = PBXGroup;
children = (
EDE8EAEA2C2DBB99002777F5 /* OpenInApplication.swift */,
ED77556D2C2C490B0051E656 /* UIAlertController+openInAppActionSheet.swift */,
);
path = OpenInAppActionSheet;
sourceTree = "<group>";
};
EDF838AB2C00B9C7007E4E67 /* iCloudTests */ = {
isa = PBXGroup;
children = (
@ -3349,6 +3376,7 @@
F6E2FBFB1E097B9F0083EBEC /* UI */ = {
isa = PBXGroup;
children = (
ED9857022C4ECFFC00694F6C /* MailComposer */,
ED43B8B92C12061600D07BAA /* DocumentPicker */,
ED99667C2B94FBC20083CE55 /* ColorPicker */,
F69018B51E9E5FEB00B3C10B /* Autoupdate */,
@ -4355,6 +4383,7 @@
3406FA161C6E0C3300E9FAD2 /* MWMMapDownloadDialog.mm in Sources */,
340416481E7BF28E00E2B6D6 /* UIView+Snapshot.swift in Sources */,
F6E2FE251E097BA00083EBEC /* MWMOpeningHoursModel.mm in Sources */,
ED77556E2C2C490B0051E656 /* UIAlertController+openInAppActionSheet.swift in Sources */,
99AAEA74244DA5ED0039D110 /* BottomMenuPresentationController.swift in Sources */,
99514BB823E82B450085D3A7 /* ElevationProfilePresenter.swift in Sources */,
34C9BD031C6DB693000DC38D /* MWMTableViewController.m in Sources */,
@ -4477,10 +4506,12 @@
FA8E808925F412E2002A1434 /* FirstSession.mm in Sources */,
F6E2FF691E097BA00083EBEC /* MWMUnitsController.mm in Sources */,
6741AA031BF340DE002C974C /* MWMActivityViewController.mm in Sources */,
ED808D0F2C38407800D52585 /* CircleImageButton.swift in Sources */,
CDCA27382237F1BD00167D87 /* BookmarkInfo.swift in Sources */,
993DF11A23F6BDB100AC231A /* UIBarButtonItemRenderer.swift in Sources */,
34AB668C1FC5AA330078E451 /* NavigationStreetNameView.swift in Sources */,
993DF12923F6BDB100AC231A /* ThemeManager.swift in Sources */,
EDE8EAEB2C2DBB99002777F5 /* OpenInApplication.swift in Sources */,
337F98A621D37B7400C8AC27 /* SearchTabViewController.swift in Sources */,
3404755F1E081A4600C92850 /* MWMLocationPredictor.mm in Sources */,
993DF11523F6BDB100AC231A /* UISearchBarRenderer.swift in Sources */,
@ -4537,6 +4568,7 @@
3404164C1E7BF42E00E2B6D6 /* UIView+Coordinates.swift in Sources */,
99F3EB0323F4178200C713F8 /* PlacePageCommonLayout.swift in Sources */,
99C6532223F2F506004322F3 /* IPlacePageLayout.swift in Sources */,
ED9857082C4ED02D00694F6C /* MailComposer.swift in Sources */,
99F8B4C623B644A6009FF0B4 /* MapStyleSheet.swift in Sources */,
99012851244732DB00C72B10 /* BottomTabBarViewController.swift in Sources */,
F63AF5061EA6162400A1DB98 /* FilterTypeCell.swift in Sources */,

View file

@ -92,6 +92,14 @@
<key>LSApplicationQueriesSchemes</key>
<array>
<string>mailto</string>
<string>comgooglemaps</string>
<string>osmandmaps</string>
<string>yandexmaps</string>
<string>dgis</string>
<string>citymapper</string>
<string>moovit</string>
<string>uber</string>
<string>waze</string>
</array>
<key>LSRequiresIPhoneOS</key>
<true/>

View file

@ -5,9 +5,6 @@ extension LocalMetadataItem {
lastModificationDate: TimeInterval) -> LocalMetadataItem {
let item = LocalMetadataItem(fileName: fileName,
fileUrl: URL(string: "url")!,
fileSize: 0,
contentType: "",
creationDate: Date().timeIntervalSince1970,
lastModificationDate: lastModificationDate)
return item
@ -22,10 +19,7 @@ extension CloudMetadataItem {
hasUnresolvedConflicts: Bool = false) -> CloudMetadataItem {
let item = CloudMetadataItem(fileName: fileName,
fileUrl: URL(string: "url")!,
fileSize: 0,
contentType: "",
isDownloaded: isDownloaded,
creationDate: Date().timeIntervalSince1970,
lastModificationDate: lastModificationDate,
isRemoved: isInTrash,
downloadingError: nil,

View file

@ -4,7 +4,6 @@ protocol BottomMenuInteractorProtocol: AnyObject {
func downloadMaps()
func donate()
func openSettings()
func shareLocation(cell: BottomMenuItemCell)
}
@objc protocol BottomMenuDelegate {
@ -61,17 +60,4 @@ extension BottomMenuInteractor: BottomMenuInteractorProtocol {
close()
mapViewController?.performSegue(withIdentifier: "Map2Settings", sender: nil)
}
func shareLocation(cell: BottomMenuItemCell) {
let lastLocation = LocationManager.lastLocation()
guard let coordinates = lastLocation?.coordinate else {
let alert = UIAlertController(title: L("unknown_current_position"), message: nil, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: L("ok"), style: .default, handler: nil))
viewController?.present(alert, animated: true, completion: nil)
return;
}
guard let viewController = viewController else { return }
let vc = ActivityViewController.share(forMyPosition: coordinates)
vc?.present(inParentViewController: viewController, anchorView: cell.anchorView)
}
}

View file

@ -8,7 +8,6 @@ class BottomMenuPresenter: NSObject {
case downloadMaps
case donate
case settings
case share
}
enum Sections: Int {
case layers
@ -60,7 +59,7 @@ extension BottomMenuPresenter {
let cell = tableView.dequeueReusableCell(cell: BottomMenuItemCell.self)!
switch CellType(rawValue: correctedRow(indexPath.row))! {
case .addPlace:
let enabled = MWMNavigationDashboardManager.shared().state == .hidden && FrameworkHelper.canEditMap()
let enabled = MWMNavigationDashboardManager.shared().state == .hidden && FrameworkHelper.canEditMapAtViewportCenter()
cell.configure(imageName: "ic_add_place",
title: L("placepage_add_place_button"),
badgeCount: 0,
@ -80,11 +79,6 @@ extension BottomMenuPresenter {
title: L("settings"),
badgeCount: 0,
enabled: true)
case .share:
cell.configure(imageName: "ic_menu_share",
title: L("share_my_location"),
badgeCount: 0,
enabled: true)
}
return cell
}
@ -94,6 +88,13 @@ extension BottomMenuPresenter {
//MARK: -- UITableDelegate
extension BottomMenuPresenter {
func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
if let cell = tableView.cellForRow(at: indexPath) as? BottomMenuItemCell {
return cell.isEnabled ? indexPath : nil
}
return indexPath
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard indexPath.section == Sections.items.rawValue else {
return
@ -108,10 +109,6 @@ extension BottomMenuPresenter {
interactor.donate()
case .settings:
interactor.openSettings()
case .share:
if let cell = tableView.cellForRow(at: indexPath) as? BottomMenuItemCell {
interactor.shareLocation(cell: cell)
}
}
}
}

View file

@ -14,7 +14,7 @@ class BottomMenuItemCell: UITableViewCell {
}
}
private var isEnabled: Bool = true
private(set) var isEnabled: Bool = true
private var isPromo: Bool = false
func configure(imageName: String, title: String, badgeCount: UInt, enabled: Bool) {

View file

@ -1,6 +1,8 @@
import UIKit
class BottomMenuLayersCell: UITableViewCell {
@IBOutlet weak var closeButton: CircleImageButton!
@IBOutlet private var subwayButton: BottomMenuLayerButton! {
didSet {
updateSubwayButton()
@ -22,6 +24,7 @@ class BottomMenuLayersCell: UITableViewCell {
override func awakeFromNib() {
super.awakeFromNib()
MapOverlayManager.add(self)
closeButton.setImage(UIImage(named: "ic_close"))
}
deinit {

View file

@ -3,7 +3,7 @@
<device id="ipad9_7" orientation="landscape" layout="fullscreen" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22684"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22685"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
@ -30,16 +30,12 @@
<userDefinedRuntimeAttribute type="string" keyPath="localizedText" value="layers_title"/>
</userDefinedRuntimeAttributes>
</label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="2xW-dK-D9y">
<rect key="frame" x="294" y="10" width="30" height="30"/>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="2xW-dK-D9y" customClass="CircleImageButton" customModule="Organic_Maps" customModuleProvider="target">
<rect key="frame" x="296" y="11" width="28" height="28"/>
<constraints>
<constraint firstAttribute="height" constant="30" id="BD2-bz-n13"/>
<constraint firstAttribute="width" constant="30" id="Thu-MY-dQm"/>
<constraint firstAttribute="height" constant="28" id="BD2-bz-n13"/>
<constraint firstAttribute="width" constant="28" id="Thu-MY-dQm" customClass="I"/>
</constraints>
<state key="normal" image="ic_clear_24"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="styleName" value="PPCloseButton"/>
</userDefinedRuntimeAttributes>
<connections>
<action selector="onCloseButtonPressed:" destination="KGk-i7-Jjw" eventType="touchUpInside" id="8vd-Pg-Suh"/>
</connections>
@ -189,6 +185,7 @@
<constraint firstItem="njF-e1-oar" firstAttribute="leading" secondItem="75k-vD-FSR" secondAttribute="leading" id="nhe-Sa-Mlo"/>
</constraints>
<connections>
<outlet property="closeButton" destination="2xW-dK-D9y" id="RQI-hb-JpS"/>
<outlet property="isoLinesButton" destination="edA-Mo-3Vx" id="qoC-8w-EqY"/>
<outlet property="outdoorButton" destination="g13-pK-Eig" id="ib1-aw-Qv9"/>
<outlet property="subwayButton" destination="4US-fZ-cyg" id="eQB-HR-Wgl"/>
@ -200,6 +197,5 @@
<image name="btn_menu_isomaps" width="151" height="151"/>
<image name="btn_menu_outdoors" width="151" height="151"/>
<image name="btn_menu_subway" width="151" height="151"/>
<image name="ic_clear_24" width="20" height="20"/>
</resources>
</document>

View file

@ -282,7 +282,7 @@ private extension AboutController {
let logFileURL = Logger.getLogFileURL()
UIApplication.shared.hideLoadingOverlay {
guard let self else { return }
self.sendEmailWith(header: "Organic Maps Bugreport", toRecipients: [link], attachmentFileURL: logFileURL)
MailComposer.default.sendEmailWith(header: "Organic Maps Bugreport", toRecipients: [link], attachmentFileURL: logFileURL)
}
}
case .reportMapDataProblem, .volunteer, .news:
@ -314,7 +314,7 @@ private extension AboutController {
self?.openUrl(socialMedia.link, externally: true)
case .organicMapsEmail:
guard let link = socialMedia.link else { fatalError("The Organic Maps email link should be provided.") }
self?.sendEmailWith(header: "Organic Maps", toRecipients: [link])
MailComposer.default.sendEmailWith(header: "Organic Maps", toRecipients: [link])
}
})
}
@ -441,111 +441,6 @@ extension AboutController: UICollectionViewDelegateFlowLayout {
return Constants.socialMediaCollectionViewSpacing
}
}
// MARK: - Mail Composing
private extension AboutController {
func sendEmailWith(header: String, toRecipients: [String], attachmentFileURL: URL? = nil) {
func emailSubject(subject: String) -> String {
let appInfo = AppInfo.shared()
return String(format:"[%@-%@ iOS] %@", appInfo.bundleVersion, appInfo.buildNumber, subject)
}
func emailBody() -> String {
let appInfo = AppInfo.shared()
return String(format: "\n\n\n\n- %@ (%@)\n- Organic Maps %@-%@\n- %@-%@\n- %@\n",
appInfo.deviceModel, UIDevice.current.systemVersion,
appInfo.bundleVersion, appInfo.buildNumber,
Locale.current.languageCode ?? "",
Locale.current.regionCode ?? "",
Locale.preferredLanguages.joined(separator: ", "))
}
func openOutlook(subject: String, body: String, recipients: [String]) -> Bool {
var components = URLComponents(string: "ms-outlook://compose")!
components.queryItems = [
URLQueryItem(name: "to", value: recipients.joined(separator: ";")),
URLQueryItem(name: "subject", value: subject),
URLQueryItem(name: "body", value: body),
]
if let url = components.url, UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url)
return true
}
return false
}
func openGmail(subject: String, body: String, recipients: [String]) -> Bool {
var components = URLComponents(string: "googlegmail://co")!
components.queryItems = [
URLQueryItem(name: "to", value: recipients.joined(separator: ";")),
URLQueryItem(name: "subject", value: subject),
URLQueryItem(name: "body", value: body),
]
if let url = components.url, UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url)
return true
}
return false
}
func openDefaultMailApp(subject: String, body: String, recipients: [String]) -> Bool {
var components = URLComponents(string: "mailto:\(recipients.joined(separator: ";"))")
components?.queryItems = [
URLQueryItem(name: "subject", value: subject),
URLQueryItem(name: "body", value: body.replacingOccurrences(of: "\n", with: "\r\n")),
]
if let url = components?.url, UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url)
return true
}
return false
}
func showMailComposingAlert() {
let text = String(format:L("email_error_body"), toRecipients.joined(separator: ";"))
let alert = UIAlertController(title: L("email_error_title"), message: text, preferredStyle: .alert)
let action = UIAlertAction(title: L("ok"), style: .default, handler: nil)
alert.addAction(action)
present(alert, animated: true, completion: nil)
}
let subject = emailSubject(subject: header)
let body = emailBody()
// If the attachment file path is provided, the default mail composer should be used.
if let attachmentFileURL {
if MWMMailViewController.canSendMail(), let attachmentData = try? Data(contentsOf: attachmentFileURL) {
let mailViewController = MWMMailViewController()
mailViewController.mailComposeDelegate = self
mailViewController.setSubject(subject)
mailViewController.setMessageBody(body, isHTML:false)
mailViewController.setToRecipients(toRecipients)
mailViewController.addAttachmentData(attachmentData, mimeType: "application/zip", fileName: attachmentFileURL.lastPathComponent)
self.present(mailViewController, animated: true, completion:nil)
} else {
showMailComposingAlert()
}
return
}
// Before iOS 14, try to open alternate email apps first, assuming that if users installed them, they're using them.
let os = ProcessInfo().operatingSystemVersion
if (os.majorVersion < 14 && (openGmail(subject: subject, body: body, recipients: toRecipients) ||
openOutlook(subject: subject, body: body, recipients: toRecipients))) {
return
}
// From iOS 14, it is possible to change the default mail app, and mailto should open a default mail app.
if !openDefaultMailApp(subject: subject, body: body, recipients: toRecipients) {
showMailComposingAlert()
}
}
}
// MARK: - UIStackView + AddArrangedSubviewWithSeparator
private extension UIStackView {
func addArrangedSubviewWithSeparator(_ view: UIView) {
@ -564,10 +459,3 @@ private extension UIStackView {
addArrangedSubview(view)
}
}
// MARK: - MFMailComposeViewControllerDelegate
extension AboutController: MFMailComposeViewControllerDelegate {
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
dismiss(animated: true, completion: nil)
}
}

View file

@ -0,0 +1,115 @@
final class MailComposer: NSObject {
static let `default` = MailComposer()
private var topViewController: UIViewController { UIViewController.topViewController() }
private override init() {}
func sendEmailWith(header: String, toRecipients: [String], attachmentFileURL: URL? = nil) {
func emailSubject(subject: String) -> String {
let appInfo = AppInfo.shared()
return String(format:"[%@-%@ iOS] %@", appInfo.bundleVersion, appInfo.buildNumber, subject)
}
func emailBody() -> String {
let appInfo = AppInfo.shared()
return String(format: "\n\n\n\n- %@ (%@)\n- Organic Maps %@-%@\n- %@-%@\n- %@\n",
appInfo.deviceModel, UIDevice.current.systemVersion,
appInfo.bundleVersion, appInfo.buildNumber,
Locale.current.languageCode ?? "",
Locale.current.regionCode ?? "",
Locale.preferredLanguages.joined(separator: ", "))
}
func openOutlook(subject: String, body: String, recipients: [String]) -> Bool {
var components = URLComponents(string: "ms-outlook://compose")!
components.queryItems = [
URLQueryItem(name: "to", value: recipients.joined(separator: ";")),
URLQueryItem(name: "subject", value: subject),
URLQueryItem(name: "body", value: body),
]
if let url = components.url, UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url)
return true
}
return false
}
func openGmail(subject: String, body: String, recipients: [String]) -> Bool {
var components = URLComponents(string: "googlegmail://co")!
components.queryItems = [
URLQueryItem(name: "to", value: recipients.joined(separator: ";")),
URLQueryItem(name: "subject", value: subject),
URLQueryItem(name: "body", value: body),
]
if let url = components.url, UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url)
return true
}
return false
}
func openDefaultMailApp(subject: String, body: String, recipients: [String]) -> Bool {
var components = URLComponents(string: "mailto:\(recipients.joined(separator: ";"))")
components?.queryItems = [
URLQueryItem(name: "subject", value: subject),
URLQueryItem(name: "body", value: body.replacingOccurrences(of: "\n", with: "\r\n")),
]
if let url = components?.url, UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url)
return true
}
return false
}
func showMailComposingAlert() {
let text = String(format:L("email_error_body"), toRecipients.joined(separator: ";"))
let alert = UIAlertController(title: L("email_error_title"), message: text, preferredStyle: .alert)
let action = UIAlertAction(title: L("ok"), style: .default, handler: nil)
alert.addAction(action)
topViewController.present(alert, animated: true, completion: nil)
}
let subject = emailSubject(subject: header)
let body = emailBody()
// If the attachment file path is provided, the default mail composer should be used.
if let attachmentFileURL {
if MWMMailViewController.canSendMail(), let attachmentData = try? Data(contentsOf: attachmentFileURL) {
let mailViewController = MWMMailViewController()
mailViewController.mailComposeDelegate = self
mailViewController.setSubject(subject)
mailViewController.setMessageBody(body, isHTML:false)
mailViewController.setToRecipients(toRecipients)
mailViewController.addAttachmentData(attachmentData, mimeType: "application/zip", fileName: attachmentFileURL.lastPathComponent)
topViewController.present(mailViewController, animated: true, completion:nil)
} else {
showMailComposingAlert()
}
return
}
// Before iOS 14, try to open alternate email apps first, assuming that if users installed them, they're using them.
let os = ProcessInfo().operatingSystemVersion
if (os.majorVersion < 14 && (openGmail(subject: subject, body: body, recipients: toRecipients) ||
openOutlook(subject: subject, body: body, recipients: toRecipients))) {
return
}
// From iOS 14, it is possible to change the default mail app, and mailto should open a default mail app.
if !openDefaultMailApp(subject: subject, body: body, recipients: toRecipients) {
showMailComposingAlert()
}
}
}
// MARK: - MFMailComposeViewControllerDelegate
extension MailComposer: MFMailComposeViewControllerDelegate {
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
controller.dismiss(animated: true, completion: nil)
}
}

View file

@ -151,10 +151,7 @@ class ActionBarViewController: UIViewController {
}
private func configButton4() {
if additionalButtons.isEmpty {
visibleButtons.append(.share)
} else {
additionalButtons.append(.share)
if !additionalButtons.isEmpty {
visibleButtons.append(.more)
}
}

View file

@ -0,0 +1,123 @@
enum OpenInApplication: Int, CaseIterable {
case osm
case googleMaps
case appleMaps
case osmAnd
case yandexMaps
case dGis
case cityMapper
case moovit
case uber
case waze
}
extension OpenInApplication {
static var allCases: [OpenInApplication] {
[
.osm,
.googleMaps,
.appleMaps,
.osmAnd,
.yandexMaps,
.dGis,
.cityMapper,
.moovit,
.uber,
.waze
]
}
static var availableApps: [OpenInApplication] {
// OSM should always be first in the list.
let sortedApps: [OpenInApplication] = [.osm] + allCases.filter { $0 != .osm }.sorted(by: { $0.name < $1.name })
return sortedApps.filter { UIApplication.shared.canOpenURL(URL(string: $0.scheme)!) }
}
var name: String {
switch self {
case .osm:
return "OpenStreetMap"
case .googleMaps:
return "Google Maps"
case .appleMaps:
return "Apple Maps"
case .osmAnd:
return "OsmAnd Maps"
case .yandexMaps:
return "Yandex Maps"
case .dGis:
return "2GIS"
case .cityMapper:
return "Citymapper"
case .moovit:
return "Moovit"
case .uber:
return "Uber"
case .waze:
return "Waze"
}
}
// Schemes should be registered in LSApplicationQueriesSchemes - see Info.plist.
var scheme: String {
switch self {
case .osm:
return "https://osm.org/go/"
case .googleMaps:
return "comgooglemaps://"
case .appleMaps:
return "https://maps.apple.com/"
case .osmAnd:
return "osmandmaps://"
case .yandexMaps:
return "yandexmaps://"
case .dGis:
return "dgis://"
case .cityMapper:
return "citymapper://"
case .moovit:
return "moovit://"
case .uber:
return "uber://"
case .waze:
return "waze://"
}
}
func linkWith(coordinates: CLLocationCoordinate2D, zoomLevel: Int = Int(FrameworkHelper.currentZoomLevel()), destinationName: String? = nil) -> String {
let latitude = String(format: "%.6f", coordinates.latitude)
let longitude = String(format: "%.6f", coordinates.longitude)
switch self {
case .osm:
return GeoUtil.formattedOsmLink(for: coordinates, zoomLevel: Int32(zoomLevel))
case .googleMaps:
return "\(scheme)?&q=\(latitude),\(longitude)&z=\(zoomLevel)"
case .appleMaps:
if let destinationName {
return "\(scheme)?q=\(destinationName)&ll=\(latitude),\(longitude)&z=\(zoomLevel)"
}
return "\(scheme)?ll=\(latitude),\(longitude)&z=\(zoomLevel)"
case .osmAnd:
if let destinationName {
return "\(scheme)?lat=\(latitude)&lon=\(longitude)&z=\(zoomLevel)&title=\(destinationName)"
}
return "\(scheme)?lat=\(latitude)&lon=\(longitude)&z=\(zoomLevel)"
case .yandexMaps:
return "\(scheme)maps.yandex.ru/?pt=\(longitude),\(latitude)&z=\(zoomLevel)"
case .dGis:
return "\(scheme)2gis.ru/geo/\(longitude),\(latitude)"
case .cityMapper:
return "\(scheme)directions?endcoord=\(latitude),\(longitude)&endname=\(destinationName ?? "")"
case .moovit:
if let destinationName {
return "\(scheme)directions?dest_lat=\(latitude)&dest_lon=\(longitude)&dest_name=\(destinationName)"
}
return "\(scheme)directions?dest_lat=\(latitude)&dest_lon=\(longitude)"
case .uber:
return "\(scheme)?client_id=&action=setPickup&pickup=my_location&dropoff[latitude]=\(latitude)&dropoff[longitude]=\(longitude)"
case .waze:
return "\(scheme)?ll=\(latitude),\(longitude)"
}
}
}

View file

@ -0,0 +1,25 @@
typealias OpenInApplicationCompletionHandler = (OpenInApplication) -> Void
extension UIAlertController {
static func presentInAppActionSheet(from sourceView: UIView,
apps: [OpenInApplication] = OpenInApplication.availableApps,
didSelectApp: @escaping OpenInApplicationCompletionHandler) -> UIAlertController {
let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
apps.forEach { app in
let action = UIAlertAction(title: app.name, style: .default) { _ in
didSelectApp(app)
}
alertController.addAction(action)
}
let cancelAction = UIAlertAction(title: L("cancel"), style: .cancel)
alertController.addAction(cancelAction)
iPadSpecific {
alertController.popoverPresentationController?.sourceView = sourceView
alertController.popoverPresentationController?.sourceRect = sourceView.bounds
}
return alertController
}
}

View file

@ -1,18 +1,13 @@
protocol PlacePageButtonsViewControllerDelegate: AnyObject {
func didPressHotels()
func didPressAddPlace()
func didPressEditPlace()
func didPressAddBusiness()
}
class PlacePageButtonsViewController: UIViewController {
// @IBOutlet var bookingButton: UIButton!
@IBOutlet var addPlaceButton: UIButton!
@IBOutlet var editPlaceButton: UIButton!
// @IBOutlet var addBusinessButton: UIButton!
private var buttons: [UIButton?] {
// [bookingButton, addPlaceButton, editPlaceButton, addBusinessButton]
[addPlaceButton, editPlaceButton]
}
@ -30,18 +25,11 @@ class PlacePageButtonsViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// bookingButton.isHidden = !buttonsData.showHotelDescription
addPlaceButton.isHidden = !buttonsData.showAddPlace
editPlaceButton.isHidden = !buttonsData.showEditPlace
// addBusinessButton.isHidden = !buttonsData.showAddBusiness
buttons.forEach {
$0?.isEnabled = buttonsEnabled
}
}
@IBAction func onBooking(_ sender: UIButton) {
delegate?.didPressHotels()
addPlaceButton.isEnabled = buttonsData.enableAddPlace
editPlaceButton.isEnabled = buttonsData.enableEditPlace
}
@IBAction func onAddPlace(_ sender: UIButton) {
@ -51,8 +39,4 @@ class PlacePageButtonsViewController: UIViewController {
@IBAction func onEditPlace(_ sender: UIButton) {
delegate?.didPressEditPlace()
}
@IBAction func onAddBusiness(_ sender: UIButton) {
delegate?.didPressAddBusiness()
}
}

Some files were not shown because too many files have changed in this diff Show more