Compare commits

...

28 Commits

Author SHA1 Message Date
Andrea Busi b12f83680a release: Increase version for release 2025-03-06 09:02:38 +01:00
Andrea Busi ee827c41ae feat: Show callout for shakemap annotations 2025-02-28 16:49:51 +01:00
Andrea Busi d0d06394f0 feat: Save selected map pin style 2025-02-28 16:49:36 +01:00
Andrea Busi b933b900ed release: Increase version for release 2025-02-28 12:54:40 +01:00
Andrea Busi 0e7de44332 refactor: Move source position in seismic card 2025-02-28 12:54:40 +01:00
Andrea Busi 547bb794f0 feat: Add intensity map 2025-02-28 12:54:40 +01:00
Andrea Busi 9b1f1f12d2 feat: Add new APIService to handle network requests 2025-02-27 16:15:37 +01:00
Andrea Busi 7fc324367d feat: Add Log class 2025-02-27 16:15:37 +01:00
Andrea Busi 3cb712f709 fix: Set 2 as min reported user for felt filter 2025-02-25 16:40:41 +01:00
Andrea Busi 993e2924c7 release: Increase version for release 2025-02-21 16:28:48 +01:00
Andrea Busi a167c989cc feat: Add filter for "user felt"
Resolves: https://gitlab.steamware.net/eqn/eqn.ios/-/issues/81
2025-02-21 15:13:00 +01:00
Andrea Busi 1b50f4fd17 fix: Set locale in date formatters, to solve parsing issues with 0-12 hours 2025-02-20 15:16:59 +01:00
Andrea Busi 0003b4607c refactor: Make filters bigger 2025-02-20 15:16:42 +01:00
Andrea Busi 85c9f333ce fix: Hide scroll bar in seismics table 2025-02-12 09:06:25 +01:00
Andrea Busi 217cbfd4e3 release: Increase version for release 2025-02-11 15:51:15 +01:00
Andrea Busi 5d8de1fb36 feat: Improve scroll indicator with tons of rectangles 2025-02-11 15:50:20 +01:00
Andrea Busi f23bb78ceb fix: Reset center position when data changes 2025-02-11 15:50:20 +01:00
Andrea Busi 0d91954614 feat: Upgrade to Xcode recommended settings 2025-02-11 14:58:35 +01:00
Andrea Busi 49f5fa91fe release: Increase version for release 2025-02-06 14:10:27 +01:00
Andrea Busi 68e560768b refactor: Replace renamed APIs for GoogleAds SDK 2025-02-06 12:00:26 +01:00
Andrea Busi 3e9c319b50 dependency: Bump GoogleAds SDK to v12 2025-02-06 12:00:26 +01:00
Andrea Busi d35e0e1b4a refactor: Replace deprecated API for Facebook SDK 2025-02-06 12:00:26 +01:00
Andrea Busi 6ede137ef7 dependency: Bump Facebook SDK to v18 2025-02-06 12:00:19 +01:00
Andrea Busi c94195d48e dependency: Update repo url for Shogun 2025-02-06 11:53:53 +01:00
Andrea Busi 28919d7b72 release: Increase version for release 2025-02-06 11:53:44 +01:00
Andrea Busi a239534b91 feat: Add scroll indicator view in seismic list 2025-02-06 11:33:22 +01:00
Andrea Busi 226342f36c dependency: Bump Firebase 2025-02-06 11:33:22 +01:00
Andrea Busi ca6afbec5f refactor: Delete workspace file, no longer used 2025-01-31 14:38:26 +01:00
40 changed files with 1154 additions and 215 deletions
+5
View File
@@ -1,5 +1,10 @@
# Changelog
## Versione 5.9
- Aggiunta barra laterale in lista sismi
- Aggiunto filtro "sismi percepiti"
- Aggiunta "Mappa intensità"
## Versione 5.8.1
- Corrette traduzioni errate (causavano crash)
- Aggiunto ordinamento in sottoscrizioni utente (prima Top10k)
@@ -13,6 +13,9 @@
650247122A61832F001AC512 /* EQNBackgroundPositionDebugHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 650247112A61832F001AC512 /* EQNBackgroundPositionDebugHelper.swift */; };
650247152A618D7F001AC512 /* Foundation+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 650247142A618D7F001AC512 /* Foundation+Extensions.swift */; };
650B23AB2632CCD3007AE752 /* UIView+EQNExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 650B23AA2632CCD3007AE752 /* UIView+EQNExtensions.swift */; };
6514FF6A2D720C3F000A7BD0 /* MapPinStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6514FF692D720C3A000A7BD0 /* MapPinStyle.swift */; };
6514FF6C2D720CBE000A7BD0 /* MapPinStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6514FF692D720C3A000A7BD0 /* MapPinStyle.swift */; };
6514FF6D2D720CBE000A7BD0 /* MapPinStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6514FF692D720C3A000A7BD0 /* MapPinStyle.swift */; };
65172F532C25C496006D2A5C /* EQNSeismicAnnotationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65172F522C25C496006D2A5C /* EQNSeismicAnnotationView.swift */; };
651901B925F5358700CAFF20 /* EQNMapAnnotationSeismic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 651901B825F5358700CAFF20 /* EQNMapAnnotationSeismic.swift */; };
6525A82625E13FD4008FE0D0 /* SeismicNetworkAdvertiseTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6525A82525E13FD4008FE0D0 /* SeismicNetworkAdvertiseTableViewCell.swift */; };
@@ -41,6 +44,9 @@
656E02162C1C4DF2008D0E92 /* EQNBaseContainerTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 656E02152C1C4DF2008D0E92 /* EQNBaseContainerTableViewCell.swift */; };
656EB9362A15FD16009DADF3 /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 656EB9382A15FD16009DADF3 /* Localizable.stringsdict */; };
656EB9412A16288A009DADF3 /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 656EB9382A15FD16009DADF3 /* Localizable.stringsdict */; };
657415E02D70B6F800F54890 /* EQNMapAnnotationShakemap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 657415DE2D70B6F700F54890 /* EQNMapAnnotationShakemap.swift */; };
657747FD2D4D12A200213830 /* SeismicNetworkData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 657747FC2D4D12A200213830 /* SeismicNetworkData.swift */; };
657747FF2D4D2BA200213830 /* SeismicNetworkScrollIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 657747FE2D4D2BA100213830 /* SeismicNetworkScrollIndicatorView.swift */; };
6586971125F44C26009C0182 /* EQNBlurredCloseButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6586971025F44C26009C0182 /* EQNBlurredCloseButton.swift */; };
658BAB7B25FE67930015C454 /* EQNBaseMapRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 658BAB7A25FE67930015C454 /* EQNBaseMapRepresentable.swift */; };
658BC0292859A456009EECAA /* RealtimeAlertViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 658BC0282859A456009EECAA /* RealtimeAlertViewController.swift */; };
@@ -73,6 +79,10 @@
65E6AC702C2DB3B60073F8FE /* FirebaseMessaging in Frameworks */ = {isa = PBXBuildFile; productRef = 65E6AC6F2C2DB3B60073F8FE /* FirebaseMessaging */; };
65EA58802A60269C0038EE9D /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 8C10B0BD2281FE7F00125C9F /* Localizable.strings */; };
65EA58822A60360D0038EE9D /* EQNRealtimePushNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65EA58812A60360D0038EE9D /* EQNRealtimePushNotification.swift */; };
65F9A6082D706592008A12B5 /* SeismicNetworksIntensityMapViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65F9A6072D706592008A12B5 /* SeismicNetworksIntensityMapViewController.swift */; };
65F9A60A2D706ADC008A12B5 /* APIService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65F9A6092D706ADC008A12B5 /* APIService.swift */; };
65F9A60C2D70781A008A12B5 /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65F9A60B2D70781A008A12B5 /* Log.swift */; };
65F9A60E2D707BFE008A12B5 /* EQNShakemap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65F9A60D2D707BFE008A12B5 /* EQNShakemap.swift */; };
65F9B49C2A8CA22800F13260 /* BackgroundTaskManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65F9B49B2A8CA22800F13260 /* BackgroundTaskManager.swift */; };
65F9B49E2A8CA2AC00F13260 /* EQNBackgroundPosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65F9B49D2A8CA2AC00F13260 /* EQNBackgroundPosition.swift */; };
65F9B4A02A8CC58200F13260 /* UpdateUserLocationTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65F9B49F2A8CC58200F13260 /* UpdateUserLocationTask.swift */; };
@@ -307,6 +317,7 @@
650247112A61832F001AC512 /* EQNBackgroundPositionDebugHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EQNBackgroundPositionDebugHelper.swift; sourceTree = "<group>"; };
650247142A618D7F001AC512 /* Foundation+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Foundation+Extensions.swift"; sourceTree = "<group>"; };
650B23AA2632CCD3007AE752 /* UIView+EQNExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+EQNExtensions.swift"; sourceTree = "<group>"; };
6514FF692D720C3A000A7BD0 /* MapPinStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapPinStyle.swift; sourceTree = "<group>"; };
65172F522C25C496006D2A5C /* EQNSeismicAnnotationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EQNSeismicAnnotationView.swift; sourceTree = "<group>"; };
651901B825F5358700CAFF20 /* EQNMapAnnotationSeismic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EQNMapAnnotationSeismic.swift; sourceTree = "<group>"; };
6525A82525E13FD4008FE0D0 /* SeismicNetworkAdvertiseTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeismicNetworkAdvertiseTableViewCell.swift; sourceTree = "<group>"; };
@@ -338,6 +349,9 @@
656EB93E2A15FD1E009DADF3 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = it; path = it.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
656EB93F2A15FD1F009DADF3 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = es; path = es.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
656EB9402A15FD1F009DADF3 /* tr-TR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "tr-TR"; path = "tr-TR.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
657415DE2D70B6F700F54890 /* EQNMapAnnotationShakemap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EQNMapAnnotationShakemap.swift; sourceTree = "<group>"; };
657747FC2D4D12A200213830 /* SeismicNetworkData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeismicNetworkData.swift; sourceTree = "<group>"; };
657747FE2D4D2BA100213830 /* SeismicNetworkScrollIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeismicNetworkScrollIndicatorView.swift; sourceTree = "<group>"; };
6586971025F44C26009C0182 /* EQNBlurredCloseButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EQNBlurredCloseButton.swift; sourceTree = "<group>"; };
658BAB7A25FE67930015C454 /* EQNBaseMapRepresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EQNBaseMapRepresentable.swift; sourceTree = "<group>"; };
658BC0282859A456009EECAA /* RealtimeAlertViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealtimeAlertViewController.swift; sourceTree = "<group>"; };
@@ -373,6 +387,10 @@
65DBFB7225E2BBF20041CBA6 /* GADTTemplateView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GADTTemplateView.m; sourceTree = "<group>"; };
65DBFB7325E2BBF20041CBA6 /* GADTMediumTemplateView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GADTMediumTemplateView.h; sourceTree = "<group>"; };
65EA58812A60360D0038EE9D /* EQNRealtimePushNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EQNRealtimePushNotification.swift; sourceTree = "<group>"; };
65F9A6072D706592008A12B5 /* SeismicNetworksIntensityMapViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeismicNetworksIntensityMapViewController.swift; sourceTree = "<group>"; };
65F9A6092D706ADC008A12B5 /* APIService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIService.swift; sourceTree = "<group>"; };
65F9A60B2D70781A008A12B5 /* Log.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Log.swift; sourceTree = "<group>"; };
65F9A60D2D707BFE008A12B5 /* EQNShakemap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EQNShakemap.swift; sourceTree = "<group>"; };
65F9B49B2A8CA22800F13260 /* BackgroundTaskManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundTaskManager.swift; sourceTree = "<group>"; };
65F9B49D2A8CA2AC00F13260 /* EQNBackgroundPosition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EQNBackgroundPosition.swift; sourceTree = "<group>"; };
65F9B49F2A8CC58200F13260 /* UpdateUserLocationTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateUserLocationTask.swift; sourceTree = "<group>"; };
@@ -663,8 +681,10 @@
65DBFB5225E2A2580041CBA6 /* Map annotation */ = {
isa = PBXGroup;
children = (
6514FF692D720C3A000A7BD0 /* MapPinStyle.swift */,
653C67FB25F3D63500FE52AC /* EQNMapAnnotationUserReport.swift */,
651901B825F5358700CAFF20 /* EQNMapAnnotationSeismic.swift */,
657415DE2D70B6F700F54890 /* EQNMapAnnotationShakemap.swift */,
654D18C825F93CD700BB6DB0 /* EQNMapAnnotationPastquake.swift */,
);
path = "Map annotation";
@@ -912,12 +932,15 @@
DC141968250E769B0059E060 /* Seismic Networks */ = {
isa = PBXGroup;
children = (
6562C80625FFA6B100C85273 /* SeismicNetworkViewModel.swift */,
DC28142E2519C21400C1AFF7 /* Cells */,
65F0857E2609288E0002615C /* Filters */,
6562C80625FFA6B100C85273 /* SeismicNetworkViewModel.swift */,
657747FC2D4D12A200213830 /* SeismicNetworkData.swift */,
657747FE2D4D2BA100213830 /* SeismicNetworkScrollIndicatorView.swift */,
DC2814372519C56100C1AFF7 /* SeismicNetworksViewController.swift */,
DCC76BD7251F56050005C4DC /* SeismicCardSettingsViewController.swift */,
65DBFB4A25E29DD60041CBA6 /* SeismicNetworksMapDetailViewController.swift */,
65F9A6072D706592008A12B5 /* SeismicNetworksIntensityMapViewController.swift */,
);
path = "Seismic Networks";
sourceTree = "<group>";
@@ -987,6 +1010,7 @@
DC10563F251E7EC0002579BB /* Extensions */,
8CF66054214C566A009F4314 /* Reachability.h */,
8CF66056214C566A009F4314 /* Reachability.m */,
65F9A60B2D70781A008A12B5 /* Log.swift */,
);
path = Libs;
sourceTree = "<group>";
@@ -1042,6 +1066,7 @@
8C4E344A2152EE5B008B0D2A /* EQNGeneratoreURLServer.m */,
8CF66057214C566B009F4314 /* ServerRequest.h */,
8CF66055214C566A009F4314 /* ServerRequest.m */,
65F9A6092D706ADC008A12B5 /* APIService.swift */,
);
path = "Server Requests";
sourceTree = "<group>";
@@ -1113,6 +1138,7 @@
65EA58812A60360D0038EE9D /* EQNRealtimePushNotification.swift */,
6552C1452926DBA1008E723C /* AppPreferences.swift */,
6590EFF72C3004EA00F41420 /* EQNOfficialPushNotification.swift */,
65F9A60D2D707BFE008A12B5 /* EQNShakemap.swift */,
);
path = Models;
sourceTree = "<group>";
@@ -1290,8 +1316,9 @@
8CBD3DBA2149B9AD0070C963 /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
LastSwiftUpdateCheck = 1410;
LastUpgradeCheck = 1410;
LastUpgradeCheck = 1620;
ORGANIZATIONNAME = "Earthquake Network";
TargetAttributes = {
65FFDC91292F672B00EA821B = {
@@ -1531,6 +1558,7 @@
files = (
65FFDC95292F672B00EA821B /* NotificationService.swift in Sources */,
6502470E2A6136F0001AC512 /* Constants.swift in Sources */,
6514FF6D2D720CBE000A7BD0 /* MapPinStyle.swift in Sources */,
6502470F2A613AD7001AC512 /* EQNUserData.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -1559,6 +1587,7 @@
DC03BEAB250BC0A60084769B /* EQNRoundedButton.swift in Sources */,
65AD23CE261B03D400E3B57C /* SubscriptionsDescriptionTableViewCell.swift in Sources */,
DCD4571C24F6CF0D00B58304 /* EQNGenericValue.swift in Sources */,
657747FD2D4D12A200213830 /* SeismicNetworkData.swift in Sources */,
8C4E343F215012FA008B0D2A /* EQNManager.m in Sources */,
DCAA913F24F68A1D00145A3D /* SettingMultivaluesTableViewCell.swift in Sources */,
DCF9E14F24F6EA07002B6B1D /* EQNSeismicNetwork.swift in Sources */,
@@ -1571,6 +1600,7 @@
6562C80725FFA6B100C85273 /* SeismicNetworkViewModel.swift in Sources */,
DCDE0BD924E58CCE00209778 /* EQNMainTabBarController.m in Sources */,
8C4E344B2152EE5B008B0D2A /* EQNGeneratoreURLServer.m in Sources */,
65F9A60A2D706ADC008A12B5 /* APIService.swift in Sources */,
6590EFF82C3004EA00F41420 /* EQNOfficialPushNotification.swift in Sources */,
DC99A50724E66E5F0071BC9F /* EQNStartupCommandsBuilder.swift in Sources */,
8CF66058214C566B009F4314 /* ServerRequest.m in Sources */,
@@ -1582,6 +1612,7 @@
DC886A5D24E92D5500F7A5D3 /* EQNBaseViewController.m in Sources */,
8C593E8A217BA2470008B260 /* EQNSegnalazione.m in Sources */,
8CBD3DCA2149B9AD0070C963 /* AllerteViewController.m in Sources */,
65F9A60E2D707BFE008A12B5 /* EQNShakemap.swift in Sources */,
DC646F32252B698B000AA5FD /* AlertsSeismicNotificationCompactTableViewCell.swift in Sources */,
DCF10DCD24D2C935009F34C3 /* EQNPurchaseAvailability.swift in Sources */,
6552C1462926DBA1008E723C /* AppPreferences.swift in Sources */,
@@ -1596,6 +1627,7 @@
8CF6604F214C0E58009F4314 /* EQNCalibrazione.m in Sources */,
65355FFF25F38D3300BB57D2 /* SegnalazioniMapViewController.swift in Sources */,
DCBB84F0252CFC4600F12633 /* AlertsNoLocationTableViewCell.swift in Sources */,
657747FF2D4D2BA200213830 /* SeismicNetworkScrollIndicatorView.swift in Sources */,
651901B925F5358700CAFF20 /* EQNMapAnnotationSeismic.swift in Sources */,
DC99A50324E66E270071BC9F /* EQNCommandProtocol.swift in Sources */,
DCF10DC624D2B8C7009F34C3 /* EQNPurchaseUtility.swift in Sources */,
@@ -1604,6 +1636,7 @@
65BBB22C26064BE6005D6CDF /* SegnalazioniLast24HoursCell.swift in Sources */,
DC47D1BC252A0C2B004119F6 /* AlertsPastEartquakesTableViewCell.swift in Sources */,
654D18C925F93CD700BB6DB0 /* EQNMapAnnotationPastquake.swift in Sources */,
6514FF6A2D720C3F000A7BD0 /* MapPinStyle.swift in Sources */,
DCEFF21724F58569009D3FE1 /* SettingSectionHeaderView.swift in Sources */,
65DB60FD2C172C4A00164366 /* EQNSettingRealTimeAlert.swift in Sources */,
DCBB267A24D1E7F500F04559 /* SubscriptionsHeaderTableViewCell.swift in Sources */,
@@ -1622,11 +1655,13 @@
DC7EEE4A252A11C9004B4A2A /* AlertsSmartphoneNetworkTableViewCell.swift in Sources */,
DC7EEE4F252A1634004B4A2A /* AlertsPriorityServiceTableViewCell.swift in Sources */,
653604E9262348FA00B2B651 /* EQNBaseMapFilter.swift in Sources */,
65F9A60C2D70781A008A12B5 /* Log.swift in Sources */,
65583A05261B83BE00ECA9F9 /* UIKit+Extensions.swift in Sources */,
65DBFB7625E2BBF20041CBA6 /* GADTTemplateView.m in Sources */,
8C5EA23D2177B51C002DC156 /* SegnalazioniViewController.m in Sources */,
656E02162C1C4DF2008D0E92 /* EQNBaseContainerTableViewCell.swift in Sources */,
656D138F2C2225560094F597 /* SubscriptionDetailsViewController.swift in Sources */,
65F9A6082D706592008A12B5 /* SeismicNetworksIntensityMapViewController.swift in Sources */,
653C67E225F3CC2E00FE52AC /* EQNCustomAnnotationView.swift in Sources */,
8CF4F4D8216D3A110057110B /* EQNAreaCheck.m in Sources */,
8C4E34452152B707008B0D2A /* EQNAccelerometroManager.m in Sources */,
@@ -1649,6 +1684,7 @@
8C8EBBA721540039002784BA /* EQNUser.m in Sources */,
8CADAA9421B2627D0044E256 /* EQNLogViewController.m in Sources */,
DC3BA11124D1A9C90062EE7F /* SubscriptionsViewController.swift in Sources */,
657415E02D70B6F800F54890 /* EQNMapAnnotationShakemap.swift in Sources */,
DC646F27252B694A000AA5FD /* AlertsSeismicNotificationExpandedTableViewCell.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -1669,6 +1705,7 @@
654D18DA25F9424700BB6DB0 /* EQNUtility+Extensions.swift in Sources */,
DC0AE1BA2538204100111307 /* EQNPastquakes.m in Sources */,
65D9938A29219DEC00F2B0EB /* UIKit+Extensions.swift in Sources */,
6514FF6C2D720CBE000A7BD0 /* MapPinStyle.swift in Sources */,
8CF12CD921DE49B600613AC5 /* NotificationViewController.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -1773,7 +1810,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 139;
CURRENT_PROJECT_VERSION = 146;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = WJA4MR4CPC;
GCC_PREPROCESSOR_DEFINITIONS = (
@@ -1790,7 +1827,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 5.8.2;
MARKETING_VERSION = 5.9.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.finazzi.distquake.notificationservice;
@@ -1815,7 +1852,7 @@
CODE_SIGN_IDENTITY = "Apple Distribution";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 139;
CURRENT_PROJECT_VERSION = 146;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = WJA4MR4CPC;
GENERATE_INFOPLIST_FILE = YES;
@@ -1828,7 +1865,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 5.8.2;
MARKETING_VERSION = 5.9.0;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.finazzi.distquake.notificationservice;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -1964,16 +2001,16 @@
8CBD3DDC2149B9AD0070C963 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = "Earthquake Network/Earthquake Network.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 139;
CURRENT_PROJECT_VERSION = 146;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = WJA4MR4CPC;
ENABLE_MODULE_VERIFIER = YES;
GCC_PREFIX_HEADER = "Earthquake Network/Earthquake Network-Prefix.pch";
INFOPLIST_FILE = "Earthquake Network/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
@@ -1981,7 +2018,8 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 5.8.2;
MARKETING_VERSION = 5.9.0;
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++14";
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -2051,15 +2089,15 @@
8CBD3DDD2149B9AD0070C963 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = "Earthquake Network/Earthquake Network.entitlements";
CODE_SIGN_IDENTITY = "Apple Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 139;
CURRENT_PROJECT_VERSION = 146;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = WJA4MR4CPC;
ENABLE_MODULE_VERIFIER = YES;
GCC_PREFIX_HEADER = "Earthquake Network/Earthquake Network-Prefix.pch";
INFOPLIST_FILE = "Earthquake Network/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
@@ -2067,7 +2105,8 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 5.8.2;
MARKETING_VERSION = 5.9.0;
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++14";
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -2140,7 +2179,7 @@
CODE_SIGN_ENTITLEMENTS = EQNNotificationContent/EQNNotificationContent.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 139;
CURRENT_PROJECT_VERSION = 146;
DEVELOPMENT_TEAM = WJA4MR4CPC;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
@@ -2155,7 +2194,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 5.8.2;
MARKETING_VERSION = 5.9.0;
PRODUCT_BUNDLE_IDENTIFIER = com.finazzi.distquake.notificationcontent;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "EQNetwork Extension Content - Development";
@@ -2174,7 +2213,7 @@
CODE_SIGN_ENTITLEMENTS = EQNNotificationContent/EQNNotificationContent.entitlements;
CODE_SIGN_IDENTITY = "Apple Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 139;
CURRENT_PROJECT_VERSION = 146;
DEVELOPMENT_TEAM = WJA4MR4CPC;
GCC_PREPROCESSOR_DEFINITIONS = (
"ADS_ENABLED=0",
@@ -2187,7 +2226,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 5.8.2;
MARKETING_VERSION = 5.9.0;
PRODUCT_BUNDLE_IDENTIFIER = com.finazzi.distquake.notificationcontent;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "EQNetwork Extension Content - AppStore";
@@ -2242,7 +2281,7 @@
/* Begin XCRemoteSwiftPackageReference section */
6552C13629262119008E723C /* XCRemoteSwiftPackageReference "Shogun" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/andreabusi-it/Shogun";
repositoryURL = "https://github.com/andreabusi-it/Shogun.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 1.0.0;
@@ -2269,7 +2308,7 @@
repositoryURL = "https://github.com/googleads/swift-package-manager-google-mobile-ads.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 11.3.0;
minimumVersion = 12.0.0;
};
};
65B16E282BDFB39B0020527E /* XCRemoteSwiftPackageReference "facebook-ios-sdk" */ = {
@@ -2277,7 +2316,7 @@
repositoryURL = "https://github.com/facebook/facebook-ios-sdk";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 14.1.0;
minimumVersion = 18.0.0;
};
};
65E6AC6C2C2DB3B60073F8FE /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */ = {
@@ -2285,7 +2324,7 @@
repositoryURL = "https://github.com/firebase/firebase-ios-sdk.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 10.28.1;
minimumVersion = 11.1.0;
};
};
/* End XCRemoteSwiftPackageReference section */
@@ -1,13 +1,13 @@
{
"originHash" : "c29b9b16ee6b4d1a6fec2debc59749097860256c8cbb2addc2abc08d3adba59d",
"originHash" : "898a30d298491e1ce821191ebfa2e7d28e7c9ea4c119cbfdfb1b245bc94ac6c3",
"pins" : [
{
"identity" : "abseil-cpp-binary",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/abseil-cpp-binary.git",
"state" : {
"revision" : "748c7837511d0e6a507737353af268484e1745e2",
"version" : "1.2024011601.1"
"revision" : "194a6706acbd25e4ef639bcaddea16e8758a3e27",
"version" : "1.2024011602.0"
}
},
{
@@ -15,8 +15,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/app-check.git",
"state" : {
"revision" : "3b62f154d00019ae29a71e9738800bb6f18b236d",
"version" : "10.19.2"
"revision" : "61b85103a1aeed8218f17c794687781505fbbef5",
"version" : "11.2.0"
}
},
{
@@ -33,8 +33,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/facebook/facebook-ios-sdk",
"state" : {
"revision" : "c19607d535864533523d1f437c84035e5fb101cf",
"version" : "14.1.0"
"revision" : "b28dde427715b45a26ebebf697929f4a81b15e04",
"version" : "18.0.0"
}
},
{
@@ -42,8 +42,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/firebase/firebase-ios-sdk.git",
"state" : {
"revision" : "eca84fd638116dd6adb633b5a3f31cc7befcbb7d",
"version" : "10.29.0"
"revision" : "075679d6b25b28f4cb167f1d7769b58fb556fb30",
"version" : "11.8.0"
}
},
{
@@ -51,8 +51,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleAppMeasurement.git",
"state" : {
"revision" : "fe727587518729046fc1465625b9afd80b5ab361",
"version" : "10.28.0"
"revision" : "be0881ff728eca210ccb628092af400c086abda3",
"version" : "11.7.0"
}
},
{
@@ -60,8 +60,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleDataTransport.git",
"state" : {
"revision" : "a637d318ae7ae246b02d7305121275bc75ed5565",
"version" : "9.4.0"
"revision" : "617af071af9aa1d6a091d59a202910ac482128f9",
"version" : "10.1.0"
}
},
{
@@ -69,8 +69,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleUtilities.git",
"state" : {
"revision" : "57a1d307f42df690fdef2637f3e5b776da02aad6",
"version" : "7.13.3"
"revision" : "53156c7ec267db846e6b64c9f4c4e31ba4cf75eb",
"version" : "8.0.2"
}
},
{
@@ -78,8 +78,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/grpc-binary.git",
"state" : {
"revision" : "e9fad491d0673bdda7063a0341fb6b47a30c5359",
"version" : "1.62.2"
"revision" : "f56d8fc3162de9a498377c7b6cea43431f4f5083",
"version" : "1.65.1"
}
},
{
@@ -150,8 +150,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/googleads/swift-package-manager-google-mobile-ads.git",
"state" : {
"revision" : "9ab66e38f5f0c2d02f2b024b1babd880130f19bf",
"version" : "11.3.0"
"revision" : "aa24c7dc03bca62c42747314dc8537f15587b50d",
"version" : "12.0.0"
}
},
{
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1410"
LastUpgradeVersion = "1620"
wasCreatedForAppExtension = "YES"
version = "2.0">
<BuildAction
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1410"
LastUpgradeVersion = "1620"
wasCreatedForAppExtension = "YES"
version = "2.0">
<BuildAction
@@ -15,7 +15,7 @@
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "8C4B0B7921CACE3F00AED489"
BlueprintIdentifier = "65FFDC91292F672B00EA821B"
BuildableName = "EQNNotificationService.appex"
BlueprintName = "EQNNotificationService"
ReferencedContainer = "container:Earthquake Network.xcodeproj">
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1410"
LastUpgradeVersion = "1620"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
+4 -4
View File
@@ -212,14 +212,14 @@
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[ATTrackingManager requestTrackingAuthorizationWithCompletionHandler:^(ATTrackingManagerAuthorizationStatus status) {
if (status == ATTrackingManagerAuthorizationStatusAuthorized) {
[FBSDKSettings.sharedSettings setAdvertiserTrackingEnabled:YES];
FBSDKSettings.sharedSettings.isAdvertiserTrackingEnabled = YES;
} else {
[FBSDKSettings.sharedSettings setAdvertiserTrackingEnabled:NO];
FBSDKSettings.sharedSettings.isAdvertiserTrackingEnabled = NO;
}
}];
});
} else {
[FBSDKSettings.sharedSettings setAdvertiserTrackingEnabled:YES];
FBSDKSettings.sharedSettings.isAdvertiserTrackingEnabled = YES;
}
}
@@ -232,7 +232,7 @@
- (void)configureFacebookSDKWithApplication:(UIApplication *)application andOptions:(NSDictionary *)launchOptions
{
[FBSDKApplicationDelegate.sharedInstance application:application didFinishLaunchingWithOptions:launchOptions];
[FBSDKSettings.sharedSettings setAdvertiserIDCollectionEnabled:YES];
FBSDKSettings.sharedSettings.isAdvertiserIDCollectionEnabled = YES;
}
#pragma mark - FIRMessagingDelegate
+4 -1
View File
@@ -56,13 +56,16 @@ extension UserDefaults {
static let UserReportExpandedView = "EQNData.UserReportExpandedView"
/// Se `true` visualizza le opzioni nella singole card in reti sismiche
static let AlertsShowCardOptions = "EQNetwork.AlertsShowAllCards"
/// Indica lo stile di pin da visualizzare nelle mappe
static let MapPinStyle = "EQNetwork.MapPinStyle"
// Migrazioni
static let AppMigrationV5_3 = "EQNUserDefaultMigrationV5_3"
static let AppMigrationV5_4 = "EQNUserDefaultMigrationV5_4"
static let AppMigrationV5_8 = "EQNUserDefaultMigrationV5_8"
static let AppMigrationV5_8_2 = "EQNUserDefaultMigrationV5_8_2"
static let AppMigrationV5_9 = "EQNUserDefaultMigrationV5_9"
static let SettingsSeismicNetworkNotificationMigrationV5_8 = "EQNUserDefaultSettingsSeismicNetworkNotificationMigrationV5_8"
static let SettingsUserReportNotificationMigrationV5_8 = "EQNUserDefaultSettingsUserReportNotificationMigrationV5_8"
static let SismicFiltersMigrationV5_8 = "EQNUserDefaultSismicFiltersMigrationV5_8"
@@ -28,7 +28,7 @@ class SeismicNetworkAdvertiseTableViewCell: UITableViewCell {
return view
}()
private lazy var bannerView: GADNativeAdView = {
private lazy var bannerView: NativeAdView = {
let view = GADTMediumTemplateView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
@@ -71,7 +71,7 @@ class SeismicNetworkAdvertiseTableViewCell: UITableViewCell {
// MARK: - Public
func loadNativeAd(_ nativeAd: GADNativeAd) {
func loadNativeAd(_ nativeAd: NativeAd) {
bannerView.nativeAd = nativeAd
}
}
@@ -15,6 +15,7 @@ protocol SeismicNetworkTableViewCellDelegate: AnyObject {
func seismicNetworkCellDidTapShare(_ cell: SeismicNetworkTableViewCell)
func seismicNetworkCellDidTapMap(_ cell: SeismicNetworkTableViewCell)
func seismicNetworkCellDidTapMapDetail(_ cell: SeismicNetworkTableViewCell)
func seismicNetworkCellDidTapIntensityMapDetail(_ cell: SeismicNetworkTableViewCell)
func seismicNetworkCellDidTapCalendar(_ cell: SeismicNetworkTableViewCell)
func seismicNetworkCellDidTapSettings(_ cell: SeismicNetworkTableViewCell)
func seismicNetworkCellDidTapClose(_ cell: SeismicNetworkTableViewCell)
@@ -23,9 +24,7 @@ protocol SeismicNetworkTableViewCellDelegate: AnyObject {
class SeismicNetworkTableViewCell: UITableViewCell {
static let Identifier = "SeismicNetworkTableViewCell"
typealias MagnitudeColors = (textColor: UIColor, startColor: UIColor, endColor: UIColor)
/// Available informations to display inside the cell
enum InformationType: Int {
case preliminary
@@ -35,6 +34,7 @@ class SeismicNetworkTableViewCell: UITableViewCell {
case population
case realtimeSmartphones
case reportUsers
case intensityMap
case buttons
}
@@ -51,6 +51,7 @@ class SeismicNetworkTableViewCell: UITableViewCell {
// MARK: - Internal
private static let DefaultButtonHeight: CGFloat = 30.0
private static let DefaultVerticalSpacing: CGFloat = 6.0
/// Seismic to show
@@ -58,8 +59,6 @@ class SeismicNetworkTableViewCell: UITableViewCell {
private(set) var displayType = DisplayType.normal
private var informationTypes = [InformationType]()
private var isPushSelected = false
private var colors: MagnitudeColors?
// MARK: - UI Components
@@ -301,10 +300,17 @@ class SeismicNetworkTableViewCell: UITableViewCell {
stackViewInformations.topAnchor.constraint(equalTo: magnitudeLabel.bottomAnchor, constant: Self.DefaultVerticalSpacing).isActive = true
stackViewInformations.leadingAnchor.constraint(equalTo: informationsLeadingAnchor, constant: 14).isActive = true
stackViewInformations.trailingAnchor.constraint(equalTo: informationsTrailingAnchor, constant: -14).isActive = true
previousView = stackViewInformations
if informationTypes.contains(.realtimeSmartphones) || informationTypes.contains(.reportUsers) {
let separator2 = addSeparator(constraintTo: stackViewInformations.bottomAnchor)
// network
containerView.addSubview(networkLabel)
networkLabel.topAnchor.constraint(equalTo: previousView.bottomAnchor, constant: Self.DefaultVerticalSpacing).isActive = true
networkLabel.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true
networkLabel.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true
previousView = networkLabel
if informationTypes.contains(.realtimeSmartphones) || informationTypes.contains(.reportUsers) || informationTypes.contains(.intensityMap) {
let separator2 = addSeparator(constraintTo: previousView.bottomAnchor)
let stackViewReports = UIStackView()
stackViewReports.translatesAutoresizingMaskIntoConstraints = false
@@ -319,30 +325,30 @@ class SeismicNetworkTableViewCell: UITableViewCell {
if informationTypes.contains(.reportUsers) {
stackViewReports.addArrangedSubview(alertsLabel)
}
if informationTypes.contains(.intensityMap) {
let buttonMap = EQNRoundedButton.make(title: "🎯 \(NSLocalizedString("shakemap", comment: ""))", target: self, action: #selector(intensityMapTapped(_:)))
stackViewReports.addArrangedSubview(buttonMap)
buttonMap.heightAnchor.constraint(equalToConstant: Self.DefaultButtonHeight).isActive = true
buttonMap.leadingAnchor.constraint(equalTo: stackViewReports.leadingAnchor).isActive = true
buttonMap.trailingAnchor.constraint(equalTo: stackViewReports.trailingAnchor).isActive = true
}
containerView.addSubview(stackViewReports)
stackViewReports.topAnchor.constraint(equalTo: separator2.bottomAnchor, constant: Self.DefaultVerticalSpacing).isActive = true
stackViewReports.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor, constant: 20.0).isActive = true
stackViewReports.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor, constant: -20.0).isActive = true
stackViewReports.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true
stackViewReports.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true
let separator3 = addSeparator(constraintTo: stackViewReports.bottomAnchor)
previousView = separator3
}
// network
containerView.addSubview(networkLabel)
networkLabel.topAnchor.constraint(equalTo: previousView.bottomAnchor, constant: Self.DefaultVerticalSpacing).isActive = true
networkLabel.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true
networkLabel.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true
previousView = networkLabel
if informationTypes.contains(.buttons) {
// buttons
let stackViewButtons = UIStackView()
stackViewButtons.translatesAutoresizingMaskIntoConstraints = false
stackViewButtons.axis = .horizontal
stackViewButtons.distribution = .fillEqually
stackViewButtons.spacing = 4
stackViewButtons.spacing = 8
let buttonMap = EQNRoundedButton.make(title: "🗺", target: self, action: #selector(mapTapped(_:)))
stackViewButtons.addArrangedSubview(buttonMap)
@@ -352,7 +358,7 @@ class SeismicNetworkTableViewCell: UITableViewCell {
stackViewButtons.addArrangedSubview(buttonSettings)
containerView.addSubview(stackViewButtons)
stackViewButtons.heightAnchor.constraint(equalToConstant: 30.0).isActive = true
stackViewButtons.heightAnchor.constraint(equalToConstant: Self.DefaultButtonHeight).isActive = true
stackViewButtons.topAnchor.constraint(equalTo: previousView.bottomAnchor, constant: Self.DefaultVerticalSpacing).isActive = true
stackViewButtons.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true
stackViewButtons.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true
@@ -374,6 +380,7 @@ class SeismicNetworkTableViewCell: UITableViewCell {
let buttonClose = EQNRoundedButton.make(title: NSLocalizedString("official_close", comment: "").uppercased(), target: self, action: #selector(closeTapped(_:)))
containerView.addSubview(buttonClose)
buttonClose.heightAnchor.constraint(equalToConstant: Self.DefaultButtonHeight).isActive = true
buttonClose.topAnchor.constraint(equalTo: previousView.bottomAnchor, constant: Self.DefaultVerticalSpacing).isActive = true
buttonClose.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true
buttonClose.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true
@@ -398,17 +405,13 @@ class SeismicNetworkTableViewCell: UITableViewCell {
let viewModel = SeismicNetworkViewModel(seismic: seismic)
if let colors = colors {
gradientView.image = .gradient(from: colors.startColor, to: colors.endColor, with: .init(origin: .zero, size: .init(width: 500, height: 1)))
} else {
gradientView.image = nil
}
gradientView.image = .gradient(from: viewModel.colors.startColor, to: viewModel.colors.endColor, with: .init(origin: .zero, size: .init(width: 500, height: 1)))
// update seismic data
placeLabel.text = viewModel.place
placeLabel.textColor = isPushSelected ? AppTheme.Colors.pureBlue : AppTheme.shared.cardTextColor
networkLabel.text = String(format: NSLocalizedString("official_provider", comment: ""), viewModel.network)
magnitudeLabel.textColor = colors?.textColor
magnitudeLabel.textColor = viewModel.colors.textColor
magnitudeLabel.text = viewModel.magnitude
depthLabel.text = viewModel.depth
timeLabel.text = "🕗 \(viewModel.time)"
@@ -461,7 +464,6 @@ class SeismicNetworkTableViewCell: UITableViewCell {
isPushSelected: Bool
) {
self.seismic = seismic
self.colors = calculateColors(for: seismic.magnitude.doubleValue)
self.displayType = type
self.informationTypes = informations
self.isPushSelected = isPushSelected
@@ -479,6 +481,9 @@ class SeismicNetworkTableViewCell: UITableViewCell {
if seismic.userNumber.intValue > 0 && !informations.contains(.reportUsers) {
self.informationTypes += [.reportUsers]
}
if seismic.isoCode == "0" && informations.contains(.intensityMap) {
self.informationTypes.removeAll { $0 == .intensityMap }
}
recreateUI()
updateUI()
@@ -512,6 +517,10 @@ class SeismicNetworkTableViewCell: UITableViewCell {
delegate?.seismicNetworkCellDidTapMapDetail(self)
}
@objc func intensityMapTapped(_ sender: Any) {
delegate?.seismicNetworkCellDidTapIntensityMapDetail(self)
}
// MARK: - Helpers
@discardableResult
@@ -541,55 +550,4 @@ class SeismicNetworkTableViewCell: UITableViewCell {
}
return zoom
}
/// Calculate colors to use for text and background of the cell
private func calculateColors(for magnitude: Double) -> MagnitudeColors {
var textColor = UIColor.black
var r = 0, g = 0, b = 0
if (magnitude < 2.0) {
let fraction: Double = 1 - (magnitude - 0.0) / (2.0 - 0.0)
r = Int(round(200.0 + (255.0 - 200.0) * fraction))
g = Int(round(226.0 + (255.0 - 226.0) * fraction))
b = Int(round(196.0 + (255.0 - 196.0) * fraction))
textColor = UIColor(red: 12.0 / 255.0, green: 115.0 / 255.0, blue: 160.0 / 255.0, alpha: 1.0)
}
if (magnitude >= 2.0 && magnitude < 3.5) {
let fraction: Double = 1 - (magnitude - 2) / (3.5 - 2)
r = Int(round(136.0 + (200.0 - 136.0) * fraction))
g = Int(round(175.0 + (226.0 - 175.0) * fraction))
b = Int(round(131.0 + (196.0 - 131.0) * fraction))
textColor = UIColor(red: 12.0 / 255.0, green: 160.0 / 255.0, blue: 35.0 / 255.0, alpha: 1.0)
}
if (magnitude >= 3.5 && magnitude < 4.5) {
let fraction: Double = 1 - (magnitude - 3.5) / (4.5 - 3.5)
r = 252
g = Int(round(233.0 + (253.0 - 233.0) * fraction))
b = Int(round(179.0 + (209.0 - 179.0) * fraction))
textColor = UIColor(red: 244.0 / 255.0, green: 195.0 / 255.0, blue: 0.0 / 255.0, alpha: 1.0)
}
if (magnitude >= 4.5 && magnitude < 5.5) {
let fraction: Double = 1 - (magnitude - 4.5) / (5.5 - 4.5)
r = 252
g = Int(round(159.0 + (197.0 - 159.0) * fraction))
b = Int(round(161.0 + (197.0 - 161.0) * fraction))
textColor = UIColor(red: 255.0 / 255.0, green: 0.0 / 255.0, blue: 0.0 / 255.0, alpha: 1.0)
}
if (magnitude >= 5.5) {
let fraction: Double = 1 - (magnitude - 5.5) / (10 - 5.5)
r = Int(round(190.0 + (254.0 - 190.0) * fraction))
g = Int(round(124.0 + (219.0 - 124.0) * fraction))
b = 255
textColor = UIColor(red: 183.0 / 255.0, green: 60.0 / 255.0, blue: 252.0 / 255.0, alpha: 1.0)
}
let r2 = min(r + 30, 255)
let g2 = min(g + 30, 255)
let b2 = min(b + 30, 255)
let startColor = UIColor(red: CGFloat(r) / 255.0, green: CGFloat(g) / 255.0, blue: CGFloat(b) / 255.0, alpha: 1.0)
let endColor = UIColor(red: CGFloat(r2) / 255.0, green: CGFloat(g2) / 255.0, blue: CGFloat(b2) / 255.0, alpha: 1.0)
return (textColor: textColor, startColor: startColor, endColor: endColor)
}
}
@@ -21,6 +21,7 @@ class SeismicFiltersViewController: UIViewController, UITableViewDelegate, UITab
case magnitudoMinima
case sismiRilevanti
case sismiTutti
case sismiPercepiti
}
weak var delegate: SeismicFiltersViewControllerDelegate?
@@ -40,6 +41,7 @@ class SeismicFiltersViewController: UIViewController, UITableViewDelegate, UITab
SettingItem(type: .slider, title: NSLocalizedString("filter_minimum_magnitude", comment: "")),
SettingItem(type: .enable, title: NSLocalizedString("filter_show_relevant", comment: "")),
SettingItem(type: .enable, title: NSLocalizedString("filter_show_all", comment: "")),
SettingItem(type: .enable, title: NSLocalizedString("filter_show_felt", comment: ""))
]
private let initialFilterType = EQNSeismic.shared.filterOption
@@ -148,6 +150,12 @@ class SeismicFiltersViewController: UIViewController, UITableViewDelegate, UITab
cell.valueChanged = { [weak self] enabled in
self?.onChangeFilterOption(enabled, filter: .worldWide)
}
case .sismiPercepiti:
let isCurrentFilter = currentFilterType == .userFelt
cell.toggleSwitch.isOn = isCurrentFilter
cell.valueChanged = { [weak self] enabled in
self?.onChangeFilterOption(enabled, filter: .userFelt)
}
default:
break
}
@@ -0,0 +1,9 @@
//
// SeismicNetworkData.swift
// Earthquake Network
//
// Created by Andrea Busi on 31/01/25.
// Copyright © 2025 Earthquake Network. All rights reserved.
//
import Foundation
@@ -0,0 +1,96 @@
//
// SeismicNetworkScrollIndicatorView.swift
// Earthquake Network
//
// Created by Andrea Busi on 31/01/25.
// Copyright © 2025 Earthquake Network. All rights reserved.
//
import UIKit
import CoreGraphics
class SeismicNetworkScrollIndicatorView: UIView {
private static let HighlightColor: UIColor = .red
var seismics: [SeismicNetworkViewModel] = [] {
didSet {
setNeedsDisplay()
}
}
var highlighted: SeismicNetworkViewModel? {
didSet {
setNeedsDisplay()
}
}
private var numberOfRectangles: Int {
seismics.count
}
// MARK: - View Lifecycle
override func draw(_ rect: CGRect) {
guard numberOfRectangles > 0 else { return }
let context = UIGraphicsGetCurrentContext()
let rectStandardWidth = rect.width
let rectStandardHeight = rect.height / CGFloat(numberOfRectangles)
let rectHighlightedMinHeight: CGFloat = 4
let smallRectangles = rectStandardHeight < 10
let highlightIndex = seismics.firstIndex(where: { $0 == highlighted }) ?? 100_000
seismics.enumerated().forEach { index, seismic in
// Disegniamo un rettangolo per ogni sisma, quello evidenziato deve avere un contorno rosso.
// Ci sono situazioni in cui ci sono molti sismi da mostrare, quindi in quel caso facciamo alcune modifiche:
// - usiamo un'altezza minma per il sisma evidenziato
// - per il sisma evidenziato, anche il contenuto è rosso (e non solo il bordo)
// - negli altri sismi, non mostriamo il bordo
if highlightIndex == index {
// Stiamo disegnando il sisma evidenziato.
// Valutiamo se utilizzare l'altezza minima.
let rectHeight = smallRectangles ? rectHighlightedMinHeight : rectStandardHeight
let yPosition = CGFloat(index) * rectStandardHeight
let rectangle = CGRect(x: 0, y: yPosition, width: rectStandardWidth, height: rectHeight)
let fillColor = smallRectangles ? Self.HighlightColor : seismic.colors.textColor.withAlphaComponent(0.3)
context?.setFillColor(fillColor.cgColor)
context?.fill(rectangle)
if !smallRectangles {
// disegniamo il bordo solo se i rettangoli non sono piccoli
let borderWidth: CGFloat = 2.0
context?.setStrokeColor(Self.HighlightColor.cgColor)
context?.setLineWidth(borderWidth) // Spessore del bordo
context?.stroke(rectangle.insetBy(dx: borderWidth / 2, dy: borderWidth / 2)) // Evita che il bordo venga tagliato
}
} else {
// Stiamo disegnando i sismi non evidenziati, utilizziamo sempre l'altezza predefinita
// Dobbiamo eventualmente calcolare un offset aggiuntivo,
// perchè il sisma evidenziato ha un'altezza maggiore (se i rettangoli sono piccoli)
let rectHeight = rectStandardHeight
let offset: CGFloat = (index > highlightIndex && smallRectangles) ? rectHighlightedMinHeight : 0
let yPosition = CGFloat(index) * rectHeight + offset
let rectangle = CGRect(x: 0, y: yPosition, width: rectStandardWidth, height: rectHeight)
let fillColor = seismic.colors.textColor.withAlphaComponent(0.3)
context?.setFillColor(fillColor.cgColor)
context?.fill(rectangle)
if !smallRectangles {
// altrimenti un bordo grigio
let borderWidth: CGFloat = 0.5
context?.setStrokeColor(AppTheme.Colors.gray.cgColor)
context?.setLineWidth(borderWidth) // Spessore del bordo
context?.stroke(rectangle) // Evita che il bordo venga tagliato
}
}
}
}
}
@@ -11,6 +11,13 @@ import Foundation
struct SeismicNetworkViewModel {
struct MagnitudeColors {
let textColor: UIColor
let startColor: UIColor
let endColor: UIColor
}
private let seismic: EQNSisma
let place: String
let network: String
let isPreliminary: Bool
@@ -23,10 +30,12 @@ struct SeismicNetworkViewModel {
let population: String
let smartphones: String
let users: String
let colors: MagnitudeColors
// MARK: - Init
init(seismic: EQNSisma) {
self.seismic = seismic
self.place = seismic.place
self.network = seismic.provider
self.rawMagnitude = seismic.magnitude.doubleValue
@@ -71,6 +80,8 @@ struct SeismicNetworkViewModel {
} else {
self.users = ""
}
self.colors = Self.calculateColors(for: seismic.magnitude.doubleValue)
}
// MARK: - Private
@@ -90,4 +101,62 @@ struct SeismicNetworkViewModel {
}
return populationString
}
/// Calculate colors to use for text and background of the cell
private static func calculateColors(for magnitude: Double) -> MagnitudeColors {
var textColor = UIColor.black
var r = 0, g = 0, b = 0
if (magnitude < 2.0) {
let fraction: Double = 1 - (magnitude - 0.0) / (2.0 - 0.0)
r = Int(round(200.0 + (255.0 - 200.0) * fraction))
g = Int(round(226.0 + (255.0 - 226.0) * fraction))
b = Int(round(196.0 + (255.0 - 196.0) * fraction))
textColor = UIColor(red: 12.0 / 255.0, green: 115.0 / 255.0, blue: 160.0 / 255.0, alpha: 1.0)
}
if (magnitude >= 2.0 && magnitude < 3.5) {
let fraction: Double = 1 - (magnitude - 2) / (3.5 - 2)
r = Int(round(136.0 + (200.0 - 136.0) * fraction))
g = Int(round(175.0 + (226.0 - 175.0) * fraction))
b = Int(round(131.0 + (196.0 - 131.0) * fraction))
textColor = UIColor(red: 12.0 / 255.0, green: 160.0 / 255.0, blue: 35.0 / 255.0, alpha: 1.0)
}
if (magnitude >= 3.5 && magnitude < 4.5) {
let fraction: Double = 1 - (magnitude - 3.5) / (4.5 - 3.5)
r = 252
g = Int(round(233.0 + (253.0 - 233.0) * fraction))
b = Int(round(179.0 + (209.0 - 179.0) * fraction))
textColor = UIColor(red: 244.0 / 255.0, green: 195.0 / 255.0, blue: 0.0 / 255.0, alpha: 1.0)
}
if (magnitude >= 4.5 && magnitude < 5.5) {
let fraction: Double = 1 - (magnitude - 4.5) / (5.5 - 4.5)
r = 252
g = Int(round(159.0 + (197.0 - 159.0) * fraction))
b = Int(round(161.0 + (197.0 - 161.0) * fraction))
textColor = UIColor(red: 255.0 / 255.0, green: 0.0 / 255.0, blue: 0.0 / 255.0, alpha: 1.0)
}
if (magnitude >= 5.5) {
let fraction: Double = 1 - (magnitude - 5.5) / (10 - 5.5)
r = Int(round(190.0 + (254.0 - 190.0) * fraction))
g = Int(round(124.0 + (219.0 - 124.0) * fraction))
b = 255
textColor = UIColor(red: 183.0 / 255.0, green: 60.0 / 255.0, blue: 252.0 / 255.0, alpha: 1.0)
}
let r2 = min(r + 30, 255)
let g2 = min(g + 30, 255)
let b2 = min(b + 30, 255)
let startColor = UIColor(red: CGFloat(r) / 255.0, green: CGFloat(g) / 255.0, blue: CGFloat(b) / 255.0, alpha: 1.0)
let endColor = UIColor(red: CGFloat(r2) / 255.0, green: CGFloat(g2) / 255.0, blue: CGFloat(b2) / 255.0, alpha: 1.0)
return .init(textColor: textColor, startColor: startColor, endColor: endColor)
}
}
extension SeismicNetworkViewModel: Equatable {
static func == (lhs: SeismicNetworkViewModel, rhs: SeismicNetworkViewModel) -> Bool {
return lhs.seismic == rhs.seismic
}
}
@@ -0,0 +1,235 @@
//
// SeismicNetworksIntensityMapViewController.swift
// Earthquake Network
//
// Created by Andrea Busi on 27/02/25.
// Copyright © 2025 Earthquake Network. All rights reserved.
//
import UIKit
import MapKit
class SeismicNetworksIntensityMapViewController: EQNBaseMapViewController {
private let seismic: EQNSisma
private var shakemaps: [EQNShakemap] = []
private var pinStyle: MapPinStyle {
get { AppPreferences.shared.mapPinStyle }
set { AppPreferences.shared.mapPinStyle = newValue }
}
override var isFilterViewVisible: Bool { false }
override var isCloseButtonVisible: Bool { false }
// MARK: - Init
init(
seismic: EQNSisma
) {
self.seismic = seismic
super.init()
}
@MainActor required init?(coder: NSCoder) {
fatalError("Plase use init(seismic:) instead")
}
// MARK: - View Lifecycle
override func extraUI() {
super.extraUI()
let descriptionView = UIView()
descriptionView.translatesAutoresizingMaskIntoConstraints = false
descriptionView.backgroundColor = AppTheme.Colors.pureBlue
view.addSubview(descriptionView)
descriptionView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
descriptionView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
descriptionView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
let descriptionLabel = UILabel()
descriptionLabel.translatesAutoresizingMaskIntoConstraints = false
descriptionLabel.numberOfLines = 0
descriptionLabel.textColor = .white
descriptionLabel.font = .preferredFont(forTextStyle: .subheadline)
descriptionLabel.textAlignment = .center
descriptionLabel.text = NSLocalizedString("shakemap_description", comment: "")
descriptionView.addSubview(descriptionLabel)
descriptionLabel.leadingAnchor.constraint(equalTo: descriptionView.leadingAnchor, constant: 2.0).isActive = true
descriptionLabel.trailingAnchor.constraint(equalTo: descriptionView.trailingAnchor, constant: -2.0).isActive = true
descriptionLabel.topAnchor.constraint(equalTo: descriptionView.topAnchor, constant: 2.0).isActive = true
descriptionLabel.bottomAnchor.constraint(equalTo: descriptionView.bottomAnchor, constant: -2.0).isActive = true
}
override func configureUI() {
super.configureUI()
navigationItem.leftBarButtonItem = UIBarButtonItem(systemItem: .done, primaryAction: .init(handler: { [weak self] _ in
self?.dismiss(animated: true)
}))
navigationItem.rightBarButtonItems = [
UIBarButtonItem(image: UIImage(named: "navbar-icon-screenshot"), primaryAction: .init(handler: { [weak self] _ in
self?.shareScreenshot()
})),
UIBarButtonItem(image: UIImage(named: "navbar-icon-pin-arrow"), primaryAction: .init(handler: { [weak self] _ in
self?.nextPinStyle()
})),
]
}
override func registerMapAnnotationViews() {
mapView.register(EQNSeismicAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNSeismicAnnotationView.identifier(for: .full))
mapView.register(EQNSeismicAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNSeismicAnnotationView.identifier(for: .light))
mapView.register(EQNSeismicAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNSeismicAnnotationView.identifier(for: .circle))
}
override func loadDataSource() {
Task {
let result = try await APIService.shared.fetchShakemap(isoCode: seismic.isoCode)
elaborateShakemaps(result)
}
}
override func elaborateMapCenter() {
setMapCenter(for: seismic.coordinate, span: MKCoordinateSpan(latitudeDelta: 2, longitudeDelta: 2))
}
// MARK: - Private
private func elaborateShakemaps(_ shakemaps: [EQNShakemap]) {
self.shakemaps = shakemaps
var shakemapPolyline = [MKPolyline]()
var shakemapAnnotations: [MKAnnotation] = []
for shakemap in shakemaps {
// create coordinates for current shakemap
let coordinates = zip(shakemap.lat, shakemap.lon).map { lat, lon in
CLLocationCoordinate2D(latitude: Double(lat) / 10_000.0, longitude: Double(lon) / 10_000.0)
}
let intensityColors = getColors(for: shakemap.intensity)
// create line to show on map
let polyline = ShakemapPolyline(coordinates: coordinates, count: coordinates.count)
polyline.intensity = shakemap.intensity
polyline.intensityColor = intensityColors.lineColor
shakemapPolyline.append(polyline)
// create annotation to show on top of the line
let middlePoint = coordinates[coordinates.count / 2]
let annotation = EQNMapAnnotationShakemap(coordinate: middlePoint, shakemap: shakemap)
annotation.intensityColor = intensityColors.lineColor
annotation.intensityTextColor = intensityColors.textColor
shakemapAnnotations.append(annotation)
}
let seismicAnnotation = EQNMapAnnotationSeismic(seismic: seismic)
shakemapAnnotations.append(seismicAnnotation)
// draw lines
mapView.addOverlays(shakemapPolyline)
updateMap(with: shakemapAnnotations)
}
private func nextPinStyle() {
pinStyle.next()
reloadMap()
}
private func shareScreenshot() {
let screenshot = createSnapshot()
let controller = UIActivityViewController(activityItems: [screenshot], applicationActivities: [])
present(controller, animated: true)
}
// MARK: - MKMapViewDelegate
override func setupAnnotationView(for annotation: MKAnnotation, on mapView: MKMapView) -> MKAnnotationView? {
switch annotation {
case let shakemapAnnotation as EQNMapAnnotationShakemap:
return shakemapAnnotation.toAnnotationView(mapView: mapView, style: .light)
case let seismicAnnotation as EQNMapAnnotationSeismic:
return seismicAnnotation.toAnnotationView(mapView: mapView, style: pinStyle)
default:
return nil
}
}
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
if let polyline = overlay as? ShakemapPolyline {
let renderer = MKPolylineRenderer(polyline: polyline)
renderer.strokeColor = polyline.intensityColor
renderer.lineWidth = 6.0
return renderer
}
return MKOverlayRenderer()
}
private func getColors(for intensity: Float) -> (textColor: UIColor, lineColor: UIColor) {
let shakemapColors: [String] = [
"#3E26A8","#3E27AC","#3F28AF","#3F29B2","#402AB4","#402BB7","#412CBA","#412DBD","#422EBF","#422FC2",
"#4330C5","#4331C8","#4332CA","#4433CD","#4434D0","#4535D2","#4537D5","#4538D7","#4639D9","#463ADC",
"#463BDE","#463DE0","#473EE1","#473FE3","#4741E5","#4742E6","#4744E8","#4745E9","#4746EB","#4848EC",
"#4849ED","#484BEE","#484CF0","#484EF1","#484FF2","#4850F3","#4852F4","#4853F5","#4854F6","#4756F7",
"#4757F7","#4759F8","#475AF9","#475BFA","#475DFA","#465EFB","#4660FB","#4661FC","#4562FC","#4564FD",
"#4465FD","#4367FD","#4368FE","#426AFE","#416BFE","#406DFE","#3F6EFF","#3E70FF","#3C71FF","#3B73FF",
"#3974FF","#3876FE","#3677FE","#3579FD","#337AFD","#327CFC","#317DFC","#307FFB","#2F80FA","#2F82FA",
"#2E83F9","#2E84F8","#2E86F8","#2E87F7","#2D88F6","#2D8AF5","#2D8BF4","#2D8CF3","#2D8EF2","#2C8FF1",
"#2C90F0","#2B91EF","#2A93EE","#2994ED","#2895EC","#2797EB","#2798EA","#2699E9","#269AE8","#259BE8",
"#259CE7","#249EE6","#249FE5","#23A0E5","#23A1E4","#22A2E4","#21A3E3","#20A5E3","#1FA6E2","#1EA7E1",
"#1DA8E1","#1DA9E0","#1CAADF","#1BABDE","#1AACDD","#19ADDC","#17AEDA","#16AFD9","#14B0D8","#12B1D6",
"#10B2D5","#0EB3D4","#0BB3D2","#08B4D1","#06B5CF","#04B6CE","#02B7CC","#01B7CA","#00B8C9","#00B9C7",
"#00BAC6","#01BAC4","#02BBC2","#04BBC1","#06BCBF","#09BDBD","#0DBDBC","#10BEBA","#14BEB8","#17BFB6",
"#1AC0B5","#1DC0B3","#20C1B1","#23C1AF","#25C2AE","#27C2AC","#29C3AA","#2BC3A8","#2CC4A6","#2EC4A5",
"#2FC5A3","#31C5A1","#32C69F","#33C79D","#35C79B","#36C899","#38C896","#39C994","#3BC992","#3DCA90",
"#40CA8D","#42CA8B","#45CB89","#48CB86","#4BCB84","#4ECC81","#51CC7F","#54CC7C","#57CC7A","#5ACC77",
"#5ECD74","#61CD72","#64CD6F","#67CD6C","#6BCD69","#6ECD66","#72CD64","#76CC61","#79CC5E","#7DCC5B",
"#81CC59","#84CC56","#88CB53","#8BCB51","#8FCB4E","#93CA4B","#96CA48","#9AC946","#9DC943","#A1C840",
"#A4C83E","#A7C73B","#ABC739","#AEC637","#B2C635","#B5C533","#B8C431","#BBC42F","#BEC32D","#C2C32C",
"#C5C22A","#C8C129","#CBC128","#CEC027","#D0BF27","#D3BF27","#D6BE27","#D9BE28","#DBBD28","#DEBC29",
"#E1BC2A","#E3BC2B","#E6BB2D","#E8BB2E","#EABA30","#ECBA32","#EFBA35","#F1BA37","#F3BA39","#F5BA3B",
"#F7BA3D","#F9BA3E","#FBBB3E","#FCBC3E","#FEBD3D","#FEBE3C","#FEC03B","#FEC13A","#FEC239","#FEC438",
"#FEC537","#FEC735","#FEC834","#FECA33","#FDCB32","#FDCD31","#FDCE31","#FCD030","#FBD22F","#FBD32E",
"#FAD52E","#F9D62D","#F9D82C","#F8D92B","#F7DB2A","#F7DD2A","#F6DE29","#F6E028","#F5E128","#F5E327",
"#F5E526","#F5E626","#F5E825","#F5E924","#F5EB23","#F5EC22","#F5EE21","#F6EF20","#F6F11F","#F6F21E",
"#F7F41C","#F7F51B","#F8F71A","#F8F818","#F9F916","#F9FB15"
]
let minIntensity = shakemaps.map { $0.intensity }.min() ?? 0
let maxIntensity = shakemaps.map { $0.intensity }.max() ?? 255
let indexColor = Int(round((intensity-minIntensity)/(maxIntensity-minIntensity)*255))
let lineColor = UIColor(hexString: shakemapColors[indexColor]) ?? .white
let textColor: UIColor = indexColor < 65 ? .white : .black
return (textColor: textColor, lineColor: lineColor)
}
}
extension EQNMapAnnotationShakemap {
func toAnnotationView(
mapView: MKMapView,
style: MapPinStyle,
isUserSelection: Bool = false
) -> MKAnnotationView? {
switch style {
case .full, .light:
let identifier = EQNSeismicAnnotationView.identifier(for: style)
let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier, for: self) as! EQNSeismicAnnotationView
annotationView.magnitude = String(format: "%.1f", shakemap.intensity)
annotationView.magnitudeTextColor = intensityTextColor ?? .black
annotationView.magnitudeBackgroundColor = intensityColor
annotationView.canShowCallout = true
return annotationView
case .circle:
return nil
}
}
}
fileprivate class ShakemapPolyline: MKPolyline {
var intensity: Float = 0
var intensityColor: UIColor = .white
}
@@ -15,21 +15,11 @@ protocol SeismicNetworksMapDetailViewControllerDelegate: AnyObject {
}
class SeismicNetworksMapDetailViewController: EQNBaseMapViewController {
private enum PinStyle: CaseIterable {
case full
case light
case circle
func next() -> Self {
let all = Self.allCases
let idx = all.firstIndex(of: self)!
let next = all.index(after: idx)
return all[next == all.endIndex ? all.startIndex : next]
}
}
private var pinStyle: PinStyle = .full
private var pinStyle: MapPinStyle {
get { AppPreferences.shared.mapPinStyle }
set { AppPreferences.shared.mapPinStyle = newValue }
}
private let eqnSeismic = EQNSeismic.shared
// MARK: - State
@@ -116,9 +106,9 @@ class SeismicNetworksMapDetailViewController: EQNBaseMapViewController {
}
override func registerMapAnnotationViews() {
mapView.register(EQNSeismicAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNSeismicAnnotationView.IdentifierFull)
mapView.register(EQNSeismicAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNSeismicAnnotationView.IdentifierLight)
mapView.register(EQNSeismicAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNSeismicAnnotationView.IdentifierCircle)
mapView.register(EQNSeismicAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNSeismicAnnotationView.identifier(for: .full))
mapView.register(EQNSeismicAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNSeismicAnnotationView.identifier(for: .light))
mapView.register(EQNSeismicAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNSeismicAnnotationView.identifier(for: .circle))
}
override func loadDataSource() {
@@ -188,7 +178,7 @@ class SeismicNetworksMapDetailViewController: EQNBaseMapViewController {
// MARK: - Private
private func nextPinStyle() {
pinStyle = pinStyle.next()
pinStyle.next()
reloadMap()
}
@@ -199,6 +189,7 @@ class SeismicNetworksMapDetailViewController: EQNBaseMapViewController {
case .inRadius: NSLocalizedString("filter_area", comment: "")
case .positionRelevant: NSLocalizedString("filter_relevant", comment: "")
case .worldWide: NSLocalizedString("filter_all", comment: "")
case .userFelt: NSLocalizedString("filter_felt", comment: "")
}
seismicsFilterLabel.text = text
}
@@ -240,22 +231,7 @@ class SeismicNetworksMapDetailViewController: EQNBaseMapViewController {
}
let isUserSelection = annotation.seismic == seismic
switch pinStyle {
case .full, .light:
let identifier = pinStyle == .full ? EQNSeismicAnnotationView.IdentifierFull : EQNSeismicAnnotationView.IdentifierLight
let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier, for: annotation) as! EQNSeismicAnnotationView
annotationView.title = annotation.title
annotationView.subtitle = annotation.subtitle
annotationView.magnitude = String(format: "M%.1f", annotation.seismic.magnitude.doubleValue)
annotationView.magnitudeColor = annotation.textColor
annotationView.isUserSelection = isUserSelection
return annotationView
case .circle:
let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: EQNSeismicAnnotationView.IdentifierCircle, for: annotation) as! EQNSeismicAnnotationView
annotationView.image = annotation.image(height: EQNSeismicAnnotationView.CircleViewHeight,
isUserSelection: isUserSelection)
return annotationView
}
return annotation.toAnnotationView(mapView: mapView, style: pinStyle, isUserSelection: isUserSelection)
}
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
@@ -270,6 +246,33 @@ class SeismicNetworksMapDetailViewController: EQNBaseMapViewController {
}
}
extension EQNMapAnnotationSeismic {
func toAnnotationView(
mapView: MKMapView,
style: MapPinStyle,
isUserSelection: Bool = false
) -> MKAnnotationView {
switch style {
case .full, .light:
let identifier = EQNSeismicAnnotationView.identifier(for: style)
let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier, for: self) as! EQNSeismicAnnotationView
annotationView.title = self.title
annotationView.subtitle = self.subtitle
annotationView.magnitude = String(format: "M%.1f", self.seismic.magnitude.doubleValue)
annotationView.magnitudeTextColor = self.textColor
annotationView.magnitudeBackgroundColor = .white
annotationView.isUserSelection = isUserSelection
return annotationView
case .circle:
let identifier = EQNSeismicAnnotationView.identifier(for: style)
let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier, for: self) as! EQNSeismicAnnotationView
annotationView.image = image(height: EQNSeismicAnnotationView.CircleViewHeight,
isUserSelection: isUserSelection)
return annotationView
}
}
}
extension SeismicNetworksMapDetailViewController: SeismicFiltersViewControllerDelegate {
func seismicFiltersControllerDidUpdateFilters(_ controller: SeismicFiltersViewController) {
delegate?.seismicNetworksMapDetailControllerWillUpdateData(self, needsDataUpdate: controller.needsDataUpdate)
@@ -15,7 +15,7 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
private enum CellType {
case seismic(EQNSisma)
case advertise(GADNativeAd)
case advertise(NativeAd)
}
private static let SegueIdentifierFilters = "ShowFilters"
@@ -24,8 +24,8 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
// MARK: - Internal
/// The ad loader
private lazy var adLoader: GADAdLoader = {
let adLoader = GADAdLoader(
private lazy var adLoader: AdLoader = {
let adLoader = AdLoader(
adUnitID: EQNAdMobAppIdNativeBanner, rootViewController: self,
adTypes: [.native], options: nil)
adLoader.delegate = self
@@ -34,6 +34,7 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
/// Cells to display (must be seismics or ad banners)
private var rows = [CellType]()
private var seismicViewModels = [SeismicNetworkViewModel]()
/// Informations to display on a single cell
private var informations = [SeismicNetworkTableViewCell.InformationType]()
/// Index path of row with map expanded
@@ -47,6 +48,8 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
private var scrollToOpenedSeismic = false
/// Current displayed controller with map
private weak var currentMapController: SeismicNetworksMapDetailViewController?
/// Keep track of the current cell at the center
private var currentCenteredIndexPath: IndexPath?
// MARK: - UI
@@ -59,6 +62,7 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.delegate = self
tableView.dataSource = self
tableView.showsVerticalScrollIndicator = false
return tableView
}()
@@ -85,6 +89,14 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
label.textAlignment = .center
return label
}()
private lazy var scrollIndicatorView: SeismicNetworkScrollIndicatorView = {
let view = SeismicNetworkScrollIndicatorView(frame: .zero)
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .clear
view.layer.borderColor = AppTheme.Colors.gray.cgColor
return view
}()
// MARK: - View Lifecycle
@@ -121,13 +133,19 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
// MARK: - Private
private func setupUI() {
view.addSubview(scrollIndicatorView)
view.addSubview(tableView)
scrollIndicatorView.topAnchor.constraint(equalTo: tableView.topAnchor).isActive = true
scrollIndicatorView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
scrollIndicatorView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
scrollIndicatorView.widthAnchor.constraint(equalToConstant: 10.0).isActive = true
tableViewTopConstraint = tableView.topAnchor.constraint(equalTo: view.topAnchor)
tableViewTopConstraint?.isActive = true
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: scrollIndicatorView.leadingAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
}
private func setupFilterView(isVisible: Bool) {
@@ -220,6 +238,12 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
self.currentMapController = controller
}
private func showIntensityMap(for seismic: EQNSisma) {
let controller = SeismicNetworksIntensityMapViewController(seismic: seismic)
let navController = UINavigationController(rootViewController: controller)
present(navController, animated: true)
}
// MARK: - Notifications
@objc func didReceiveDownloadCompleteNotification(_ sender: Notification) {
@@ -246,6 +270,7 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
}
tableView.reloadData()
updateCenterCellIndexPath()
if scrollToOpenedSeismic, let index = getSeismics().firstIndex(where: { isSeismicToHighlight(seismic: $0) }) {
tableView.scrollToRow(at: IndexPath(row: index, section: 0), at: .middle, animated: true)
@@ -254,7 +279,7 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
}
private func loadAd() {
adLoader.load(GADRequest())
adLoader.load(Request())
}
private func loadData(forced: Bool) {
@@ -266,7 +291,8 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
let allSeismics = EQNManager.manager().retiSismiche
let filteredSeismics = EQNSeismic.shared.filterSeismicList(allSeismics ?? [])
rows = filteredSeismics.map { .seismic($0) }
seismicViewModels = filteredSeismics.map(SeismicNetworkViewModel.init)
#if ADS_ENABLED
// if is not a pro user, show an advertise
if !EQNPurchaseUtility.isProVersionEnabled() {
@@ -278,6 +304,9 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
if let mapController = currentMapController {
mapController.updateSeismics(filteredSeismics)
}
scrollIndicatorView.seismics = seismicViewModels
currentCenteredIndexPath = nil
}
private func getSeismics() -> [EQNSisma] {
@@ -560,6 +589,37 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
return false
}
private func getCenterCellIndexPath() -> IndexPath? {
let centerPoint = CGPoint(x: tableView.bounds.midX, y: tableView.bounds.midY)
if let indexPath = tableView.indexPathForRow(at: centerPoint) {
return indexPath
}
// Se il metodo diretto fallisce, cerchiamo la cella più vicina
if let visibleIndexPaths = tableView.indexPathsForVisibleRows {
return visibleIndexPaths.min(by: { (indexPath1, indexPath2) -> Bool in
let rect1 = tableView.rectForRow(at: indexPath1)
let rect2 = tableView.rectForRow(at: indexPath2)
let distance1 = abs(rect1.midY - centerPoint.y)
let distance2 = abs(rect2.midY - centerPoint.y)
return distance1 < distance2
})
}
return nil
}
private func updateCenterCellIndexPath() {
if let centerIndexPath = getCenterCellIndexPath(), centerIndexPath != currentCenteredIndexPath {
currentCenteredIndexPath = centerIndexPath
let row = rows[centerIndexPath.row]
if case .seismic = row, seismicViewModels.count > centerIndexPath.row {
scrollIndicatorView.highlighted = seismicViewModels[centerIndexPath.row]
}
}
}
// MARK: - Actions
@IBAction func refreshDataTapped(_ sender: Any) {
@@ -618,6 +678,12 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
}
}
// MARK: - UIScrollViewDelegate
func scrollViewDidScroll(_ scrollView: UIScrollView) {
updateCenterCellIndexPath()
}
// MARK: - Private
private func openCalendar(for seismic: EQNSisma) {
@@ -673,18 +739,18 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
}
}
extension SeismicNetworksViewController: GADNativeAdLoaderDelegate {
func adLoader(_ adLoader: GADAdLoader, didReceive nativeAd: GADNativeAd) {
print("[GADAdLoader] didReceive")
extension SeismicNetworksViewController: NativeAdLoaderDelegate {
func adLoader(_ adLoader: AdLoader, didReceive nativeAd: NativeAd) {
print("[AdLoader] didReceive")
let adPosition = min(3, rows.count)
rows.insert(.advertise(nativeAd), at: adPosition)
tableView.reloadData()
}
func adLoader(_ adLoader: GADAdLoader, didFailToReceiveAdWithError error: Error) {
func adLoader(_ adLoader: AdLoader, didFailToReceiveAdWithError error: Error) {
// nope
print("[GADAdLoader] didFailToReceiveAdWithError: \(error.localizedDescription)")
print("[AdLoader] didFailToReceiveAdWithError: \(error.localizedDescription)")
}
}
@@ -722,6 +788,12 @@ extension SeismicNetworksViewController: SeismicNetworkTableViewCellDelegate {
showMapDetail(for: seismic)
}
func seismicNetworkCellDidTapIntensityMapDetail(_ cell: SeismicNetworkTableViewCell) {
guard let index = tableView.indexPath(for: cell), case let .seismic(seismic) = rows[index.row] else { return }
showIntensityMap(for: seismic)
}
func seismicNetworkCellDidTapCalendar(_ cell: SeismicNetworkTableViewCell) {
guard let index = tableView.indexPath(for: cell), case let .seismic(seismic) = rows[index.row] else { return }
+151
View File
@@ -0,0 +1,151 @@
//
// Log.swift
// Earthquake Network
//
// Created by Andrea Busi on 27/02/25.
// Copyright © 2025 Earthquake Network. All rights reserved.
//
import Foundation
import OSLog
/// Use this protocol to have a base TAG in a Swift class
public protocol Loggable {
static var TAG: String { get }
}
public extension Loggable {
static var TAG: String {
String(describing: Self.self)
}
}
extension UIViewController: Loggable { }
public class Log {
private static let dumpDateFormatter: DateFormatter = {
// create the default date formatter using ISO8601 date format
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
return formatter
}()
private static let shared = Log()
// MARK: - Properties
private let maxNumberOfLogsInDump: Int
private let logsLifespanMillis: Int
/// Subsystem for OSLog
private let subsystem: String
/// Logging in everything in a single "APP" category
private let appCategory: String = "APP"
private lazy var logger: os.Logger = {
os.Logger(subsystem: subsystem, category: appCategory)
}()
// MARK: - Init
@objc
public init(
subsystem: String = Bundle.main.bundleIdentifier!,
maxNumberOfLogsInDump: Int = 5000,
logsLifespanMillis: Int = 3 * 24 * 3600 * 1000
) {
self.subsystem = subsystem
self.maxNumberOfLogsInDump = maxNumberOfLogsInDump
self.logsLifespanMillis = logsLifespanMillis
}
// MARK: - Internal
public static func error(tag: String?, _ message: String?, _ functionName: String = #function) {
shared.log(level: .fault, tag: tag ?? "nil", message: message ?? "nil", functionName: functionName)
}
public static func warning(tag: String?, _ message: String?, _ functionName: String = #function) {
shared.log(level: .error, tag: tag ?? "nil", message: message ?? "nil", functionName: functionName)
}
public static func info(tag: String?, _ message: String?, _ functionName: String = #function) {
shared.log(level: .info, tag: tag ?? "nil", message: message ?? "nil", functionName: functionName)
}
public static func debug(tag: String?, _ message: String?, _ functionName: String = #function) {
shared.log(level: .debug, tag: tag ?? "nil", message: message ?? "nil", functionName: functionName)
}
public static func verbose(tag: String?, _ message: String?, _ functionName: String = #function) {
shared.log(level: .debug, tag: tag ?? "nil", message: message ?? "nil", functionName: functionName)
}
@available(iOS 15.0, *)
public func dumpLog() async -> String {
return (try? await getLogEntries()) ?? ""
}
// MARK: - Private
private func log(level: OSLogType, tag: String, message: String, functionName: String) {
let formattedMessage = "[\(tag)] \(functionName): \(message)"
switch level {
case .fault: logger.fault("\(formattedMessage, privacy: .public)")
case .error: logger.error("\(formattedMessage, privacy: .public)")
case .default: logger.notice("\(formattedMessage, privacy: .public)")
case .info: logger.info("\(formattedMessage, privacy: .public)")
default: logger.debug("\(formattedMessage, privacy: .public)")
}
}
/// Retrieve log entries from a specified time.
/// - Returns: String of log entries, newlines separated
@available(iOS 15.0, *)
private func getLogEntries() async throws -> String {
let logTask = Task.init(priority: .utility) { () -> String in
let logs = try retrieveLogEntries()
let text = logs
.compactMap { "\(Self.dumpDateFormatter.string(from: $0.date)) [\($0.level)] \($0.composedMessage)" }
.joined(separator: "\n")
return text
}
return try await logTask.value
}
@available(iOS 15.0, *)
private func retrieveLogEntries() throws -> [OSLogEntryLog] {
// Open the log store.
let logStore = try OSLogStore(scope: .currentProcessIdentifier)
// Fetch log objects from the given time interval
let intervalPosition = logStore.position(date: Date().addingTimeInterval(TimeInterval(-logsLifespanMillis / 1000)))
let allEntries = try logStore.getEntries(at: intervalPosition)
// Filter the log to be relevant for our specific subsystem
// and remove other elements (signposts, etc).
return allEntries
.compactMap { $0 as? OSLogEntryLog }
.filter { $0.subsystem == subsystem }
.suffix(maxNumberOfLogsInDump)
}
}
@available(iOS 15.0, *)
extension OSLogEntryLog.Level: @retroactive CustomStringConvertible {
public var description: String {
switch self {
case .fault: return "FAULT"
case .error: return "ERROR"
case .notice: return "WARNING"
case .info: return "INFO"
case .debug: return "DEBUG"
case .undefined: return "UNDEFINED"
@unknown default:
return "UNKNOWN"
}
}
}
@@ -29,4 +29,14 @@ class AppPreferences: NSObject {
get { UserDefaults.standard.bool(forKey: UserDefaults.AlertsShowCardOptions) }
set { UserDefaults.standard.set(newValue, forKey: UserDefaults.AlertsShowCardOptions) }
}
var mapPinStyle: MapPinStyle {
get {
let saved = UserDefaults.standard.integer(forKey: UserDefaults.MapPinStyle)
return MapPinStyle(rawValue: saved) ?? .circle
}
set {
UserDefaults.standard.set(newValue.rawValue, forKey: UserDefaults.MapPinStyle)
}
}
}
@@ -22,6 +22,7 @@ public class EQNUserDefaultsCommand: EQNCommandProtocol {
migrationV5_8()
migrationFirstAppStat()
migrationCriticalAlerts()
migrationV5_9()
}
// MARK: - Private
@@ -30,7 +31,7 @@ public class EQNUserDefaultsCommand: EQNCommandProtocol {
// seismic card settings
if UserDefaults.standard.array(forKey: EQNUserDefaultKeySesmicInformations) == nil {
let informations: [SeismicNetworkTableViewCell.InformationType] = [.buttons, .distance, .coordinate, .population]
let informations: [SeismicNetworkTableViewCell.InformationType] = [.buttons, .distance, .coordinate, .population, .intensityMap]
UserDefaults.standard.set(informations.map { $0.rawValue }, forKey: EQNUserDefaultKeySesmicInformations)
}
}
@@ -107,4 +108,24 @@ public class EQNUserDefaultsCommand: EQNCommandProtocol {
userDefaults.set(true, forKey: UserDefaults.AppMigrationV5_8_2)
}
private func migrationV5_9() {
let userDefaults = UserDefaults.standard
let migrationPerformed = userDefaults.bool(forKey: UserDefaults.AppMigrationV5_9)
if migrationPerformed {
print("[EQNUserDefaultsCommand] Migration v5.9 already performed")
return
}
if let saved = userDefaults.array(forKey: EQNUserDefaultKeySesmicInformations) as? [Int] {
var informations = saved.compactMap { SeismicNetworkTableViewCell.InformationType(rawValue: $0) }
if !informations.contains(.intensityMap) {
informations.append(.intensityMap)
print("[EQNUserDefaultsCommand] Add intensityMap to seismic informations")
}
userDefaults.set(informations.map { $0.rawValue }, forKey: EQNUserDefaultKeySesmicInformations)
}
userDefaults.set(true, forKey: UserDefaults.AppMigrationV5_9)
}
}
@@ -15,6 +15,7 @@ import Foundation
case inRadius
case positionRelevant
case worldWide
case userFelt
}
enum Sort: Int {
@@ -173,11 +174,16 @@ import Foundation
} else if magnitude < 1.5 && distance > 20 {
keep = false
}
} else {
} else if filterOption == .worldWide {
//filtro che mostra tutti i sismi a livello mondiale di magnitudo>=2
if magnitude < 2 {
keep = false
}
} else if filterOption == .userFelt {
//filtro che mostra i sismi segnalati da più di 1 utente
if seismic.userNumber.intValue < 2 {
keep = false
}
}
}
@@ -0,0 +1,15 @@
//
// EQNShakemap.swift
// Earthquake Network
//
// Created by Andrea Busi on 27/02/25.
// Copyright © 2025 Earthquake Network. All rights reserved.
//
import Foundation
struct EQNShakemap: Decodable {
let lat: [Int]
let lon: [Int]
let intensity: Float
}
@@ -33,6 +33,8 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, strong) NSNumber *preliminary;
@property (nonatomic, strong) NSNumber *smartphoneNumber;
@property (nonatomic, strong) NSNumber *userNumber;
/// Code to show "intensity map"
@property (nonatomic, strong) NSString *isoCode;
- (instancetype)initWithInfo:(NSDictionary *)info;
@@ -42,6 +42,8 @@
self.preliminary = info[@"py"];
self.smartphoneNumber = info[@"sm"];
self.userNumber = info[@"rp"];
self.isoCode = info[@"iso"];
}
return self;
}
@@ -65,6 +67,7 @@
[encoder encodeObject:self.preliminary forKey:@"preliminary"];
[encoder encodeObject:self.smartphoneNumber forKey:@"smartphoneNumber"];
[encoder encodeObject:self.userNumber forKey:@"userNumber"];
[encoder encodeObject:self.isoCode forKey:@"isoCode"];
}
- (instancetype)initWithCoder:(NSCoder *)decoder
@@ -86,6 +89,7 @@
self.preliminary = [decoder decodeObjectForKey:@"preliminary"];
self.smartphoneNumber = [decoder decodeObjectForKey:@"smartphoneNumber"];
self.userNumber = [decoder decodeObjectForKey:@"userNumber"];
self.isoCode = [decoder decodeObjectForKey:@"isoCode"];
}
return self;
}
@@ -20,6 +20,7 @@
{
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.timeZone = [NSTimeZone timeZoneWithName:@"Europe/Rome"];
formatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US"];
[formatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
return [formatter dateFromString:dateString];
}
@@ -0,0 +1,48 @@
//
// EQNMapAnnotationShakemap.swift
// Earthquake Network
//
// Created by Andrea Busi on 27/02/25.
// Copyright © 2025 Earthquake Network. All rights reserved.
//
import UIKit
import MapKit
class EQNMapAnnotationShakemap: NSObject, MKAnnotation {
let coordinate: CLLocationCoordinate2D
let shakemap: EQNShakemap
var intensityColor: UIColor?
var intensityTextColor: UIColor?
let title: String?
let subtitle: String? = nil
init(
coordinate: CLLocationCoordinate2D,
shakemap: EQNShakemap
) {
self.coordinate = coordinate
self.shakemap = shakemap
self.title = Self.title(for: shakemap.intensity)
}
// MARK: - Private
private static func title(for intensity: Float) -> String {
return switch intensity {
case 0..<2.5: NSLocalizedString("mercalli_II", comment: "")
case 2.5..<3.5: NSLocalizedString("mercalli_III", comment: "")
case 3.5..<4.5: NSLocalizedString("mercalli_IV", comment: "")
case 4.5..<5.5: NSLocalizedString("mercalli_V", comment: "")
case 5.5..<6.5: NSLocalizedString("mercalli_VI", comment: "")
case 6.5..<7.5: NSLocalizedString("mercalli_VII", comment: "")
case 7.5..<8.5: NSLocalizedString("mercalli_VIII", comment: "")
case 8.5..<9.5: NSLocalizedString("mercalli_IX", comment: "")
case 9.5..<10.5: NSLocalizedString("mercalli_X", comment: "")
case 10.5..<11.5: NSLocalizedString("mercalli_XI", comment: "")
default: NSLocalizedString("mercalli_XII", comment: "")
}
}
}
@@ -0,0 +1,23 @@
//
// MapPinStyle.swift
// Earthquake Network
//
// Created by Andrea Busi on 28/02/25.
// Copyright © 2025 Earthquake Network. All rights reserved.
//
import Foundation
enum MapPinStyle: Int, CaseIterable {
case circle
case light
case full
mutating func next() {
let all = Self.allCases
let idx = all.firstIndex(of: self)!
let next = all.index(after: idx)
let newValue = all[next == all.endIndex ? all.startIndex : next]
self = newValue
}
}
@@ -0,0 +1,122 @@
//
// APIService.swift
// Earthquake Network
//
// Created by Andrea Busi on 27/02/25.
// Copyright © 2025 Earthquake Network. All rights reserved.
//
import Foundation
import Shogun
fileprivate enum EQNetworkAPI {
static let scheme = "https"
static let host = "cache.earthquakenetwork.it"
static let timeout: TimeInterval = 20.0
}
class APIService {
static let shared = APIService()
private enum Endpoint: String {
case shakemap = "distquake_download_shakemap.php"
var method: HttpMethod {
.get
}
}
private enum HttpMethod: String {
case get = "GET"
case post = "POST"
case put = "PUT"
case delete = "DELETE"
case patch = "PATCH"
}
// MARK: - Public
func fetchShakemap(
isoCode: String
) async throws -> [EQNShakemap] {
try await performRequest(
endpoint: .shakemap,
paramaters: ["iso_id": isoCode]
)
}
// MARK: - Private
private func performRequest<T: Decodable>(
endpoint: Endpoint,
paramaters: [String: String] = [:],
body: Encodable? = nil
) async throws -> T {
var components = makeComponents(for: endpoint)
// add query string parameters to the existing ones
let queryParameters = paramaters.map { key, value in URLQueryItem(name: key, value: value) }
let queryItems = components.queryItems ?? []
components.queryItems = queryItems + queryParameters
guard let url = components.url else {
Log.error(tag: Self.TAG, "Unable to create URL for the request")
throw "Unable to create url for the request"
}
var request = URLRequest(
url: url,
cachePolicy: .reloadIgnoringLocalCacheData,
timeoutInterval: EQNetworkAPI.timeout
)
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.httpMethod = endpoint.method.rawValue
Log.debug(tag: Self.TAG, "Request url: \(url)")
if let body {
do {
let bodyData = try JSONEncoder().encode(body)
request.httpBody = bodyData
} catch {
Log.error(tag: Self.TAG, "Unable to encode body data: \(error)")
throw error
}
}
do {
let response = try await URLSession.shared.data(for: request).0
let responseString = String(data: response, encoding: .utf8) ?? "No data"
Log.info(tag: Self.TAG, responseString)
return try JSONDecoder().decode(T.self, from: response)
} catch {
Log.error(tag: Self.TAG, "Decoding error: \(error)")
throw error
}
}
// MARK: - Internal
private func makeComponents(for endpoint: Endpoint) -> URLComponents {
var components = URLComponents()
components.scheme = EQNetworkAPI.scheme
components.host = EQNetworkAPI.host
components.path = "/" + endpoint.rawValue
return components
}
}
extension APIService: Loggable { }
/// Easily throw generic errors with a text description.
extension String: @retroactive Error {}
extension String: @retroactive LocalizedError {
public var errorDescription: String? {
return self
}
}
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="CWo-PE-Dqp">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23504" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="CWo-PE-Dqp">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22685"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23506"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
@@ -122,28 +122,28 @@
<blurEffect style="light"/>
</visualEffectView>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="rX7-cf-eHr">
<rect key="frame" x="41.5" y="313" width="331" height="270.5"/>
<rect key="frame" x="31" y="313" width="352" height="270.5"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Card settings" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="pYc-TM-AIW">
<rect key="frame" x="12" y="12" width="307" height="33.5"/>
<rect key="frame" x="12" y="12" width="328" height="33.5"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleTitle1"/>
<color key="textColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="center" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="Dzd-L1-MsF">
<rect key="frame" x="12" y="75.5" width="307" height="133"/>
<rect key="frame" x="12" y="75.5" width="328" height="133"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="dKu-4z-j0d">
<rect key="frame" x="0.0" y="0.0" width="307" height="31"/>
<rect key="frame" x="0.0" y="0.0" width="328" height="31"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Distance" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="dRl-jP-icD">
<rect key="frame" x="0.0" y="0.0" width="238" height="31"/>
<rect key="frame" x="0.0" y="0.0" width="259" height="31"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="g8g-7l-bCj">
<rect key="frame" x="258" y="0.0" width="51" height="31"/>
<rect key="frame" x="279" y="0.0" width="51" height="31"/>
<connections>
<action selector="switchChanged:" destination="Rfp-kt-2Kx" eventType="valueChanged" id="bzY-wG-8Qw"/>
</connections>
@@ -151,16 +151,16 @@
</subviews>
</stackView>
<stackView opaque="NO" contentMode="scaleToFill" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="toG-0g-rVB">
<rect key="frame" x="0.0" y="51" width="307" height="31"/>
<rect key="frame" x="0.0" y="51" width="328" height="31"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Coordinates" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="vyf-82-r1X">
<rect key="frame" x="0.0" y="0.0" width="238" height="31"/>
<rect key="frame" x="0.0" y="0.0" width="259" height="31"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Jjd-6X-t3e">
<rect key="frame" x="258" y="0.0" width="51" height="31"/>
<rect key="frame" x="279" y="0.0" width="51" height="31"/>
<connections>
<action selector="switchChanged:" destination="Rfp-kt-2Kx" eventType="valueChanged" id="QI3-0U-fDp"/>
</connections>
@@ -168,16 +168,16 @@
</subviews>
</stackView>
<stackView opaque="NO" contentMode="scaleToFill" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="CU8-Db-J5u">
<rect key="frame" x="0.0" y="102" width="307" height="31"/>
<rect key="frame" x="0.0" y="102" width="328" height="31"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Population" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Hpe-Qx-6yX">
<rect key="frame" x="0.0" y="0.0" width="238" height="31"/>
<rect key="frame" x="0.0" y="0.0" width="259" height="31"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="N7g-C0-b1h">
<rect key="frame" x="258" y="0.0" width="51" height="31"/>
<rect key="frame" x="279" y="0.0" width="51" height="31"/>
<connections>
<action selector="switchChanged:" destination="Rfp-kt-2Kx" eventType="valueChanged" id="U53-Iv-BcF"/>
</connections>
@@ -195,7 +195,7 @@
</constraints>
</stackView>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="l7i-w5-sp0">
<rect key="frame" x="219" y="228.5" width="100" height="30"/>
<rect key="frame" x="219" y="228.5" width="121" height="30"/>
<constraints>
<constraint firstAttribute="height" constant="30" id="2j0-dH-u0p"/>
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="100" id="Dpt-7I-vJu"/>
@@ -227,7 +227,7 @@
<constraint firstItem="8QE-80-TMN" firstAttribute="leading" secondItem="1tH-EZ-OWj" secondAttribute="leading" id="FLK-lL-lW7"/>
<constraint firstItem="rX7-cf-eHr" firstAttribute="centerX" secondItem="Q06-4a-yjE" secondAttribute="centerX" id="LDx-5Z-nve"/>
<constraint firstItem="1tH-EZ-OWj" firstAttribute="trailing" secondItem="8QE-80-TMN" secondAttribute="trailing" id="NEe-0Z-Ut5"/>
<constraint firstItem="rX7-cf-eHr" firstAttribute="width" secondItem="Q06-4a-yjE" secondAttribute="width" multiplier="0.8" id="P27-yy-HrC"/>
<constraint firstItem="rX7-cf-eHr" firstAttribute="width" secondItem="Q06-4a-yjE" secondAttribute="width" multiplier="0.85" id="P27-yy-HrC"/>
<constraint firstItem="8QE-80-TMN" firstAttribute="top" secondItem="Q06-4a-yjE" secondAttribute="top" id="gh4-42-aNP"/>
<constraint firstItem="rX7-cf-eHr" firstAttribute="centerY" secondItem="Q06-4a-yjE" secondAttribute="centerY" id="kEj-37-bWM"/>
<constraint firstAttribute="bottom" secondItem="8QE-80-TMN" secondAttribute="bottom" id="vey-5u-Ovd"/>
@@ -433,16 +433,16 @@
<blurEffect style="light"/>
</visualEffectView>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="dnj-0w-OVz">
<rect key="frame" x="41.5" y="112" width="331" height="672"/>
<rect key="frame" x="31" y="112" width="352" height="672"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Filters" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="3tl-cp-QLk">
<rect key="frame" x="12" y="16" width="307" height="20.5"/>
<rect key="frame" x="12" y="16" width="328" height="20.5"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="MVi-r1-SFm">
<rect key="frame" x="276" y="630" width="39" height="30"/>
<rect key="frame" x="297" y="630" width="39" height="30"/>
<constraints>
<constraint firstAttribute="height" constant="30" id="NoD-eT-hcR"/>
</constraints>
@@ -452,7 +452,7 @@
</connections>
</button>
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="UD6-Ee-0GM">
<rect key="frame" x="0.0" y="46.5" width="331" height="573.5"/>
<rect key="frame" x="0.0" y="46.5" width="352" height="573.5"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<connections>
<outlet property="dataSource" destination="6LP-zk-O1z" id="9ZD-Dr-D9f"/>
@@ -481,7 +481,7 @@
<constraint firstItem="cc3-jb-kKt" firstAttribute="leading" secondItem="Lp8-R3-fW9" secondAttribute="leading" id="A0P-4p-dRN"/>
<constraint firstAttribute="bottom" secondItem="cc3-jb-kKt" secondAttribute="bottom" id="EO3-iK-i5i"/>
<constraint firstItem="dnj-0w-OVz" firstAttribute="height" secondItem="9GQ-SZ-2Mt" secondAttribute="height" multiplier="0.75" id="EWy-XB-uoC"/>
<constraint firstItem="dnj-0w-OVz" firstAttribute="width" secondItem="9GQ-SZ-2Mt" secondAttribute="width" multiplier="0.8" id="ZHH-KA-oym"/>
<constraint firstItem="dnj-0w-OVz" firstAttribute="width" secondItem="9GQ-SZ-2Mt" secondAttribute="width" multiplier="0.85" id="ZHH-KA-oym"/>
<constraint firstItem="dnj-0w-OVz" firstAttribute="centerX" secondItem="9GQ-SZ-2Mt" secondAttribute="centerX" id="fUr-JO-gPl"/>
<constraint firstItem="Lp8-R3-fW9" firstAttribute="trailing" secondItem="cc3-jb-kKt" secondAttribute="trailing" id="gfa-oV-YnD"/>
<constraint firstItem="dnj-0w-OVz" firstAttribute="centerY" secondItem="9GQ-SZ-2Mt" secondAttribute="centerY" id="i3x-eo-Lvg"/>
@@ -12,11 +12,6 @@ import MapKit
class EQNSeismicAnnotationView: MKAnnotationView {
static let IdentifierFull = "EQNSeismicAnnotationViewFull"
static let IdentifierLight = "EQNSeismicAnnotationViewLight"
static let IdentifierCircle = "EQNSeismicAnnotationViewCircle"
private static let LabelHeight: CGFloat = 15.0
private static let MagnitudeHeight: CGFloat = 25.0
private static let MagnitudeWidth: CGFloat = 45.0
@@ -43,11 +38,16 @@ class EQNSeismicAnnotationView: MKAnnotationView {
get { magnitudeLabel.text }
}
var magnitudeColor: UIColor {
var magnitudeTextColor: UIColor {
set { magnitudeLabel.textColor = newValue }
get { magnitudeLabel.textColor }
}
var magnitudeBackgroundColor: UIColor? {
set { magnitudeView.backgroundColor = newValue }
get { magnitudeView.backgroundColor }
}
var isUserSelection: Bool = false {
didSet {
magnitudeView.layer.borderColor = isUserSelection ? AppTheme.Colors.red.cgColor : AppTheme.Colors.darkGray.cgColor
@@ -107,13 +107,13 @@ class EQNSeismicAnnotationView: MKAnnotationView {
backgroundColor = .clear
if reuseIdentifier == Self.IdentifierFull {
if reuseIdentifier == Self.identifier(for: .full) {
frame = CGRect(x: 0, y: 0, width: Self.FullViewWidth, height: Self.FullViewHeight)
setupFullUI()
} else if reuseIdentifier == Self.IdentifierLight {
} else if reuseIdentifier == Self.identifier(for: .light) {
frame = CGRect(x: 0, y: 0, width: Self.SmallViewWidth, height: Self.SmallViewHeight)
setupLightUI()
} else if reuseIdentifier == Self.IdentifierCircle {
} else if reuseIdentifier == Self.identifier(for: .circle) {
frame = CGRect(x: 0, y: 0, width: Self.CircleViewHeight, height: Self.CircleViewHeight)
setupCircleUI()
}
@@ -158,3 +158,14 @@ class EQNSeismicAnnotationView: MKAnnotationView {
addSubview(imageView)
}
}
extension EQNSeismicAnnotationView {
static func identifier(for style: MapPinStyle) -> String {
return switch style {
case .circle: "EQNSeismicAnnotationViewCircle"
case .light: "EQNSeismicAnnotationViewLight"
case .full: "EQNSeismicAnnotationViewFull"
}
}
}
@@ -33,6 +33,7 @@
"filter_show_area" = "إظهار كافة الزلازل ضمن دائرة نصف قطرها:";
"filter_show_relevant" = "إظهار الزلازل ذات الصلة بموقعي فقط";
"filter_show_all" = "عرض جميع الزلازل على مستوى العالم (M≥2)";
"filter_show_felt" = "إظهار الزلازل التي يشعر بها المستخدمون فقط";
"filter_minimum_magnitude" = "والحجم الأدنى:";
"main_understood" = "مفهوم";
"options_low_magnitude" = "الانتباه إلى أن ليست كل الشبكات الزلزالية توفر بيانات الزلازل التي تقل عن 2.0 درجة. علاوة على ذلك ، فإنك تزيد بشكل كبير من نقل البيانات واستخدام البطارية بسبب الإخطارات. ما لم يتم تصنيع جهازك حديثا ، ستلاحظ أيضا تباطؤا عاما.";
@@ -53,6 +54,7 @@
"filter_area" = "الزلازل المعروضة: في دائرة نصف قطرها";
"filter_relevant" = "الزلازل المعروضة: ذات صلة";
"filter_all" = "الزلازل المعروضة: الكل";
"filter_felt" = "الزلازل المعروضة: شعرت";
"liveview_unknown_location" = "موقفك غير معروف. تمكين موقع الهاتف الذكي من تكوين الهاتف الذكي";
"map_number" = "تم الكشف عن زلزال بواسطة %@ الهواتف الذكية";
"permission_location_no" = "لقد اخترت منع التطبيق من قراءة موقع الجهاز. لن تستلم تنبيهات وإشعارات في الوقت الفعلي";
@@ -142,6 +144,8 @@
"mercalli_XI" = "\U200FXI - تدمير مراكز حضرية بأكملها ، العديد من الضحايا ، شقوق في الأرض وانهيارات أرضيةlandslides";
"mercalli_XII" = "\U200FXII - اضطراب التربة ، إزاحة قشرة الأرض";
"mercalli_intensity" = "شدة %@";
"shakemap" = "خريطة اهتزازية";
"shakemap_description" = "خريطة الكثافة بناءً على التقارير المحسوسة";
// values
"official_magnitude_value_00" = "درجة >= 0.0";
@@ -33,6 +33,7 @@
"filter_show_area" = "Εμφάνιση όλων των σεισμών σε ακτίνα:";
"filter_show_relevant" = "Εμφάνιση μόνο των σχετικών σεισμών σε σχέση με την τοποθεσία μου";
"filter_show_all" = "Εμφάνιση όλων των σεισμών παγκοσμίως (M≥2)";
"filter_show_felt" = "Εμφάνιση μόνο των σεισμών που αισθάνθηκαν οι χρήστες";
"filter_minimum_magnitude" = "και ελάχιστο μέγεθος:";
"main_understood" = "Κατάλαβα";
"options_low_magnitude" = "Λάβε υπόψη ότι δεν παρέχουν όλα τα σεισμικά δίκτυα δεδομένα για σεισμούς με μέγεθος κάτω από 2.0. Επίσης, αυξάνεις σημαντικά την μεταφορά δεδομένων και την χρήση μπαταρίας λόγω κοινοποιήσεων. Αν η συσκευή σου δεν είναι πρόσφατης κατασκευής, θα παρατηρήσεις επίσης μια γενική επιβράδυνση.";
@@ -53,6 +54,7 @@
"filter_area" = "Εμφανίζονται σεισμοί: στην ακτίνα";
"filter_relevant" = "Εμφανίζονται σεισμοί: σχετικοί";
"filter_all" = "Εμφανίζονται σεισμοί: όλοι (M≥2)";
"filter_felt" = "Εμφανίζονται σεισμοί: αισθητοί";
"liveview_unknown_location" = "Η θέση σου είναι άγνωστη. Ενεργοποίησε την τοποθεσία του smartphone από την διαμόρφωση του smartphone";
"map_number" = "Ανιχνεύθηκε δόνηση από %@ smartphone";
"permission_location_no" = "Έχεις επιλέξει να αποτρέπεις την εφαρμογή από την ανάγνωση της τοποθεσίας της συσκευής. ΔΕΝ θα λαμβάνεις κοινοποιήσεις και ειδοποιήσεις σε πραγματικό χρόνο";
@@ -142,6 +144,8 @@
"mercalli_XI" = "XI - Καταστροφές ολόκληρων αστικών κέντρων, πολλά θύματα, ρωγμές στο έδαφος και κατολισθήσεις";
"mercalli_XII" = "XII - Διάσπαση του εδάφους, μετατόπιση του φλοιού της γης";
"mercalli_intensity" = "Ένταση %@";
"shakemap" = "Χάρτης έντασης";
"shakemap_description" = "Χάρτης έντασης με βάση τις αναφορές χρηστών";
// values
"official_magnitude_value_00" = "Μέγεθος >= 0.0";
@@ -33,6 +33,7 @@
"filter_show_area" = "Show all earthquakes within a radius of:";
"filter_show_relevant" = "Show only the relevant earthquakes with respect to my location";
"filter_show_all" = "Show all earthquakes globally (M≥2)";
"filter_show_felt" = "Show only the earthquakes felt by the users";
"filter_minimum_magnitude" = "and minimum magnitude:";
"main_understood" = "Understood";
"options_low_magnitude" = "Beware that not all seismic networks provide earthquake data below magnitude 2.0. Moreover, you significantly increase the data transfer and the battery usage due to notifications. Unless your device is recently manufactured, you will also notice a general slowdown.";
@@ -53,6 +54,7 @@
"filter_area" = "Quakes shown: in the radius";
"filter_relevant" = "Quakes shown: relevant";
"filter_all" = "Quakes shown: all (M≥2)";
"filter_felt" = "Quakes shown: felt";
"liveview_unknown_location" = "Your position is unknown. Enable smartphone location from smartphone configuration";
"map_number" = "Quake detected by %@ smartphones";
"permission_location_no" = "You have chosen to prevent the app from reading the location of the device. You will NOT receive real-time notifications and alerts";
@@ -142,6 +144,8 @@
"mercalli_XI" = "XI - Destruction of entire urban centers, many victims, crevices in the ground and landslides";
"mercalli_XII" = "XII - Disruption of the soil, displacement of the earth's crust";
"mercalli_intensity" = "Intensity %@";
"shakemap" = "Shakemap";
"shakemap_description" = "Intensity map based on user reports";
// values
"official_magnitude_value_00" = "Magnitude >= 0.0";
@@ -33,6 +33,7 @@
"filter_show_area" = "Mostrar todos los sismos en un radio de:";
"filter_show_relevant" = "Mostrar solo los sismos relevantes con respecto a mi ubicación";
"filter_show_all" = "Mostrar todos los sismos globalmente (M≥2)";
"filter_show_felt" = "Mostrar solo los sismos sentidos por los usuarios";
"filter_minimum_magnitude" = "y magnitud mínima:";
"main_understood" = "Entendido";
"options_low_magnitude" = "Tenga en cuenta que no todas las redes sísmicas proporcionan datos para sismos de magnitud inferior a 2.0. Además, aumenta significativamente la transferencia de datos con el servidor y el uso de la batería debido a la mayor cantidad de notificaciones. Si tu dispositivo no se ha fabricado recientemente, también notará una desaceleración general de la aplicación.";
@@ -53,6 +54,7 @@
"filter_area" = "Sismos mostrados: en el radio";
"filter_relevant" = "Sismos mostrados: relevantes";
"filter_all" = "Sismos mostrados: todos (M≥2)";
"filter_felt" = "Sismos mostrados: sentidos";
"liveview_unknown_location" = "Tu posición es desconocida. Habilitar la ubicación del smartphone desde la página de configuración del smartphone";
"map_number" = "Sismo detectado por %@ smartphones";
"permission_location_no" = "Ha elegido evitar que la aplicación lea la ubicación de tu dispositivo. NO recibirá notificaciones y alertas en tiempo real";
@@ -142,6 +144,8 @@
"mercalli_XI" = "XI - Destrucción de centros urbanos enteros, muchas víctimas, grietas en el suelo y deslizamientos de tierra";
"mercalli_XII" = "XII - Alteración del suelo, desplazamiento de la corteza terrestre.";
"mercalli_intensity" = "Intensidad %@";
"shakemap" = "Mapa intensidad";
"shakemap_description" = "Mapa de intensidad basado en informes de usuarios";
// values
"official_magnitude_value_00" = "Magnitud >= 0.0";
@@ -33,6 +33,7 @@
"filter_show_area" = "Afficher tous les tremblements de terre dans un rayon de :";
"filter_show_relevant" = "Afficher uniquement les tremblements de terre pertinents par rapport à ma position";
"filter_show_all" = "Afficher tous les tremblements de terre globalement (M≥2)";
"filter_show_felt" = "Afficher uniquement les tremblements de terre ressentis par les utilisateurs";
"filter_minimum_magnitude" = "et magnitude minimale :";
"main_understood" = "J'ai compris";
"options_low_magnitude" = "Attention, les réseaux sismiques ne fournissent pas tous des données pour des séismes de magnitude inférieure à 2,0. De plus, vous augmentez significativement le transfert de données et l'utilisation de la batterie en raison du plus grand nombre de notifications. Sauf si votre appareil est de fabrication récente, vous remarquerez également un ralentissement général.";
@@ -53,6 +54,7 @@
"filter_area" = "Séismes affichés : dans le rayon";
"filter_relevant" = "Séismes affichés : pertinents";
"filter_all" = "Séismes affichés : tous (M≥2)";
"filter_felt" = "Séismes affichés : ressentis";
"liveview_unknown_location" = "Votre position est inconnue. Activez la localisation à partir de la page de configuration de votre appareil";
"map_number" = "Séisme détecté par %@ smartphones";
"permission_location_no" = "Vous avez choisi d'empêcher l'app de lire la position de votre appareil. Vous ne recevrez PAS de notifications et d'alertes en temps réel";
@@ -142,6 +144,8 @@
"mercalli_XI" = "XI - Destruction de centres urbains entiers, nombreuses victimes, crevasses dans le sol et glissements de terrain";
"mercalli_XII" = "XII - Perturbation du sol, déplacement de la croûte terrestre";
"mercalli_intensity" = "Intensité %@";
"shakemap" = "Carte d'intensité";
"shakemap_description" = "Carte d'intensité basée sur les rapports ressentis";
// values
"official_magnitude_value_00" = "Magnitude >= 0.0";
@@ -33,6 +33,7 @@
"filter_show_area" = "Prikaži sve potrese u radijusu od:";
"filter_show_relevant" = "Prikaži samo relevantne potrese s obzirom na moju lokaciju";
"filter_show_all" = "Prikaži sve potrese globalno (M≥2)";
"filter_show_felt" = "Prikaži samo potrese koje su korisnici osjetili";
"filter_minimum_magnitude" = "i minimalna veličina:";
"main_understood" = "Razumijem";
"options_low_magnitude" = "Imajte na umu da ne pružaju sve seizmološke mreže podatke o potresima jačine ispod 2,0. Štoviše, znatno povećavate prijenos podataka i upotrebu baterije zbog obavijesti. Ako vaš uređaj nije nedavno proizveden, primijetit ćete i opće usporavanje.";
@@ -53,6 +54,7 @@
"filter_area" = "Prikazani potresi: u polumjeru";
"filter_relevant" = "Prikazani potresi: relevantni";
"filter_all" = "Prikazani potresi: svi (M≥2)";
"filter_felt" = "Prikazani potresi: osjetili";
"liveview_unknown_location" = "Vaš položaj nije poznat. Omogući lokaciju u konfiguraciji pametnog telefona";
"map_number" = "Potres je otkrio sljedeći broj pametnih telefona: %@";
"permission_location_no" = "Odlučili ste onemogućiti aplikaciji očitavanje lokacije uređaja. NEĆETE primati obavijesti i upozorenja u stvarnom vremenu";
@@ -142,6 +144,8 @@
"mercalli_XI" = "XI - Uništavanje čitavih urbanih središta, mnogo žrtava, pukotine u zemlji i klizišta";
"mercalli_XII" = "XII - Poremećaj tla, pomicanje zemljine kore";
"mercalli_intensity" = "Intenzitet %@";
"shakemap" = "Mapa intenziteta";
"shakemap_description" = "Karta intenziteta na temelju izvješća o filu";
// values
"official_magnitude_value_00" = "Jačina >= 0.0";
@@ -33,6 +33,7 @@
"filter_show_area" = "Tampilkan semua gempa dalam radius:";
"filter_show_relevant" = "Tampilkan hanya gempa bumi yang relevan dengan lokasi saya";
"filter_show_all" = "Tampilkan semua gempa bumi secara global (M≥2)";
"filter_show_felt" = "Hanya menampilkan gempa bumi yang dirasakan oleh pengguna";
"filter_minimum_magnitude" = "dan besaran minimum:";
"main_understood" = "Paham";
"options_low_magnitude" = "Harap diperhatikan bahwa tidak semua jaringan seismik menyediakan data gempa di bawah magnitudo 2,0 SR. Selain itu, pemberitahuan yang diaktifkan akan menambah transfer data dan penggunaan baterai secara signifikan. Perangkat juga mungkin berjalan lebih lambat, kecuali jika perangkat Anda tergolong baru diproduksi.";
@@ -53,6 +54,7 @@
"filter_area" = "Gempa yang ditampilkan: dalam radius";
"filter_relevant" = "Gempa bumi yang ditampilkan: relevan";
"filter_all" = "Gempa bumi ditampilkan: semua (M≥2)";
"filter_felt" = "Gempa yang ditampilkan: terasa";
"liveview_unknown_location" = "Posisi Anda tidak diketahui. Aktifkan lokasi smartphone dari konfigurasi smartphone";
"map_number" = "Gempa terdeteksi oleh %@ smartphone";
"permission_location_no" = "Anda telah memilih untuk mencegah aplikasi membaca lokasi perangkat. Anda TIDAK akan menerima pemberitahuan dan peringatan secara real time";
@@ -142,6 +144,8 @@
"mercalli_XI" = "XI - Penghancuran seluruh pusat kota, banyak korban, celah-celah di tanah dan tanah longsor";
"mercalli_XII" = "XII - Gangguan tanah, perpindahan kerak bumi";
"mercalli_intensity" = "Intensitas %@";
"shakemap" = "Peta intensitas";
"shakemap_description" = "Peta intensitas berdasarkan laporan pengguna";
// values
"official_magnitude_value_00" = "Magnitudo >= 0.0";
@@ -33,6 +33,7 @@
"filter_show_area" = "Mostra tutti i sismi nel raggio di:";
"filter_show_relevant" = "Mostra solo i sismi rilevanti rispetto alla mia posizione";
"filter_show_all" = "Mostra tutti i sismi a livello globale (M≥2)";
"filter_show_felt" = "Mostra solo i sismi percepiti dagli utenti";
"filter_minimum_magnitude" = "e magnitudo minima:";
"main_understood" = "Ho capito";
"options_low_magnitude" = "Considera che non tutte le reti sismiche forniscono dati per sismi sotto magnitudo 2.0. Inoltre, incrementi significativamente il trasferimento dati con il server e l'utilizzo batteria dovuto al maggior numero di notifiche. Se il tuo dispositivo non è di recente fabbricazione, noterai anche un generale rallentamento dell'app.";
@@ -53,6 +54,7 @@
"filter_area" = "Sismi mostrati: nel raggio";
"filter_relevant" = "Sismi mostrati: rilevanti";
"filter_all" = "Sismi mostrati: tutti (M≥2)";
"filter_felt" = "Sismi mostrati: percepiti";
"liveview_unknown_location" = "La tua posizione è sconosciuta. Abilita la localizzazione dalla pagina di configurazione del tuo dispositivo";
"map_number" = "Sisma rilevato da %@ smartphone";
"permission_location_no" = "Hai scelto di impedire alla app di leggere la posizione del tuo dispositivo. NON riceverai notifiche ed allerte in tempo reale.";
@@ -142,6 +144,8 @@
"mercalli_XI" = "XI - Distruzione di interi centri urbani, moltissime vittime, crepacci nel suolo e frane ";
"mercalli_XII" = "XII - Sconvolgimento del suolo, dislocamento della crosta terrestre";
"mercalli_intensity" = "Intensità %@";
"shakemap" = "Mappa intensità";
"shakemap_description" = "Mappa di intensità basata sulle segnalazioni degli utenti";
// values
"official_magnitude_value_00" = "Magnitudo >= 0.0";
@@ -33,6 +33,7 @@
"filter_show_area" = "Aşağıdaki yarıçap içindeki tüm depremleri göster:";
"filter_show_relevant" = "Konumuma göre yalnızca ilgili depremleri göster";
"filter_show_all" = "Dünyadaki tüm depremleri göster (M≥2)";
"filter_show_felt" = "Sadece kullanıcıların hissettiği depremleri göster";
"filter_minimum_magnitude" = "ve minimum büyüklük:";
"main_understood" = "Anladım";
"options_low_magnitude" = "Tüm sismik ağların 2.0'nin altında büyüklüğe sahip depremlerin verilerini sağlamadığını unutmayın. Ayrıca, bildirimler nedeniyle veri aktarımınız ve pil kullanımınız önemli ölçüde artar. Cihazınız yakın zamanda üretilmediği sürece, genel bir yavaşlama da fark edersiniz.";
@@ -53,6 +54,7 @@
"filter_area" = "Gösterilen depremler: yarıçap içinde";
"filter_relevant" = "Gösterilen depremler: alakalı";
"filter_all" = "Gösterilen depremler: tümü (M≥2)";
"filter_felt" = "Gösterilen depremler: hissedildi";
"liveview_unknown_location" = "Konumunuz bilinmiyor. Akıllı telefon yapılandırmasından akıllı telefon konumunu etkinleştirin";
"map_number" = "%@ akıllı telefon tarafından tespit edilen deprem";
"permission_location_no" = "Uygulamanın cihazın konumunu okumasını engellemeyi seçtiniz. Gerçek zamanlı bildirim ve uyarılar ALMAYACAKSINIZ";
@@ -142,6 +144,8 @@
"mercalli_XI" = "XI - Tüm şehir merkezlerinin, birçok kurbanın, zemindeki yarıkların ve toprak kaymalarının yok edilmesi";
"mercalli_XII" = "XII - Toprağın bozulması, yer kabuğunun yer değiştirmesi";
"mercalli_intensity" = "Şiddeti %@";
"shakemap" = "Yoğunluk haritası";
"shakemap_description" = "Kullanıcı raporlarına dayalı yoğunluk haritası";
// values
"official_magnitude_value_00" = "Büyüklük >= 0.0";