Compare commits

...

249 Commits

Author SHA1 Message Date
Andrea Busi 6be5f72360 release: Increase version for release 2024-07-16 11:45:52 +02:00
Andrea Busi ccd1b9de59 dependency: Update Firebase 2024-07-16 11:45:43 +02:00
Andrea Busi 5737eb5b02 feat: Sort user subscriptions (top10k first) 2024-07-16 11:43:58 +02:00
Andrea Busi c549bb6ea5 fix: Solve wrong localized AR string 2024-07-16 09:23:39 +02:00
Andrea Busi ff80905033 fix: Solve crash due to wrong string format 2024-07-16 09:09:57 +02:00
Andrea Busi dad2bc5648 release: Increase version for release 2024-07-08 15:04:01 +02:00
Andrea Busi 10c74e278e refactor: Rework layout for restore subscriptions 2024-07-08 13:51:54 +02:00
Andrea Busi 96dbf960d2 refactor: Change tab official translations 2024-07-08 13:51:39 +02:00
Andrea Busi 81bfdd02a6 release: Increase version for release 2024-07-05 11:51:52 +02:00
Andrea Busi 2ab3267981 dependency: SPM 2024-07-05 11:45:29 +02:00
Andrea Busi 48b6941ed5 feat: Change nav bar color 2024-07-05 11:45:23 +02:00
Andrea Busi 669cb3c4f3 fix: Improve translation 2024-07-05 11:40:52 +02:00
Andrea Busi 638d819d35 refactor: Improve log 2024-07-05 09:03:48 +02:00
Andrea Busi a9884d8a8d release: Increase version for release 2024-07-04 15:22:58 +02:00
Andrea Busi 2ef3560011 feat: Scroll to opened seismic 2024-07-04 15:20:05 +02:00
Andrea Busi 05093bb7a4 chore: Update push payloads 2024-07-04 15:19:55 +02:00
Andrea Busi 55f84ab46d feat: Add new string for notification body 2024-07-04 15:19:45 +02:00
Andrea Busi 03b4d0ddd6 feat: Show right arrow to priority cell 2024-07-03 11:18:50 +02:00
Andrea Busi 3c5f26bc94 fix: Set background color to the proper container 2024-07-03 11:18:41 +02:00
Andrea Busi 8c79d45b19 release: Increase version for release 2024-07-03 10:10:37 +02:00
Andrea Busi 931d04c5e1 refactor: Reorganize files 2024-07-03 10:10:04 +02:00
Andrea Busi 4d62fbbbd3 fix: Solve wrong distance in filter evaluation 2024-07-03 10:00:55 +02:00
Andrea Busi 1c7065ece7 release: Increase version for release 2024-07-02 20:59:37 +02:00
Andrea Busi 6dfa51e013 refactor: Bigger fonts 2024-07-02 19:13:10 +02:00
Andrea Busi b8b21d1458 fix: Remove separator from table view 2024-07-02 18:15:03 +02:00
Andrea Busi 88317f79e8 fix: Missing rounded corners 2024-07-02 17:57:01 +02:00
Andrea Busi 4e1147e782 refactor: Remove no longer used class 2024-07-02 17:56:55 +02:00
Andrea Busi 579969d507 fix: Missing callback 2024-07-02 17:55:25 +02:00
Andrea Busi 4d991d9a10 refactor: Recreate expanded notification cell via code and change some UI elements 2024-07-02 17:55:19 +02:00
Andrea Busi 41491b5ee7 refactor: Change colors as per specifications 2024-07-02 12:25:18 +02:00
Andrea Busi 197b375c28 refactor: Remove setting for filter type in seismic notifications 2024-07-01 11:18:47 +02:00
Andrea Busi f41e6b50ec refactor: Create UI in code to properly manage filter view 2024-07-01 10:40:50 +02:00
Andrea Busi 796e4b5895 release: Increase version for release 2024-07-01 10:40:26 +02:00
Andrea Busi e43a93979d fix: Solve layout issue with gradient background 2024-06-29 16:16:47 +02:00
Andrea Busi ef1aaa7d71 refactor: Use extension for view rounded corners and shadow 2024-06-29 16:16:47 +02:00
Andrea Busi 22d78baa8a feat: Highlight seismic card title for push notification
Resolves: https://gitlab.steamware.net/eqn/eqn.ios/-/issues/76
2024-06-29 16:16:47 +02:00
Andrea Busi e4588aa731 chore: Update payload for official push notification 2024-06-29 16:16:47 +02:00
Andrea Busi 07764f91ed feat: Add logic to update filter when push is opened
Resolves: https://gitlab.steamware.net/eqn/eqn.ios/-/issues/76
2024-06-29 16:16:47 +02:00
Andrea Busi a0a238e384 refactor" Don't show image for official notifications 2024-06-27 18:19:30 +02:00
Andrea Busi e61a45f78f fix: Resolve deprecation 2024-06-27 17:04:02 +02:00
Andrea Busi 0fdc60b938 chore: Fix push payload sample 2024-06-27 17:04:02 +02:00
Andrea Busi 5f02e2b8bb dependency: Update Firebase 2024-06-27 17:04:02 +02:00
Andrea Busi b17a57b98e refactor: Remove unused strings 2024-06-24 17:47:54 +02:00
Andrea Busi 2379077272 release: Increase version for release 2024-06-24 09:02:50 +02:00
Andrea Busi 78f0cfb2fa feat: Add gradient background in seismic cards
Resolves: https://gitlab.steamware.net/eqn/eqn.ios/-/issues/74
2024-06-24 09:00:49 +02:00
Andrea Busi f6bfe3fca0 dependency: Update Shogun 2024-06-24 09:00:16 +02:00
Andrea Busi d5ab49b807 release: Increase version for release 2024-06-23 16:33:19 +02:00
Andrea Busi b8bd547d65 refactor: Minor changes to new map 2024-06-23 16:24:31 +02:00
Andrea Busi 547c503726 release: Increase version for release 2024-06-22 11:06:07 +02:00
Andrea Busi 234622bcfd feat: Share screenshot feature in base map controller 2024-06-22 11:04:27 +02:00
Andrea Busi 589466c8c6 refactor: Minor changes in subscription page 2024-06-22 11:04:27 +02:00
Andrea Busi 2e7742951e feat: Change map annotation layout in seismic map
Resolves: https://gitlab.steamware.net/eqn/eqn.ios/-/issues/71
2024-06-22 11:04:27 +02:00
Andrea Busi 3ed77ff1af feat: Support zPriority in base map 2024-06-21 17:42:15 +02:00
Andrea Busi c98530fc54 release: Increase version for release 2024-06-21 15:59:57 +02:00
Andrea Busi 98cc7e7c4c feat: Complete subscription page refactor 2024-06-21 15:56:55 +02:00
Andrea Busi 4db0bb6316 feat: Base cell can show a right chevron 2024-06-20 16:26:19 +02:00
Andrea Busi 8c3f2dad6d refactor: Rework model for in app products 2024-06-20 16:26:19 +02:00
Andrea Busi e0f346a4dc refactor: Create subscriptions controller from code 2024-06-20 16:26:19 +02:00
Andrea Busi eac0f8249e feat: New layout for subscription details 2024-06-20 16:26:19 +02:00
Andrea Busi d7c691101c refactor: Use methods from Shogun 2024-06-18 23:43:34 +02:00
Andrea Busi 49edbe1a14 refactor: Don't use static constants for fonts 2024-06-18 15:33:58 +02:00
Andrea Busi c5b3750ee7 release: Increase version for release 2024-06-15 15:21:31 +02:00
Andrea Busi 98fb65a640 refactor: Remove some unused strings 2024-06-15 15:20:18 +02:00
Andrea Busi c20041127b feat: Don't open filters from map and change displayed label
https://gitlab.steamware.net/eqn/eqn.ios/-/issues/71
2024-06-15 15:16:24 +02:00
Andrea Busi 3995c29b22 refactor: Migrate Segnalazioni cells to code 2024-06-15 15:15:42 +02:00
Andrea Busi dfa07d0d10 fix: Solve wrong seismic download filter 2024-06-14 21:57:39 +02:00
Andrea Busi ce6fbb24ff release: Increase version for release 2024-06-14 16:30:07 +02:00
Andrea Busi 382dcfa794 refactor: Migrate subscription products cell to code 2024-06-14 16:15:26 +02:00
Andrea Busi d46a2e1559 refactor: Create extensions for padding constants 2024-06-14 16:12:21 +02:00
Andrea Busi b0d1cde42b refactor: Migarate active subscription and description to code 2024-06-14 16:03:00 +02:00
Andrea Busi d8612e33a3 refactor: Migrate AlertsPositionDataTableViewCell to code 2024-06-14 16:03:00 +02:00
Andrea Busi 73826d7520 refactor: Migrate AlertsSeismicNotificationCompactTableViewCell to code 2024-06-14 16:03:00 +02:00
Andrea Busi 3f57ac9b96 refactor: Migrate AlertsPastEartquakesTableViewCell to code 2024-06-14 16:03:00 +02:00
Andrea Busi 54c78aac0f refactor: Migrate AlertsNoLocationTableViewCell to code 2024-06-14 16:03:00 +02:00
Andrea Busi 975f5ed5bc refactor: Migrate AlertsPriorityServiceTableViewCell to code 2024-06-14 16:03:00 +02:00
Andrea Busi 52142486cf refactor: Migrate AlertsSmartphoneNetworkTableViewCell to code 2024-06-14 16:03:00 +02:00
Andrea Busi b4b676ca8d refactor: Add extension to create EQNRoundedButton 2024-06-14 13:09:48 +02:00
Andrea Busi dd9ef878e2 feat: Support multiline titles in EQNRoundedButton 2024-06-14 12:11:48 +02:00
Andrea Busi 5b978e535c release: Increase version for release 2024-06-12 22:23:09 +02:00
Andrea Busi 242c15ba58 feat: Support dynamic font in some views 2024-06-12 22:22:35 +02:00
Andrea Busi a224837dcb fix: Some UI fixes 2024-06-11 17:40:33 +02:00
Andrea Busi a21c16a01c release: Increase version for release 2024-06-11 11:17:47 +02:00
Andrea Busi 1496f25251 feat: Manage missing location in Seismic list and filters 2024-06-11 11:13:40 +02:00
Andrea Busi ad6eb6619c refactor: Remove no longer needed ObjC bridging 2024-06-11 10:56:31 +02:00
Andrea Busi f9a8dffad5 feat: Minor UI change for depth
Resolves: https://gitlab.steamware.net/eqn/eqn.ios/-/issues/69
2024-06-10 22:20:14 +02:00
Andrea Busi a708a0f79a refactor: Migrate EQNSettingRealTimeAlert to Swift 2024-06-10 22:14:01 +02:00
Andrea Busi 49431a760c feat: Align user report to new values and recreate in Swift 2024-06-10 22:14:01 +02:00
Andrea Busi a57e883409 refactor: Align names for "seismic network notifications" settings 2024-06-10 15:01:23 +02:00
Andrea Busi b373dc1d60 refactor: Migrate SettingsRealTimeAlertsViewController to Swift 2024-06-10 15:01:23 +02:00
Andrea Busi 01f1df9c01 feat: Force notification settings upload on first app start after migration
Resolves: https://gitlab.steamware.net/eqn/eqn.ios/-/issues/68
2024-06-10 13:25:34 +02:00
Andrea Busi 72441d0532 refactor: Create Swift version of SettingsBaseViewController 2024-06-10 13:25:34 +02:00
Andrea Busi a4afb84e6d feat: Add sort feature in Seismic list 2024-06-10 08:55:25 +02:00
Andrea Busi 45a59e30ba feat: Rework seismic filters with new specifications
Resolves: https://gitlab.steamware.net/eqn/eqn.ios/-/issues/70
2024-06-09 16:15:34 +02:00
Andrea Busi dac13acb9e refactor: Update parameters for upload settings
Resolves: https://gitlab.steamware.net/eqn/eqn.ios/-/issues/68
2024-06-09 16:15:34 +02:00
Andrea Busi a9e264d666 feat: Rework notification settings for network alerts
Resolves: https://gitlab.steamware.net/eqn/eqn.ios/-/issues/66
2024-06-09 16:15:34 +02:00
Andrea Busi e64aaf2469 refactor: Move network label in card
Resolves: https://gitlab.steamware.net/eqn/eqn.ios/-/issues/69
2024-06-09 16:15:34 +02:00
Andrea Busi 30c7536d4c refactor: Remove bell icon in Seismic Networks
Resolves: https://gitlab.steamware.net/eqn/eqn.ios/-/issues/69
2024-06-09 16:15:34 +02:00
Andrea Busi 70e82a67b1 refactor: Remove networks selection in Seismic Networks
Resolves: https://gitlab.steamware.net/eqn/eqn.ios/-/issues/69
2024-06-09 16:15:34 +02:00
Andrea Busi f020ac70a1 feat: Increase minimum target to iOS 14 2024-06-09 16:15:12 +02:00
Andrea Busi dc4ccd796d dependency: Update packages 2024-06-09 16:15:05 +02:00
Andrea Busi f66d6558b5 refactor: Move getDeltaMinute to EQNUtility 2024-06-07 17:03:13 +02:00
Andrea Busi 536ed32fb9 refactor: Remove unused constants 2024-06-07 17:03:08 +02:00
Andrea Busi 2e1a2a8e04 feat: Add method to retrieve enum from user defaults 2024-06-06 14:50:32 +02:00
Andrea Busi 527132b7eb dependency: Update Shogun 2024-06-06 14:46:53 +02:00
Andrea Busi 8cf69a9d12 fix: Don't perform user registration if Firebase token is null
Resolves: https://gitlab.steamware.net/eqn/eqn.ios/-/issues/65
2024-06-06 10:54:31 +02:00
Andrea Busi 6cba42994d refactor: Remove CocoaPods 2024-06-06 10:41:57 +02:00
Andrea Busi fd7821c083 dependency: Migrate Firebase to SPM 2024-06-06 10:41:57 +02:00
Andrea Busi 5fab419d0e refactor: Use new property from Facebook SDK 2024-04-29 14:25:34 +02:00
Andrea Busi befe46465b dependency: Migrate FBSDKCoreKit to SPM 2024-04-29 14:25:34 +02:00
Andrea Busi 5e6ee892ce dependency: Migrate Google-Mobile-Ads-SDK to SPM 2024-04-29 14:25:34 +02:00
Andrea Busi 79d4b3b3bd refactor: Change nullability 2024-04-29 14:25:34 +02:00
Andrea Busi 357bdd47e3 dependency: Migrate DZNEmptyDataSet to SPM 2024-04-29 14:25:34 +02:00
Andrea Busi 1e4dd507da dependency: Migrate Solar as SPM 2024-04-29 14:25:34 +02:00
Andrea Busi bdfcb7a5c4 release: Increase version for release 2023-09-14 17:02:58 +02:00
Andrea Busi 40fcb4707f fix: Be sure to download updated data when country is changed 2023-09-14 17:02:17 +02:00
Andrea Busi 37f9a856b1 feat: Filter modify notification settings by default 2023-09-14 17:02:17 +02:00
Andrea Busi f42b9f1b53 refactor: Migrate some UserDefaults constants 2023-09-14 17:02:17 +02:00
Andrea Busi 4fd9966435 release: Update changelog 2023-09-14 17:02:17 +02:00
Andrea Busi df2c0a94a4 refactor: Remove unused import 2023-09-14 17:02:17 +02:00
Andrea Busi cdd1a8d875 refactor: Remove deprecated method, useless with new BGTask implementation 2023-09-14 17:02:17 +02:00
Andrea Busi 31f1cb5f35 refactor: Can enable/disable background position debug 2023-09-14 17:02:17 +02:00
Andrea Busi d426f15c8e refactor: Remove deprecated code 2023-09-14 17:02:17 +02:00
Andrea Busi 037a74061d feat: Perform WS request to upload user position 2023-09-14 17:02:17 +02:00
Andrea Busi ea6172226d feat: Add a debug screen for saved background positions 2023-09-11 15:52:27 +02:00
Andrea Busi ec94db29b9 feat: Add background task to get user location 2023-09-11 15:52:27 +02:00
Andrea Busi 91a9bce03c release: Update changelog 2023-08-16 12:03:09 +02:00
Andrea Busi f54f4a2312 refactor: Remove no longer needed available checks 2023-08-16 12:02:32 +02:00
Andrea Busi a959df7cd9 dependency: Update Pods & SPM 2023-08-16 12:02:18 +02:00
Andrea Busi e95a93ff2c feat: Increase target to iOS 13 2023-08-16 11:57:49 +02:00
Andrea Busi b7c1f7379d refactor: Migrate some user default constants 2023-08-16 11:54:52 +02:00
Andrea Busi 0f71e0fea9 refactor: Disable monitoring background logic 2023-08-14 16:33:54 +02:00
Andrea Busi 92de4c534c release: Increase version for release 2023-08-10 16:50:50 +02:00
Andrea Busi 3c237c5b18 docs: Add push payload for AR 2023-08-10 16:17:38 +02:00
Andrea Busi 16dc2410bc fix: Get user location if manager one is null 2023-08-10 15:58:59 +02:00
Andrea Busi 3c83cb97cb release: Increase version for release 2023-08-04 16:20:13 +02:00
Andrea Busi 4796e3d5a7 fix: Update AR string 2023-08-04 16:09:08 +02:00
Andrea Busi 56f53550da release: Increase version for release 2023-07-31 07:19:45 +02:00
Andrea Busi d52b980959 release: Increase version for release 2023-07-27 17:35:16 +02:00
Andrea Busi b9f87c130d fix: Add missing AR strings 2023-07-27 17:35:16 +02:00
Andrea Busi b7acbc70df feat: Add arabic language 2023-07-27 17:35:09 +02:00
Andrea Busi 093b6471e8 release: Increase version for release 2023-07-27 17:06:41 +02:00
Andrea Busi 8ca814561b fix: Improve colors in home 2023-07-27 17:06:33 +02:00
Andrea Busi 359667b659 release: Increase version for release 2023-07-24 17:31:39 +02:00
Andrea Busi 468659ee9f refactor: Align card colors with Android app 2023-07-24 17:30:28 +02:00
Andrea Busi ff50abd58a fix: Solve glitch in allerte table 2023-07-24 17:20:27 +02:00
Andrea Busi 4770578ae3 release: Increase version for release 2023-07-21 08:08:55 +02:00
Andrea Busi f0fe102901 feat: Use intensity color for alert card 2023-07-21 08:08:55 +02:00
Andrea Busi 9dacb33736 release: Increase version for release 2023-07-14 17:26:16 +02:00
Andrea Busi aed78e44cd feat: Add background animation in realtime alert 2023-07-14 16:59:58 +02:00
Andrea Busi e531088c86 feat: Add helper class for debug purpose 2023-07-14 16:17:26 +02:00
Andrea Busi 7e0112bf94 refactor: Use new extension method to evaluate difference between dates 2023-07-14 16:17:26 +02:00
Andrea Busi 5d12a86cfe refactor: Move alert expiration login inside new EQNRealtimePushNotification class 2023-07-14 16:17:26 +02:00
Andrea Busi 09e7786e65 feat: Handle realtime alert notification message based on intensity value 2023-07-14 16:17:26 +02:00
Andrea Busi 8671533e91 feat: Store lastLocation in appGroup instead of app user defaults 2023-07-14 16:17:15 +02:00
Andrea Busi 9bd94def0f refactor: Migrate UserDefaults key for app migration 2023-07-14 16:17:15 +02:00
Andrea Busi 29c325b7e2 refactor: Migrate UserDefaults key for user data informations 2023-07-14 16:17:15 +02:00
Andrea Busi 8366f2eabb refactor: Use new UserDefaults constants in NotificationService 2023-07-14 16:17:15 +02:00
Andrea Busi 7e26fee45b refactor: Migrate some UserDefaults from Macro to new constants 2023-07-14 16:17:15 +02:00
Andrea Busi 76d551b847 refactor: Create Swift constants file for UserDefaults 2023-07-14 16:17:15 +02:00
Andrea Busi 8f55553759 refactor: Print notification payload 2023-07-14 16:17:15 +02:00
Andrea Busi b44a0a2e27 refactor: Create a model to handle realtime push notification 2023-07-14 16:17:15 +02:00
Andrea Busi 2b98a4e292 dependency: Update Pods 2023-07-13 17:20:56 +02:00
Andrea Busi 44a27536ad refactor: Remove PurchasePro controller 2023-05-19 17:07:12 +02:00
Andrea Busi 76dcabdc5e fix: Use localized name for time zone in position cell 2023-05-19 17:07:03 +02:00
Andrea Busi 2405be895c refactor: Remove radius and intensity filters for real time notifications
Resolves: https://gitlab.steamware.net/eqn/eqn.ios/-/issues/61
2023-05-19 17:06:54 +02:00
Andrea Busi 89ca785864 refactor: Remove unused logic for Do not disturb notifications 2023-05-19 17:06:32 +02:00
Andrea Busi 60678d0839 refactor: Use proper way to manage plural in localized strings 2023-05-18 12:02:01 +02:00
Andrea Busi 88b36a501d refactor: Align strings with Android app and some reorder 2023-05-18 12:01:26 +02:00
Andrea Busi c5b4448830 refactor: Let Xcode reorganize storyboard 2023-05-18 12:00:43 +02:00
Andrea Busi 58b8960e21 refactor: Remove unused "Convert to pro" cell in allerts 2023-05-18 12:00:43 +02:00
Andrea Busi 49d210eca1 refactor: Remove unused strings 2023-05-12 10:46:14 +02:00
Andrea Busi 068e457297 release: Increase version for release 2023-03-28 18:10:53 +02:00
Andrea Busi 9796a40e0e release: Update changelg 2023-03-28 18:09:40 +02:00
Andrea Busi 01e8996572 refactor: Use new request parameters for network downloads endpoint 2023-03-28 18:09:40 +02:00
Andrea Busi 19c6b3d642 refactor: Move selected seismis networks management to EQNUserData 2023-03-28 18:09:40 +02:00
Andrea Busi 0c63a59f19 feat: Add UOA as seismic network 2023-03-28 18:09:40 +02:00
Andrea Busi f0c56584b8 chore: Update gitignore 2023-03-28 18:09:40 +02:00
Andrea Busi e9961af792 fix: Try to improve ObjC-Swift interoperability 2023-03-24 09:54:39 +01:00
Andrea Busi 806b4b67bf feat: Migrate to new network downloads endpoint and remove weather feature 2023-03-24 09:54:27 +01:00
Andrea Busi 851ece0a3b feat: Migrate smartphone download and subscription counter to new cached endpoints 2023-03-24 09:53:10 +01:00
Andrea Busi 0a76768f88 feat: Migrate user report endpoint to cache 2023-03-24 09:52:21 +01:00
Andrea Busi b15efe83e0 dependency: Rework Firebase configuration, as per official documentation 2023-03-21 18:06:18 +01:00
Andrea Busi d84dc8657a dependency: Update Shogun 2023-03-21 17:58:07 +01:00
Andrea Busi bac5e909bb dependency: Update Pods 2023-03-21 17:55:22 +01:00
Andrea Busi 15088b744f release: Increase version for release 2022-12-30 00:18:07 +01:00
Andrea Busi ac03a0cccb refactor: Update translations for tracking permission 2022-12-30 00:17:57 +01:00
Andrea Busi 76a26e3100 release: Increase version for release 2022-12-07 17:56:52 +01:00
Andrea Busi cac6ed67ac feat: Configure AppTracking and Facebook required flags 2022-12-07 09:26:37 +01:00
Andrea Busi 094c682dbd refactor: Reorganize Firebase configuration 2022-12-07 09:26:03 +01:00
Andrea Busi c44f46ca46 refactor: Reorganize push notification configuration 2022-12-07 09:25:38 +01:00
Andrea Busi 5e8c3d0796 release: Increase version fo release 2022-12-06 09:12:53 +01:00
Andrea Busi 61ce27ed4b feat: Enable Facebook SDK in app 2022-12-02 17:33:42 +01:00
Andrea Busi 3aea60e560 dependency: Add Facebook SDK 2022-12-02 15:18:18 +01:00
Andrea Busi 84b61fd7e2 release: Increase version for release 2022-11-28 22:21:51 +01:00
Andrea Busi a5a8c6f5c5 fix: Solve missing critical alerts after notification service migration 2022-11-28 10:33:30 +01:00
Andrea Busi 61587a0341 release: Increase version for release 2022-11-24 11:08:22 +01:00
Andrea Busi a56a04a4ad refactor: Recreate NotificationService (in Swift) to solve unpredictable behaviour
Resolves: https://gitlab.steamware.net/eqn/eqn.ios/-/issues/54
2022-11-24 11:08:22 +01:00
Andrea Busi 7173dc7031 dependency: Update Shogun 2022-11-24 10:52:39 +01:00
Andrea Busi 35dbdbab28 fix: Reduce space 2022-11-18 10:57:08 +01:00
Andrea Busi 14614267d4 feat: Add color in user report total 2022-11-18 10:50:24 +01:00
Andrea Busi 32833010ed dependency: Update Shogun 2022-11-18 10:50:08 +01:00
Andrea Busi ecc77e9f2b fix: Solve runtime warning 2022-11-18 08:34:41 +01:00
Andrea Busi e69747f133 refactor: Migrate preference to AppPreferences class 2022-11-18 08:33:35 +01:00
Andrea Busi 09685fd4a7 feat: Store user selection for compact/expanded view in suer report 2022-11-17 22:21:59 +01:00
Andrea Busi 3e122240dc refactor: Rework image creation 2022-11-17 22:13:18 +01:00
Andrea Busi 95a214403b release: Increase version for release 2022-11-17 15:44:07 +01:00
Andrea Busi a7db3c6fa1 docs: Add some example payloads 2022-11-17 15:42:32 +01:00
Andrea Busi 4805c79ed6 refactor: Merge icons and colors in a single asset, to solve issue with notification extensions
For some reasons, notification extension doesn't support multiple xcasset
2022-11-17 15:42:32 +01:00
Andrea Busi c6ec20e180 refactor: Migrate some extensions usage to Shogun 2022-11-17 15:42:32 +01:00
Andrea Busi f6dfd4a761 dependency: Add Shogun library 2022-11-17 15:42:32 +01:00
Andrea Busi 0212d0a15f fix: Minor UI tweaks in user reports map 2022-11-17 15:42:32 +01:00
Andrea Busi 3176bde5ed release: Increase version for release 2022-11-17 15:42:32 +01:00
Andrea Busi dfdbbd0ed4 feat: Update new user report data in notification extension 2022-11-17 15:42:00 +01:00
Andrea Busi e9986e0fe1 feat: Align user report map to Android app 2022-11-15 22:47:42 +01:00
Andrea Busi beb264f95e refactor: Move create snapshot to an extension method 2022-11-15 22:41:04 +01:00
Andrea Busi 217b5edbcf fix: Properly evaluate if ads is visible 2022-11-15 22:37:32 +01:00
Andrea Busi e13f95aa5d feat: Add color assets for user report 2022-11-15 12:28:54 +01:00
Andrea Busi fc5bdbcc92 fix: Solve issue with new registration logic 2022-11-15 12:28:54 +01:00
Andrea Busi d0ee637449 refactor: Remove send comment in manual report 2022-11-15 12:28:54 +01:00
Andrea Busi 66e9d7035e fix: Increase size for new report earthquake section buttons 2022-11-15 12:28:34 +01:00
Andrea Busi 7db48e381f release: Update changelog 2022-11-15 12:28:34 +01:00
Andrea Busi f9bdb84ad9 feat: Align 'last 24h user reports' card to Android 2022-11-11 15:16:16 +01:00
Andrea Busi bee18f6407 refactor: Use existing category for dictionary parsing 2022-11-11 15:16:16 +01:00
Andrea Busi 96f3a44db4 feat: Reset Firebase token to force a new server registration 2022-11-11 15:16:16 +01:00
Andrea Busi e394259f24 feat: New report earthquake section
Resolves: https://gitlab.steamware.net/eqn/eqn.ios/-/issues/51
2022-11-11 15:16:13 +01:00
Andrea Busi 3b53350969 refactor: Store lastLocation in EQNUserData 2022-11-11 15:15:47 +01:00
Andrea Busi f3c3c19e39 refactor: Store userId in EQNUserData 2022-11-11 15:15:47 +01:00
Andrea Busi 8404d72d8f refactor: Rework user registration to solve double registration
Resolves: https://gitlab.steamware.net/eqn/eqn.ios/-/issues/50
2022-11-11 15:15:40 +01:00
Andrea Busi a3b0499ed3 dependency: Update Pods 2022-10-28 12:21:29 +02:00
Andrea Busi d923b37fbd refactor: Update to Xcode 14.1 recommended settings 2022-10-28 12:12:26 +02:00
Andrea Busi 282803cf98 release: Increase version for release 2022-07-18 12:54:07 +02:00
Andrea Busi 039e7b82a1 fix: Don't show debug view when tap 5 times on Settings 2022-07-18 12:53:57 +02:00
Andrea Busi b098caf2ef chore: Update push payloads 2022-06-23 11:33:50 +02:00
Andrea Busi 73caf9647c release: Increase version for release 2022-06-22 16:16:24 +02:00
Andrea Busi 8700e200f9 chore: Update push payloads 2022-06-22 16:15:50 +02:00
Andrea Busi e99845ff1b fix: Add missing check for null alert 2022-06-22 09:27:47 +02:00
Andrea Busi a0161e8f4c release: Increase version for release 2022-06-17 14:42:47 +02:00
Andrea Busi 1b9944a7ca fix: Solve retain cycle and crashes 2022-06-17 14:42:47 +02:00
Andrea Busi 4c00e4ef6a refactor: Review time to show full screen view or card only 2022-06-17 14:42:47 +02:00
Andrea Busi 63592e6cfb refactor: Create a model for the realtime alert 2022-06-17 14:42:47 +02:00
Andrea Busi 2877dff23c refactor: Increase card interspace 2022-06-17 10:09:04 +02:00
Andrea Busi f2386a1abb refactor: Don't show countdown in expanded card 2022-06-17 10:09:04 +02:00
Andrea Busi 5e4a500f03 dependency: Update Pods 2022-06-17 10:09:04 +02:00
Andrea Busi 2b8f2db7c5 feat: Add new realtime alert screen 2022-06-17 10:09:04 +02:00
Andrea Busi 11d994696d release: Increase version for release 2022-06-09 22:20:56 +02:00
Andrea Busi cd6e20c1b2 fix: Solve issue with EMSC networks 2022-06-09 22:20:48 +02:00
Andrea Busi af5371571c release: Increase version for release 2022-06-03 17:44:54 +02:00
Andrea Busi 6291b22df0 fix: Add a delay before calling the completion, to let iOS remove already posted notification 2022-06-03 17:44:32 +02:00
Andrea Busi 2a7cfd3079 release: Increase version for release 2022-06-01 18:10:01 +02:00
600 changed files with 10005 additions and 7844 deletions
+3
View File
@@ -1,3 +1,6 @@
# MacOS files
.DS_Store
# Exclude Pods
Sources/Pods
+67
View File
@@ -1,5 +1,72 @@
# Changelog
## Versione 5.8.1
- Corrette traduzioni errate (causavano crash)
- Aggiunto ordinamento in sottoscrizioni utente (prima Top10k)
## Versione 5.8
- Modifica algoritmo filtro lista (issue #70)
- Modifica impostazioni "Notifiche da reti sismiche" (issue #66)
- Modifica invio impostazioni app per notifiche (issue #68)
- Modifica impostazioni "Notifiche segnalazioni utente" (issue #67)
- Modifica tab Reti Sismiche (issue #69)
## Versione 5.7
- Aumentato target ad iOS 13
- Disattivata logica calibrazione/monitoraggio
- Aggiunto invio posizione in background
## Versione 5.6
- Aggiunta lingua araba
## Versione 5.5
- Aggiornata integrazione Firebase
- Migrati alcuni endpooint a cache.earthquakenetwork.it
- Rimossa funzionalità meteo
- Aggiunta rete UOA
- Rimosse stringhe non utilizzate
- Rimosse sezioni non utilizzate (es PRO)
- Aggiunta gestione localizzata plurali
- Rimossi filtri intensità da notifiche in tempo reale
- Rivista gestione notifiche push allerte
- Migrate costanti in nuova struttura Swift
## Versione 5.4
- Aggiunto SDK Facebook
## Versione 5.3.2
- Corretto problema con notifiche critiche
## Versione 5.3.1
- Ricreato progetto NotificationService
- Correzioni minori
## Versione 5.3
- Rivista gestione registrazione utente
- Modifica in segnalazioni utente
- Allineata mappa segnalazioni utente ad Android
## Versione 5.2.1
- Disattivata schermata di debug
## Versione 5.2
- Nuova schermata per allerta in tempo reale
## Versione 5.1
### Build (91)
- Corretto problema con selezioni reti EMSC
### Build (90)
- Rimozione notifiche dello stesso tipo
## Versione 5.0.3
### Build (89)
+34
View File
@@ -0,0 +1,34 @@
{
"magnitude_range" : "0",
"google.c.a.e" : "1",
"provider" : "INGV",
"google.c.fid" : "fFjFx_Em8E-op_zHYXZpSr",
"preliminary" : "0",
"longitude" : "15.2917",
"gcm.message_id" : "1719991146578422",
"latitude" : "40.7738",
"google.c.sender.id" : "899482329945",
"type" : "official",
"magnitude" : "1.4",
"difference" : "13",
"depth" : "14.6",
"aps" : {
"mutable-content" : 1,
"alert" : {
"body" : "Sisma rilevato a",
"title-loc-key" : "Segnalazione da rete sismica",
"title" : "Segnalazione da rete sismica",
"loc-key" : "Sisma rilevato a",
"action-loc-key" : "",
"loc-args" : [
"2 km SW Laviano (SA) - M1.4"
]
},
"content-available" : 1,
"sound" : "default"
},
"data" : "2024-07-03 09:05:52",
"magnitude_type" : "ML",
"place" : "2 km SW Laviano (SA)",
"pop100" : "6824"
}
@@ -0,0 +1,34 @@
{
"Simulator Target Bundle": "com.finazzi.distquake",
"aps": {
"alert": {
"loc-args": [
"2 km da Foligno"
],
"loc-key": "Sisma segnalato da utente a",
"title-loc-key": "Allerta sismica in tempo reale"
},
"category": "notifica_con_mappa",
"content-available": 1,
"mutable-content": 1,
"sound": "alert_star_trek.wav"
},
"counter": 10,
"datetime": "2022-06-22 19:12:00",
"delay": 4,
"detection_latitude": "45.64664",
"detection_longitude": "9.188540",
"gcm.message_id": "1614708857742608",
"google.c.a.e": 1,
"google.c.sender.id": "899482329945",
"magnitude": 20,
"latitude": "42.958",
"location": "2 km da Foligno",
"longitude": "12.702",
"peak": "0.4",
"randcode": 100,
"test": 0,
"type": "manual",
"wave_speed": "4.7",
"critical": true
}
@@ -0,0 +1,34 @@
{
"Simulator Target Bundle": "com.finazzi.distquake",
"aps": {
"alert": {
"loc-args": [
"14 km Al Qunfudhah"
],
"loc-key": "Rilevato sisma forte a",
"title-loc-key": "Allerta sismica in tempo reale"
},
"category": "notifica_con_mappa",
"content-available": 1,
"mutable-content": 1,
"sound": "alert_star_trek.wav"
},
"counter": 10,
"datetime": "2023-08-10 15:57:00",
"delay": 4,
"detection_latitude": "18.65",
"detection_longitude": "42.76",
"gcm.message_id": "1614708857742608",
"google.c.a.e": 1,
"google.c.sender.id": "899482329945",
"intensity": 2,
"latitude": "18.65",
"location": "14 km Al Qunfudhah",
"longitude": "42.76",
"peak": "1.2",
"randcode": 100,
"test": 0,
"type": "eqn",
"wave_speed": "4.7",
"critical": true
}
+10 -9
View File
@@ -3,7 +3,7 @@
"aps": {
"alert": {
"loc-args": [
"150 km (Test)"
"15 χλμ από το Αίγιο"
],
"loc-key": "Rilevato sisma forte a",
"title-loc-key": "Allerta sismica in tempo reale"
@@ -14,20 +14,21 @@
"sound": "alert_star_trek.wav"
},
"counter": 10,
"datetime": "2021-04-15 21:34:50",
"datetime": "2022-06-23 10:10:00",
"delay": 4,
"detection_latitude": "37.983810",
"detection_longitude": "23.727539",
"gcm.message_id": "1614708857742608",
"google.c.a.e": 1,
"google.c.sender.id": "899482329945",
"intensity": 2,
"latitude": "37.683810",
"location": "150 km (Test)",
"longitude": "23.327539",
"peak": "-1",
"randcode": 0,
"test": 1,
"latitude": "38.19",
"location": "15 χλμ από το Αίγιο",
"longitude": "22.26",
"peak": "0.4",
"randcode": 100,
"test": 0,
"type": "eqn",
"wave_speed": "4.7",
"critical": true
}
}
+9 -8
View File
@@ -3,7 +3,7 @@
"aps": {
"alert": {
"loc-args": [
"150 km (Test)"
"14 km from California City"
],
"loc-key": "Rilevato sisma forte a",
"title-loc-key": "Allerta sismica in tempo reale"
@@ -14,19 +14,20 @@
"sound": "alert_star_trek.wav"
},
"counter": 10,
"datetime": "2021-04-16 12:41:20",
"datetime": "2022-06-23 07:55:00",
"delay": 4,
"detection_latitude": "37.3229978",
"detection_longitude": "-122.0321823",
"gcm.message_id": "1614708857742608",
"google.c.a.e": 1,
"google.c.sender.id": "899482329945",
"intensity": 2,
"latitude": "37.9229978",
"location": "150 km (Test)",
"longitude": "-121.0321823",
"peak": "-1",
"randcode": 0,
"test": 1,
"latitude": "35.15",
"location": "14 km from California City",
"longitude": "-117.78",
"peak": "0.4",
"randcode": 100,
"test": 0,
"type": "eqn",
"wave_speed": "4.7",
"critical": true
+9 -8
View File
@@ -3,7 +3,7 @@
"aps": {
"alert": {
"loc-args": [
"150 km (Test)"
"5 km de Las Cujas"
],
"loc-key": "Rilevato sisma forte a",
"title-loc-key": "Allerta sismica in tempo reale"
@@ -14,19 +14,20 @@
"sound": "alert_star_trek.wav"
},
"counter": 10,
"datetime": "2021-04-15 20:39:00",
"datetime": "2022-06-23 10:19:00",
"delay": 4,
"detection_latitude": "-34.603722",
"detection_longitude": "-58.381592",
"gcm.message_id": "1614708857742608",
"google.c.a.e": 1,
"google.c.sender.id": "899482329945",
"intensity": 2,
"latitude": "-34.103722",
"location": "150 km (Test)",
"longitude": "-58.781592",
"peak": "-1",
"randcode": 0,
"test": 1,
"latitude": "-32.57",
"location": "5 km de Las Cujas",
"longitude": "-71.46",
"peak": "0.4",
"randcode": 100,
"test": 0,
"type": "eqn",
"wave_speed": "4.7",
"critical": true
+9 -8
View File
@@ -3,7 +3,7 @@
"aps": {
"alert": {
"loc-args": [
"150 km (Test)"
"15 km de Dieppe"
],
"loc-key": "Rilevato sisma forte a",
"title-loc-key": "Allerta sismica in tempo reale"
@@ -14,19 +14,20 @@
"sound": "alert_star_trek.wav"
},
"counter": 10,
"datetime": "2021-04-15 21:37:40",
"datetime": "2022-06-23 10:07:00",
"delay": 4,
"detection_latitude": "48.856614",
"detection_longitude": "2.8522219",
"gcm.message_id": "1614708857742608",
"google.c.a.e": 1,
"google.c.sender.id": "899482329945",
"intensity": 2,
"latitude": "48.456614",
"location": "150 km (Test)",
"longitude": "2.3522219",
"peak": "-1",
"randcode": 0,
"test": 1,
"latitude": "49.77",
"location": "15 km de Dieppe",
"longitude": "1.05",
"peak": "0.4",
"randcode": 100,
"test": 0,
"type": "eqn",
"wave_speed": "4.7",
"critical": true
+9 -8
View File
@@ -3,7 +3,7 @@
"aps": {
"alert": {
"loc-args": [
"150 km (Test)"
"5 km od Novog"
],
"loc-key": "Rilevato sisma forte a",
"title-loc-key": "Allerta sismica in tempo reale"
@@ -14,19 +14,20 @@
"sound": "alert_star_trek.wav"
},
"counter": 10,
"datetime": "2021-04-15 21:39:50",
"datetime": "2022-06-23 10:02:00",
"delay": 4,
"detection_latitude": "45.813177",
"detection_longitude": "15.977048",
"gcm.message_id": "1614708857742608",
"google.c.a.e": 1,
"google.c.sender.id": "899482329945",
"intensity": 2,
"latitude": "45.413177",
"location": "150 km (Test)",
"longitude": "15.677048",
"peak": "-1",
"randcode": 0,
"test": 1,
"latitude": "45.09",
"location": "5 km od Novog",
"longitude": "14.87",
"peak": "0.4",
"randcode": 100,
"test": 0,
"type": "eqn",
"wave_speed": "4.7",
"critical": true
+9 -8
View File
@@ -3,7 +3,7 @@
"aps": {
"alert": {
"loc-args": [
"150 km (Test)"
"35 km dari Sindanbarang"
],
"loc-key": "Rilevato sisma forte a",
"title-loc-key": "Allerta sismica in tempo reale"
@@ -14,19 +14,20 @@
"sound": "alert_star_trek.wav"
},
"counter": 10,
"datetime": "2021-04-15 21:42:40",
"datetime": "2022-06-23 10:13:00",
"delay": 4,
"detection_latitude": "-2.548926",
"detection_longitude": "118.0148634",
"gcm.message_id": "1614708857742608",
"google.c.a.e": 1,
"google.c.sender.id": "899482329945",
"intensity": 2,
"latitude": "-2.948926",
"location": "150 km (Test)",
"longitude": "118.6148634",
"peak": "-1",
"randcode": 0,
"test": 1,
"latitude": "-7.38",
"location": "35 km dari Sindanbarang",
"longitude": "106.83",
"peak": "0.4",
"randcode": 100,
"test": 0,
"type": "eqn",
"wave_speed": "4.7",
"critical": true
+9 -8
View File
@@ -3,7 +3,7 @@
"aps": {
"alert": {
"loc-args": [
"150 km (Test)"
"2 km da Foligno"
],
"loc-key": "Rilevato sisma forte a",
"title-loc-key": "Allerta sismica in tempo reale"
@@ -14,19 +14,20 @@
"sound": "alert_star_trek.wav"
},
"counter": 10,
"datetime": "2021-04-15 20:32:20",
"datetime": "2022-06-22 19:12:00",
"delay": 4,
"detection_latitude": "45.64664",
"detection_longitude": "9.188540",
"gcm.message_id": "1614708857742608",
"google.c.a.e": 1,
"google.c.sender.id": "899482329945",
"intensity": 2,
"latitude": "45.164664",
"location": "150 km (Test)",
"longitude": "9.788540",
"peak": "-1",
"randcode": 0,
"test": 1,
"latitude": "42.958",
"location": "2 km da Foligno",
"longitude": "12.702",
"peak": "0.4",
"randcode": 100,
"test": 0,
"type": "eqn",
"wave_speed": "4.7",
"critical": true
+9 -8
View File
@@ -3,7 +3,7 @@
"aps": {
"alert": {
"loc-args": [
"150 km (Test)"
"45 km de Puebla"
],
"loc-key": "Rilevato sisma forte a",
"title-loc-key": "Allerta sismica in tempo reale"
@@ -14,19 +14,20 @@
"sound": "alert_star_trek.wav"
},
"counter": 10,
"datetime": "2021-04-16 12:39:40",
"datetime": "2022-06-23 10:16:00",
"delay": 4,
"detection_latitude": "19.432608",
"detection_longitude": "-99.133209",
"gcm.message_id": "1614708857742608",
"google.c.a.e": 1,
"google.c.sender.id": "899482329945",
"intensity": 2,
"latitude": "19.932608",
"location": "150 km (Test)",
"longitude": "-98.033209",
"peak": "-1",
"randcode": 0,
"test": 1,
"latitude": "18.72",
"location": "45 km de Puebla",
"longitude": "-97.90",
"peak": "0.4",
"randcode": 100,
"test": 0,
"type": "eqn",
"wave_speed": "4.7",
"critical": true
+10 -9
View File
@@ -3,7 +3,7 @@
"aps": {
"alert": {
"loc-args": [
"150 km (Test)"
"Bandırma'ya 25 km"
],
"loc-key": "Rilevato sisma forte a",
"title-loc-key": "Allerta sismica in tempo reale"
@@ -14,20 +14,21 @@
"sound": "alert_star_trek.wav"
},
"counter": 10,
"datetime": "2021-04-15 21:44:30",
"datetime": "2022-06-23 10:22:00",
"delay": 4,
"detection_latitude": "41.008238",
"detection_longitude": "28.978359",
"gcm.message_id": "1614708857742608",
"google.c.a.e": 1,
"google.c.sender.id": "899482329945",
"intensity": 2,
"latitude": "41.608238",
"location": "150 km (Test)",
"longitude": "28.678359",
"peak": "-1",
"randcode": 0,
"test": 1,
"latitude": "40.33",
"location": "Bandırma'ya 25 km",
"longitude": "27.66",
"peak": "0.4",
"randcode": 100,
"test": 0,
"type": "eqn",
"wave_speed": "4.7",
"critical": true
}
}
@@ -41,6 +41,7 @@
[super viewDidLoad];
[self.mapView registerClass:[EQNCustomAnnotationView class] forAnnotationViewWithReuseIdentifier:EQNCustomAnnotationView.SingleLineIdentifier];
[self.mapView registerClass:[EQNCustomAnnotationView class] forAnnotationViewWithReuseIdentifier:EQNCustomAnnotationView.SmallIdentifier];
}
- (void)didReceiveNotification:(UNNotification *)notification
@@ -67,9 +68,8 @@
[self.mapView addAnnotation:annotation];
} else if ([userInfo[@"type"] isEqualToString:@"manual"]){
EQNMapAnnotationUserReport *annotation = [[EQNMapAnnotationUserReport alloc] initWithTitle:@""
coordinate:coordinate.coordinate
magnitude:[userInfo[@"magnitudo"] intValue]];
EQNMapAnnotationUserReport *annotation = [[EQNMapAnnotationUserReport alloc] initWithMagnitude:[userInfo[@"magnitude"] intValue]
coordinate:coordinate.coordinate];
[self.mapView addAnnotation:annotation];
}
@@ -94,7 +94,7 @@
NSTimeInterval difference = MAX([self.userSeismicTimestamp timeIntervalSinceDate:now], 0);
NSInteger seconds = (int)lround(difference);
self.waveLabel.text = [NSString stringWithFormat:NSLocalizedString(@"alert_wave", @""), seconds];
self.waveLabel.text = [NSString localizedStringWithFormat:NSLocalizedString(@"alert_wave", @""), seconds];
if (difference <= 0) {
// stop the countdown
@@ -118,9 +118,9 @@
} else if ([annotation isKindOfClass:[EQNMapAnnotationUserReport class]]) {
EQNMapAnnotationUserReport *report = (EQNMapAnnotationUserReport *)annotation;
EQNCustomAnnotationView *annotationView = (EQNCustomAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:EQNCustomAnnotationView.SingleLineIdentifier];
annotationView.image = report.image;
annotationView.title = report.title;
EQNCustomAnnotationView *annotationView = (EQNCustomAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:EQNCustomAnnotationView.SmallIdentifier];
annotationView.image = [report imageWithHeight:EQNCustomAnnotationView.SmallViewImageHeight];
annotationView.title = report.timeDifference;
return annotationView;
}
return nil;
@@ -1,5 +0,0 @@
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//
#import "NotificationService.h"
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

+1 -24
View File
@@ -2,35 +2,12 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>EQNNotificationService</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.usernotifications.service</string>
<key>NSExtensionPrincipalClass</key>
<string>NotificationService</string>
<string>$(PRODUCT_MODULE_NAME).NotificationService</string>
</dict>
</dict>
</plist>
@@ -1,42 +0,0 @@
//
// NotificationService+Extension.swift
// EQNNotificationService
//
// Created by Andrea Busi on 28/05/22.
// Copyright © 2022 Earthquake Network. All rights reserved.
//
import Foundation
import UserNotifications
extension NotificationService {
@objc(removeNotificationsForType:completion:)
func removeNotifications(
for type: String?,
completion: @escaping() -> Void
) {
guard let type = type else {
completion()
return
}
let notificationCenter = UNUserNotificationCenter.current()
notificationCenter.getDeliveredNotifications { notifications in
let sameTypeNotifications = notifications.filter { notification in
let payload = notification.request.content.userInfo
if let notificationType = payload["type"] as? String {
return notificationType == type
}
return false
}
let identifiers = sameTypeNotifications.map { $0.request.identifier }
notificationCenter.removeDeliveredNotifications(withIdentifiers: identifiers)
completion()
}
}
}
@@ -1,13 +0,0 @@
//
// NotificationService.h
// EQNNotificationService
//
// Refactored by Andrea Busi
// Copyright © 2020 Earthquake Network. All rights reserved.
//
#import <UserNotifications/UserNotifications.h>
@interface NotificationService : UNNotificationServiceExtension
@end
@@ -1,181 +0,0 @@
//
// NotificationService.m
// EQNNotificationService
//
// Refactored by Andrea Busi
// Copyright © 2020 Earthquake Network. All rights reserved.
//
#import "NotificationService.h"
#import "EQNAllertaSismica.h"
#import "Costanti.h"
#import "EQNNotificationService-Swift.h"
@interface NotificationService ()
@property (nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver);
@property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent;
@end
@implementation NotificationService
static NSString * const EQNSoundNotificationEQN = @"alert_sound.wav";
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent *_Nonnull))contentHandler
{
self.contentHandler = contentHandler;
self.bestAttemptContent = [request.content mutableCopy];
// Configure the notification's payload.
UNMutableNotificationContent* content = [[UNMutableNotificationContent alloc] init];
content.title = [NSString localizedUserNotificationStringForKey:request.content.title arguments:nil];
content.body = [NSString localizedUserNotificationStringForKey:request.content.body arguments:nil];
self.bestAttemptContent.title = content.title;
self.bestAttemptContent.body = content.body;
// check for required informations
NSDictionary *userInfo = request.content.userInfo;
NSString *notificationType = [userInfo objectForKey:@"type"];
if (userInfo == nil || notificationType == nil) {
[self contentComplete];
return;
}
NSString *iconName = @"";
if ([notificationType isEqualToString:@"eqn"]) {
// check if user has enabled critical alerts
NSUserDefaults *sharedDefaults = [[NSUserDefaults alloc] initWithSuiteName:EQNUserDefaultAppGroupSuite];
BOOL criticalAlertsEnabled = [sharedDefaults boolForKey:NOTIFICHE_ALLERA_SISMICA_CRITICAL_ALERTS];
// !!WORKAROUND
// this is a workaround to use critical alerts with legacy FCM api
// when the server implementation will be migrated to Firebase v1 APIs, this could be removed
BOOL isCritical = [[userInfo objectForKey:@"critical"] boolValue];
if (isCritical && criticalAlertsEnabled) {
self.bestAttemptContent.sound = [UNNotificationSound criticalSoundNamed:EQNSoundNotificationEQN withAudioVolume:1.0];
} else {
self.bestAttemptContent.sound = [UNNotificationSound soundNamed:EQNSoundNotificationEQN];
}
NSString *intensity = [userInfo objectForKey:@"intensity"];
switch ([intensity intValue]) {
case 0:
iconName = @"star_realtime_white.png";
break;
case 1:
iconName = @"star_realtime_lightblue.png";
break;
case 2:
iconName = @"star_realtime_blue.png";
break;
default:
break;
}
} else if ([notificationType isEqualToString:@"manual"]) {
NSString *intensity = [userInfo objectForKey:@"magnitude"];
switch ([intensity intValue]) {
case 0:
iconName = @"star_green.png";
break;
case 1:
iconName = @"star_yellow.png";
break;
case 2:
iconName = @"star_red.png";
break;
default:
break;
}
} else if ([notificationType isEqualToString:@"official"]) {
NSString *provaider = [userInfo objectForKey:@"provider"];
double intensity = [[userInfo objectForKey:@"magnitude"] doubleValue];
NSString *colore = @"";
if (intensity < 2.0) {
colore = @"_white";
} else if (intensity < 3.5) {
colore = @"_green";
} else if (intensity < 4.5) {
colore = @"_yellow";
} else if (intensity < 5.5) {
colore = @"_red";
} else {
colore = @"_purple";
}
if ([provaider isEqualToString:@"USGS"]) {
iconName = [NSString stringWithFormat:@"star%@.png", colore];
} else if ([provaider isEqualToString:@"SGC"]) {
iconName = [NSString stringWithFormat:@"star3%@.png", colore];
} else if ([provaider isEqualToString:@"CSN"]) {
iconName = [NSString stringWithFormat:@"star3f%@.png", colore];
} else if ([provaider isEqualToString:@"SSN"]) {
iconName = [NSString stringWithFormat:@"star4%@.png", colore];
} else if ([provaider isEqualToString:@"INPRES"]) {
iconName = [NSString stringWithFormat:@"star4r%@.png", colore];
} else if ([provaider isEqualToString:@"FUNVISIS"]) {
iconName = [NSString stringWithFormat:@"star6%@.png", colore];
} else if ([provaider isEqualToString:@"Ineter"]) {
iconName = [NSString stringWithFormat:@"triangle%@.png", colore];
} else if ([provaider isEqualToString:@"RSN"]) {
iconName = [NSString stringWithFormat:@"triangle2%@.png", colore];
} else if ([provaider isEqualToString:@"PHIVOLCS"]) {
iconName = [NSString stringWithFormat:@"triround_inner%@.png", colore];
} else if ([provaider isEqualToString:@"IGEPN"]) {
iconName = [NSString stringWithFormat:@"triround%@.png", colore];
} else if ([provaider isEqualToString:@"INGV"]) {
iconName = [NSString stringWithFormat:@"circle%@.png", colore];
} else if ([provaider isEqualToString:@"EMSC"]) {
iconName = [NSString stringWithFormat:@"dyamond%@.png", colore];
} else if ([provaider isEqualToString:@"IGP"]) {
iconName = [NSString stringWithFormat:@"dyamond_round%@.png", colore];
} else if ([provaider isEqualToString:@"JMA"]) {
iconName = [NSString stringWithFormat:@"esa%@.png", colore];
} else if ([provaider isEqualToString:@"GEONET"]) {
iconName = [NSString stringWithFormat:@"oct%@.png", colore];
} if ([provaider isEqualToString:@"CSI"]) {
iconName = [NSString stringWithFormat:@"penta%@.png", colore];
} else if ([provaider isEqualToString:@"IGN"]) {
iconName = [NSString stringWithFormat:@"square%@.png", colore];
} else if ([provaider isEqualToString:@"UASD"] || [provaider isEqualToString:@"BDTIM"] || [provaider isEqualToString:@"NCS"]) {
iconName = [NSString stringWithFormat:@"thick_star%@.png", colore];
} else if ([provaider isEqualToString:@"RSPR"]) {
iconName = [NSString stringWithFormat:@"star6f%@.png", colore];
}
}
if (![iconName isEqualToString:@""]) {
iconName = [iconName stringByReplacingOccurrencesOfString:@".png" withString:@""];
NSURL *imageUrl = [NSBundle.mainBundle URLForResource:iconName withExtension:@"png"];
NSError *attachmentError = nil;
UNNotificationAttachment *attachment = [UNNotificationAttachment attachmentWithIdentifier:iconName
URL:imageUrl
options:nil
error:&attachmentError];
if (!attachmentError) {
self.bestAttemptContent.attachments = @[attachment];
}
}
// remove same type posted notification
[self removeNotificationsForType:notificationType completion:^{
[self contentComplete];
}];;
}
- (void)serviceExtensionTimeWillExpire
{
// Called just before the extension will be terminated by the system.
// Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
[self contentComplete];
}
- (void)contentComplete
{
self.contentHandler(self.bestAttemptContent);
}
@end
@@ -0,0 +1,197 @@
//
// NotificationService.swift
// EQNNotificationService
//
// Created by Andrea Busi on 24/11/22.
// Copyright © 2022 Earthquake Network. All rights reserved.
//
import UserNotifications
import CoreLocation
import Shogun
class NotificationService: UNNotificationServiceExtension {
private static let EQNSoundNotification = UNNotificationSoundName("alert_sound.wav")
var contentHandler: ((UNNotificationContent) -> Void)?
var bestAttemptContent: UNMutableNotificationContent?
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
self.contentHandler = contentHandler
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
guard let bestAttemptContent = bestAttemptContent else {
print("[NotificationService] Unable to get notification content")
contentComplete()
return
}
let userInfo = request.content.userInfo
guard let notificationType = userInfo.string(forKey: "type") else {
print("[NotificationService] Unable to get notification type")
contentComplete()
return
}
var iconName = ""
switch notificationType.lowercased() {
case "eqn":
// check if user has enabled critical alerts
let criticalAlertsEnabled = UserDefaults.appGroup?.bool(forKey: UserDefaults.AllertaSismicaCriticalAlerts) ?? false
// !!WORKAROUND
// this is a workaround to use critical alerts with legacy FCM api
// when the server implementation will be migrated to Firebase v1 APIs, this could be removed
let isCritical = userInfo.integer(forKey: "critical", orDefault: 0) == 1
if isCritical && criticalAlertsEnabled {
bestAttemptContent.sound = UNNotificationSound.criticalSoundNamed(Self.EQNSoundNotification)
} else {
bestAttemptContent.sound = UNNotificationSound(named: Self.EQNSoundNotification)
}
let intensity = userInfo.integer(forKey: "intensity")
switch intensity {
case 0:
iconName = "star_realtime_white.png"
case 1:
iconName = "star_realtime_lightblue.png"
case 2:
iconName = "star_realtime_blue.png"
default:
break
}
// evaluate intensity and get proper string to display
guard let latitude = userInfo.double(forKey: "latitude"),
let longitude = userInfo.double(forKey: "longitude"),
let peak = userInfo.double(forKey: "peak") else {
print("[NotificationService] Unable to get base info for intensity calculation")
return
}
let location = CLLocation(latitude: latitude, longitude: longitude)
guard let distance = EQNUserData.shared.lastLocation?.distance(from: location) else {
print("[NotificationService] Unable to calculate distance or get last location")
return
}
let distanceKm = distance / 1_000
let intensita = peak * exp(-distanceKm/peak/250)
let stringSuffix: String
if intensita < 0.004 {
stringSuffix = "no_shaking"
} else if intensita < 0.30 {
stringSuffix = "mild"
} else if intensita < 0.70 {
stringSuffix = "moderate"
} else {
stringSuffix = "strong"
}
bestAttemptContent.body = "alert_intensity_\(stringSuffix)".localized
case "manual":
// there are 12 levels, so a customized icon doesn't make sense
// use a generic warning icon instead
iconName = "warning_yellow.png"
case "official":
// don't show any images
break
default:
break
}
// add the icon as notification attachment
if !iconName.isEmpty {
iconName = iconName.replacingOccurrences(of: ".png", with: "")
if let imageUrl = Bundle(for: NotificationService.self).url(forResource: iconName, withExtension: "png"),
let attachment = try? UNNotificationAttachment(identifier: iconName, url: imageUrl) {
bestAttemptContent.attachments = [ attachment ]
}
}
// remove same type posted notification
removeNotifications(for: notificationType) { [weak self] in
self?.contentComplete()
}
}
override func serviceExtensionTimeWillExpire() {
// Called just before the extension will be terminated by the system.
// Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
contentHandler(bestAttemptContent)
}
}
// MARK: - Private
private func contentComplete() {
if let bestAttemptContent {
contentHandler?(bestAttemptContent)
}
}
private func removeNotifications(
for type: String?,
completion: @escaping() -> Void
) {
guard let type = type else {
completion()
return
}
let notificationCenter = UNUserNotificationCenter.current()
notificationCenter.getDeliveredNotifications { notifications in
let sameTypeNotifications = notifications.filter { notification in
let payload = notification.request.content.userInfo
if let notificationType = payload["type"] as? String {
return notificationType == type
}
return false
}
let identifiers = sameTypeNotifications.map { $0.request.identifier }
notificationCenter.removeDeliveredNotifications(withIdentifiers: identifiers)
// !! Note: this is a known issue/bug
// we need to add a delay before invoking the completion, otherwise the notification will not be remved
// ref: https://stackoverflow.com/questions/53697279/why-are-notifications-not-removed-with-removedeliverednotifications
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
completion()
}
}
}
// MARK: - Helpers
private func manualIconName(for provider: String, color: String) -> String {
switch provider.uppercased() {
case "USGS": return "star\(color).png"
case "SGC": return "star3\(color).png"
case "CSN": return "star3f\(color).png"
case "SSN": return "star4\(color).png"
case "INPRES": return "star4r\(color).png"
case "FUNVISIS": return "star6\(color).png"
case "INETER": return "triangle\(color).png"
case "RSN": return "triangle2\(color).png"
case "PHIVOLCS": return "triround_inner\(color).png"
case "IGEPN": return "triround\(color).png"
case "INGV": return "circle\(color).png"
case "EMSC": return "dyamond\(color).png"
case "IGP": return "dyamond_round\(color).png"
case "JMA": return "esa\(color).png"
case "GEONET": return "oct\(color).png"
case "CSI": return "penta\(color).png"
case "IGN": return "square\(color).png"
case "UASD", "BDTIM", "NCS": return "thick_star\(color).png"
case "RSPR": return "star6f\(color).png"
case "UOA": return "triangle\(color).png"
default: return ""
}
}
}
File diff suppressed because it is too large Load Diff
@@ -2,6 +2,6 @@
<Workspace
version = "1.0">
<FileRef
location = "self:Earthquake Network.xcodeproj">
location = "self:">
</FileRef>
</Workspace>
@@ -0,0 +1,177 @@
{
"originHash" : "c29b9b16ee6b4d1a6fec2debc59749097860256c8cbb2addc2abc08d3adba59d",
"pins" : [
{
"identity" : "abseil-cpp-binary",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/abseil-cpp-binary.git",
"state" : {
"revision" : "748c7837511d0e6a507737353af268484e1745e2",
"version" : "1.2024011601.1"
}
},
{
"identity" : "app-check",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/app-check.git",
"state" : {
"revision" : "3b62f154d00019ae29a71e9738800bb6f18b236d",
"version" : "10.19.2"
}
},
{
"identity" : "dznemptydataset",
"kind" : "remoteSourceControl",
"location" : "https://github.com/dzenbot/DZNEmptyDataSet",
"state" : {
"branch" : "master",
"revision" : "9bffa69a83a9fa58a14b3cf43cb6dd8a63774179"
}
},
{
"identity" : "facebook-ios-sdk",
"kind" : "remoteSourceControl",
"location" : "https://github.com/facebook/facebook-ios-sdk",
"state" : {
"revision" : "c19607d535864533523d1f437c84035e5fb101cf",
"version" : "14.1.0"
}
},
{
"identity" : "firebase-ios-sdk",
"kind" : "remoteSourceControl",
"location" : "https://github.com/firebase/firebase-ios-sdk.git",
"state" : {
"revision" : "eca84fd638116dd6adb633b5a3f31cc7befcbb7d",
"version" : "10.29.0"
}
},
{
"identity" : "googleappmeasurement",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleAppMeasurement.git",
"state" : {
"revision" : "fe727587518729046fc1465625b9afd80b5ab361",
"version" : "10.28.0"
}
},
{
"identity" : "googledatatransport",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleDataTransport.git",
"state" : {
"revision" : "a637d318ae7ae246b02d7305121275bc75ed5565",
"version" : "9.4.0"
}
},
{
"identity" : "googleutilities",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleUtilities.git",
"state" : {
"revision" : "57a1d307f42df690fdef2637f3e5b776da02aad6",
"version" : "7.13.3"
}
},
{
"identity" : "grpc-binary",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/grpc-binary.git",
"state" : {
"revision" : "e9fad491d0673bdda7063a0341fb6b47a30c5359",
"version" : "1.62.2"
}
},
{
"identity" : "gtm-session-fetcher",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/gtm-session-fetcher.git",
"state" : {
"revision" : "a2ab612cb980066ee56d90d60d8462992c07f24b",
"version" : "3.5.0"
}
},
{
"identity" : "interop-ios-for-google-sdks",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/interop-ios-for-google-sdks.git",
"state" : {
"revision" : "2d12673670417654f08f5f90fdd62926dc3a2648",
"version" : "100.0.0"
}
},
{
"identity" : "leveldb",
"kind" : "remoteSourceControl",
"location" : "https://github.com/firebase/leveldb.git",
"state" : {
"revision" : "a0bc79961d7be727d258d33d5a6b2f1023270ba1",
"version" : "1.22.5"
}
},
{
"identity" : "nanopb",
"kind" : "remoteSourceControl",
"location" : "https://github.com/firebase/nanopb.git",
"state" : {
"revision" : "b7e1104502eca3a213b46303391ca4d3bc8ddec1",
"version" : "2.30910.0"
}
},
{
"identity" : "promises",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/promises.git",
"state" : {
"revision" : "540318ecedd63d883069ae7f1ed811a2df00b6ac",
"version" : "2.4.0"
}
},
{
"identity" : "shogun",
"kind" : "remoteSourceControl",
"location" : "https://github.com/andreabusi-it/Shogun",
"state" : {
"revision" : "ad890190d6be90f7712c2e56a38ef0937d9f8c1a",
"version" : "1.8.0"
}
},
{
"identity" : "solar",
"kind" : "remoteSourceControl",
"location" : "https://github.com/ceeK/Solar.git",
"state" : {
"revision" : "c2b96f2d5fb7f835b91cefac5e83101f54643901",
"version" : "3.0.1"
}
},
{
"identity" : "swift-package-manager-google-mobile-ads",
"kind" : "remoteSourceControl",
"location" : "https://github.com/googleads/swift-package-manager-google-mobile-ads.git",
"state" : {
"revision" : "9ab66e38f5f0c2d02f2b024b1babd880130f19bf",
"version" : "11.3.0"
}
},
{
"identity" : "swift-package-manager-google-user-messaging-platform",
"kind" : "remoteSourceControl",
"location" : "https://github.com/googleads/swift-package-manager-google-user-messaging-platform.git",
"state" : {
"revision" : "9b68aa69fb508f0274853e226c734151a973c7b7",
"version" : "2.4.0"
}
},
{
"identity" : "swift-protobuf",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-protobuf.git",
"state" : {
"revision" : "9f0c76544701845ad98716f3f6a774a892152bcb",
"version" : "1.26.0"
}
}
],
"version" : 3
}
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1200"
LastUpgradeVersion = "1410"
wasCreatedForAppExtension = "YES"
version = "2.0">
<BuildAction
@@ -74,6 +74,7 @@
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES"
askForAppToLaunch = "Yes"
launchAutomaticallySubstyle = "2">
<BuildableProductRunnable
runnableDebuggingMode = "0">
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1200"
LastUpgradeVersion = "1410"
wasCreatedForAppExtension = "YES"
version = "2.0">
<BuildAction
@@ -74,6 +74,7 @@
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES"
askForAppToLaunch = "Yes"
launchAutomaticallySubstyle = "2">
<BuildableProductRunnable
runnableDebuggingMode = "0">
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1200"
LastUpgradeVersion = "1410"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Earthquake Network.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>
@@ -1,5 +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/>
</plist>
+97 -59
View File
@@ -8,21 +8,19 @@
#import "AppDelegate.h"
#import "Costanti.h"
#import "ServerRequest.h"
#import "EQNGeneratoreURLServer.h"
#import "EQNUser.h"
#import "EQNAccelerometroManager.h"
#import "EQNManager.h"
#import "EQNUtility.h"
#import "EQNAllertaSismica.h"
#import "EQNNotificheSegnalazioniUtente.h"
#import "EQNNotificheReteSismiche.h"
#import "EQNMainTabBarController.h"
#import "NSDictionary+EQNExtensions.h"
@import Firebase;
@import FirebaseCrashlytics;
@import UserNotifications;
@import AppTrackingTransparency;
@import FirebaseCore;
@import FirebaseMessaging;
@import FirebaseCrashlytics;
@import GoogleMobileAds;
@import FBSDKCoreKit;
@interface AppDelegate () <FIRMessagingDelegate, UNUserNotificationCenterDelegate>
@property (strong, nonatomic) NSArray<id<EQNCommandProtocol>> *commands;
@@ -43,29 +41,25 @@
}];
#endif
[EQNUser defaultUser];
[EQNManager defaultManager];
[FIRApp configure];
[FIRMessaging messaging].delegate = self;
// add some generic logs for Crashlytics
NSString *language = [[NSLocale preferredLanguages] firstObject];
[[FIRCrashlytics crashlytics] setCustomValue:language forKey:@"lang"];
// execute commands
EQNStartupCommandsBuilder *builder = [[EQNStartupCommandsBuilder alloc] init];
self.commands = [builder build];
[builder executeWithCommands:self.commands];
[UNUserNotificationCenter currentNotificationCenter].delegate = self;
UNAuthorizationOptions authOptions = UNAuthorizationOptionAlert | UNAuthorizationOptionSound | UNAuthorizationOptionBadge;
[[UNUserNotificationCenter currentNotificationCenter]
requestAuthorizationWithOptions:authOptions
completionHandler:^(BOOL granted, NSError * _Nullable error) {
[self registraNotifica];
}];
[EQNUser defaultUser];
[EQNManager defaultManager];
[self configureFirebase];
[self configureFacebookSDKWithApplication:application andOptions:launchOptions];
// schedule background tasks
[BackgroundTaskManager.shared registerTasks];
[BackgroundTaskManager.shared scheduleUpdateServerPosition];
// add some generic logs for Crashlytics
NSString *language = [[NSLocale preferredLanguages] firstObject];
[[FIRCrashlytics crashlytics] setCustomValue:language forKey:@"lang"];
[self configurePushNotifications];
[application setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];
[application registerForRemoteNotifications];
return YES;
@@ -86,12 +80,14 @@
- (void)applicationDidEnterBackground:(UIApplication *)application {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
[[EQNManager defaultManager] avviaManager];
[[EQNAccelerometroManager sharedInstance] startUpdatingLocationBackground];
NSUInteger counter = [[NSUserDefaults standardUserDefaults] integerForKey:EQNUserDefaultProDiscountOpenCounter];
// disabilitiamo logica calibrazione/monitoraggio perchè attualmente non utilizzata dal server
//[[EQNManager defaultManager] avviaManager];
//[[EQNAccelerometroManager sharedInstance] startUpdatingLocationBackground];
NSUInteger counter = [[NSUserDefaults standardUserDefaults] integerForKey:NSUserDefaults.UserDataProDiscountOpenCounter];
counter += 1;
[[NSUserDefaults standardUserDefaults] setInteger:counter forKey:EQNUserDefaultProDiscountOpenCounter];
[[NSUserDefaults standardUserDefaults] setInteger:counter forKey:NSUserDefaults.UserDataProDiscountOpenCounter];
}
@@ -125,8 +121,8 @@
{
NSString *token = [self stringWithDeviceToken:deviceToken];
NSLog(@"[DEBUG] push token: %@", token);
[[NSUserDefaults standardUserDefaults] setObject:token forKey:EQNUserDefaultPushToken];
[[NSUserDefaults standardUserDefaults] setObject:token forKey:NSUserDefaults.UserDataPushToken];
FIRMessaging.messaging.APNSToken = deviceToken;
}
@@ -149,47 +145,47 @@
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler
{
// execute common logic and refresh the proper tab
NSDictionary *userInfo = notification.request.content.userInfo;
[self handlePushNotificationWithUserInfo:userInfo];
UNNotificationContent *content = notification.request.content;
[self handlePushNotificationWithNotificationContent:content];
// Change this to your preferred presentation option
completionHandler(UNNotificationPresentationOptionAlert | UNNotificationPresentationOptionSound);
completionHandler(UNNotificationPresentationOptionList | UNNotificationPresentationOptionBanner | UNNotificationPresentationOptionSound);
}
// Handle notification messages after display notification is tapped by the user.
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)(void))completionHandler
{
NSLog(@"[DEBUG] push payload\n%@", [response.notification.request.content.userInfo eqn_jsonStringWithPrettyPrint:YES]);
// execute common logic and refresh the proper tab
NSDictionary *userInfo = response.notification.request.content.userInfo;
[self handlePushNotificationWithUserInfo:userInfo];
UNNotificationContent *content = response.notification.request.content;
[self handlePushNotificationWithNotificationContent:content];
completionHandler();
}
- (void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
NSURL *url = [EQNGeneratoreURLServer urlPosizione];
[[ServerRequest defaultServerConnectionSingleton] inviaInformazioniAlServerWithURL:url richiesta:EQNTipoChiamataPosizione success:^(id result) {
completionHandler(UIBackgroundFetchResultNewData);
} failure:^(NSError *error) {
completionHandler(UIBackgroundFetchResultFailed);
}];
}
#pragma mark - Private
- (void)handlePushNotificationWithUserInfo:(NSDictionary *)userInfo
- (void)handlePushNotificationWithNotificationContent:(UNNotificationContent *)content
{
NSString *type = content.userInfo[@"type"];
// Store both original payload and modified title/body
// This will be usefull to avoid to re-evaluate logic for title display.
NSDictionary *notification = @{
@"title": content.title,
@"body": content.body,
@"userInfo": content.userInfo
};
EQNTabBarSection section = EQNTabBarSectionAllerte;
if ([userInfo[@"type"] isEqualToString:@"eqn"]) {
NSDate *dataRicezione = [NSDate date];
[[NSUserDefaults standardUserDefaults] setObject:dataRicezione forKey:NOTIFICHE_RETE_SMARTPHONE_DATA_NOTIFICA];
[EQNUtility storeDictionary:userInfo toUserDefaultForKey:NOTIFICHE_RETE_SMARTPHONE_DIZIONARIO_NOTIFICA];
if ([type isEqualToString:@"eqn"]) {
[EQNRealtimePushNotification storeNotificationWithPayload:notification];
section = EQNTabBarSectionAllerte;
} else if([userInfo[@"type"] isEqualToString:@"manual"]) {
} else if([type isEqualToString:@"manual"]) {
section = EQNTabBarSectionSegnalazioni;
} else if([userInfo[@"type"] isEqualToString:@"official"]) {
} else if([type isEqualToString:@"official"]) {
[EQNOfficialPushNotification storeNotificationWithPayload:notification];
section = EQNTabBarSectionRetiSismiche;
}
@@ -197,19 +193,61 @@
[self.mainTabBarController selectSection:section];
}
#pragma mark - Configurations
- (void)configurePushNotifications
{
[UNUserNotificationCenter currentNotificationCenter].delegate = self;
UNAuthorizationOptions authOptions = UNAuthorizationOptionAlert | UNAuthorizationOptionSound | UNAuthorizationOptionBadge;
[[UNUserNotificationCenter currentNotificationCenter] requestAuthorizationWithOptions:authOptions completionHandler:^(BOOL granted, NSError * _Nullable error) {
[self registraNotifica];
[self configureAppTracking];
}];
}
- (void)configureAppTracking
{
if (@available(iOS 14, *)) {
// add a delay otherwise the alert will not be displayed
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];
} else {
[FBSDKSettings.sharedSettings setAdvertiserTrackingEnabled:NO];
}
}];
});
} else {
[FBSDKSettings.sharedSettings setAdvertiserTrackingEnabled:YES];
}
}
- (void)configureFirebase
{
[FIRApp configure];
[FIRMessaging messaging].delegate = self;
}
- (void)configureFacebookSDKWithApplication:(UIApplication *)application andOptions:(NSDictionary *)launchOptions
{
[FBSDKApplicationDelegate.sharedInstance application:application didFinishLaunchingWithOptions:launchOptions];
[FBSDKSettings.sharedSettings setAdvertiserIDCollectionEnabled:YES];
}
#pragma mark - FIRMessagingDelegate
- (void)messaging:(FIRMessaging *)messaging didReceiveRegistrationToken:(NSString *)fcmToken
{
NSLog(@"[Firebase] fcmToken %@", fcmToken);
if (![[NSUserDefaults standardUserDefaults] objectForKey:EQNUserDefaultUserFirebaseToken]) {
if (EQNUserData.sharedData.isFirstStart) {
// save default values for notification settings
[EQNAllertaSismica saveDefaultValues];
[EQNNotificheSegnalazioniUtente saveDefaultValues];
[EQNNotificheReteSismiche saveDefaultValues];
[EQNSettingRealTimeAlert saveDefaultValues];
[EQNSettingUserReportNotification saveDefaultValues];
[EQNSettingSeismicNetworkNotification saveDefaultValues];
}
[EQNUser defaultUser].tokenUser = fcmToken;
[EQNUser.defaultUser registerUserIfNeededWithFirebaseToken:fcmToken];
}
@end
+104
View File
@@ -0,0 +1,104 @@
//
// Constants.swift
// Earthquake Network
//
// Created by Andrea Busi on 14/07/23.
// Copyright © 2023 Earthquake Network. All rights reserved.
//
import Foundation
@objc
extension UserDefaults {
// UserDefaults condivisi con l'AppGroup
@objc(appGroupUserDefaults)
static let appGroup = UserDefaults(suiteName: "group.com.finazzi.distquake")
// Impostazioni della sezione `Allerta in tempo reale`
static let AllertaSismicaAbilitato = "NOTIFICHE_ALLERA_SISMICA_ABILITATO"
static let AllertaSismicaCriticalAlerts = "NOTIFICHE_ALLERA_SISMICA_CRITICAL_ALERTS"
static let AllertaSismicaSismiDaNotificare = "NOTIFICHE_ALLERA_SISMICA_SISMI_DA_NOTIFICARE"
static let AllertaSismicaRaggioSismiLievi = "NOTIFICHE_ALLERA_SISMICA_RAGGIO_SISMI_LIEVI"
static let AllertaSismicaRaggioSismiForti = "NOTIFICHE_ALLERA_SISMICA_RAGGIO_SISMI_FORTI"
// Impostazioni della sezione `Notifiche da reti sismiche`
static let NotificheRetiSismicheAbilitato = "NOTIFICHE_ATTIVA_RETI_SISMICHE"
static let NotificheRetiSismicheMagnitudoMinima = "NOTIFICHE_ATTIVA_RETI_ENERGIA_SISMI"
static let NotificheRetiSismicheDistanzaMassima = "NOTIFICHE_DISTANZA_POSIZIONE_RETI_SISMICHE"
static let NotificheRetiSismicheFiltroNotifiche = "NOTIFICHE_FILTRO_NOTIFICHE_RETI_SISMICHE"
// Impostazioni della sezione `Notifiche segnalazioni utente`
static let NotificheSegnalazioniUtenteAbilitato = "NOTIFICHE_SU_ATTIVA_SEGNALAZIONE_UTENTE"
static let NotificheSegnalazioniUtenteDistanzaPosizione = "NOTIFICHE_SU_DISTANZA_POSIZIONE"
// Messaggio in segnalazione utente
static let UserReportMessage = "DATA_MESSAGE_EQN"
static let UserReportCodeStatus = "CODE_MESSAGE_EQN"
// Proprietà e preferenze dell'utente
/// Ultima posizione conosciuta dell'utente
static let UserDataLastLocation = "EQNLast_Location"
/// Token Firebase dell'utente corrente
static let UserDataFirebaseToken = "EQNToken_User"
/// Server user ID dell'utente corrente
static let UserDataUserId = "EQNUSER_ID"
/// Reti sismiche selezionate
static let UserDataSelectedSeismicNetworks = "IMPOSTAZIONE_ENTI_RETI_SISMICHEI"
/// Token delle notifiche push
static let UserDataPushToken = "EQNetwork.PushToken"
/// Numero di aperture dell'app per sbloccare la versione Pro scontata
static let UserDataProDiscountOpenCounter = "CONTEGGIO_APERTURE_PER_SCONTO"
/// Prezzo scontato per la versione pro scaduto
static let UserDataProDiscountExpired = "PREZZO_SCONTATO_SCADUTO"
/// Se `true` visualizza il tempo nelle annotazioni della mappa segnalazioni utente
static let UserReportExpandedView = "EQNData.UserReportExpandedView"
/// Se `true` visualizza le opzioni nella singole card in reti sismiche
static let AlertsShowCardOptions = "EQNetwork.AlertsShowAllCards"
// Migrazioni
static let AppMigrationV5_3 = "EQNUserDefaultMigrationV5_3"
static let AppMigrationV5_4 = "EQNUserDefaultMigrationV5_4"
static let AppMigrationV5_8 = "EQNUserDefaultMigrationV5_8"
static let SettingsSeismicNetworkNotificationMigrationV5_8 = "EQNUserDefaultSettingsSeismicNetworkNotificationMigrationV5_8"
static let SettingsUserReportNotificationMigrationV5_8 = "EQNUserDefaultSettingsUserReportNotificationMigrationV5_8"
static let SismicFiltersMigrationV5_8 = "EQNUserDefaultSismicFiltersMigrationV5_8"
static let SaveSettingsNotificationMigrationV5_8 = "EQNUserDefaultSaveSettingsNotificationMigrationV5_8"
// Notifica allerta salvata
static let RealTimeAlertPayload = "EQNData.RealtimePushNotificationPayload"
static let RealTimeAlertDate = "EQNData.RealtimeAlertDate"
// Notifica rete sismica aperta
static let OfficialAlertPayload = "EQNData.OfficialPushNotificationPayload"
// Filtri sezioni reti sismiche
static let SeismicFilterOption = "EQN_SISMI_TIPOLOGIA_FILTRO"
static let SeismicSort = "EQN_SISMI_TIPOLOGIA_ORDINAMENTO"
static let SeismicMagnitudoMinima = "EQN_MAGNITUDO_MINIMA"
static let SeismicDistanzaMassima = "EQN_DISTANZA_MASSIMA"
}
extension UserDefaults {
/// Get a generic stored values
/// - Parameters:
/// - key: A key in the current users defaults database.
/// - defaultValue: Default value to return if the key is not found
/// - Returns: The object associated with the specified key, or `defaultValue` if the key was not found.
func object<T>(forKey key: String, or defaultValue: T) -> T {
if let value = UserDefaults.standard.object(forKey: key) as? T {
return value
}
return defaultValue
}
func enumObject<T: RawRepresentable>(forKey key: String, or defaultValue: T) -> T {
if let rawValue = UserDefaults.standard.object(forKey: key) as? T.RawValue,
let value = T.init(rawValue: rawValue) {
return value
}
return defaultValue
}
}
@@ -26,9 +26,6 @@
@implementation AllerteViewController
static NSString * const SegueIdentifierProVersion = @"ShowProVersion";
static NSString * const SegueIdentifierPrioritySubscriptions = @"ShowPrioritySubscriptions";
/// Sections inside the app
typedef NS_ENUM(NSInteger, AllerteTableRow) {
AllerteTableRowLocationPermission = 0,
@@ -36,7 +33,6 @@ typedef NS_ENUM(NSInteger, AllerteTableRow) {
AllerteTableRowAllertePassate,
AllerteTableRowReteSmartphone,
AllerteTableRowServizioPriorita,
AllerteTableRowVersionePro, // non più visualizzata con passaggio dell'app a pagamento
AllerteTableRowDatiPosizione
};
@@ -106,6 +102,17 @@ typedef NS_ENUM(NSInteger, AllerteTableRow) {
self.title = [NSLocalizedString(@"tab_network", nil) capitalizedString];
self.tableView.estimatedRowHeight = 200.0;
self.tableView.rowHeight = UITableViewAutomaticDimension;
[self.tableView registerClass:[AlertsSmartphoneNetworkTableViewCell class] forCellReuseIdentifier:@"SmartphoneNetworkCell"];
[self.tableView registerClass:[AlertsPriorityServiceTableViewCell class] forCellReuseIdentifier:@"PriorityCell"];
[self.tableView registerClass:[AlertsNoLocationTableViewCell class] forCellReuseIdentifier:@"NoLocationCell"];
[self.tableView registerClass:[AlertsPastEartquakesTableViewCell class] forCellReuseIdentifier:@"PastEarthquakesCell"];
[self.tableView registerClass:[AlertsSeismicNotificationCompactTableViewCell class] forCellReuseIdentifier:@"SeismicNotificationCompactCell"];
[self.tableView registerClass:[AlertsSeismicNotificationExpandedTableViewCell class] forCellReuseIdentifier:@"SeismicNotificationExpandedCell"];
[self.tableView registerClass:[AlertsPositionDataTableViewCell class] forCellReuseIdentifier:@"PositionDataCell"];
if (EQNBackgroundPositionDebugHelper.shared.isEnabled) {
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemBookmarks target:self action:@selector(backgroundPositionDebugTapped:)];
}
}
- (void)refreshUI
@@ -113,18 +120,22 @@ typedef NS_ENUM(NSInteger, AllerteTableRow) {
[super refreshUI];
// `AllerteTableRowReteSmartphone` and `AllerteTableRowDatiPosizione` are hidden bu default, user can show them
BOOL showAllCards = [[NSUserDefaults standardUserDefaults] boolForKey:EQNUserDefaultKeyAlertsShowAllCards];
BOOL showAllCards = AppPreferences.shared.alertsShowAllCards;
self.expandeCollapseButton.image = showAllCards ? [UIImage imageNamed:@"navbar-icon-arrow-collapse"] : [UIImage imageNamed:@"navbar-icon-arrow-expand"];
NSDate *date = [[NSUserDefaults standardUserDefaults] objectForKey:NOTIFICHE_RETE_SMARTPHONE_DATA_NOTIFICA];
if (date) {
if ([EQNUtility getDifferenceMinute:date] < TEMPO_VISUALIZZAZIONE_NOTIFICA)
self.isNotificaAttiva = YES;
else{
self.isNotificaAttiva = NO;
[[NSUserDefaults standardUserDefaults] removeObjectForKey:NOTIFICHE_RETE_SMARTPHONE_DATA_NOTIFICA];
[[NSUserDefaults standardUserDefaults] synchronize];
// controlliamo se c'è una notifica in tempo reale da mostrare
EQNRealtimePushNotification *notification = [EQNRealtimePushNotification storedNotification];
if (notification) {
self.isNotificaAttiva = YES;
// mostriamo la schermata solo se il countdown non è a zero
if (![notification isCountdownExpired]) {
RealtimeAlertViewController *controller = [[RealtimeAlertViewController alloc] initWithNotification:notification];
controller.modalInPresentation = YES;
[self presentViewController:controller animated:YES completion:nil];
}
} else {
[self resetRealtimeAlert];
}
[self.tableItems removeAllObjects];
@@ -152,6 +163,12 @@ typedef NS_ENUM(NSInteger, AllerteTableRow) {
[self.tableView reloadData];
}
- (void)resetRealtimeAlert
{
[EQNRealtimePushNotification removeStoredNotification];
self.isNotificaAttiva = NO;
}
#pragma mark - Actions
- (IBAction)refreshDataTapped:(id)sender
@@ -162,20 +179,21 @@ typedef NS_ENUM(NSInteger, AllerteTableRow) {
- (IBAction)collapseExpandTapped:(id)sender
{
// toggle saved value
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
BOOL showAll = [defaults boolForKey:EQNUserDefaultKeyAlertsShowAllCards];
[defaults setBool:!showAll forKey:EQNUserDefaultKeyAlertsShowAllCards];
AppPreferences.shared.alertsShowAllCards = !AppPreferences.shared.alertsShowAllCards;
[self refreshUI];
}
- (IBAction)backgroundPositionDebugTapped:(id)sender
{
EQNBackgroundPositionDebugViewController *controller = [[EQNBackgroundPositionDebugViewController alloc] init];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:controller];
[self presentViewController:navController animated:YES completion:nil];
}
- (void)actionCloseNotification
{
[[NSUserDefaults standardUserDefaults] removeObjectForKey:NOTIFICHE_RETE_SMARTPHONE_DATA_NOTIFICA];
[[NSUserDefaults standardUserDefaults] removeObjectForKey:NOTIFICHE_RETE_SMARTPHONE_DIZIONARIO_NOTIFICA];
[[NSUserDefaults standardUserDefaults] synchronize];
self.isNotificaAttiva = NO;
[self resetRealtimeAlert];
[self.tableView reloadData];
}
@@ -252,14 +270,16 @@ typedef NS_ENUM(NSInteger, AllerteTableRow) {
if (tableRow == AllerteTableRowLocationPermission) {
AlertsNoLocationTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"NoLocationCell" forIndexPath:indexPath];
cell.status = CLLocationManager.authorizationStatus;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
[cell updateWith:CLLocationManager.authorizationStatus];
return cell;
} else if (tableRow == AllerteTableRowSismiRilevati) {
if (self.isNotificaAttiva) {
AlertsSeismicNotificationExpandedTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"SeismicNotificationExpandedCell" forIndexPath:indexPath];
NSDictionary *info = [EQNUtility loadDictionaryFromUserDefaultsForKey:NOTIFICHE_RETE_SMARTPHONE_DIZIONARIO_NOTIFICA];
cell.notification = info;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
EQNRealtimePushNotification *notification = [EQNRealtimePushNotification storedNotification];
[cell updateWith:notification];
__weak AllerteViewController *weakSelf = self;
cell.onTapClose = ^{
@@ -278,7 +298,8 @@ typedef NS_ENUM(NSInteger, AllerteTableRow) {
return cell;
}
AlertsSeismicNotificationCompactTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"SeismicNotificationCell" forIndexPath:indexPath];
AlertsSeismicNotificationCompactTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"SeismicNotificationCompactCell" forIndexPath:indexPath];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
__weak AllerteViewController *weakSelf = self;
cell.onTapAlertTest = ^{
@@ -298,8 +319,9 @@ typedef NS_ENUM(NSInteger, AllerteTableRow) {
} else if (tableRow == AllerteTableRowAllertePassate) {
AlertsPastEartquakesTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"PastEarthquakesCell" forIndexPath:indexPath];
cell.smartphoneNetwork = [EQNManager defaultManager].rete_smartphone;
cell.onTapMapButton = ^{
cell.selectionStyle = UITableViewCellSelectionStyleNone;
[cell updateWith:[EQNManager defaultManager].rete_smartphone];
cell.onTapMap = ^{
PasquakesMapViewController *controller = [[PasquakesMapViewController alloc] init];
[self presentViewController:controller animated:YES completion:nil];
};
@@ -307,7 +329,8 @@ typedef NS_ENUM(NSInteger, AllerteTableRow) {
} else if (tableRow == AllerteTableRowReteSmartphone) {
AlertsSmartphoneNetworkTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"SmartphoneNetworkCell" forIndexPath:indexPath];
cell.smartphoneNetwork = [EQNManager defaultManager].rete_smartphone;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
[cell updateWith:[EQNManager defaultManager].rete_smartphone];
cell.onTapButton = ^{
[self visualizzaCopertura];
};
@@ -315,16 +338,13 @@ typedef NS_ENUM(NSInteger, AllerteTableRow) {
} else if (tableRow == AllerteTableRowServizioPriorita) {
AlertsPriorityServiceTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"PriorityCell" forIndexPath:indexPath];
cell.smartphoneNetwork = [EQNManager defaultManager].rete_smartphone;
return cell;
} else if (tableRow == AllerteTableRowVersionePro) {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"ProVersionCell" forIndexPath:indexPath];
[cell updateWith:[EQNManager defaultManager].rete_smartphone];
return cell;
} else if (tableRow == AllerteTableRowDatiPosizione) {
AlertsPositionDataTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"PositionDataCell" forIndexPath:indexPath];
cell.position = [EQNUser defaultUser].lastPosition;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
[cell updateWith:[EQNUser defaultUser].lastPosition];
return cell;
}
@@ -337,12 +357,10 @@ typedef NS_ENUM(NSInteger, AllerteTableRow) {
AllerteTableRow tableRow = [self.tableItems[indexPath.row] integerValue];
switch (tableRow) {
case AllerteTableRowServizioPriorita:
[self performSegueWithIdentifier:SegueIdentifierPrioritySubscriptions sender:nil];
break;
case AllerteTableRowVersionePro:
[self performSegueWithIdentifier:SegueIdentifierProVersion sender:nil];
break;
case AllerteTableRowServizioPriorita: {
SubscriptionsViewController *controller = [[SubscriptionsViewController alloc] init];
[self.navigationController pushViewController:controller animated:YES];
}; break;
default:
break;
}
@@ -9,41 +9,70 @@
import UIKit
import CoreLocation
class AlertsNoLocationTableViewCell: EQNBaseTableViewCell {
@objc var status: CLAuthorizationStatus = .notDetermined {
didSet {
updateUI()
}
@objc
class AlertsNoLocationTableViewCell: EQNBaseContainerTableViewCell {
override var isHeaderVisible: Bool { false }
// MARK: - UI
private lazy var messageLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
label.textColor = AppTheme.Colors.red
label.font = .preferredFont(forTextStyle: .body)
return label
}()
private lazy var actionButton: UIButton = {
let button = EQNRoundedButton.make(title: NSLocalizedString("permission_location_no_background_solve", comment: ""), target: self, action: #selector(solveTapped(_:)))
return button
}()
// MARK: - Internal
override func setupUI() {
super.setupUI()
containerView.addSubview(messageLabel)
containerView.addSubview(actionButton)
messageLabel.topAnchor.constraint(equalTo: topView.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
messageLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: .cardPadding).isActive = true
messageLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: .cardPadding.negative).isActive = true
actionButton.heightAnchor.constraint(greaterThanOrEqualToConstant: 40.0).isActive = true
actionButton.topAnchor.constraint(equalTo: messageLabel.bottomAnchor, constant: .cardPadding).isActive = true
actionButton.leadingAnchor.constraint(equalTo: messageLabel.leadingAnchor).isActive = true
actionButton.trailingAnchor.constraint(equalTo: messageLabel.trailingAnchor).isActive = true
actionButton.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: .cardPadding.negative).isActive = true
}
@IBOutlet private weak var messageLabel: UILabel!
@IBOutlet private weak var actionButton: UIButton!
override func updateUI() {
super.updateUI()
actionButton.backgroundColor = AppTheme.Colors.lightGray
}
// MARK: - Private
// MARK: - Public
private func updateUI() {
var message = ""
switch status {
case .authorizedAlways:
message = ""
case .authorizedWhenInUse:
message = NSLocalizedString("permission_location_no_background", comment: "")
default:
message = NSLocalizedString("permission_location_no", comment: "")
@objc
func update(with status: CLAuthorizationStatus) {
messageLabel.text = switch status {
case .authorizedAlways: ""
case .authorizedWhenInUse: NSLocalizedString("permission_location_no_background", comment: "")
default: NSLocalizedString("permission_location_no", comment: "")
}
messageLabel.text = message
actionButton.setLocalizedTitle(key: "permission_location_no_background_solve")
}
// MARK: - Actions
@IBAction private func solveTapped(_ sender: UIButton) {
@objc private func solveTapped(_ sender: UIButton) {
guard let settingsUrl = URL(string: UIApplication.openSettingsURLString) else {
return
}
UIApplication.shared.open(settingsUrl, options: [:], completionHandler: nil)
}
}
@@ -8,50 +8,87 @@
import UIKit
class AlertsPastEartquakesTableViewCell: EQNBaseTableViewCell {
@objc var smartphoneNetwork: EQNReteSmartphone? {
didSet {
updateUI()
}
}
@objc
class AlertsPastEartquakesTableViewCell: EQNBaseContainerTableViewCell {
@objc var onTapMapButton: (() -> Void)?
@objc var onTapMap: (() -> Void)?
override var headerText: String { NSLocalizedString("main_past_quakes", comment: "") }
// MARK: - UI
private lazy var last24hLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
label.textColor = AppTheme.shared.cardTextColor
label.font = .preferredFont(forTextStyle: .title3)
label.textAlignment = .center
return label
}()
private lazy var from2013Label: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
label.textColor = AppTheme.shared.cardTextColor
label.font = .preferredFont(forTextStyle: .title3)
label.textAlignment = .center
return label
}()
private lazy var mapButton: UIButton = {
let button = EQNRoundedButton.make(target: self, action: #selector(mapTapped(_:)))
return button
}()
// MARK: - Internal
@IBOutlet private weak var headerLabel: UILabel!
@IBOutlet private weak var last24hLabel: UILabel!
@IBOutlet private weak var from2013Label: UILabel!
@IBOutlet private weak var mapButton: UIButton!
// MARK: - View Lifecycle
override func awakeFromNib() {
super.awakeFromNib()
override func setupUI() {
super.setupUI()
localizeUI()
containerView.addSubview(last24hLabel)
containerView.addSubview(from2013Label)
containerView.addSubview(mapButton)
last24hLabel.topAnchor.constraint(equalTo: topView.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
last24hLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: .cardPadding).isActive = true
last24hLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: .cardPadding.negative).isActive = true
from2013Label.topAnchor.constraint(equalTo: last24hLabel.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
from2013Label.leadingAnchor.constraint(equalTo: last24hLabel.leadingAnchor).isActive = true
from2013Label.trailingAnchor.constraint(equalTo: last24hLabel.trailingAnchor).isActive = true
mapButton.heightAnchor.constraint(greaterThanOrEqualToConstant: 40.0).isActive = true
mapButton.topAnchor.constraint(equalTo: from2013Label.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
mapButton.leadingAnchor.constraint(equalTo: from2013Label.leadingAnchor).isActive = true
mapButton.trailingAnchor.constraint(equalTo: from2013Label.trailingAnchor).isActive = true
mapButton.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: .cardPadding.negative).isActive = true
}
// MARK: - Private
private func localizeUI() {
headerLabel.text = NSLocalizedString("main_past_quakes", comment: "")
override func updateUI() {
super.updateUI()
last24hLabel.text = NSLocalizedString("main_recent_quakes_initial", comment: "")
from2013Label.text = NSLocalizedString("main_total_quakes_initial", comment: "")
mapButton.setLocalizedTitle(key: "official_button_map", uppercased: true, emoji: "🗺")
}
private func updateUI() {
guard let smartphoneNetwork = smartphoneNetwork else { return }
// MARK: - Public
@objc
func update(with smartphoneNetwork: EQNReteSmartphone?) {
guard let smartphoneNetwork else { return }
last24hLabel.text = String(format: NSLocalizedString("main_recent_quakes", comment: ""), smartphoneNetwork.counterLastDayAlerts)
from2013Label.text = String(format: NSLocalizedString("main_total_quakes", comment: ""), smartphoneNetwork.counterTotalAlerts)
}
// MARK: - Actions
@IBAction func mapTapped(_ sender: UIButton) {
onTapMapButton?()
@objc private func mapTapped(_ sender: UIButton) {
onTapMap?()
}
}
@@ -9,20 +9,11 @@
import UIKit
import Solar
class AlertsPositionDataTableViewCell: EQNBaseTableViewCell {
@objc var position: CLLocation? {
didSet {
updateUI()
}
}
@objc
class AlertsPositionDataTableViewCell: EQNBaseContainerTableViewCell {
// MARK: - Internal
override var headerText: String { NSLocalizedString("weather_location", comment: "") }
@IBOutlet private weak var headerLabel: UILabel!
@IBOutlet private weak var positionLabel: UILabel!
@IBOutlet private weak var sunriseTimeLabel: UILabel!
@IBOutlet private weak var sunsetTimeLabel: UILabel!
private lazy var dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .none
@@ -30,25 +21,117 @@ class AlertsPositionDataTableViewCell: EQNBaseTableViewCell {
return formatter
}()
// MARK: - Private
// MARK: - UI
private func updateUI() {
headerLabel.text = NSLocalizedString("weather_location", comment: "")
private lazy var positionImage: UIImageView = {
let imageView = UIImageView(image: .init(named: "world_old"))
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFit
imageView.heightAnchor.constraint(equalToConstant: 30.0).isActive = true
imageView.widthAnchor.constraint(equalTo: imageView.heightAnchor).isActive = true
return imageView
}()
private lazy var positionLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
label.textColor = AppTheme.shared.cardTextColor
label.font = .preferredFont(forTextStyle: .body)
return label
}()
private lazy var sunriseImage: UIImageView = {
let imageView = UIImageView(image: .init(named: "sunrise"))
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFit
imageView.heightAnchor.constraint(equalToConstant: 30.0).isActive = true
imageView.widthAnchor.constraint(equalTo: imageView.heightAnchor).isActive = true
return imageView
}()
private lazy var sunriseTimeLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
label.textColor = AppTheme.shared.cardTextColor
label.font = .preferredFont(forTextStyle: .body)
return label
}()
private lazy var sunsetImage: UIImageView = {
let imageView = UIImageView(image: .init(named: "sunset"))
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFit
imageView.heightAnchor.constraint(equalToConstant: 30.0).isActive = true
imageView.widthAnchor.constraint(equalTo: imageView.heightAnchor).isActive = true
return imageView
}()
private lazy var sunsetTimeLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
label.textColor = AppTheme.shared.cardTextColor
label.font = .preferredFont(forTextStyle: .body)
return label
}()
// MARK: - Internal
override func setupUI() {
super.setupUI()
containerView.addSubview(positionImage)
containerView.addSubview(positionLabel)
containerView.addSubview(sunriseImage)
containerView.addSubview(sunriseTimeLabel)
containerView.addSubview(sunsetImage)
containerView.addSubview(sunsetTimeLabel)
positionImage.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: .cardPadding).isActive = true
positionImage.centerYAnchor.constraint(equalTo: positionLabel.centerYAnchor).isActive = true
positionImage.trailingAnchor.constraint(equalTo: positionLabel.leadingAnchor, constant: .cardPadding.negative).isActive = true
positionLabel.topAnchor.constraint(equalTo: topView.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
positionLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: .cardPadding.negative).isActive = true
sunriseImage.leadingAnchor.constraint(equalTo: positionImage.leadingAnchor).isActive = true
sunriseImage.centerYAnchor.constraint(equalTo: sunriseTimeLabel.centerYAnchor).isActive = true
sunriseImage.trailingAnchor.constraint(equalTo: sunriseTimeLabel.leadingAnchor, constant: .cardPadding.negative).isActive = true
sunriseTimeLabel.topAnchor.constraint(equalTo: positionLabel.bottomAnchor, constant: .cardVerticalSpacing.x2).isActive = true
sunriseTimeLabel.trailingAnchor.constraint(equalTo: positionLabel.trailingAnchor).isActive = true
sunsetImage.leadingAnchor.constraint(equalTo: sunriseImage.leadingAnchor).isActive = true
sunsetImage.centerYAnchor.constraint(equalTo: sunsetTimeLabel.centerYAnchor).isActive = true
sunsetImage.trailingAnchor.constraint(equalTo: sunsetTimeLabel.leadingAnchor, constant: .cardPadding.negative).isActive = true
sunsetTimeLabel.topAnchor.constraint(equalTo: sunriseTimeLabel.bottomAnchor, constant: .cardVerticalSpacing.x2).isActive = true
sunsetTimeLabel.trailingAnchor.constraint(equalTo: sunriseTimeLabel.trailingAnchor).isActive = true
sunsetTimeLabel.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: .cardPadding.negative).isActive = true
}
override func updateUI() {
super.updateUI()
positionLabel.text = "n.d."
sunriseTimeLabel.text = "n.d."
sunsetTimeLabel.text = "n.d."
guard let position = position else { return }
}
// MARK: - Public
@objc
func update(with position: CLLocation?) {
guard let position else { return }
positionLabel.text = EQNUtility.coordinateString(coordinate: position.coordinate)
if let solar = Solar(coordinate: position.coordinate) {
if let sunrise = solar.sunrise {
sunriseTimeLabel.text = dateFormatter.string(from: sunrise) + " (\(TimeZone.current.identifier))"
}
if let sunset = solar.sunset {
sunsetTimeLabel.text = dateFormatter.string(from: sunset) + " (\(TimeZone.current.identifier))"
}
guard let solar = Solar(coordinate: position.coordinate) else { return }
let timeZone = TimeZone.current.localizedName(for: .generic, locale: .current) ?? TimeZone.current.identifier
if let sunrise = solar.sunrise {
sunriseTimeLabel.text = dateFormatter.string(from: sunrise) + " \(timeZone)"
}
if let sunset = solar.sunset {
sunsetTimeLabel.text = dateFormatter.string(from: sunset) + " \(timeZone)"
}
}
}
@@ -8,39 +8,81 @@
import UIKit
class AlertsPriorityServiceTableViewCell: EQNBaseTableViewCell {
@objc var smartphoneNetwork: EQNReteSmartphone? {
didSet {
updateUI()
}
}
@objc
class AlertsPriorityServiceTableViewCell: EQNBaseContainerTableViewCell {
override var headerText: String { NSLocalizedString("inapp_list", comment: "") }
override var isRightArrowVisbile: Bool { true }
// MARK: - UI
private lazy var descriptionLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
label.textColor = AppTheme.Colors.darkGray
label.font = .preferredFont(forTextStyle: .body)
return label
}()
private lazy var lastSubscriptionLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
label.textColor = AppTheme.Colors.pureRed
label.font = .preferredFont(forTextStyle: .body)
return label
}()
// MARK: - Internal
@IBOutlet private weak var headerLabel: UILabel!
@IBOutlet private weak var descriptionLabel: UILabel!
@IBOutlet private weak var lastSubscriptionLabel: UILabel!
// MARK: - View Lifecycle
override func awakeFromNib() {
super.awakeFromNib()
override func setupUI() {
super.setupUI()
localizeUI()
containerView.addSubview(descriptionLabel)
containerView.addSubview(lastSubscriptionLabel)
descriptionLabel.topAnchor.constraint(equalTo: topView.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
descriptionLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: .cardPadding).isActive = true
descriptionLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: .cardPadding.negative).isActive = true
lastSubscriptionLabel.topAnchor.constraint(equalTo: descriptionLabel.bottomAnchor, constant: .cardVerticalSpacing/2.0).isActive = true
lastSubscriptionLabel.leadingAnchor.constraint(equalTo: descriptionLabel.leadingAnchor).isActive = true
lastSubscriptionLabel.trailingAnchor.constraint(equalTo: descriptionLabel.trailingAnchor).isActive = true
lastSubscriptionLabel.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: .cardPadding.negative).isActive = true
}
// MARK: - Private
private func localizeUI() {
headerLabel.text = NSLocalizedString("inapp_list", comment: "")
override func updateUI() {
super.updateUI()
backgroundColor = AppTheme.Colors.cardBackgroundOrange
descriptionLabel.text = NSLocalizedString("inapp_adv", comment: "")
}
private func updateUI() {
guard let smartphoneNetwork = smartphoneNetwork else { return }
// MARK: - Public
@objc
func update(with smartphoneNetwork: EQNReteSmartphone?) {
guard let smartphoneNetwork else { return }
let formattedTime = EQNUtility.formattedString(forTimeDifference: smartphoneNetwork.lastSubscriptionDiff)
lastSubscriptionLabel.text = String(format: NSLocalizedString("inapp_adv_time", comment: ""), formattedTime)
lastSubscriptionLabel.text = subscriptionText(for: smartphoneNetwork.lastSubscriptionDiff)
}
private func subscriptionText(for time: Int) -> String {
var format = ""
var finalValue = time
// check for minutes, hours or days
if time < 60 {
format = NSLocalizedString("inapp_adv_minutes", comment: "")
} else if time < 1440 {
finalValue = time / 60
format = NSLocalizedString("inapp_adv_hours", comment: "")
} else {
finalValue = time / 1440
format = NSLocalizedString("inapp_adv_days", comment: "")
}
return String.localizedStringWithFormat(format, finalValue)
}
}
@@ -1,30 +0,0 @@
//
// AlertsProVersionTableViewCell.swift
// Earthquake Network
//
// Created by Andrea Busi on 05/04/21.
// Copyright © 2021 Earthquake Network. All rights reserved.
//
import UIKit
class AlertsProVersionTableViewCell: EQNBaseTableViewCell {
@IBOutlet private weak var headerLabel: UILabel!
@IBOutlet private weak var descriptionLabel: UILabel!
// MARK: - View Lifecycle
override func awakeFromNib() {
super.awakeFromNib()
localizeUI()
}
// MARK: - Private
private func localizeUI() {
headerLabel.text = NSLocalizedString("network_pro", comment: "")
descriptionLabel.text = NSLocalizedString("network_topro", comment: "")
}
}
@@ -9,54 +9,112 @@
import UIKit
class AlertsSeismicNotificationCompactTableViewCell: EQNBaseTableViewCell {
@objc
class AlertsSeismicNotificationCompactTableViewCell: EQNBaseContainerTableViewCell {
typealias DefaultCompletion = () -> Void
@objc var onTapAlertTest: DefaultCompletion?
@objc var onTapSimulator: DefaultCompletion?
@objc var onTapHowItWorks: DefaultCompletion?
@objc var onTapShareApp: DefaultCompletion?
override var isHeaderVisible: Bool { false }
// MARK: - UI
private lazy var descriptionLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.textColor = AppTheme.Colors.green
label.font = .preferredFont(forTextStyle: .title3)
label.textAlignment = .center
label.numberOfLines = 0
return label
}()
@IBOutlet private weak var descriptionLabel: UILabel!
@IBOutlet private weak var testAlertButton: UIButton!
@IBOutlet private weak var simulatorAlertButton: UIButton!
@IBOutlet private weak var howItWorksAlertButton: UIButton!
@IBOutlet private weak var shareAppButton: UIButton!
private lazy var testAlertButton: UIButton = {
let button = EQNRoundedButton.make(target: self, action: #selector(testAlertTapped(_:)))
return button
}()
// MARK: - View Lifecycle
private lazy var simulatorAlertButton: UIButton = {
let button = EQNRoundedButton.make(target: self, action: #selector(simulatorTapped(_:)))
return button
}()
override func awakeFromNib() {
super.awakeFromNib()
private lazy var howItWorksAlertButton: UIButton = {
let button = EQNRoundedButton.make(target: self, action: #selector(howItWorksTapped(_:)))
return button
}()
private lazy var shareAppButton: UIButton = {
let button = EQNRoundedButton.make(target: self, action: #selector(shareAppTapped(_:)))
return button
}()
// MARK: - Internal
override func setupUI() {
super.setupUI()
localizeUI()
containerView.addSubview(descriptionLabel)
containerView.addSubview(testAlertButton)
containerView.addSubview(simulatorAlertButton)
containerView.addSubview(howItWorksAlertButton)
containerView.addSubview(shareAppButton)
descriptionLabel.topAnchor.constraint(equalTo: topView.bottomAnchor, constant: .cardPadding).isActive = true
descriptionLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: .cardPadding).isActive = true
descriptionLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: .cardPadding.negative).isActive = true
testAlertButton.heightAnchor.constraint(greaterThanOrEqualToConstant: 40.0).isActive = true
simulatorAlertButton.heightAnchor.constraint(equalTo: testAlertButton.heightAnchor).isActive = true
howItWorksAlertButton.heightAnchor.constraint(equalTo: testAlertButton.heightAnchor).isActive = true
shareAppButton.heightAnchor.constraint(equalTo: testAlertButton.heightAnchor).isActive = true
testAlertButton.topAnchor.constraint(equalTo: descriptionLabel.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
testAlertButton.leadingAnchor.constraint(equalTo: descriptionLabel.leadingAnchor).isActive = true
testAlertButton.trailingAnchor.constraint(equalTo: simulatorAlertButton.leadingAnchor, constant: .cardPadding.negative).isActive = true
simulatorAlertButton.trailingAnchor.constraint(equalTo: descriptionLabel.trailingAnchor).isActive = true
simulatorAlertButton.centerYAnchor.constraint(equalTo: testAlertButton.centerYAnchor).isActive = true
simulatorAlertButton.widthAnchor.constraint(equalTo: testAlertButton.widthAnchor).isActive = true
howItWorksAlertButton.topAnchor.constraint(equalTo: testAlertButton.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
howItWorksAlertButton.leadingAnchor.constraint(equalTo: testAlertButton.leadingAnchor).isActive = true
howItWorksAlertButton.trailingAnchor.constraint(equalTo: shareAppButton.leadingAnchor, constant: .cardPadding.negative).isActive = true
shareAppButton.trailingAnchor.constraint(equalTo: descriptionLabel.trailingAnchor).isActive = true
shareAppButton.centerYAnchor.constraint(equalTo: howItWorksAlertButton.centerYAnchor).isActive = true
shareAppButton.widthAnchor.constraint(equalTo: howItWorksAlertButton.widthAnchor).isActive = true
shareAppButton.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: .cardPadding.negative).isActive = true
}
// MARK: - Private
private func localizeUI() {
override func updateUI() {
super.updateUI()
backgroundColor = AppTheme.Colors.cardBackgroundGreen
descriptionLabel.text = NSLocalizedString("main_nodetection", comment: "")
testAlertButton.setLocalizedTitle(key: "main_alerttest", uppercased: true, emoji: "🚨")
simulatorAlertButton.setLocalizedTitle(key: "main_simulator", uppercased: true, emoji: "")
howItWorksAlertButton.setLocalizedTitle(key: "main_how_it_work", uppercased: true, emoji: "💡")
shareAppButton.setLocalizedTitle(key: "main_share_app", uppercased: true, emoji: "👥")
}
// MARK: - Actions
@IBAction private func testAlertTapped() {
@objc private func testAlertTapped(_ sender: UIButton) {
onTapAlertTest?()
}
@IBAction private func simulatorTapped() {
@objc private func simulatorTapped(_ sender: UIButton) {
onTapSimulator?()
}
@IBAction private func howItWorksTapped() {
@objc private func howItWorksTapped(_ sender: UIButton) {
onTapHowItWorks?()
}
@IBAction private func shareAppTapped() {
@objc private func shareAppTapped(_ sender: UIButton) {
onTapShareApp?()
}
}
@@ -8,123 +8,198 @@
import UIKit
import MapKit
import Shogun
class AlertsSeismicNotificationExpandedTableViewCell: EQNBaseTableViewCell, MKMapViewDelegate {
class AlertsSeismicNotificationExpandedTableViewCell: EQNBaseContainerTableViewCell, MKMapViewDelegate {
override var isHeaderVisible: Bool { false }
typealias DefaultCompletion = () -> Void
@objc var notification: [String: Any]? {
didSet {
startCountdown()
updateUI()
}
}
@objc var onTapOpenTwitter: DefaultCompletion?
@objc var onTapRateApp: DefaultCompletion?
@objc var onTapClose: DefaultCompletion?
@objc var onTapShareApp: DefaultCompletion?
// MARK: - UI
private lazy var notificationTitleLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
label.textColor = AppTheme.shared.cardTextColor
label.font = .preferredFont(forTextStyle: .title1)
label.textAlignment = .center
return label
}()
private lazy var notificationIntensityLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
label.textColor = AppTheme.shared.cardTextColor
label.font = .preferredFont(forTextStyle: .title1)
label.textAlignment = .center
return label
}()
private lazy var notificationDescriptionLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
label.textColor = AppTheme.shared.cardTextColor
label.font = .preferredFont(forTextStyle: .body)
label.textAlignment = .center
return label
}()
private lazy var mapView: MKMapView = {
let mapView = MKMapView()
mapView.translatesAutoresizingMaskIntoConstraints = false
mapView.delegate = self
mapView.isScrollEnabled = false
mapView.isZoomEnabled = false
mapView.register(EQNCustomAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNCustomAnnotationView.SingleLineIdentifier)
return mapView
}()
private lazy var shareButton: UIButton = {
let button = EQNRoundedButton.make(target: self, action: #selector(shareAppTapped(_:)))
return button
}()
private lazy var rateAppButton: UIButton = {
let button = EQNRoundedButton.make(target: self, action: #selector(rateAppTapped(_:)))
return button
}()
private lazy var viewOnTwitterButton: UIButton = {
let button = EQNRoundedButton.make(target: self, action: #selector(viewInTwitterTapped(_:)))
return button
}()
private lazy var closeButton: UIButton = {
let button = EQNRoundedButton.make(target: self, action: #selector(closeTapped(_:)))
return button
}()
private lazy var descriptionLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
label.textColor = AppTheme.shared.cardTextColor
label.font = .preferredFont(forTextStyle: .body)
label.textAlignment = .center
return label
}()
// MARK: - Internal
@IBOutlet weak var notificationTitleLabel: UILabel!
@IBOutlet weak var notificationDescriptionLabel: UILabel!
@IBOutlet weak var waveTimeLabel: UILabel!
@IBOutlet weak var mapView: MKMapView! {
didSet {
mapView.delegate = self
mapView.isScrollEnabled = false
mapView.isZoomEnabled = false
mapView.register(EQNCustomAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNCustomAnnotationView.SingleLineIdentifier)
}
}
@IBOutlet private weak var shareButton: UIButton!
@IBOutlet private weak var rateAppButton: UIButton!
@IBOutlet private weak var viewOnTwitterButton: UIButton!
@IBOutlet private weak var closeButton: UIButton!
@IBOutlet private weak var descriptionLabel: UILabel!
private var impactTimestamp: Date?
private var countdownTimer: Timer?
// MARK: - View Lifecycle
override func awakeFromNib() {
super.awakeFromNib()
override func setupUI() {
super.setupUI()
localizeUI()
let stackView = UIStackView(arrangedSubviews: [shareButton, rateAppButton])
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .horizontal
stackView.distribution = .fillEqually
stackView.spacing = .cardVerticalSpacing
containerView.addSubview(notificationTitleLabel)
containerView.addSubview(notificationIntensityLabel)
containerView.addSubview(notificationDescriptionLabel)
containerView.addSubview(mapView)
containerView.addSubview(stackView)
containerView.addSubview(viewOnTwitterButton)
containerView.addSubview(descriptionLabel)
containerView.addSubview(closeButton)
notificationTitleLabel.topAnchor.constraint(equalTo: containerView.topAnchor, constant: .cardPadding).isActive = true
notificationTitleLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: .cardPadding).isActive = true
notificationTitleLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: .cardPadding.negative).isActive = true
notificationIntensityLabel.topAnchor.constraint(equalTo: notificationTitleLabel.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
notificationIntensityLabel.leadingAnchor.constraint(equalTo: notificationTitleLabel.leadingAnchor).isActive = true
notificationIntensityLabel.trailingAnchor.constraint(equalTo: notificationTitleLabel.trailingAnchor).isActive = true
notificationDescriptionLabel.topAnchor.constraint(equalTo: notificationIntensityLabel.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
notificationDescriptionLabel.leadingAnchor.constraint(equalTo: notificationTitleLabel.leadingAnchor).isActive = true
notificationDescriptionLabel.trailingAnchor.constraint(equalTo: notificationTitleLabel.trailingAnchor).isActive = true
mapView.topAnchor.constraint(equalTo: notificationDescriptionLabel.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
mapView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor).isActive = true
mapView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor).isActive = true
mapView.heightAnchor.constraint(greaterThanOrEqualToConstant: 240.0).isActive = true
shareButton.heightAnchor.constraint(greaterThanOrEqualToConstant: 40.0).isActive = true
rateAppButton.heightAnchor.constraint(equalTo: shareButton.heightAnchor).isActive = true
viewOnTwitterButton.heightAnchor.constraint(equalTo: shareButton.heightAnchor).isActive = true
closeButton.heightAnchor.constraint(equalTo: shareButton.heightAnchor).isActive = true
stackView.topAnchor.constraint(equalTo: mapView.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
stackView.leadingAnchor.constraint(equalTo: notificationDescriptionLabel.leadingAnchor).isActive = true
stackView.trailingAnchor.constraint(equalTo: notificationDescriptionLabel.trailingAnchor).isActive = true
viewOnTwitterButton.topAnchor.constraint(equalTo: stackView.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
viewOnTwitterButton.leadingAnchor.constraint(equalTo: stackView.leadingAnchor).isActive = true
viewOnTwitterButton.trailingAnchor.constraint(equalTo: stackView.trailingAnchor).isActive = true
descriptionLabel.topAnchor.constraint(equalTo: viewOnTwitterButton.bottomAnchor, constant: .cardPadding).isActive = true
descriptionLabel.leadingAnchor.constraint(equalTo: viewOnTwitterButton.leadingAnchor).isActive = true
descriptionLabel.trailingAnchor.constraint(equalTo: viewOnTwitterButton.trailingAnchor).isActive = true
closeButton.topAnchor.constraint(equalTo: descriptionLabel.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
closeButton.leadingAnchor.constraint(equalTo: descriptionLabel.leadingAnchor).isActive = true
closeButton.trailingAnchor.constraint(equalTo: descriptionLabel.trailingAnchor).isActive = true
closeButton.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: .cardVerticalSpacing.negative).isActive = true
}
// MARK: - Private
private func localizeUI() {
override func updateUI() {
super.updateUI()
shareButton.setLocalizedTitle(key: "main_share_app")
rateAppButton.setLocalizedTitle(key: "main_vote")
viewOnTwitterButton.setLocalizedTitle(key: "main_twitter_see")
closeButton.setLocalizedTitle(key: "official_close")
descriptionLabel.text = NSLocalizedString("map_smartphone_magnitude", comment: "")
}
// MARK: - Public
private func startCountdown() {
guard let notification = notification else { return }
// calculate the impact timestamp and start a timer for the countdown label
if let impactTimestamp = EQNUtility.calculateUserSeismicTimestamp(fromUserInfo: notification) {
self.impactTimestamp = impactTimestamp
countdownTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(countdownTimerFired(_:)), userInfo: nil, repeats: true)
countdownTimer?.fire()
}
}
private func updateUI() {
@objc
func update(with notification: EQNRealtimePushNotification?) {
// clearn any other previous notifications
notificationTitleLabel.text = ""
notificationDescriptionLabel.text = ""
notificationTitleLabel.text = "Sisma rilevato a 150km (TEST)"
notificationIntensityLabel.text = "Previsto uno scuotimento forte"
notificationDescriptionLabel.text = "Distanza 150 km - 13 minuti fa"
mapView.removeAnnotations(mapView.annotations)
guard let notification = notification,
let aps = notification["aps"] as? [String: Any],
let alert = aps["alert"] as? [String: Any] else { return }
guard let notification = notification else { return }
backgroundColor = backgroundColor(for: notification.relativeIntensity())
notificationTitleLabel.text = notification.title
notificationIntensityLabel.text = notification.displayBody
notificationIntensityLabel.textColor = notification.relativeIntensityColor
let intensity = notification.eqn_intValue(for: "intensity") ?? 0
containerView.backgroundColor = color(for: intensity)
if let title = alert["loc-key"] as? String, let args = alert["loc-args"] as? [String], let arg = args.first {
notificationTitleLabel.text = String(format: NSLocalizedString(title, comment: ""), arg)
}
// get coordinate
var coordinate: CLLocation?
if let latitude = notification.eqn_doubleValue(for: "latitude"),
let longitude = notification.eqn_doubleValue(for: "longitude") {
if let date = notification.dateTime {
coordinate = CLLocation(latitude: latitude, longitude: longitude)
}
if let coordinate = coordinate,
let counter = notification["counter"],
let dateString = notification["datetime"] as? String,
let date = EQNUtility.getDateFrom(dateString) {
let distance = EQNUser.default().lastPosition?.distance(from: coordinate) ?? 0.0
let distance = EQNUser.default().lastPosition?.distance(from: notification.coordinate) ?? 0.0
let distanceRound = Int(round(distance / 1_000))
let difference = Int(NSDate().timeIntervalSince(date) / 60.0)
notificationDescriptionLabel.text = ""
+ NSLocalizedString("official_card_distance", comment: "") + " \(distanceRound) km"
+ " - " + EQNUtility.formattedString(forTimeDifference: difference)
+ "\n" + String(format: NSLocalizedString("map_number", comment: ""), "\(counter)")
}
if let coordinate = coordinate {
let span = MKCoordinateSpan(latitudeDelta: 10.5, longitudeDelta: 10.5)
let region = MKCoordinateRegion(center: coordinate.coordinate, span: span)
mapView.setCenter(coordinate.coordinate, animated: false)
mapView.setRegion(region, animated: true)
let annotation = EQNMapAnnotationPastquake(title: "", coordinate: coordinate.coordinate, intensity: intensity)
mapView.addAnnotation(annotation)
}
let span = MKCoordinateSpan(latitudeDelta: 10.5, longitudeDelta: 10.5)
let region = MKCoordinateRegion(center: notification.coordinate.coordinate, span: span)
mapView.setCenter(notification.coordinate.coordinate, animated: false)
mapView.setRegion(region, animated: true)
let annotation = EQNMapAnnotationPastquake(title: "", coordinate: notification.coordinate.coordinate, intensity: notification.intensity)
mapView.addAnnotation(annotation)
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
@@ -138,44 +213,36 @@ class AlertsSeismicNotificationExpandedTableViewCell: EQNBaseTableViewCell, MKMa
return annotationView
}
private func color(for intensity: Int) -> UIColor {
switch intensity {
case 0: return UIColor(red: 208.0/255.0, green: 234.0/255.0, blue: 201.0/255.0, alpha:1.0)
case 1: return UIColor(red: 254.0/255.0, green: 252.0/255.0, blue: 203.0/255.0, alpha:1.0)
case 2: return UIColor(red: 254.0/255.0, green: 186.0/255.0, blue: 186.0/255.0, alpha:1.0)
default: return UIColor.white
}
}
// MARK: - Actions
@objc private func countdownTimerFired(_ sender: Timer) {
guard let impactTimestamp = impactTimestamp else { return }
let now = Date()
let difference = lround(max(impactTimestamp.timeIntervalSince(now), 0))
waveTimeLabel.text = String(format: NSLocalizedString("alert_wave", comment: ""), difference)
if difference <= 0 {
// stop the countdown
countdownTimer?.invalidate()
countdownTimer = nil
}
}
@IBAction private func shareAppTapped(_ sender: UIButton) {
@objc private func shareAppTapped(_ sender: UIButton) {
onTapShareApp?()
}
@IBAction private func rateAppTapped(_ sender: UIButton) {
@objc private func rateAppTapped(_ sender: UIButton) {
onTapRateApp?()
}
@IBAction private func viewInTwitterTapped(_ sender: UIButton) {
@objc private func viewInTwitterTapped(_ sender: UIButton) {
onTapOpenTwitter?()
}
@IBAction private func closeTapped(_ sender: UIButton) {
@objc private func closeTapped(_ sender: UIButton) {
onTapClose?()
}
// MARK: - Private
private func backgroundColor(for intensity: Double) -> UIColor {
switch intensity {
case _ where intensity < 0.004:
return AppTheme.Colors.cardBackgroundGray
case _ where intensity < 0.30:
return AppTheme.Colors.cardBackgroundGreen
case _ where intensity < 0.70:
return AppTheme.Colors.cardBackgroundYellow
default:
return AppTheme.Colors.cardBackgroundRed
}
}
}
@@ -8,48 +8,82 @@
import UIKit
class AlertsSmartphoneNetworkTableViewCell: EQNBaseTableViewCell {
@objc var smartphoneNetwork: EQNReteSmartphone? {
didSet {
updateUI()
}
}
@objc
class AlertsSmartphoneNetworkTableViewCell: EQNBaseContainerTableViewCell {
@objc var onTapButton: (() -> Void)?
override var headerText: String { NSLocalizedString("main_network", comment: "") }
// MARK: - UI
private lazy var counterLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.textColor = AppTheme.Colors.green
label.font = .preferredFont(forTextStyle: .largeTitle)
label.textAlignment = .center
return label
}()
private lazy var descriptionLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
label.textColor = AppTheme.shared.cardTextColor
label.font = .preferredFont(forTextStyle: .body)
label.textAlignment = .center
return label
}()
private lazy var coverageButton: UIButton = {
let button = EQNRoundedButton.make(target: self, action: #selector(localCovergeTapped(_:)))
return button
}()
// MARK: - Internal
@IBOutlet private weak var headerLabel: UILabel!
@IBOutlet private weak var smartphoneCounterLabel: UILabel!
@IBOutlet private weak var coverageDescriptionLabel: UILabel!
@IBOutlet private weak var localCoverageButton: UIButton!
// MARK: - View Lifecycle
override func awakeFromNib() {
super.awakeFromNib()
override func setupUI() {
super.setupUI()
localizeUI()
containerView.addSubview(counterLabel)
containerView.addSubview(descriptionLabel)
containerView.addSubview(coverageButton)
counterLabel.topAnchor.constraint(equalTo: topView.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
counterLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: .cardPadding).isActive = true
counterLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: .cardPadding.negative).isActive = true
descriptionLabel.topAnchor.constraint(equalTo: counterLabel.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
descriptionLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: .cardPadding).isActive = true
descriptionLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: .cardPadding.negative).isActive = true
coverageButton.heightAnchor.constraint(greaterThanOrEqualToConstant: 40.0).isActive = true
coverageButton.topAnchor.constraint(equalTo: descriptionLabel.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
coverageButton.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: .cardPadding).isActive = true
coverageButton.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: .cardPadding.negative).isActive = true
coverageButton.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: .cardPadding.negative).isActive = true
}
// MARK: - Private
private func localizeUI() {
headerLabel.text = NSLocalizedString("main_network", comment: "")
coverageDescriptionLabel.text = NSLocalizedString("main_monitoring_currently2", comment: "")
localCoverageButton.setLocalizedTitle(key: "main_coverage", uppercased: true, emoji: "🗺")
override func updateUI() {
super.updateUI()
coverageButton.setLocalizedTitle(key: "main_coverage", uppercased: true, emoji: "🗺")
descriptionLabel.text = NSLocalizedString("main_monitoring_currently2", comment: "")
}
private func updateUI() {
guard let smartphoneNetwork = smartphoneNetwork else { return }
smartphoneCounterLabel.text = "\(smartphoneNetwork.counterSmartphones)"
// MARK: - Public
@objc
func update(with smartphoneNetwork: EQNReteSmartphone?) {
guard let smartphoneNetwork else { return }
counterLabel.text = "\(smartphoneNetwork.counterSmartphones)"
}
// MARK: - Actions
@IBAction private func localCovergeTapped(_ sender: UIButton) {
@objc private func localCovergeTapped(_ sender: UIButton) {
onTapButton?()
}
}
@@ -65,6 +65,8 @@ class PasquakesMapViewController: EQNBaseMapViewController {
}
override func didTapAnnotation(_ annotation: MKAnnotation) {
mapView.deselectAnnotation(annotation, animated: true)
guard let annotation = annotation as? EQNMapAnnotationPastquake, let pastquake = annotation.pastquake else {
return
}
@@ -96,8 +98,8 @@ class PasquakesMapViewController: EQNBaseMapViewController {
.first
// controlliamo che sia inferiore al raggio massimo impostato per le notifiche
if let radiusLow = Double(EQNAllertaSismica.shared().raggioSismiLievi),
let radiusStrong = Double(EQNAllertaSismica.shared().raggioSismiForti),
if let radiusLow = Double(EQNSettingRealTimeAlert.shared.raggioSismiLievi),
let radiusStrong = Double(EQNSettingRealTimeAlert.shared.raggioSismiForti),
let nearestPastquake = nearestPastquake {
let radius = max(radiusLow, radiusStrong)
if abs(nearestPastquake.coordinate.distance(from: userPosition)) < radius {
@@ -0,0 +1,84 @@
//
// EQNBackgrounPositionDebugViewController.swift
// Earthquake Network
//
// Created by Andrea Busi on 14/08/23.
// Copyright © 2023 Earthquake Network. All rights reserved.
//
import UIKit
import CoreLocation
class EQNBackgroundPositionDebugViewController: UITableViewController {
private var positions = [EQNBackgroundPosition]()
private let formatter: DateFormatter = {
let formatter = DateFormatter()
formatter.timeStyle = .medium
formatter.dateStyle = .medium
return formatter
}()
private let helper = EQNBackgroundPositionDebugHelper()
// MARK: - Init
convenience init() {
self.init(style: .insetGrouped)
}
// MARK: - View Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
configureUI()
loadData()
}
private func configureUI() {
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 60.0
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .trash, target: self, action: #selector(onTapDeleteButton(_:)))
}
private func loadData() {
positions = helper.loadPosition()
tableView.reloadData()
}
// MARK: - Table view data source
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
positions.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let position = positions[indexPath.row]
let cell = UITableViewCell(style: .subtitle, reuseIdentifier: "PositionCell")
cell.textLabel?.text = formatter.string(from: position.date)
cell.detailTextLabel?.text = String(format: "Lat: %.4f - Lon: %.4f", position.coordinate.latitude, position.coordinate.longitude)
var imageView: UIImageView?
switch position.request {
case .none:
imageView = nil
case .some(true):
imageView = .init(image: .init(systemName: "checkmark.circle.fill"))
imageView?.tintColor = AppTheme.Colors.green
case .some(false):
imageView = .init(image: .init(systemName: "x.circle.fill"))
imageView?.tintColor = AppTheme.Colors.red
}
cell.accessoryView = imageView
return cell
}
// MARK: - Actions
@objc private func onTapDeleteButton(_ sender: UIBarButtonItem) {
helper.resetPositions()
loadData()
}
}
@@ -57,8 +57,8 @@ class EQNDebugViewController: UIViewController {
}
private func loadDebugData() {
let firebaseToken = UserDefaults.standard.string(forKey: EQNUserDefaultUserFirebaseToken) ?? ""
let pushToken = UserDefaults.standard.string(forKey: EQNUserDefaultPushToken) ?? ""
let firebaseToken = UserDefaults.standard.string(forKey: UserDefaults.UserDataFirebaseToken) ?? ""
let pushToken = UserDefaults.standard.string(forKey: UserDefaults.UserDataPushToken) ?? ""
let text =
"""
@@ -29,7 +29,7 @@
[[EQNManager defaultManager] avviaManager];
[[EQNAccelerometroManager sharedInstance] startUpdatingLocationBackground];
self.testo = [NSString stringWithFormat:@" LOG ID UTENTE %@\n\nTOKEN FIREBASE:\n%@\n\n", [EQNUser defaultUser].user_ID, [EQNUser defaultUser].tokenUser];
self.testo = [NSString stringWithFormat:@" LOG ID UTENTE %@\n\nTOKEN FIREBASE:\n%@\n\n", [EQNUser defaultUser].user_ID, EQNUserData.sharedData.firebaseToken];
self.logView.text = self.testo;
}
@@ -21,7 +21,11 @@
- (BOOL)isBannerVisible
{
#if ADS_ENABLED
return ![EQNPurchaseUtility isProVersionEnabled];
#else
return NO;
#endif
}
#pragma mark - View Lifecycle
@@ -88,12 +92,9 @@
}
// Determine the view width to use for the ad width.
CGRect frame = self.view.frame;
// Here safe area is taken into account, hence the view frame is used after
// the view has been laid out.
if (@available(iOS 11.0, *)) {
frame = UIEdgeInsetsInsetRect(self.view.frame, self.view.safeAreaInsets);
}
CGRect frame = UIEdgeInsetsInsetRect(self.view.frame, self.view.safeAreaInsets);
CGFloat viewWidth = frame.size.width;
// Step 3 - Get Adaptive GADAdSize and set the ad view.
@@ -9,7 +9,6 @@
#import "EQNMainTabBarController.h"
#import "AppDelegate.h"
#import "EQNBaseViewController.h"
#import "SettingsBaseViewController.h"
#import "EQNManager.h"
#import "ServerRequest.h"
@@ -20,9 +19,6 @@
@implementation EQNMainTabBarController
static NSString * const SegueIdentifierSettings = @"ShowSettings";
static NSString * const SegueIdentifierLogs = @"ShowLogs";
#pragma mark - View Lifecycle
- (void)viewDidLoad
@@ -41,6 +37,7 @@ static NSString * const SegueIdentifierLogs = @"ShowLogs";
object:nil];
[self sincronizza];
[self migrationV5_8];
}
#pragma mark - Private
@@ -53,6 +50,23 @@ static NSString * const SegueIdentifierLogs = @"ShowLogs";
self.tabBar.items[EQNTabBarSectionImpostazioni].title = [NSLocalizedString(@"drawer_main_settings", comment: "") capitalizedString];
}
- (void)migrationV5_8
{
// forziamo il salvataggio delle impostazioni di notifica, perchè i vari valori devono essere migrati
BOOL alreadyMigrated = [NSUserDefaults.standardUserDefaults boolForKey:NSUserDefaults.SaveSettingsNotificationMigrationV5_8];
if (alreadyMigrated) {
return;
}
NSLog(@"[MIGRATION] perform notification settings save");
[SettingsBaseTableViewController saveSettingsWithCompletion:^(BOOL success) {
if (success) {
NSLog(@"[MIGRATION] settings saved");
[NSUserDefaults.standardUserDefaults setBool:true forKey:NSUserDefaults.SaveSettingsNotificationMigrationV5_8];
}
}];
}
#pragma mark - Notification
- (void)serverRegistrationFailedNotification:(NSNotification *)notification
@@ -63,9 +77,9 @@ static NSString * const SegueIdentifierLogs = @"ShowLogs";
preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"retry", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
// retry server registration
[[EQNUser defaultUser] verificaRegistrazione];
[[EQNUser defaultUser] retryUserRegistration];
}]];
[alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"options_cancel", nil) style:UIAlertActionStyleCancel handler:nil]];
[alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"status_cancel", nil) style:UIAlertActionStyleCancel handler:nil]];
[self presentViewController:alert animated:YES completion:nil];
});
}
@@ -119,7 +133,7 @@ static NSString * const SegueIdentifierLogs = @"ShowLogs";
// if user switch from settings page, we need to force a settings save
UIViewController *controller = [self getTopControllerFromController:tabBarController.selectedViewController];
if ([controller isKindOfClass:[SettingsViewController class]]) {
[SettingsBaseViewController saveSettings];
[SettingsBaseTableViewController saveSettings];
}
return YES;
@@ -136,7 +150,7 @@ static NSString * const SegueIdentifierLogs = @"ShowLogs";
}
// tap 5 times on "Settings" to open debug view
if ([controller isKindOfClass:[SettingsViewController class]]) {
if ([controller isKindOfClass:[SettingsViewController class]] && EQNEnableDebugView) {
self.debugTapCounter += 1;
if (self.debugTapCounter == 5) {
self.debugTapCounter = 0;
@@ -1,181 +0,0 @@
//
// PurchaseProVersionViewController.swift
// Earthquake Network
//
// Created by Busi Andrea on 29/07/2020.
// Copyright © 2020 Earthquake Network. All rights reserved.
//
import UIKit
import SafariServices
import StoreKit
class PurchaseProVersionViewController: UIViewController {
@IBOutlet private weak var containerView: UIView!
@IBOutlet private weak var titleLabel: UILabel!
@IBOutlet private weak var subtitleLabel: UILabel!
@IBOutlet private weak var discountTextLabel: UILabel!
@IBOutlet private weak var descriptionTextLabel: UILabel!
@IBOutlet private weak var openPrivacyButton: UIButton!
@IBOutlet private weak var openTermsButton: UIButton!
@IBOutlet private weak var payingLabel: UILabel!
@IBOutlet private weak var priceLabel: UILabel!
@IBOutlet private weak var purchaseButton: UIButton!
// MARK: - Internal
private var products = [SKProduct]()
private var proProduct: SKProduct?
private var restoreTapped = false
/// Time remaining (in hours) for discounted price. If zero, no discount available
private var discountTimeRemaining: Int = 0
// MARK: - View Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(handlePurchaseNotification(_:)),
name: .EQNInAppPurchaseDidComplete,
object: nil)
configureUI()
loadProducts()
checkDiscountPrice()
}
// MARK: - Private
private func configureUI() {
let restoreButton = UIBarButtonItem(title: NSLocalizedString("purchase_pro_restore", comment: ""),
style: .plain,
target: self,
action: #selector(restoreTapped(_:)))
navigationItem.rightBarButtonItem = restoreButton
purchaseButton.isEnabled = false
titleLabel.text = NSLocalizedString("network_pro", comment: "")
subtitleLabel.text = NSLocalizedString("network_pro_subtitle", comment: "")
descriptionTextLabel.text = NSLocalizedString("purchase_pro_description", comment: "")
discountTextLabel.text = NSLocalizedString("purchase_pro_discount", comment: "")
discountTextLabel.isHidden = true
openPrivacyButton.setTitle(NSLocalizedString("network_pro_privacy_disclaimer", comment: ""), for: .normal)
openTermsButton.setTitle(NSLocalizedString("network_pro_terms_conditions", comment: ""), for: .normal)
payingLabel.text = NSLocalizedString("network_pro_paying", comment: "")
purchaseButton.setTitle(NSLocalizedString("network_pro_convert", comment: "").uppercased(), for: .normal)
containerView.eqn_applyShadowAndRoundedCorners()
}
private func updateUI() {
// search for the Pro product
let isDiscountEnabled = discountTimeRemaining > 0
let identifier = isDiscountEnabled ? VersioneProProducts.Identifier.ProVersionDiscounted : VersioneProProducts.Identifier.ProVersionFullPrice
guard let proProduct = products.first(where: { $0.productIdentifier.lowercased() == identifier.lowercased() }) else {
return
}
self.proProduct = proProduct
priceFormatter.locale = proProduct.priceLocale
if isDiscountEnabled {
discountTextLabel.isHidden = false
let string = NSLocalizedString("purchase_pro_discount", comment: "")
discountTextLabel.text = String(format: string, discountTimeRemaining)
}
priceLabel.text = priceFormatter.string(from: proProduct.price)
purchaseButton.isEnabled = true
if UserDefaults.standard.bool(forKey: VersioneProProducts.Identifier.ProVersionFullPrice) ||
UserDefaults.standard.bool(forKey: VersioneProProducts.Identifier.ProVersionDiscounted) ||
UserDefaults.standard.bool(forKey: VersioneProProducts.Identifier.Subscription10kYearly) ||
UserDefaults.standard.bool(forKey: VersioneProProducts.Identifier.Subscription10kYearlyDiscounted) ||
UserDefaults.standard.bool(forKey: VersioneProProducts.Identifier.Subscription100kYearly) ||
UserDefaults.standard.bool(forKey: VersioneProProducts.Identifier.Subscription100kYearlyDiscounted) {
purchaseButton.isEnabled = false
priceLabel.text = "-"
}
}
private func loadProducts() {
VersioneProProducts.store.requestProducts { [weak self] success, products in
guard let self = self, let products = products, success == true else { return }
self.products = products
self.updateUI()
}
}
private func checkDiscountPrice() {
EQNPurchaseUtility.offerTimeRemaining { (timeRemaining) in
DispatchQueue.main.async {
self.discountTimeRemaining = timeRemaining
self.updateUI()
}
}
}
// MARK: - Actions
@objc func restoreTapped(_ sender: Any) {
restoreTapped = true
VersioneProProducts.store.restorePurchases()
}
@IBAction func purchaseTapped(_ sender: UIButton) {
guard let product = proProduct else { return }
VersioneProProducts.store.buyProduct(product)
}
@IBAction func openExternalLinkTapped(_ sender: UIButton) {
var linkUrl: URL?
if sender == openPrivacyButton {
linkUrl = URL(string: "\(EQNWebsiteAddress)/privacy/")
} else if sender == openTermsButton {
linkUrl = URL(string: "\(EQNWebsiteAddress)/terms-conditions/")
}
if let url = linkUrl {
let controller = SFSafariViewController(url: url)
present(controller, animated: true, completion: nil)
}
}
// MARK: - Notifications
@objc func handlePurchaseNotification(_ notification: Notification) {
guard let productId = notification.object as? String,
products.contains(where: { $0.productIdentifier == productId }) else {
print("[PurchasePro] Unable to find the product")
return
}
if restoreTapped {
restoreTapped = false
let alert = UIAlertController(title: NSLocalizedString("purchase_pro_restore_alert_title", comment: ""),
message: NSLocalizedString("purchase_pro_restore_alert_message", comment: ""),
preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "ok", style: .default) { [weak self] _ in
self?.navigationController?.popViewController(animated: true)
})
present(alert, animated: true)
} else {
navigationController?.popViewController(animated: true)
}
}
// MARK: - Helper
private var priceFormatter: NumberFormatter = {
let formatter = NumberFormatter()
formatter.formatterBehavior = .behavior10_4
formatter.numberStyle = .currency
return formatter
}()
}
@@ -1,151 +0,0 @@
//
// SubscriptionDetailViewController.swift
// Earthquake Network
//
// Created by Busi Andrea on 29/07/2020.
// Copyright © 2020 Earthquake Network. All rights reserved.
//
import UIKit
import SafariServices
import StoreKit
class SubscriptionDetailViewController: UIViewController {
/// Enable this allows shake to enable the current subscription
private static let ShakeToEnableSubscription = false
var product: SKProduct? {
didSet {
updateUI()
}
}
@IBOutlet private weak var containerView: UIView!
@IBOutlet private weak var productTitleLabel: UILabel!
@IBOutlet private weak var productImageView: UIImageView!
@IBOutlet private weak var productDescriptionLabel: UILabel!
@IBOutlet private weak var subscriptionDetailsLabel: UILabel!
@IBOutlet private weak var openPrivacyButton: UIButton!
@IBOutlet private weak var openTermsButton: UIButton!
@IBOutlet private weak var purchaseRecapLabel: UILabel!
@IBOutlet private weak var productPriceLabel: UILabel!
@IBOutlet private weak var purchaseButton: UIButton!
// MARK: - View Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(handlePurchaseNotification(_:)),
name: .EQNInAppPurchaseDidComplete,
object: nil)
updateUI()
setupUI()
}
// MARK: - Private
private func setupUI() {
containerView.eqn_applyShadowAndRoundedCorners()
}
private func updateUI() {
guard let product = product, isViewLoaded else { return }
productImageView.image = VersioneProProducts.image(for: product.productIdentifier)
productTitleLabel.text = product.localizedTitle
productDescriptionLabel.text = product.localizedDescription
var purchaseRecapString = ""
var subscriptionDetailsString = ""
switch product.productIdentifier {
case VersioneProProducts.Identifier.Subscription10kMonthly,
VersioneProProducts.Identifier.Subscription100kMonthly:
purchaseRecapString = "inapp_monthly_payment"
subscriptionDetailsString = "inapp_detail_description"
case VersioneProProducts.Identifier.Subscription100kYearly,
VersioneProProducts.Identifier.Subscription100kYearlyDiscounted,
VersioneProProducts.Identifier.Subscription10kYearly,
VersioneProProducts.Identifier.Subscription10kYearlyDiscounted:
purchaseRecapString = "inapp_yearly_payment"
subscriptionDetailsString = "inapp_detail_description"
case VersioneProProducts.Identifier.Subscription10kPerpetual,
VersioneProProducts.Identifier.Subscription100kPerpetual:
purchaseRecapString = "inapp_lifetime_payment"
subscriptionDetailsString = "inapp_lifetime_detail_description"
default:
break
}
subscriptionDetailsLabel.text = NSLocalizedString(subscriptionDetailsString, comment: "")
openPrivacyButton.setTitle(NSLocalizedString("network_pro_privacy_disclaimer", comment: ""), for: .normal)
openTermsButton.setTitle(NSLocalizedString("network_pro_terms_conditions", comment: ""), for: .normal)
purchaseRecapLabel.text = "\(product.localizedDescription), \(NSLocalizedString(purchaseRecapString, comment: ""))"
priceFormatter.locale = product.priceLocale
productPriceLabel.text = priceFormatter.string(from: product.price)
purchaseButton.setTitle(NSLocalizedString("inapp_purchase", comment: ""), for: .normal)
}
// MARK: - Notifications
@objc func handlePurchaseNotification(_ notification: Notification) {
navigationController?.popViewController(animated: true)
}
// MARK: - Actions
@IBAction func openExternalLinkTapped(_ sender: UIButton) {
var linkUrl: URL?
if sender == openPrivacyButton {
linkUrl = URL(string: "\(EQNWebsiteAddress)/privacy/")
} else if sender == openTermsButton {
linkUrl = URL(string: "\(EQNWebsiteAddress)/terms-conditions/")
}
if let url = linkUrl {
let controller = SFSafariViewController(url: url)
present(controller, animated: true, completion: nil)
}
}
@IBAction func subscribeTapped(_ sender: UIButton) {
guard let product = product else { return }
VersioneProProducts.store.buyProduct(product)
}
// MARK: - Helper
private var priceFormatter: NumberFormatter = {
let formatter = NumberFormatter()
formatter.formatterBehavior = .behavior10_4
formatter.numberStyle = .currency
return formatter
}()
}
extension SubscriptionDetailViewController {
override func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
guard let product = product, event?.subtype == .motionShake, Self.ShakeToEnableSubscription else {
return
}
let alert = UIAlertController(title: "🧑‍💻", message: "Please select an action", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Reset all purchases", style: .default) { action in
EQNPurchaseUtility.resetInAppPurchases()
})
alert.addAction(UIAlertAction(title: "Activate this subscription", style: .default) { action in
EQNPurchaseUtility.simulateProPurchase(identifier: product.productIdentifier)
})
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
present(alert, animated: true, completion: nil)
}
}
@@ -0,0 +1,204 @@
//
// SubscriptionDetailsTableViewCell.swift
// Earthquake Network
//
// Created by Andrea Busi on 18/06/24.
// Copyright © 2024 Earthquake Network. All rights reserved.
//
import UIKit
class SubscriptionDetailsTableViewCell: EQNBaseContainerTableViewCell {
var onTapPrivacy: () -> Void = { }
var onTapTerms: () -> Void = { }
var onTapPurchase: () -> Void = { }
var onChangePlan: (_ type: EQNInAppProducts.Plan) -> Void = { _ in }
override var isHeaderVisible: Bool { false }
// MARK: - UI
lazy var planSegmentedControl: UISegmentedControl = {
let control = UISegmentedControl(items: EQNInAppProducts.Plan.allCases.map(\.localizedTitle))
control.translatesAutoresizingMaskIntoConstraints = false
control.addTarget(self, action: #selector(onChangeSegmentedControl(_:)), for: .valueChanged)
return control
}()
lazy var productTitleLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.textColor = AppTheme.shared.cardTextColor
label.font = .preferredFont(forTextStyle: .title1)
label.textAlignment = .center
label.numberOfLines = 0
return label
}()
lazy var productImageView: UIImageView = {
let imageView = UIImageView(image: .init(named: "top_100k"))
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFit
imageView.heightAnchor.constraint(greaterThanOrEqualToConstant: 50.0).isActive = true
return imageView
}()
lazy var subscriptionDetailsLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.textColor = AppTheme.shared.cardTextColor
label.font = .preferredFont(forTextStyle: .body)
label.textAlignment = .justified
label.numberOfLines = 0
return label
}()
lazy var openPrivacyButton: UIButton = {
let button = UIButton(type: .system)
button.translatesAutoresizingMaskIntoConstraints = false
button.addTarget(self, action: #selector(onTapOpenPrivacyButton(_:)), for: .touchUpInside)
button.contentHorizontalAlignment = .leading
return button
}()
lazy var openTermsButton: UIButton = {
let button = UIButton(type: .system)
button.translatesAutoresizingMaskIntoConstraints = false
button.addTarget(self, action: #selector(onTapOpenTermsButton(_:)), for: .touchUpInside)
button.contentHorizontalAlignment = .leading
return button
}()
lazy var purchaseRecapLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.textColor = AppTheme.shared.cardTextColor
label.font = .preferredFont(forTextStyle: .headline)
label.numberOfLines = 0
label.textAlignment = .center
return label
}()
lazy var productPriceLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.textColor = AppTheme.shared.cardTextColor
label.font = .preferredFont(forTextStyle: .largeTitle)
label.numberOfLines = 0
label.textAlignment = .center
return label
}()
lazy var purchaseButton: UIButton = {
let button = UIButton(type: .system)
button.translatesAutoresizingMaskIntoConstraints = false
button.addTarget(self, action: #selector(onTapPurchaseButton(_:)), for: .touchUpInside)
button.titleLabel?.font = .preferredFont(forTextStyle: .headline)
button.heightAnchor.constraint(equalToConstant: 50.0).isActive = true
button.backgroundColor = .systemGroupedBackground
button.eqn_applyShadowAndRoundedCorners()
return button
}()
// MARK: - Internal
override func setupUI() {
super.setupUI()
containerView.addSubview(planSegmentedControl)
containerView.addSubview(productTitleLabel)
containerView.addSubview(productImageView)
containerView.addSubview(subscriptionDetailsLabel)
containerView.addSubview(openPrivacyButton)
containerView.addSubview(openTermsButton)
containerView.addSubview(purchaseRecapLabel)
containerView.addSubview(productPriceLabel)
containerView.addSubview(purchaseButton)
let leading: NSLayoutXAxisAnchor = planSegmentedControl.leadingAnchor
let trailing: NSLayoutXAxisAnchor = planSegmentedControl.trailingAnchor
planSegmentedControl.topAnchor.constraint(equalTo: topView.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
planSegmentedControl.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: .cardPadding).isActive = true
planSegmentedControl.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: .cardPadding.negative).isActive = true
productTitleLabel.topAnchor.constraint(equalTo: planSegmentedControl.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
productTitleLabel.leadingAnchor.constraint(equalTo: leading, constant: .cardPadding).isActive = true
productTitleLabel.trailingAnchor.constraint(equalTo: trailing, constant: .cardPadding.negative).isActive = true
productImageView.topAnchor.constraint(equalTo: productTitleLabel.bottomAnchor, constant: .cardVerticalSpacing.x2).isActive = true
productImageView.leadingAnchor.constraint(equalTo: leading).isActive = true
productImageView.trailingAnchor.constraint(equalTo: trailing).isActive = true
purchaseRecapLabel.topAnchor.constraint(equalTo: productImageView.bottomAnchor, constant: .cardVerticalSpacing.x2).isActive = true
purchaseRecapLabel.leadingAnchor.constraint(equalTo: leading).isActive = true
purchaseRecapLabel.trailingAnchor.constraint(equalTo: trailing).isActive = true
productPriceLabel.topAnchor.constraint(equalTo: purchaseRecapLabel.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
productPriceLabel.leadingAnchor.constraint(equalTo: leading).isActive = true
productPriceLabel.trailingAnchor.constraint(equalTo: trailing).isActive = true
purchaseButton.topAnchor.constraint(equalTo: productPriceLabel.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
purchaseButton.leadingAnchor.constraint(equalTo: leading).isActive = true
purchaseButton.trailingAnchor.constraint(equalTo: trailing).isActive = true
subscriptionDetailsLabel.topAnchor.constraint(equalTo: purchaseButton.bottomAnchor, constant: .cardVerticalSpacing.x2).isActive = true
subscriptionDetailsLabel.leadingAnchor.constraint(equalTo: leading).isActive = true
subscriptionDetailsLabel.trailingAnchor.constraint(equalTo: trailing).isActive = true
openPrivacyButton.topAnchor.constraint(equalTo: subscriptionDetailsLabel.bottomAnchor, constant: .cardVerticalSpacing.x2).isActive = true
openPrivacyButton.leadingAnchor.constraint(equalTo: leading).isActive = true
openPrivacyButton.trailingAnchor.constraint(equalTo: trailing).isActive = true
openTermsButton.topAnchor.constraint(equalTo: openPrivacyButton.bottomAnchor, constant: .cardPadding).isActive = true
openTermsButton.leadingAnchor.constraint(equalTo: leading).isActive = true
openTermsButton.trailingAnchor.constraint(equalTo: trailing).isActive = true
openTermsButton.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: .cardVerticalSpacing.x2.negative).isActive = true
}
override func updateUI() {
super.updateUI()
openPrivacyButton.setTitle(NSLocalizedString("network_pro_privacy_disclaimer", comment: ""), for: .normal)
openTermsButton.setTitle(NSLocalizedString("network_pro_terms_conditions", comment: ""), for: .normal)
purchaseButton.setTitle(NSLocalizedString("inapp_purchase", comment: ""), for: .normal)
}
// MARK: - Actions
@objc private func onTapOpenPrivacyButton(_ sender: UIButton) {
onTapPrivacy()
}
@objc private func onTapOpenTermsButton(_ sender: UIButton) {
onTapTerms()
}
@objc private func onTapPurchaseButton(_ sender: UIButton) {
onTapPurchase()
}
@objc private func onChangeSegmentedControl(_ sender: UISegmentedControl) {
let type: EQNInAppProducts.Plan = .from(index: sender.selectedSegmentIndex)
onChangePlan(type)
}
}
extension EQNInAppProducts.Plan {
var index: Int {
switch self {
case .monthly: 0
case .yearly: 1
case .perpetual: 2
}
}
static func from(index: Int) -> Self {
switch index {
case 0: .monthly
case 1: .yearly
default: .perpetual
}
}
}
@@ -0,0 +1,182 @@
//
// SubscriptionDetailsViewController.swift
// Earthquake Network
//
// Created by Andrea Busi on 18/06/24.
// Copyright © 2024 Earthquake Network. All rights reserved.
//
import UIKit
import StoreKit
import SafariServices
import Shogun
class SubscriptionDetailsViewController: UITableViewController {
/// Enable this allows shake to enable the current subscription
private static let ShakeToEnableSubscription = false
// MARK: - Internal
private let products: [EQNInAppProducts]
private var selectedProduct: EQNInAppProducts {
didSet {
onProductSelected()
}
}
private var priceFormatter: NumberFormatter = {
let formatter = NumberFormatter()
formatter.formatterBehavior = .behavior10_4
formatter.numberStyle = .currency
return formatter
}()
// MARK: - Init
init(
products: [EQNInAppProducts]
) {
self.products = products
self.selectedProduct = products.first(where: { $0.plan == .monthly }) ?? products.first!
super.init(style: .plain)
}
required init?(coder: NSCoder) {
fatalError("Please use init(products:) instead.")
}
// MARK: - View Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
configureUI()
addObservers()
}
private func addObservers() {
NotificationCenter.default.addObserver(self, selector: #selector(handlePurchaseNotification(_:)),
name: .EQNInAppPurchaseDidComplete,
object: nil)
}
private func configureUI() {
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 2000.0
tableView.separatorStyle = .none
tableView.backgroundColor = .systemGroupedBackground
tableView.registerCell(for: SubscriptionDetailsTableViewCell.self)
}
// MARK: - Notifications
@objc private func handlePurchaseNotification(_ notification: Notification) {
navigationController?.popViewController(animated: true)
}
// MARK: - Table view delegate & data source
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
1
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(cellIdentifiable: SubscriptionDetailsTableViewCell.self, for: indexPath)
cell.selectionStyle = .none
cell.productTitleLabel.text = selectedProduct.product.localizedTitle
cell.productImageView.image = selectedProduct.category.image
var purchaseRecapString = ""
var subscriptionDetailsString = ""
switch selectedProduct.productIdentifier {
case EQNInAppProducts.Identifier.Subscription10kMonthly,
EQNInAppProducts.Identifier.Subscription100kMonthly:
purchaseRecapString = "inapp_monthly_payment"
subscriptionDetailsString = "inapp_detail_description"
case EQNInAppProducts.Identifier.Subscription100kYearly,
EQNInAppProducts.Identifier.Subscription100kYearlyDiscounted,
EQNInAppProducts.Identifier.Subscription10kYearly,
EQNInAppProducts.Identifier.Subscription10kYearlyDiscounted:
purchaseRecapString = "inapp_yearly_payment"
subscriptionDetailsString = "inapp_detail_description"
case EQNInAppProducts.Identifier.Subscription10kPerpetual,
EQNInAppProducts.Identifier.Subscription100kPerpetual:
purchaseRecapString = "inapp_lifetime_payment"
subscriptionDetailsString = "inapp_lifetime_detail_description"
default:
break
}
cell.subscriptionDetailsLabel.text = NSLocalizedString(subscriptionDetailsString, comment: "")
cell.onTapPrivacy = { [weak self] in
self?.openExternalLink("\(EQNWebsiteAddress)/privacy/")
}
cell.onTapTerms = { [weak self] in
self?.openExternalLink("\(EQNWebsiteAddress)/terms-conditions/")
}
cell.onTapPurchase = { [weak self] in
self?.purchaseSelectedProduct()
}
cell.onChangePlan = { [weak self] type in
if let product = self?.productFromProductType(type) {
self?.selectedProduct = product
}
}
cell.planSegmentedControl.selectedSegmentIndex = selectedProduct.plan.index
cell.purchaseRecapLabel.text = "\(selectedProduct.product.localizedDescription), \(NSLocalizedString(purchaseRecapString, comment: ""))"
cell.productPriceLabel.text = priceFormatter.string(from: selectedProduct.product.price)
return cell
}
// MARK: - Private
private func onProductSelected() {
priceFormatter.locale = selectedProduct.product.priceLocale
tableView.reloadData()
}
private func openExternalLink(_ stringUrl: String) {
if let url = URL(string: stringUrl) {
let controller = SFSafariViewController(url: url)
present(controller, animated: true, completion: nil)
}
}
private func purchaseSelectedProduct() {
EQNInAppProducts.store.buyProduct(selectedProduct.product)
}
private func productFromProductType(_ type: EQNInAppProducts.Plan) -> EQNInAppProducts? {
let product: EQNInAppProducts?
switch type {
case .monthly:
product = products.first { $0.plan == .monthly }
case .yearly:
product = products.first { $0.plan == .yearly }
case .perpetual:
product = products.first { $0.plan == .perpetual }
}
return product
}
}
extension SubscriptionDetailsViewController {
override func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
guard event?.subtype == .motionShake, Self.ShakeToEnableSubscription else {
return
}
let alert = UIAlertController(title: "🧑‍💻", message: "Please select an action", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Reset all purchases", style: .default) { action in
EQNPurchaseUtility.resetInAppPurchases()
})
alert.addAction(UIAlertAction(title: "Activate this subscription", style: .default) { action in
EQNPurchaseUtility.simulateProPurchase(identifier: self.selectedProduct.productIdentifier)
})
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
present(alert, animated: true, completion: nil)
}
}
@@ -9,57 +9,81 @@
import UIKit
import StoreKit
class SubscriptionProductTableViewCell: UITableViewCell {
class SubscriptionProductTableViewCell: EQNBaseContainerTableViewCell {
override var isHeaderVisible: Bool { false }
override var isRightArrowVisbile: Bool { true }
// MARK: - UI
private lazy var productImageView: UIImageView = {
let imageView = UIImageView(frame: .zero)
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFit
return imageView
}()
var product: SKProduct? {
didSet {
updateUI()
}
}
var availability: EQNPurchaseAvailability? {
didSet {
updateUI()
}
}
private lazy var productTitleLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.textColor = AppTheme.shared.cardTextColor
label.font = .preferredFont(forTextStyle: .headline)
label.numberOfLines = 0
return label
}()
@IBOutlet private weak var productImageView: UIImageView!
@IBOutlet private weak var productTitleLabel: UILabel!
@IBOutlet private weak var productDescriptionLabel: UILabel?
@IBOutlet private weak var productInfoLabel: UILabel!
// MARK: - View Lifecycle
// force an inset to have the same style of EQNBaseTableViewCell
override var frame: CGRect {
get {
return super.frame
}
set (newFrame) {
let inset: CGFloat = 8
var frame = newFrame
frame.origin.x += inset
frame.size.width -= 2 * inset
super.frame = frame
}
}
// MARK: - Private
private func updateUI() {
guard let product = product else { return }
private lazy var productInfoLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.textColor = AppTheme.Colors.red
label.font = .preferredFont(forTextStyle: .body)
label.numberOfLines = 0
return label
}()
productImageView.image = VersioneProProducts.image(for: product.productIdentifier)
productTitleLabel.text = product.localizedTitle
productDescriptionLabel?.text = product.localizedDescription
// MARK: - Internal
override func setupUI() {
super.setupUI()
let infoKey = VersioneProProducts.is100kSubscription(for: product.productIdentifier) ? "inapp_available_100k" : "inapp_available_10k"
let counter = availability(for: product.productIdentifier)
containerView.addSubview(productImageView)
containerView.addSubview(productTitleLabel)
containerView.addSubview(productInfoLabel)
productImageView.widthAnchor.constraint(equalToConstant: 80.0).isActive = true
productImageView.heightAnchor.constraint(equalToConstant: 40.0).isActive = true
productTitleLabel.topAnchor.constraint(equalTo: topView.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
productTitleLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: .cardPadding.negative).isActive = true
productImageView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: .cardPadding).isActive = true
productImageView.trailingAnchor.constraint(equalTo: productTitleLabel.leadingAnchor, constant: .cardPadding.negative).isActive = true
productImageView.centerYAnchor.constraint(equalTo: productTitleLabel.centerYAnchor).isActive = true
productInfoLabel.topAnchor.constraint(equalTo: productTitleLabel.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
productInfoLabel.leadingAnchor.constraint(equalTo: productImageView.leadingAnchor).isActive = true
productInfoLabel.trailingAnchor.constraint(equalTo: productTitleLabel.trailingAnchor).isActive = true
productInfoLabel.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: .cardVerticalSpacing.negative).isActive = true
}
// MARK: - Public
func update(
category: EQNInAppProducts.Category,
availability: EQNPurchaseAvailability?
) {
productImageView.image = category.image
productTitleLabel.text = category.localizedTitle
let infoKey = category == .top100k ? "inapp_available_100k" : "inapp_available_10k"
let counter = availabilityCounter(for: category, availability: availability)
productInfoLabel.text = String(format: NSLocalizedString(infoKey, comment: ""), counter)
}
private func availability(for productIdentifier: String) -> Int {
if VersioneProProducts.is100kSubscription(for: productIdentifier) {
private func availabilityCounter(
for category: EQNInAppProducts.Category,
availability: EQNPurchaseAvailability?
) -> Int {
if category == .top100k {
return availability?.top100kAvailable ?? 0
}
return availability?.top10kAvailable ?? 0
@@ -9,38 +9,87 @@
import UIKit
import StoreKit
class SubscriptionsActiveTableViewCell: EQNBaseTableViewCell {
var product: SKProduct? {
didSet {
updateUI()
}
}
class SubscriptionsActiveTableViewCell: EQNBaseContainerTableViewCell {
override var headerText: String { NSLocalizedString("inapp_active", comment: "") }
var onTapRestore: () -> Void = { }
// MARK: - UI
private lazy var noSubscriptionsLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.textColor = AppTheme.shared.cardTextColor
label.font = .preferredFont(forTextStyle: .body)
label.textAlignment = .center
label.numberOfLines = 0
return label
}()
@IBOutlet private weak var headerLabel: UILabel!
@IBOutlet private weak var noSubscriptionsLabel: UILabel!
@IBOutlet private weak var activeSubscriptionImageView: UIImageView!
private lazy var activeSubscriptionImageView: UIImageView = {
let imageView = UIImageView(frame: .zero)
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFit
return imageView
}()
// MARK: - View Lifecycle
private lazy var restoreButton: UIButton = {
let button = UIButton(type: .system)
button.translatesAutoresizingMaskIntoConstraints = false
button.addTarget(self, action: #selector(restoreSubscriptionsTapped(_:)), for: .touchUpInside)
button.titleLabel?.font = .preferredFont(forTextStyle: .headline)
button.backgroundColor = .systemGroupedBackground
button.eqn_applyShadowAndRoundedCorners()
return button
}()
// MARK: - Internal
override func awakeFromNib() {
super.awakeFromNib()
override func setupUI() {
super.setupUI()
localizeUI()
let stackView = UIStackView(arrangedSubviews: [ activeSubscriptionImageView, noSubscriptionsLabel, restoreButton ])
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.alignment = .center
stackView.distribution = .equalSpacing
stackView.axis = .vertical
stackView.spacing = 20.0
containerView.addSubview(stackView)
activeSubscriptionImageView.widthAnchor.constraint(equalToConstant: 150.0).isActive = true
activeSubscriptionImageView.heightAnchor.constraint(equalToConstant: 50.0).isActive = true
restoreButton.heightAnchor.constraint(equalToConstant: 40.0).isActive = true
restoreButton.leadingAnchor.constraint(equalTo: stackView.leadingAnchor).isActive = true
restoreButton.trailingAnchor.constraint(equalTo: stackView.trailingAnchor).isActive = true
stackView.topAnchor.constraint(equalTo: topView.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
stackView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: .cardPadding).isActive = true
stackView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: .cardPadding.negative).isActive = true
stackView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: .cardVerticalSpacing.negative).isActive = true
}
// MARK: - Private
private func localizeUI() {
headerLabel.text = NSLocalizedString("inapp_active", comment: "")
override func updateUI() {
super.updateUI()
noSubscriptionsLabel.text = NSLocalizedString("inapp_nosub", comment: "")
restoreButton.setTitle(NSLocalizedString("purchase_pro_restore", comment: ""), for: .normal)
}
private func updateUI() {
if let productIdentifier = product?.productIdentifier {
// MARK: - Actions
@objc private func restoreSubscriptionsTapped(_ sender: UIButton) {
onTapRestore()
}
// MARK: - Public
func update(with product: EQNInAppProducts?) {
if let product {
noSubscriptionsLabel.isHidden = true
activeSubscriptionImageView.isHidden = false
activeSubscriptionImageView.image = VersioneProProducts.image(for: productIdentifier)
activeSubscriptionImageView.image = product.category.image
} else {
noSubscriptionsLabel.isHidden = false
activeSubscriptionImageView.isHidden = true
@@ -8,21 +8,40 @@
import UIKit
class SubscriptionsDescriptionTableViewCell: EQNBaseTableViewCell {
@IBOutlet private weak var headerLabel: UILabel!
@IBOutlet private weak var descriptionLabel: UILabel!
class SubscriptionsDescriptionTableViewCell: EQNBaseContainerTableViewCell {
override func awakeFromNib() {
super.awakeFromNib()
override var headerText: String { NSLocalizedString("inapp_list", comment: "") }
// MARK: - UI
private lazy var descriptionLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.textColor = AppTheme.shared.cardTextColor
label.font = .preferredFont(forTextStyle: .body)
label.textAlignment = .center
label.numberOfLines = 0
label.textAlignment = .justified
return label
}()
// MARK: - Internal
override func setupUI() {
super.setupUI()
localizeUI()
containerView.addSubview(descriptionLabel)
descriptionLabel.topAnchor.constraint(equalTo: topView.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
descriptionLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: .cardPadding).isActive = true
descriptionLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: .cardPadding.negative).isActive = true
descriptionLabel.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: .cardVerticalSpacing.negative).isActive = true
}
// MARK: - Private
private func localizeUI() {
headerLabel.text = NSLocalizedString("inapp_list", comment: "")
override func updateUI() {
super.updateUI()
descriptionLabel.text = NSLocalizedString("inapp_description", comment: "")
}
}
@@ -8,26 +8,53 @@
import UIKit
class SubscriptionsHeaderTableViewCell: UITableViewCell {
var isLoading = false {
didSet {
updateUI()
}
class SubscriptionsHeaderTableViewCell: UITableViewHeaderFooterView {
// MARK: - UI
private lazy var headerTitleLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = UIFont.preferredFont(forTextStyle: .title2)
label.textColor = AppTheme.Colors.darkGray
return label
}()
private lazy var loadingActivityIndicator: UIActivityIndicatorView = {
let spinner = UIActivityIndicatorView(style: .medium)
spinner.translatesAutoresizingMaskIntoConstraints = false
spinner.hidesWhenStopped = true
return spinner
}()
// MARK: - Init
override init(reuseIdentifier: String?) {
super.init(reuseIdentifier: reuseIdentifier)
setupUI()
}
var title: String? = nil {
didSet {
updateUI()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupUI()
}
@IBOutlet private weak var headerTitleLabel: UILabel!
@IBOutlet private weak var loadingActivityIndicator: UIActivityIndicatorView!
// MARK: - Private
private func updateUI() {
private func setupUI() {
contentView.addSubview(headerTitleLabel)
contentView.addSubview(loadingActivityIndicator)
headerTitleLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
headerTitleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: .cardPadding).isActive = true
loadingActivityIndicator.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: .cardPadding.negative).isActive = true
loadingActivityIndicator.centerYAnchor.constraint(equalTo: headerTitleLabel.centerYAnchor).isActive = true
}
// MARK: - Public
func update(isLoading: Bool, title: String?) {
headerTitleLabel.text = title
if isLoading && title != nil {
@@ -8,39 +8,31 @@
import UIKit
import StoreKit
import Shogun
@objc
class SubscriptionsViewController: UITableViewController {
private static let SegueIdentifierSubscriptionDetail = "ShowSubscriptionDetail"
private static let CellHeightDescription: CGFloat = 320.0
// sezioni
private enum TableSection: CaseIterable {
case active
case description
case monthly
case yearly
case perpetual
case products
var sectionTitle: String? {
switch self {
case .monthly: return NSLocalizedString("inapp_monthly_subscriptions", comment: "")
case .yearly: return NSLocalizedString("inapp_yearly_subscriptions", comment: "")
case .perpetual: return NSLocalizedString("inapp_lifetime_subscriptions", comment: "")
default: return nil
case .products: NSLocalizedString("subscriptions_available", comment: "")
default: nil
}
}
}
private let sections = TableSection.allCases
private var allProducts = [SKProduct]()
private var monthlyProducts = [SKProduct]()
private var yearlyProducts = [SKProduct]()
private var perpetualProducts = [SKProduct]()
/// All products retrieved from AppStore
private var products = [EQNInAppProducts]()
/// Product already bought by the user
private var subscribedProduct: SKProduct?
private var productSubscribed: EQNInAppProducts?
/// Availability for subscriptions
private var availability: EQNPurchaseAvailability?
/// Tells if products are loading
@@ -48,6 +40,13 @@ class SubscriptionsViewController: UITableViewController {
/// Tells if a restore is in progress
private var isRestorePurchase = false
// MARK: - Init
@objc
convenience init() {
self.init(style: .plain)
}
// MARK: - View Lifecycle
override func viewDidLoad() {
@@ -63,15 +62,7 @@ class SubscriptionsViewController: UITableViewController {
loadData()
checkAvailabilities()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == Self.SegueIdentifierSubscriptionDetail,
let controller = segue.destination as? SubscriptionDetailViewController,
let product = sender as? SKProduct {
controller.product = product
}
}
// MARK: - Private
private func addObservers() {
@@ -89,75 +80,52 @@ class SubscriptionsViewController: UITableViewController {
}
private func configureUI() {
let restoreButton = UIBarButtonItem(title: NSLocalizedString("purchase_pro_restore", comment: ""),
style: .plain,
target: self,
action: #selector(restoreTapped(_:)))
navigationItem.rightBarButtonItem = restoreButton
// if is presented in Simulator, add done button
if navigationController?.viewControllers.first == self {
let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(closeTapped(_:)))
navigationItem.leftBarButtonItem = doneButton
}
navigationItem.largeTitleDisplayMode = .never
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = Self.CellHeightDescription;
}
private func updateUI() {
monthlyProducts.removeAll()
yearlyProducts.removeAll()
perpetualProducts.removeAll()
// creates list to show
let isDiscountAvailable = checkDiscountPrice()
allProducts.forEach { (product) in
if isDiscountAvailable {
if product.productIdentifier == VersioneProProducts.Identifier.Subscription10kMonthly ||
product.productIdentifier == VersioneProProducts.Identifier.Subscription100kMonthly {
monthlyProducts.append(product)
} else if product.productIdentifier == VersioneProProducts.Identifier.Subscription10kYearlyDiscounted ||
product.productIdentifier == VersioneProProducts.Identifier.Subscription100kYearlyDiscounted {
yearlyProducts.append(product)
}
} else {
if product.productIdentifier == VersioneProProducts.Identifier.Subscription10kMonthly ||
product.productIdentifier == VersioneProProducts.Identifier.Subscription100kMonthly {
monthlyProducts.append(product)
}
else if product.productIdentifier == VersioneProProducts.Identifier.Subscription10kYearly ||
product.productIdentifier == VersioneProProducts.Identifier.Subscription100kYearly {
yearlyProducts.append(product)
}
}
// perpetual scribuscriptions doesn't have discounted version
if product.productIdentifier == VersioneProProducts.Identifier.Subscription10kPerpetual ||
product.productIdentifier == VersioneProProducts.Identifier.Subscription100kPerpetual {
perpetualProducts.append(product)
}
}
tableView.reloadData()
tableView.estimatedRowHeight = 600.0
tableView.separatorStyle = .none
tableView.backgroundColor = .systemGroupedBackground
tableView.registerCell(for: SubscriptionsActiveTableViewCell.self)
tableView.registerCell(for: SubscriptionsDescriptionTableViewCell.self)
tableView.registerCell(for: SubscriptionProductTableViewCell.self)
tableView.registerHeaderFooterView(for: SubscriptionsHeaderTableViewCell.self)
}
private func loadData() {
isLoading = true
VersioneProProducts.store.requestProducts{ [weak self] success, products in
EQNInAppProducts.store.requestProducts { [weak self] success, storeProducts in
self?.isLoading = false
guard let self = self, let products = products, success == true else { return }
guard let self = self, let storeProducts, success == true else { return }
let purchased = products.filter { (product) -> Bool in
let isPurchased = VersioneProProducts.store.isProductPurchased(product.productIdentifier)
let isSubscription = VersioneProProducts.isSubscription(for: product.productIdentifier)
return isPurchased && isSubscription
}
self.subscribedProduct = purchased.first
self.allProducts = products.sorted(by: { $0.productIdentifier > $1.productIdentifier })
let products = storeProducts.compactMap { EQNInAppProducts.from(product: $0) }
self.updateUI()
let purchased = products
.filter { (product) -> Bool in
// filter for subscriptions
let isPurchased = EQNInAppProducts.store.isProductPurchased(product.productIdentifier)
let isSubscription = product.isSubscription
return isPurchased && isSubscription
}.sorted { lProduct, rProduct in
// if user has more than one subscriptions,
// show first the Top10k
let lIs10k = lProduct.isTop100k
let rIs10k = rProduct.isTop100k
if lIs10k {
return lIs10k
}
return rIs10k
}
self.productSubscribed = purchased.first
self.products = products.sorted(by: { $0.productIdentifier > $1.productIdentifier })
self.tableView.reloadData()
}
}
@@ -170,18 +138,13 @@ class SubscriptionsViewController: UITableViewController {
EQNPurchaseUtility.availableSubscriptions { (availability) in
DispatchQueue.main.async {
self.availability = availability
self.updateUI()
self.tableView.reloadData()
}
}
}
// MARK: - Actions
@objc func restoreTapped(_ sender: AnyObject) {
isRestorePurchase = true
VersioneProProducts.store.restorePurchases()
}
@objc func closeTapped(_ sender: AnyObject) {
dismiss(animated: true, completion: nil)
}
@@ -189,7 +152,7 @@ class SubscriptionsViewController: UITableViewController {
// MARK: - Notifications
@objc func fail(_ notification: Notification){
VersioneProProducts.store.loadPurchase()
EQNInAppProducts.store.loadPurchase()
}
@objc func handlePurchaseNotification(_ notification: Notification) {
@@ -208,7 +171,7 @@ class SubscriptionsViewController: UITableViewController {
present(alert, animated: true, completion: nil)
}
VersioneProProducts.store.loadPurchase()
EQNInAppProducts.store.loadPurchase()
loadData()
}
@@ -227,12 +190,9 @@ class SubscriptionsViewController: UITableViewController {
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let tableSection = sections[section]
if let cell = tableView.dequeueReusableCell(withIdentifier: "SectionHeaderCell") as? SubscriptionsHeaderTableViewCell {
cell.title = tableSection.sectionTitle
cell.isLoading = isLoading
return cell
}
return nil
let view = tableView.dequeueHeaderFooterView(cellIdentifiable: SubscriptionsHeaderTableViewCell.self)
view.update(isLoading: isLoading, title: tableSection.sectionTitle)
return view
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
@@ -248,96 +208,59 @@ class SubscriptionsViewController: UITableViewController {
switch tableSection {
case .active: return 1
case .description: return 1
case .monthly,
.yearly,
.perpetual:
return availableProducts(for: tableSection).count
case .products: return products.isEmpty ? 0 : 2
}
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let tableSection = sections[indexPath.section]
if tableSection == .description {
// autolayout in description doesn't work 🤷
return Self.CellHeightDescription
}
return UITableView.automaticDimension
}
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let tableSection = sections[indexPath.section]
if tableSection == .active || tableSection == .description {
return
}
// add round borders to first and last row in products cells
let cornerRadius = AppTheme.shared.cardCornerRadius
var corners: UIRectCorner = []
if indexPath.row == 0 {
corners.update(with: .topLeft)
corners.update(with: .topRight)
}
if indexPath.row == tableView.numberOfRows(inSection: indexPath.section) - 1 {
corners.update(with: .bottomLeft)
corners.update(with: .bottomRight)
}
let maskLayer = CAShapeLayer()
maskLayer.path = UIBezierPath(roundedRect: cell.bounds,
byRoundingCorners: corners,
cornerRadii: CGSize(width: cornerRadius, height: cornerRadius)).cgPath
cell.layer.mask = maskLayer
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let tableSection = sections[indexPath.section]
if tableSection == .active {
let cell = tableView.dequeueReusableCell(withIdentifier: "ActiveSubscriptionsCell", for: indexPath) as! SubscriptionsActiveTableViewCell
cell.product = subscribedProduct
return cell
}
if tableSection == .description {
let cell = tableView.dequeueReusableCell(withIdentifier: "DescriptionCell", for: indexPath) as! SubscriptionsDescriptionTableViewCell
return cell
}
let products = availableProducts(for: tableSection)
let cell = tableView.dequeueReusableCell(withIdentifier: "SubscriptionCell", for: indexPath) as! SubscriptionProductTableViewCell
cell.product = products[indexPath.row]
cell.availability = availability
return cell
switch tableSection {
case .active:
let cell = tableView.dequeueReusableCell(cellIdentifiable: SubscriptionsActiveTableViewCell.self, for: indexPath)
cell.selectionStyle = .none
cell.update(with: productSubscribed)
cell.onTapRestore = { [weak self] in
guard let self else { return }
self.isRestorePurchase = true
EQNInAppProducts.store.restorePurchases()
}
return cell
case .description:
let cell = tableView.dequeueReusableCell(cellIdentifiable: SubscriptionsDescriptionTableViewCell.self, for: indexPath)
cell.selectionStyle = .none
return cell
case .products:
let cell = tableView.dequeueReusableCell(cellIdentifiable: SubscriptionProductTableViewCell.self, for: indexPath)
let category: EQNInAppProducts.Category = switch indexPath.row {
case 0: .top10k
case 1: .top100k
default: .top100k
}
cell.update(category: category, availability: availability)
return cell
}
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let tableSection = sections[indexPath.section]
let products = availableProducts(for: tableSection)
let products = availableProducts(for: indexPath)
if !products.isEmpty {
performSegue(withIdentifier: Self.SegueIdentifierSubscriptionDetail, sender: products[indexPath.row])
let controller = SubscriptionDetailsViewController(products: products)
navigationController?.pushViewController(controller, animated: true)
}
}
// MARK: - Helpers
private func availableProducts(for section: TableSection) -> [SKProduct] {
switch section {
case .monthly: return monthlyProducts
case .yearly: return yearlyProducts
case .perpetual: return perpetualProducts
private func availableProducts(for indexPath: IndexPath) -> [EQNInAppProducts] {
let section = sections[indexPath.section]
switch (section, indexPath.row) {
case (.products, 0): return products.filter { $0.isTop10k }
case (.products, 1): return products.filter { $0.isTop100k }
default: return []
}
}
}
extension SubscriptionsViewController: StoryboardInitializable {
static var storyboardName: String {
"Main"
}
static var storyboardControllerId: String {
"subscriptionsController"
}
}
@@ -0,0 +1,226 @@
//
// RealtimeAlertView.swift
// Earthquake Network
//
// Created by Andrea Busi on 15/06/22.
// Copyright © 2022 Earthquake Network. All rights reserved.
//
import UIKit
import MapKit
class RealtimeAlertContainerView: UIView {
lazy var alertView: RealtimeAlertView = {
let view = RealtimeAlertView()
view.translatesAutoresizingMaskIntoConstraints = false
view.eqn_applyRoundedCorners()
view.clipsToBounds = true
return view
}()
var animationColor: UIColor = .white
// MARK: - Init
convenience init() {
self.init(frame: .zero)
configureUI()
}
// MARK: - Private
private func configureUI() {
backgroundColor = .white
addSubview(alertView)
let margin: CGFloat = 24
alertView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: margin).isActive = true
alertView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -margin).isActive = true
alertView.topAnchor.constraint(equalTo: topAnchor, constant: margin).isActive = true
alertView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -margin).isActive = true
}
// MARK: - Public
func startBackgroundAnimation() {
let animation = CABasicAnimation(keyPath: "backgroundColor")
animation.fromValue = UIColor.white.cgColor
animation.toValue = animationColor.cgColor
animation.duration = 2.0
animation.beginTime = CACurrentMediaTime() + 1
animation.autoreverses = true
animation.repeatCount = .infinity
layer.add(animation, forKey: "backgroundColor")
}
}
class RealtimeAlertView: UIView {
let titleLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = .preferredFont(forTextStyle: .title2)
label.text = NSLocalizedString("app_name", comment: "")
return label
}()
let descriptionLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = .preferredFont(forTextStyle: .body)
label.numberOfLines = 0
label.textAlignment = .center
return label
}()
let waveTimeLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = .preferredFont(forTextStyle: .largeTitle)
label.textColor = AppTheme.Colors.red
label.text = String.localizedStringWithFormat(NSLocalizedString("alert_wave", comment: ""), 0)
label.textAlignment = .center
label.numberOfLines = 2
label.isHidden = true
return label
}()
let intensityLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = .preferredFont(forTextStyle: .largeTitle)
label.textColor = AppTheme.Colors.red
label.textAlignment = .center
label.numberOfLines = 2
return label
}()
lazy var mapView: MKMapView = {
let map = MKMapView()
map.translatesAutoresizingMaskIntoConstraints = false
map.delegate = self
map.register(EQNCustomAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNCustomAnnotationView.SingleLineIdentifier)
map.showsUserLocation = true
return map
}()
let closeButton: EQNBlurredCloseButton = {
let button = EQNBlurredCloseButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitleColor(.darkGray, for: .normal)
return button
}()
// MARK: - Init
convenience init() {
self.init(frame: .zero)
configureUI()
}
// MARK: - Private
private func configureUI() {
backgroundColor = AppTheme.Colors.lightGray
addSubview(closeButton)
addSubview(titleLabel)
addSubview(descriptionLabel)
addSubview(mapView)
closeButton.addDefaultConstraint(to: self)
titleLabel.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor).isActive = true
titleLabel.trailingAnchor.constraint(equalTo: closeButton.leadingAnchor, constant: -10.0).isActive = true
titleLabel.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor, constant: 10.0).isActive = true
descriptionLabel.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor).isActive = true
descriptionLabel.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor).isActive = true
descriptionLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 20.0).isActive = true
let stackView = UIStackView(arrangedSubviews: [waveTimeLabel, intensityLabel])
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical
stackView.distribution = .equalSpacing
stackView.spacing = 20.0
addSubview(stackView)
stackView.leadingAnchor.constraint(equalTo: descriptionLabel.leadingAnchor).isActive = true
stackView.trailingAnchor.constraint(equalTo: descriptionLabel.trailingAnchor).isActive = true
stackView.topAnchor.constraint(equalTo: descriptionLabel.bottomAnchor, constant: 20.0).isActive = true
mapView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
mapView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
mapView.topAnchor.constraint(equalTo: stackView.bottomAnchor, constant: 20.0).isActive = true
mapView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
}
// MARK: - Public
func addMapCircle(
center: CLLocationCoordinate2D,
radius: CLLocationDistance,
overlayId: String
) {
// remove any other existing overlays
let overlays = mapView.overlays.filter { $0.title == overlayId }
mapView.removeOverlays(overlays)
// add new overlay
let circle = MKCircle(center: center, radius: radius)
circle.title = overlayId
mapView.addOverlay(circle)
}
func addMapLine(
coordinates: [CLLocationCoordinate2D]
) {
let polyline = MKPolyline(coordinates: coordinates, count: coordinates.count)
mapView.addOverlay(polyline)
}
func addMapAnnotation(
title: String = "",
center: CLLocationCoordinate2D,
intensity: Int
) {
let annotation = EQNMapAnnotationPastquake(title: title, coordinate: center, intensity: intensity)
mapView.addAnnotation(annotation)
}
}
extension RealtimeAlertView: MKMapViewDelegate {
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
switch overlay {
case let circle as MKCircle:
let circleRenderer = MKCircleRenderer(overlay: circle)
circleRenderer.strokeColor = AppTheme.Colors.red
circleRenderer.fillColor = AppTheme.Colors.red.withAlphaComponent(0.2)
circleRenderer.lineWidth = 3.0
return circleRenderer
case let polyline as MKPolyline:
let polylineRenderer = MKPolylineRenderer(polyline: polyline)
polylineRenderer.strokeColor = .blue
polylineRenderer.lineWidth = 2.0
return polylineRenderer
default:
return MKOverlayRenderer(overlay: overlay)
}
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
guard let annotation = annotation as? EQNMapAnnotationPastquake else {
return nil
}
let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: EQNCustomAnnotationView.SingleLineIdentifier, for: annotation) as! EQNCustomAnnotationView
annotationView.image = annotation.image
annotationView.title = annotation.title
return annotationView
}
}
@@ -0,0 +1,196 @@
//
// RealtimeAlertViewController.swift
// Earthquake Network
//
// Created by Andrea Busi on 15/06/22.
// Copyright © 2022 Earthquake Network. All rights reserved.
//
import UIKit
import MapKit
class RealtimeAlertViewController: UIViewController, MKMapViewDelegate {
@objc var onClose: () -> Void = {}
// MARK: - Internal
private let containerView = RealtimeAlertContainerView()
private var notificationView: RealtimeAlertView {
containerView.alertView
}
/// Alert to display
private let realtimeAlert: EQNRealtimePushNotification
/// Timer to constantly update countdown label
private var countdownTimer: Timer?
/// Refresh time for wave animation
private let waveAnimationRefreshRate = 0.1
/// Current radius of the wave animation on the map
private var waveAnimationCurrentRadius: CLLocationDistance = 0
private var waveAnimationVelocity: Double = 1_000
/// Timer to simulate animation for the wave
private var waveAnimationTimer: Timer?
// MARK: - Init
@objc
init(notification: EQNRealtimePushNotification) {
self.realtimeAlert = notification
super.init(nibName: nil, bundle: nil)
self.waveAnimationCurrentRadius = currentWavePosition()
self.waveAnimationVelocity = evaluateWaveAnimationVelocity()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
// importante togliere il delegato, altrimenti causa crash
notificationView.mapView.delegate = nil
}
// MARK: - View Lifecycle
override func loadView() {
view = containerView
}
override func viewDidLoad() {
super.viewDidLoad()
configureUI()
updateUI()
startCountdown()
startWaveAnimation()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
containerView.startBackgroundAnimation()
}
// MARK: - Private
private func configureUI() {
notificationView.closeButton.addTarget(self, action: #selector(onTapClose(_:)), for: .touchUpInside)
// configure color for animation
containerView.animationColor = realtimeAlert.relativeIntensityColor
}
private func updateUI() {
notificationView.descriptionLabel.text = realtimeAlert.title
// update title with distance from earthquake
let distanceRound = Int(round(realtimeAlert.distanceFromUser() / 1_000))
notificationView.descriptionLabel.text = (notificationView.descriptionLabel.text ?? "")
+ ".\n"
+ String(format: NSLocalizedString("official_distance", comment: ""), distanceRound)
notificationView.intensityLabel.text = realtimeAlert.displayBody
notificationView.intensityLabel.textColor = realtimeAlert.relativeIntensityColor
// center map on the earthquake coordinate
let span = MKCoordinateSpan(latitudeDelta: 10.5, longitudeDelta: 10.5)
let region = MKCoordinateRegion(center: realtimeAlert.coordinate.coordinate, span: span)
notificationView.mapView.setCenter(realtimeAlert.coordinate.coordinate, animated: false)
notificationView.mapView.setRegion(region, animated: true)
// aggiungiamo annotation con epicentro sisma
notificationView.addMapAnnotation(center: realtimeAlert.coordinate.coordinate, intensity: realtimeAlert.intensity)
// simuliamo animazione dell'onda sismica
notificationView.addMapCircle(center: realtimeAlert.coordinate.coordinate, radius: waveAnimationCurrentRadius, overlayId: "wave_animation")
// aggiungiamo un segmento tra la posizione del sisma e quella dell'utente
if let lastPosition = EQNUser.default().lastPosition {
notificationView.addMapLine(coordinates: [realtimeAlert.coordinate.coordinate, lastPosition.coordinate])
}
}
private func startCountdown() {
// show countdown only if time is less than 300 seconds
if realtimeAlert.currentCountdown() < 300 {
// start a timer for the countdown label
notificationView.waveTimeLabel.isHidden = false
countdownTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(countdownTimerFired(_:)), userInfo: nil, repeats: true)
countdownTimer?.fire()
}
}
private func startWaveAnimation() {
waveAnimationTimer = Timer.scheduledTimer(timeInterval: waveAnimationRefreshRate, target: self, selector: #selector(mapWaveAnimationFired(_:)), userInfo: nil, repeats: true)
waveAnimationTimer?.fire()
}
// MARK: - Action
@objc private func onTapClose(_ sender: UIButton) {
// invalidiamo i timer, altri
countdownTimer?.invalidate()
countdownTimer = nil
waveAnimationTimer?.invalidate()
waveAnimationTimer = nil
onClose()
dismiss(animated: true)
}
// MARK: - Timer
@objc private func countdownTimerFired(_ sender: Timer) {
let countdown = realtimeAlert.currentCountdown()
notificationView.waveTimeLabel.text = String.localizedStringWithFormat(NSLocalizedString("alert_wave", comment: ""), countdown)
notificationView.waveTimeLabel.textColor = waveTimeTextColor(for: countdown)
if countdown <= 0 {
// stop the countdown
countdownTimer?.invalidate()
countdownTimer = nil
}
}
@objc private func mapWaveAnimationFired(_ sender: Timer) {
waveAnimationCurrentRadius += waveAnimationVelocity
notificationView.addMapCircle(center: realtimeAlert.coordinate.coordinate, radius: waveAnimationCurrentRadius, overlayId: "wave_animation")
}
// MARK: - Helpers
/// Evaluate current position for the wave
/// Used to define initial position for the wave circle
/// - Returns: Distance of the wave from the original earthquake point
private func currentWavePosition() -> Double {
// distanza tra utente e terremoto
let distance = realtimeAlert.distanceFromUser()
// calcoliamo la distanza rimanente da mostrare, perchè la schermata potrebbe anche essere aperta in ritardo
let remainingDistance = realtimeAlert.waveSpeed * Double(realtimeAlert.currentCountdown())
return distance - remainingDistance
}
/// Evaluate wave velocity based on push notification data
/// - Returns: Wave velocity, used for animation
private func evaluateWaveAnimationVelocity() -> Double {
let velocity = realtimeAlert.waveSpeed
return velocity * waveAnimationRefreshRate
}
/// Returns the text color based on impact countdown
private func waveTimeTextColor(for countdown: Int) -> UIColor {
switch countdown {
case _ where countdown > 15:
return UIColor(red: 255.0/255.0, green: 140.0/255.0, blue: 0.0, alpha: 1.0)
case _ where countdown > 5:
return UIColor(red: 255.0/255.0, green: 100.0/255.0, blue: 0.0, alpha: 1.0)
default:
return UIColor(red: 255.0/255.0, green: 0.0/255.0, blue: 0.0, alpha: 1.0)
}
}
}
@@ -7,51 +7,123 @@
//
import UIKit
import Shogun
class SegnalazioniLast24HoursCell: EQNBaseTableViewCell {
@objc
class SegnalazioniLast24HoursCell: EQNBaseContainerTableViewCell {
@IBOutlet private weak var headerLabel: UILabel!
@IBOutlet private weak var mildReportsLabel: UILabel!
@IBOutlet private weak var strongReportsLabel: UILabel!
@IBOutlet private weak var veryStrongReportsLabel: UILabel!
@IBOutlet private weak var twitterButton: UIButton! {
didSet {
twitterButton.imageView?.contentMode = .scaleAspectFit
}
}
@IBOutlet private weak var telegramButton: UIButton! {
didSet {
telegramButton.imageView?.contentMode = .scaleAspectFit
}
}
@IBOutlet private weak var mapButton: UIButton!
@objc var onTapTwitter: (() -> Void)?
@objc var onTapMap: (() -> Void)?
@objc var onTapTelegram: (() -> Void)?
// MARK: - View Lifecycle
override var headerText: String { NSLocalizedString("tab_manual", comment: "") }
override func awakeFromNib() {
super.awakeFromNib()
// MARK: - UI
private lazy var reportsLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.textColor = AppTheme.Colors.red
label.font = .preferredFont(forTextStyle: .largeTitle)
label.textAlignment = .center
label.numberOfLines = 0
return label
}()
private lazy var reportsDescriptionLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.textColor = AppTheme.shared.cardTextColor
label.font = .preferredFont(forTextStyle: .body)
label.numberOfLines = 0
label.textAlignment = .center
return label
}()
private lazy var twitterButton: UIButton = {
let button = EQNRoundedButton.make(target: self, action: #selector(twitterButtonTapped(_:)))
button.imageView?.contentMode = .scaleAspectFit
button.setImage(.init(named: "twitter_icon"), for: .normal)
return button
}()
private lazy var mapButton: UIButton = {
let button = EQNRoundedButton.make(title: NSLocalizedString("official_button_map", comment: ""), target: self, action: #selector(mapButtonTapped(_:)))
return button
}()
private lazy var telegramButton: UIButton = {
let button = EQNRoundedButton.make(target: self, action: #selector(telegramButtonTapped(_:)))
button.imageView?.contentMode = .scaleAspectFit
button.setImage(.init(named: "telegram_icon"), for: .normal)
return button
}()
// MARK: - Internal
override func setupUI() {
super.setupUI()
localizeUI()
containerView.addSubview(reportsLabel)
containerView.addSubview(reportsDescriptionLabel)
let stackView = UIStackView(arrangedSubviews: [twitterButton, mapButton, telegramButton])
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .horizontal
stackView.spacing = .cardVerticalSpacing
stackView.distribution = .fillEqually
containerView.addSubview(stackView)
reportsLabel.topAnchor.constraint(equalTo: topView.bottomAnchor, constant: .cardPadding).isActive = true
reportsLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: .cardPadding).isActive = true
reportsLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: .cardPadding.negative).isActive = true
reportsDescriptionLabel.topAnchor.constraint(equalTo: reportsLabel.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
reportsDescriptionLabel.leadingAnchor.constraint(equalTo: reportsLabel.leadingAnchor).isActive = true
reportsDescriptionLabel.trailingAnchor.constraint(equalTo: reportsLabel.trailingAnchor).isActive = true
stackView.heightAnchor.constraint(greaterThanOrEqualToConstant: 40.0).isActive = true
stackView.topAnchor.constraint(equalTo: reportsDescriptionLabel.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
stackView.leadingAnchor.constraint(equalTo: reportsDescriptionLabel.leadingAnchor).isActive = true
stackView.trailingAnchor.constraint(equalTo: reportsDescriptionLabel.trailingAnchor).isActive = true
stackView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: .cardPadding.negative).isActive = true
}
// MARK: - Private
private func localizeUI() {
headerLabel.text = NSLocalizedString("main_map", comment: "")
mapButton.setTitle(NSLocalizedString("official_button_map", comment: ""), for: .normal)
override func updateUI() {
super.updateUI()
reportsDescriptionLabel.text = NSLocalizedString("main_map", comment: "")
}
// MARK: - Public
@objc func updateUI(for smartphoneNetwork: EQNReteSmartphone?) {
guard let smartphoneNetwork = smartphoneNetwork else {
return
}
@objc
func update(with smartphoneNetwork: EQNReteSmartphone?) {
guard let smartphoneNetwork = smartphoneNetwork else { return }
self.mildReportsLabel.text = "\(smartphoneNetwork.manualGreen)"
self.strongReportsLabel.text = "\(smartphoneNetwork.manualYellow)"
self.veryStrongReportsLabel.text = "\(smartphoneNetwork.manualRed)"
let reports = smartphoneNetwork.manual
self.reportsLabel.text = "\(reports)"
if reports < 100 {
self.reportsLabel.textColor = UIColor(hex6: 0x01b400)
} else if reports < 1000 {
self.reportsLabel.textColor = UIColor(hex6: 0xe8da00)
} else {
self.reportsLabel.textColor = UIColor(hex6: 0xff0000)
}
}
// MARK: - Actions
@objc private func twitterButtonTapped(_ sender: UIButton) {
onTapTwitter?()
}
@objc private func mapButtonTapped(_ sender: UIButton) {
onTapMap?()
}
@objc private func telegramButtonTapped(_ sender: UIButton) {
onTapTelegram?()
}
}
@@ -8,6 +8,7 @@
import Foundation
import MapKit
import Shogun
class SegnalazioniMapViewController: EQNBaseMapViewController {
@@ -16,11 +17,46 @@ class SegnalazioniMapViewController: EQNBaseMapViewController {
let circle: MKCircle
}
override var isCloseButtonVisible: Bool {
false
}
private let appPreferences = AppPreferences.shared
/// Contains circles and related colors to draw overlays on the map
private var mapCircles = [MapCircle]()
/// Reports currently showned on the map
private var filteredReports = [EQNSegnalazione]()
// MARK: - UI
private lazy var magnitudeLegendView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
let stackView = UIStackView()
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .horizontal
stackView.distribution = .fillEqually
[20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120].forEach { magnitude in
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.text = (magnitude / 10).romanNumber()
label.backgroundColor = UIColor(named: "Mercalli \(magnitude)")
label.textAlignment = .center
label.font = .preferredFont(forTextStyle: .callout)
label.textColor = magnitude >= 100 ? .white : .black
stackView.addArrangedSubview(label)
}
view.addSubview(stackView)
stackView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
stackView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
return view
}()
// MARK: - View Lifecycle
override func viewDidLoad() {
@@ -29,8 +65,26 @@ class SegnalazioniMapViewController: EQNBaseMapViewController {
// MARK: - Public
override func extraUI() {
view.addSubview(magnitudeLegendView)
magnitudeLegendView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
magnitudeLegendView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
magnitudeLegendView.heightAnchor.constraint(equalToConstant: 25.0).isActive = true
magnitudeLegendView.topAnchor.constraint(equalTo: mapView.topAnchor).isActive = true
}
override func configureUI() {
navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(onTapCloseButton(_:)))
navigationItem.rightBarButtonItems = [
UIBarButtonItem(image: UIImage(named: "navbar-icon-screenshot"), style: .plain, target: self, action: #selector(onTapScreenshotButton(_:))),
UIBarButtonItem(image: UIImage(named: "navbar-icon-pin-arrow"), style: .plain, target: self, action: #selector(onTapMapDetailStyleButton(_:)))
]
}
override func registerMapAnnotationViews() {
mapView.register(EQNCustomAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNCustomAnnotationView.SingleLineIdentifier)
mapView.register(EQNCustomAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNCustomAnnotationView.SmallIdentifier)
}
override func loadDataSource() {
@@ -66,7 +120,7 @@ class SegnalazioniMapViewController: EQNBaseMapViewController {
.first
// controlliamo che sia inferiore al raggio impostato per le notifiche
if let radius = Double(EQNNotificheSegnalazioniUtente.shared().distanzaPosizione),
if let radius = Double(EQNSettingUserReportNotification.shared.distanzaMassima),
let nearestCluser = nearestCluser,
abs(nearestCluser.distance(from: userPosition)) < radius {
centerLocation = nearestCluser
@@ -96,27 +150,28 @@ class SegnalazioniMapViewController: EQNBaseMapViewController {
}
}
override func didTapAnnotation(_ annotation: MKAnnotation) {
guard let annotation = annotation as? EQNMapAnnotationUserReport, let report = annotation.report else {
return
// MARK: - Actions
@objc private func onTapCloseButton(_ sender: Any) {
dismiss(animated: true)
}
@objc private func onTapMapDetailStyleButton(_ sender: Any) {
appPreferences.userReportExpandedView.toggle()
reloadMap()
}
@objc private func onTapScreenshotButton(_ sender: Any) {
let screenshot = createSnapshot {
// nascondiamo la legenda
magnitudeLegendView.isHidden = true
} restore: {
// ri-visualizziamo la legenda
magnitudeLegendView.isHidden = false
}
let difference = Int(Date().timeIntervalSince(report.date) / 60.0)
let title = EQNUtility.formattedString(forTimeDifference: difference) + " - \(report.intensity.description)"
var message = ""
+ "🏢 " + report.address
+ "\n" + EQNUtility.formattedDate(from: report.date) + " \(NSLocalizedString("share_yourtime", comment: ""))"
if !report.message.isEmpty {
message += "\n💬 \(report.message)"
}
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("main_share", comment: ""), style: .default, handler: { [unowned self] _ in
self.openShareActivity(for: report)
}))
alert.addAction(UIAlertAction(title: NSLocalizedString("official_close", comment: ""), style: .cancel, handler: nil))
present(alert, animated: true, completion: nil)
let controller = UIActivityViewController(activityItems: [screenshot], applicationActivities: [])
present(controller, animated: true)
}
// MARK: - Private
@@ -125,17 +180,17 @@ class SegnalazioniMapViewController: EQNBaseMapViewController {
let vector_latitude = reports.map { $0.coordinate.coordinate.latitude }
let vector_longitude = reports.map { $0.coordinate.coordinate.longitude }
let vector_date = reports.map { $0.date }
let vector_state = reports.map { $0.intensity.rawValue }
let vector_state = reports.map { $0.intensity }
let minutes: TimeInterval = filter.minutes
var cluster_code = 0
var vector_cluster = [Int](repeating: 0, count: vector_latitude.count)
for i in 0..<vector_latitude.count {
let deltaMinute_i = getDeltaMinute(vector_date[i])
let deltaMinute_i = EQNUtility.getDeltaMinute(vector_date[i])
if vector_cluster[i] == 0 && deltaMinute_i <= minutes {
for j in 0..<vector_latitude.count {
let deltaMinute_j = getDeltaMinute(vector_date[j])
let deltaMinute_j = EQNUtility.getDeltaMinute(vector_date[j])
if i != j && deltaMinute_j <= minutes {
if abs(vector_latitude[i] - vector_latitude[j]) < 4 && abs(vector_longitude[i] - vector_longitude[j]) < 4 && abs(deltaMinute_i - deltaMinute_j) <= 20 {
if vector_cluster[j] > 0 {
@@ -200,21 +255,7 @@ class SegnalazioniMapViewController: EQNBaseMapViewController {
}
let circles = Array(0..<cluster_code).map { (i) -> MapCircle in
var value_distance = max_distance[i] / 20.0
if value_distance > 1.0 {
value_distance = 1.0
}
let value_intensity = (cluster_intensity[i]-1.0) / 2.0
let value_reference = max(value_distance, value_intensity)
let color: UIColor
if value_reference <= 0.5 {
let red = round(value_reference * 510)
color = UIColor(red: CGFloat(red / 255.0), green: 230.0/255.0, blue: 0.0, alpha: 1.0)
} else {
let green = round(230 - (value_reference - 0.5) * 460)
color = UIColor(red: 255.0, green: CGFloat(green / 255.0), blue: 0.0, alpha: 1.0)
}
let color: UIColor = AppTheme.Colors.darkGray
let centre = CLLocation(latitude: lat_centre[i], longitude: lon_centre[i])
let farest = CLLocation(latitude: lat_farest[i], longitude: lon_farest[i])
@@ -240,29 +281,6 @@ class SegnalazioniMapViewController: EQNBaseMapViewController {
mapView.addOverlays(overlays)
}
private func getDeltaMinute(_ date: Date) -> TimeInterval {
Date().timeIntervalSince(date) / 60.0
}
private func openShareActivity(for report: EQNSegnalazione) {
// create message to share
let intensity = report.intensity.description
let difference = Int(Date().timeIntervalSince(report.date) / 60.0)
let time = EQNUtility.formattedString(forTimeDifference: difference)
let message = [
NSLocalizedString("share_hashtag", comment: ""),
intensity,
NSLocalizedString("share_felt", comment: ""),
report.address,
time + ".",
NSLocalizedString("share_notified", comment: "")
].joined(separator: " ")
let controller = UIActivityViewController(activityItems: [message], applicationActivities: [])
present(controller, animated: true)
}
// MARK: - Map
override func setupAnnotationView(for annotation: MKAnnotation, on mapView: MKMapView) -> MKAnnotationView? {
@@ -270,10 +288,17 @@ class SegnalazioniMapViewController: EQNBaseMapViewController {
return nil
}
let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: EQNCustomAnnotationView.SingleLineIdentifier, for: annotation) as! EQNCustomAnnotationView
let identifier = appPreferences.userReportExpandedView ? EQNCustomAnnotationView.SingleLineIdentifier : EQNCustomAnnotationView.SmallIdentifier
let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier, for: annotation) as! EQNCustomAnnotationView
annotationView.image = annotation.image
annotationView.title = annotation.title
let size = appPreferences.userReportExpandedView ? EQNCustomAnnotationView.SingleLineImageHeight : EQNCustomAnnotationView.SmallViewImageHeight
annotationView.image = annotation.image(with: size)
annotationView.title = annotation.timeDifference
annotationView.canShowCallout = true
// Psizioniamo più in alto le segnalazioni con intensità maggiore.
// Valori maggiori di anchorPointZ mettono la view più in basso,
// quindi invertiamo il valore dell'intensità
annotationView.layer.anchorPointZ = (1000 - CGFloat(annotation.report?.intensity ?? 0))
return annotationView
}
@@ -7,45 +7,111 @@
//
import UIKit
import Shogun
class SegnalazioniSendReportCell: EQNBaseTableViewCell {
class SegnalazioniSendReportCell: EQNBaseContainerTableViewCell {
@IBOutlet private weak var headerLabel: UILabel!
@IBOutlet private weak var descriptionLabel: UILabel!
@IBOutlet private weak var mildLabel: UILabel!
@IBOutlet private weak var mildButton: UIButton!
@IBOutlet private weak var strongLabel: UILabel!
@IBOutlet private weak var strongButton: UIButton!
@IBOutlet private weak var veryStrongLabel: UILabel!
@IBOutlet private weak var veryStrongButton: UIButton!
// MARK: - View Lifecycle
override func awakeFromNib() {
super.awakeFromNib()
private struct Report: Equatable {
let magnitude: Int
let text: String
let color: UIColor
localizeUI()
static func == (lhs: Self, rhs: Self) -> Bool {
lhs.magnitude == rhs.magnitude
}
}
@objc var onTapReport: (_ magnitude: Int) -> Void = { _ in }
override var headerText: String { NSLocalizedString("main_feel", comment: "") }
// MARK: - UI
private let reports: [Report] = [
.init(magnitude: 20, text: NSLocalizedString("mercalli_II", comment: ""), color: .init(named: "Mercalli 20")!),
.init(magnitude: 30, text: NSLocalizedString("mercalli_III", comment: ""), color: .init(named: "Mercalli 30")!),
.init(magnitude: 40, text: NSLocalizedString("mercalli_IV", comment: ""), color: .init(named: "Mercalli 40")!),
.init(magnitude: 50, text: NSLocalizedString("mercalli_V", comment: ""), color: .init(named: "Mercalli 50")!),
.init(magnitude: 60, text: NSLocalizedString("mercalli_VI", comment: ""), color: .init(named: "Mercalli 60")!),
.init(magnitude: 70, text: NSLocalizedString("mercalli_VII", comment: ""), color: .init(named: "Mercalli 70")!),
.init(magnitude: 80, text: NSLocalizedString("mercalli_VIII", comment: ""), color: .init(named: "Mercalli 80")!),
.init(magnitude: 90, text: NSLocalizedString("mercalli_IX", comment: ""), color: .init(named: "Mercalli 90")!),
.init(magnitude: 100, text: NSLocalizedString("mercalli_X", comment: ""), color: .init(named: "Mercalli 100")!),
.init(magnitude: 110, text: NSLocalizedString("mercalli_XI", comment: ""), color: .init(named: "Mercalli 110")!),
.init(magnitude: 120, text: NSLocalizedString("mercalli_XII", comment: ""), color: .init(named: "Mercalli 120")!)
]
// MARK: - Internal
override func setupUI() {
super.setupUI()
var previousView = topView
reports.enumerated().forEach { index, report in
let view = createContentView(magnitude: report.magnitude, text: report.text, color: report.color)
containerView.addSubview(view)
view.leadingAnchor.constraint(equalTo: containerView.leadingAnchor).isActive = true
view.trailingAnchor.constraint(equalTo: containerView.trailingAnchor).isActive = true
let padding: CGFloat = report == reports.first ? .cardPadding : 0
view.topAnchor.constraint(equalTo: previousView.bottomAnchor, constant: padding).isActive = true
if report == reports.last {
view.bottomAnchor.constraint(equalTo: containerView.bottomAnchor).isActive = true
}
previousView = view
}
}
// MARK: - Private
private func localizeUI() {
headerLabel.text = NSLocalizedString("main_feel", comment: "")
descriptionLabel.text = NSLocalizedString("manual_usebutton", comment: "")
mildLabel.text = NSLocalizedString("manual_mild", comment: "")
strongLabel.text = NSLocalizedString("manual_strong", comment: "")
veryStrongLabel.text = NSLocalizedString("manual_verystrong", comment: "")
private func createContentView(
magnitude: Int,
text: String,
color: UIColor
) -> UIView {
let view = UIView(frame: .zero)
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = color
let label = UILabel(frame: .zero)
label.translatesAutoresizingMaskIntoConstraints = false
label.font = .preferredFont(forTextStyle: .body)
label.textColor = AppTheme.shared.cardTextColor
label.numberOfLines = 0
label.text = text
let button = UIButton(type: .system)
button.translatesAutoresizingMaskIntoConstraints = false
button.backgroundColor = .clear
button.tag = magnitude
button.addTarget(self, action: #selector(onTapMagnitudeButton(_:)), for: .touchUpInside)
view.addSubview(label)
view.addSubview(button)
// use a custom vertical spacing to make single lines bigger
let verticalSpacing: CGFloat = 15.0
label.topAnchor.constraint(equalTo: view.topAnchor, constant: verticalSpacing).isActive = true
label.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: .cardPadding).isActive = true
label.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: .cardPadding.negative).isActive = true
label.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -verticalSpacing).isActive = true
button.constraint(to: view)
return view
}
override func layoutSubviews() {
super.layoutSubviews()
let labels: [UILabel] = [mildLabel, strongLabel, veryStrongLabel]
labels.forEach { (label) in
label.layer.borderWidth = AppTheme.shared.buttonBorderWidth
label.layer.borderColor = AppTheme.shared.buttonBorderColor.cgColor
label.layer.cornerRadius = AppTheme.shared.buttonCornerRadius
label.clipsToBounds = true
}
// MARK: - Actions
@IBAction private func onTapReportButton(_ sender: UIButton) {
let magnitude = sender.tag
onTapReport(magnitude)
}
@objc private func onTapMagnitudeButton(_ sender: UIButton) {
let magnitude = sender.tag
onTapReport(magnitude)
}
}
@@ -40,17 +40,19 @@
self.title = [NSLocalizedString(@"tab_manual", nil) capitalizedString];
self.tableView.estimatedRowHeight = 500.0;
self.tableView.rowHeight = UITableViewAutomaticDimension;
[self.tableView registerClass:[SegnalazioniLast24HoursCell class] forCellReuseIdentifier:@"Last24HCell"];
[self.tableView registerClass:[SegnalazioniSendReportCell class] forCellReuseIdentifier:@"ReportEarthquakeCell"];
}
- (void)refreshUI
{
[super refreshUI];
if ([self.userDefoult objectForKey:DATA_MESSAGE_EQN]){
NSDate *dateMessage = [self.userDefoult objectForKey:DATA_MESSAGE_EQN];
if ([EQNUtility getDifferenceMinute:dateMessage] >= EQNSendReportDelayBetweenComments){
[self.userDefoult removeObjectForKey:DATA_MESSAGE_EQN];
[self.userDefoult removeObjectForKey:CODE_MESSAGE_EQN];
if ([self.userDefoult objectForKey:NSUserDefaults.UserReportMessage]){
NSDate *dateMessage = [self.userDefoult objectForKey:NSUserDefaults.UserReportMessage];
if (![dateMessage isBeforeInterval:EQNSendReportDelayBetweenComments]) {
[self.userDefoult removeObjectForKey:NSUserDefaults.UserReportMessage];
[self.userDefoult removeObjectForKey:NSUserDefaults.UserReportCodeStatus];
}
}
@@ -70,43 +72,48 @@
return 2;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
switch (indexPath.row) {
case 0: return 140;
default: return UITableViewAutomaticDimension;
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.row == 0) {
SegnalazioniLast24HoursCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Last24HCell" forIndexPath:indexPath];
EQNReteSmartphone *reteSmartPhone = [EQNManager defaultManager].rete_smartphone;
[cell updateUIFor:reteSmartPhone];
[cell updateWith:[EQNManager defaultManager].rete_smartphone];
__weak SegnalazioniViewController *weakSelf = self;
cell.onTapMap = ^{
[weakSelf openMap];
};
cell.onTapTwitter = ^{
[weakSelf openTwitter];
};
cell.onTapTelegram = ^{
[weakSelf openTelegram];
};
return cell;
}
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"ReportEarthquakeCell" forIndexPath:indexPath];
SegnalazioniSendReportCell *cell = [tableView dequeueReusableCellWithIdentifier:@"ReportEarthquakeCell" forIndexPath:indexPath];
cell.onTapReport = ^(NSInteger magnitude) {
[self sendReportTappedWithMagnitude:magnitude];
};
return cell;
}
#pragma mark - Actions
- (IBAction)openMapTapped:(id)sender
- (void)openMap
{
SegnalazioniMapViewController *controller = [[SegnalazioniMapViewController alloc] init];
[self presentViewController:controller animated:YES completion:nil];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:controller];
[self presentViewController:navController animated:YES completion:nil];
}
- (IBAction)openTwitterTapped:(id)sender
- (void)openTwitter
{
NSURL *twitterUrl = [NSURL URLWithString:EQNTwitterProfileUrl];
SFSafariViewController *controller = [[SFSafariViewController alloc] initWithURL:twitterUrl];
[self presentViewController:controller animated:YES completion:nil];
}
- (IBAction)openTelegramTapped:(id)sender
- (void)openTelegram
{
NSURL *telegramUrl = [NSURL URLWithString:EQNTelegramUrl];
[[UIApplication sharedApplication] openURL:telegramUrl options:@{} completionHandler:nil];
@@ -117,12 +124,12 @@
[[EQNManager defaultManager] sincronizza];
}
- (IBAction)sendReportTapped:(UIButton *)sender
- (void)sendReportTappedWithMagnitude:(NSInteger)magnitude
{
// check to avoid multiple consecutive reports
if ([self.userDefoult objectForKey:CODE_MESSAGE_EQN]) {
NSDate *dateMessage = [self.userDefoult objectForKey:DATA_MESSAGE_EQN];
if ([EQNUtility getDifferenceMinute:dateMessage] <= EQNSendReportDelayBetweenMessages){
if ([self.userDefoult objectForKey:NSUserDefaults.UserReportCodeStatus]) {
NSDate *dateMessage = [self.userDefoult objectForKey:NSUserDefaults.UserReportMessage];
if (![dateMessage isBeforeInterval:EQNSendReportDelayBetweenMessages]) {
NSString *message = NSLocalizedString(@"manual_wait", @"");
[self showErrorAlertWithMessage:message];
[self performSelectorOnMainThread:@selector(sincronizzazione) withObject:nil waitUntilDone:YES];
@@ -133,9 +140,9 @@
// ask for user confirmation
UIAlertController *alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"attention", @"") message:NSLocalizedString(@"manual_sure", nil) preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"manual_yes", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
[self executeSendReportWithMagnitude:sender.tag];
[self executeSendReportWithMagnitude:magnitude];
}]];
[alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"options_cancel", nil) style:UIAlertActionStyleCancel handler:nil]];
[alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"status_cancel", nil) style:UIAlertActionStyleCancel handler:nil]];
[self presentViewController:alert animated:YES completion:nil];
}
@@ -158,16 +165,14 @@
[[ServerRequest defaultServerConnectionSingleton] inviaInformazioniAlServerWithURL:url richiesta:EQNTipoChiamataSegnalazioneTerremoto success:^(id result) {
[self.userDefoult setObject:result forKey:CODE_MESSAGE_EQN];
[self.userDefoult setObject:[NSDate date] forKey:DATA_MESSAGE_EQN];
[self.userDefoult setObject:result forKey:NSUserDefaults.UserReportCodeStatus];
[self.userDefoult setObject:[NSDate date] forKey:NSUserDefaults.UserReportMessage];
dispatch_async(dispatch_get_main_queue(), ^{
UIAlertController *alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"report", @"")
message:NSLocalizedString(@"manual_ok", @"")
preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
[self sendComment];
}]];
[alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleDefault handler:nil]];
[self presentViewController:alert animated:YES completion:nil];
});
} failure:^(NSError * error) {
@@ -177,42 +182,6 @@
}];
}
- (void)sendComment
{
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"manual_sendmessage_button" , @"")
message:NSLocalizedString(@"manual_sendmessage", @"")
preferredStyle:UIAlertControllerStyleAlert];
[alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
textField.clearButtonMode = UITextFieldViewModeWhileEditing;
}];
[alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok" ,@"") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
UITextField * messaggio = alertController.textFields.firstObject;
NSURL *url = [EQNGeneratoreURLServer urlInvioCommentoTerremoto:messaggio.text codeMessage:[self.userDefoult objectForKey:CODE_MESSAGE_EQN]];
[[ServerRequest defaultServerConnectionSingleton] inviaInformazioniAlServerWithURL:url richiesta:EQNTipoChiamataCommentoTerremoto success:^(id result) {
dispatch_async(dispatch_get_main_queue(), ^{
UIAlertController *alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"manual_sendmessage_button" , @"")
message:NSLocalizedString(@"manual_message_received", @"")
preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"ok", nil) style:UIAlertActionStyleCancel handler:nil]];
[self presentViewController:alert animated:YES completion:nil];
});
} failure:^(NSError * error) {
[self showErrorAlertWithMessage:error.localizedDescription];
}];
dispatch_async(dispatch_get_main_queue(), ^{
[self refreshUI];
});
}]];
[alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"options_cancel", @"") style:UIAlertActionStyleCancel handler:nil]];
[self presentViewController:alertController animated:YES completion:nil];
}
#pragma mark - Private
- (void)showErrorAlertWithMessage:(NSString *)errorMessage
@@ -9,12 +9,12 @@
import UIKit
import MapKit
import CoreLocation
import Shogun
protocol SeismicNetworkTableViewCellDelegate: AnyObject {
func seismicNetworkCellDidTapShare(_ cell: SeismicNetworkTableViewCell)
func seismicNetworkCellDidTapMap(_ cell: SeismicNetworkTableViewCell)
func seismicNetworkCellDidTapMapDetail(_ cell: SeismicNetworkTableViewCell)
func seismicNetworkCellDidTapWeather(_ cell: SeismicNetworkTableViewCell, hasValidWeatherData: Bool)
func seismicNetworkCellDidTapCalendar(_ cell: SeismicNetworkTableViewCell)
func seismicNetworkCellDidTapSettings(_ cell: SeismicNetworkTableViewCell)
func seismicNetworkCellDidTapClose(_ cell: SeismicNetworkTableViewCell)
@@ -44,8 +44,6 @@ class SeismicNetworkTableViewCell: UITableViewCell {
case normal
/// Cell with map visible
case mapExpanded
/// Cell with weather info visible
case weatherExpanded
}
/// Delegate
@@ -54,13 +52,12 @@ class SeismicNetworkTableViewCell: UITableViewCell {
// MARK: - Internal
private static let DefaultVerticalSpacing: CGFloat = 6.0
private static let DefaultBodyFont = UIFont.preferredFont(forTextStyle: .body)
private static let DefaultBodyFontLight = UIFont.preferredFont(for: .body, weight: .light)
/// Seismic to show
private var seismic: EQNSisma?
private(set) var displayType = DisplayType.normal
private var informationTypes = [InformationType]()
private var isPushSelected = false
private var colors: MagnitudeColors?
@@ -69,27 +66,26 @@ class SeismicNetworkTableViewCell: UITableViewCell {
private lazy var containerView: UIView = {
let view = UIView(frame: .zero)
view.translatesAutoresizingMaskIntoConstraints = false
view.layer.cornerRadius = AppTheme.shared.cardCornerRadius
view.layer.masksToBounds = false
// add shadow
view.layer.shadowColor = UIColor.black.cgColor
view.layer.shadowOpacity = 0.5
view.layer.shadowOffset = CGSize(width: 0, height: 2)
view.layer.shadowRadius = 2
view.backgroundColor = .white
view.clipsToBounds = true
return view
}()
private lazy var titleImageView: UIImageView = {
let imageView = UIImageView(frame: .zero)
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
private lazy var gradientView: UIImageView = {
// Per gestire il gradiente, utilizziamo una image view in cui inseriamo un'immagine
// creata ad-hoc con il gradiente desiderato.
// Le prove fatte utilizzando una view normale sono fallite perchè al momento di
// disegnare la view non abbiamo le misure corrette.
let view = UIImageView(frame: .zero)
view.translatesAutoresizingMaskIntoConstraints = false
view.contentMode = .scaleToFill
return view
}()
private lazy var placeLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = UIFont.preferredFont(for: .title2, weight: .semibold)
label.font = UIFont.preferredFont(forTextStyle: .title2, weight: .semibold)
label.numberOfLines = 3
return label
}()
@@ -97,8 +93,9 @@ class SeismicNetworkTableViewCell: UITableViewCell {
private lazy var networkLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.backgroundColor = UIColor.white.withAlphaComponent(0.5)
label.textAlignment = .center
label.textAlignment = .right
label.font = .preferredFont(forTextStyle: .subheadline)
label.numberOfLines = 2
return label
}()
@@ -113,35 +110,40 @@ class SeismicNetworkTableViewCell: UITableViewCell {
private lazy var depthLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = Self.DefaultBodyFontLight
label.font = .preferredFont(forTextStyle: .body)
label.numberOfLines = 2
return label
}()
private lazy var timeLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = Self.DefaultBodyFontLight
label.font = .preferredFont(forTextStyle: .body)
label.numberOfLines = 2
return label
}()
private lazy var distanceLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = Self.DefaultBodyFontLight
label.font = .preferredFont(forTextStyle: .body)
label.numberOfLines = 2
return label
}()
private lazy var coordinateLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = Self.DefaultBodyFontLight
label.font = .preferredFont(forTextStyle: .body)
label.numberOfLines = 2
return label
}()
private lazy var populationLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = Self.DefaultBodyFontLight
label.font = .preferredFont(forTextStyle: .body)
label.numberOfLines = 2
return label
}()
@@ -149,8 +151,9 @@ class SeismicNetworkTableViewCell: UITableViewCell {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
label.font = Self.DefaultBodyFont
label.font = .preferredFont(forTextStyle: .body)
label.textAlignment = .center
label.numberOfLines = 2
return label
}()
@@ -158,8 +161,9 @@ class SeismicNetworkTableViewCell: UITableViewCell {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
label.font = Self.DefaultBodyFont
label.font = .preferredFont(forTextStyle: .body)
label.textAlignment = .center
label.numberOfLines = 2
return label
}()
@@ -173,20 +177,6 @@ class SeismicNetworkTableViewCell: UITableViewCell {
return mapView
}()
private lazy var weatherImageView: UIImageView = {
let imageView = UIImageView(frame: .zero)
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
private lazy var weatherInfoLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
label.font = Self.DefaultBodyFontLight
return label
}()
// MARK: - Init
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
@@ -199,11 +189,19 @@ class SeismicNetworkTableViewCell: UITableViewCell {
setupUI()
}
// MARK: - View Lifecycle
override func layoutSubviews() {
super.layoutSubviews()
containerView.eqn_applyShadowAndRoundedCorners()
gradientView.eqn_applyRoundedCorners()
}
// MARK: - Setup
private func setupUI() {
selectionStyle = .default
translatesAutoresizingMaskIntoConstraints = false
backgroundColor = .clear
// container view
@@ -213,6 +211,9 @@ class SeismicNetworkTableViewCell: UITableViewCell {
containerView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 8.0).isActive = true
containerView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -4.0).isActive = true
containerView.addSubview(gradientView)
gradientView.constraint(to: containerView)
// this variable is used to keep track of the previous view, in order to attach proper constraints
var previousView: UIView = containerView
@@ -247,16 +248,9 @@ class SeismicNetworkTableViewCell: UITableViewCell {
shareButton.setImage(UIImage(named: "share_icon"), for: .normal)
shareButton.addTarget(self, action: #selector(shareTapped(_:)), for: .touchUpInside)
stackViewTitle.addArrangedSubview(titleImageView)
stackViewTitle.addArrangedSubview(placeLabel)
stackViewTitle.addArrangedSubview(networkLabel)
stackViewTitle.addArrangedSubview(shareButton)
titleImageView.heightAnchor.constraint(equalToConstant: titleComponentsHeight).isActive = true
titleImageView.widthAnchor.constraint(equalTo: titleImageView.heightAnchor).isActive = true
networkLabel.heightAnchor.constraint(equalToConstant: 34.0).isActive = true
networkLabel.setContentHuggingPriority(.init(800), for: .horizontal)
networkLabel.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
placeLabel.setContentHuggingPriority(.init(200), for: .horizontal)
placeLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
shareButton.widthAnchor.constraint(equalToConstant: titleComponentsHeight).isActive = true
@@ -335,6 +329,13 @@ class SeismicNetworkTableViewCell: UITableViewCell {
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()
@@ -343,13 +344,11 @@ class SeismicNetworkTableViewCell: UITableViewCell {
stackViewButtons.distribution = .fillEqually
stackViewButtons.spacing = 4
let buttonMap = createRoundedButton(title: "🗺", action: #selector(mapTapped(_:)))
let buttonMap = EQNRoundedButton.make(title: "🗺", target: self, action: #selector(mapTapped(_:)))
stackViewButtons.addArrangedSubview(buttonMap)
let buttonWeather = createRoundedButton(title: "🌤", action: #selector(weatherTapped(_:)))
stackViewButtons.addArrangedSubview(buttonWeather)
let buttonCalendar = createRoundedButton(title: "📆", action: #selector(calendarTapped(_:)))
let buttonCalendar = EQNRoundedButton.make(title: "📆", target: self, action: #selector(calendarTapped(_:)))
stackViewButtons.addArrangedSubview(buttonCalendar)
let buttonSettings = createRoundedButton(title: "🔧", action: #selector(settingsTapped(_:)))
let buttonSettings = EQNRoundedButton.make(title: "🔧", target: self, action: #selector(settingsTapped(_:)))
stackViewButtons.addArrangedSubview(buttonSettings)
containerView.addSubview(stackViewButtons)
@@ -369,33 +368,10 @@ class SeismicNetworkTableViewCell: UITableViewCell {
mapView.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true
previousView = mapView
} else if displayType == .weatherExpanded {
let weatherTitleLabel = UILabel()
weatherTitleLabel.translatesAutoresizingMaskIntoConstraints = false
weatherTitleLabel.text = NSLocalizedString("weather_weather", comment: "")
weatherTitleLabel.font = UIFont.preferredFont(forTextStyle: .headline)
containerView.addSubview(weatherTitleLabel)
weatherTitleLabel.topAnchor.constraint(equalTo: previousView.bottomAnchor, constant: Self.DefaultVerticalSpacing).isActive = true
weatherTitleLabel.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true
weatherTitleLabel.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true
containerView.addSubview(weatherInfoLabel)
containerView.addSubview(weatherImageView)
weatherImageView.heightAnchor.constraint(equalToConstant: 60.0).isActive = true
weatherImageView.widthAnchor.constraint(equalTo: weatherImageView.heightAnchor).isActive = true
weatherImageView.leadingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.leadingAnchor).isActive = true
weatherImageView.trailingAnchor.constraint(equalTo: weatherInfoLabel.leadingAnchor, constant: -8.0).isActive = true
weatherImageView.centerYAnchor.constraint(equalTo: weatherInfoLabel.centerYAnchor).isActive = true
weatherInfoLabel.topAnchor.constraint(equalTo: weatherTitleLabel.bottomAnchor, constant: 4.0).isActive = true
weatherInfoLabel.trailingAnchor.constraint(equalTo: containerView.layoutMarginsGuide.trailingAnchor).isActive = true
previousView = weatherInfoLabel
}
if (displayType == .mapExpanded || displayType == .weatherExpanded) {
let buttonClose = createRoundedButton(title: NSLocalizedString("official_close", comment: "").uppercased(), action: #selector(closeTapped(_:)))
if (displayType == .mapExpanded) {
let buttonClose = EQNRoundedButton.make(title: NSLocalizedString("official_close", comment: "").uppercased(), target: self, action: #selector(closeTapped(_:)))
containerView.addSubview(buttonClose)
buttonClose.topAnchor.constraint(equalTo: previousView.bottomAnchor, constant: Self.DefaultVerticalSpacing).isActive = true
@@ -406,6 +382,9 @@ class SeismicNetworkTableViewCell: UITableViewCell {
else {
previousView.bottomAnchor.constraint(equalTo: containerView.layoutMarginsGuide.bottomAnchor).isActive = true
}
containerView.eqn_applyShadowAndRoundedCorners()
gradientView.eqn_applyRoundedCorners()
}
private func recreateUI() {
@@ -419,14 +398,16 @@ class SeismicNetworkTableViewCell: UITableViewCell {
let viewModel = SeismicNetworkViewModel(seismic: seismic)
containerView.backgroundColor = colors?.startColor
let notified = couldBeNotified(for: seismic)
titleImageView.image = notified ? UIImage(named: "bell") : UIImage(named: "bell_disabled")
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
}
// update seismic data
placeLabel.text = viewModel.place
networkLabel.text = viewModel.network + " " // add some padding
placeLabel.textColor = isPushSelected ? AppTheme.Colors.pureBlue : AppTheme.shared.cardTextColor
networkLabel.text = String(format: NSLocalizedString("official_provider", comment: ""), viewModel.network)
magnitudeLabel.textColor = colors?.textColor
magnitudeLabel.text = viewModel.magnitude
depthLabel.text = viewModel.depth
@@ -463,14 +444,6 @@ class SeismicNetworkTableViewCell: UITableViewCell {
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(mapDetailTapped(_:)))
mapView.addGestureRecognizer(tapRecognizer)
} else if displayType == .weatherExpanded {
weatherInfoLabel.text = ""
+ String(format: NSLocalizedString("weather_temperature", comment: ""), seismic.weatherTemperature.doubleValue - EQNMathKelvin) + "\n"
+ String(format: NSLocalizedString("weather_pressure", comment: ""), seismic.weatherPressure) + "\n"
+ String(format: NSLocalizedString("weather_windspeed", comment: ""), seismic.weatherWindSpeed) + "\n"
+ String(format: NSLocalizedString("weather_humidity", comment: ""), seismic.weatherHumidity) + "\n"
+ String(format: NSLocalizedString("weather_clouds", comment: ""), seismic.weatherCloud)
weatherImageView.image = UIImage(named: "weather_\(seismic.weatherIcon).png")
}
}
@@ -481,11 +454,17 @@ class SeismicNetworkTableViewCell: UITableViewCell {
/// - seismic: Seismic to display
/// - type: Type of cell
/// - informations: Informations to show
public func configure(with seismic: EQNSisma, type: DisplayType, informations: [InformationType]) {
public func configure(
with seismic: EQNSisma,
type: DisplayType,
informations: [InformationType],
isPushSelected: Bool
) {
self.seismic = seismic
self.colors = calculateColors(for: seismic.magnitude.doubleValue)
self.displayType = type
self.informationTypes = informations
self.isPushSelected = isPushSelected
if !informations.contains(.time) {
self.informationTypes += [.time]
@@ -505,16 +484,6 @@ class SeismicNetworkTableViewCell: UITableViewCell {
updateUI()
}
/// Creates a snapshot of the current cell
/// - Returns: Image with the snapshot of the cell
public func createSnapshot() -> UIImage {
let renderer = UIGraphicsImageRenderer(size: contentView.bounds.size)
let image = renderer.image { ctx in
contentView.drawHierarchy(in: contentView.bounds, afterScreenUpdates: true)
}
return image
}
// MARK: - Actions
@objc func shareTapped(_ sender: UIButton) {
@@ -526,13 +495,6 @@ class SeismicNetworkTableViewCell: UITableViewCell {
delegate?.seismicNetworkCellDidTapMap(self)
}
}
@objc func weatherTapped(_ sender: UIButton) {
if displayType != .weatherExpanded {
let validData = seismic?.weatherCode != nil
delegate?.seismicNetworkCellDidTapWeather(self, hasValidWeatherData: validData)
}
}
@objc func calendarTapped(_ sender: UIButton) {
delegate?.seismicNetworkCellDidTapCalendar(self)
@@ -566,48 +528,7 @@ class SeismicNetworkTableViewCell: UITableViewCell {
return separator
}
private func createRoundedButton(title: String, action: Selector) -> EQNRoundedButton {
let button = EQNRoundedButton(frame: .zero)
button.translatesAutoresizingMaskIntoConstraints = false
button.addTarget(self, action: action, for: .touchUpInside)
button.setTitle(title, for: .normal)
button.setTitleColor(AppTheme.Colors.darkGray, for: .normal)
button.backgroundColor = UIColor.white.withAlphaComponent(0.5)
return button
}
/// Check if the user could be received a notification for this seismic
private func couldBeNotified(for seismic: EQNSisma) -> Bool {
let settings = EQNNotificheReteSismiche.shared()
if !settings.isAbilitato {
return false
}
if !settings.listaEnti.contains(seismic.provider) {
return false
}
var notified = true
if let radius = Double(settings.distanzaPosizione), seismic.userDistance > radius {
notified = false
}
if let magnitude = Double(settings.energiaSisma), seismic.magnitude.doubleValue < magnitude {
notified = false
}
if settings.isAbilitaVicini, seismic.userDistance < 50 {
notified = true
}
if settings.isTerremortiForti, let strongMagnitude = Double(settings.energiaTerremotiForti), seismic.magnitude.doubleValue >= strongMagnitude {
notified = true
}
return notified
}
/// Determines the zoom for the map, based on the involved population
private func mapSpanLongitude(population: Double) -> CLLocationDegrees {
var zoom: CLLocationDegrees = 1
@@ -1,48 +0,0 @@
//
// FiltersViewModel.swift
// Earthquake Network
//
// Created by Andrea Busi on 22/03/21.
// Copyright © 2021 Earthquake Network. All rights reserved.
//
import Foundation
struct FiltersViewModel {
let magnitude: String
let distance: String
let timeframe: String
init() {
let magnitudoMinima = EQNData.magitudoDebole(for: EQNSeismic.shared.magnitudoMinima)
self.magnitude = Self.formattedMagnitude(magnitudoMinima.value)
let distanzaMassima = EQNData.raggioSisma(for: EQNSeismic.shared.distanzaMassima)
self.distance = Self.formattedDistance(distanzaMassima.value)
let periodoTemporale = EQNData.periodoTemporale(for: EQNSeismic.shared.periodoTemporale)
self.timeframe = Self.formattedTimeframe(periodoTemporale.value)
}
// MARK: - Private
private static func formattedMagnitude(_ magnitude: String) -> String {
return magnitude
}
private static func formattedDistance(_ distance: String) -> String {
if distance == EQNData.MaxRaggioSisma {
return ""
}
return "\(distance)km"
}
private static func formattedTimeframe(_ timeframe: String) -> String {
let time = Int(timeframe) ?? 0
if time < 60 {
return "\(time)m"
}
return "\(time/60)h"
}
}
@@ -7,6 +7,7 @@
//
import UIKit
import Shogun
protocol SeismicFiltersViewControllerDelegate: AnyObject {
func seismicFiltersControllerDidUpdateFilters(_ controller: SeismicFiltersViewController)
@@ -15,13 +16,11 @@ protocol SeismicFiltersViewControllerDelegate: AnyObject {
class SeismicFiltersViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
private enum RowIdentifier: Int {
case magnitudoMinima
case sismiNelRaggio
case distanzaMassima
case periodoTemporale
case sismiFortiAbilita
case sismiFortiDistanza
case sismiQualsiasiMagnitudo
case modificaImpostazioni
case magnitudoMinima
case sismiRilevanti
case sismiTutti
}
weak var delegate: SeismicFiltersViewControllerDelegate?
@@ -36,29 +35,20 @@ class SeismicFiltersViewController: UIViewController, UITableViewDelegate, UITab
@IBOutlet private weak var closeButton: UIButton!
private var settings = [
SettingItem(type: .slider, title: NSLocalizedString("filter_magnitude", comment: "")),
SettingItem(type: .slider, title: NSLocalizedString("Distanza massima", comment: "")),
SettingItem(type: .slider, title: NSLocalizedString("filter_timeframe", comment: "")),
SettingItem(type: .enable, title: NSLocalizedString("filter_strong", comment: "")),
SettingItem(type: .slider, title: NSLocalizedString("options_strong_magnitude", comment: "")),
SettingItem(type: .enable, title: NSLocalizedString("filter_near", comment: "")),
SettingItem(type: .enable, title: NSLocalizedString("filter_reflect", comment: ""))
SettingItem(type: .enable, title: NSLocalizedString("filter_show_area", comment: "")),
SettingItem(type: .slider, title: ""),
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: "")),
]
private let dataSourceMagnitudoMinima = EQNData.magitudoDeboli()
private let dataSourceDistanzaMassima = EQNData.raggioSismi()
private let dataSourcePeriodoTemporale = EQNData.periodiTemporali()
private let dataSourceSismiForti = EQNData.magitudoForti()
private var initialMagnitudoMinima: EQNGenericValue?
private var initialQualsiasiMagnitudo: Bool?
private let initialFilterType = EQNSeismic.shared.filterOption
private var currentFilterType = EQNSeismic.FilterType.inRadius
private var currentMaximumDistance = EQNData.DefaultFilterRadius
private var currentMinimumMagnitude = EQNData.DefaultFilterMagnitude
private var currentMagnitudoMinima = EQNData.DefaultMagitudoDebole
private var currentDistanzaMassima = EQNData.DefaultRaggioSisma
private var currentPeriodoTemporale = EQNData.DefaultPeriodoTemporale
private var currentSismiFortiAbilitati = false
private var currentSismiFortiDistanza = EQNData.DefaultMagitudoForte
private var currentSismiQualsiasiMagnitudo = false
private var currentModificaImpostazioni = false
private let dataSourceMaximumDistance = EQNData.filterRadius
private let dataSourceMinimumMagnitude = EQNData.filterMagnitude
// MARK: - View Lifecycle
@@ -85,19 +75,9 @@ class SeismicFiltersViewController: UIViewController, UITableViewDelegate, UITab
}
private func loadDataSource() {
currentMagnitudoMinima = EQNData.magitudoDebole(for: EQNSeismic.shared.magnitudoMinima)
if initialMagnitudoMinima == nil {
initialMagnitudoMinima = currentMagnitudoMinima
}
currentDistanzaMassima = EQNData.raggioSisma(for: EQNSeismic.shared.distanzaMassima)
currentPeriodoTemporale = EQNData.periodoTemporale(for: EQNSeismic.shared.periodoTemporale)
currentSismiFortiAbilitati = EQNSeismic.shared.sismiFortiAbilitati
currentSismiFortiDistanza = EQNData.magitudoForte(for: EQNSeismic.shared.sismiFortiMagnitudo)
currentSismiQualsiasiMagnitudo = EQNSeismic.shared.sismiQualsiasiAbilitati
if initialQualsiasiMagnitudo == nil {
initialQualsiasiMagnitudo = currentSismiQualsiasiMagnitudo
}
currentModificaImpostazioni = EQNSeismic.shared.modificaImpostazioniAbilitato
currentFilterType = EQNSeismic.shared.filterOption
currentMaximumDistance = EQNData.filterRadius(for: EQNSeismic.shared.maximumDistance)
currentMinimumMagnitude = EQNData.filterMagnitude(for: EQNSeismic.shared.minimumMagnitude)
}
// MARK: - Table view delegate and data source
@@ -107,45 +87,36 @@ class SeismicFiltersViewController: UIViewController, UITableViewDelegate, UITab
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let identifier = RowIdentifier(rawValue: indexPath.row) else {
return UITableViewCell()
}
let setting = settings[indexPath.row]
let isLocationAvailable = EQNUser.default().lastPosition != nil
switch setting.type {
case .slider:
let cell = SettingSliderTableViewCell(style: .default, reuseIdentifier: nil)
cell.titleLabel.text = setting.displayTitle
if indexPath.row == RowIdentifier.magnitudoMinima.rawValue {
cell.configureSlider(with: dataSourceMagnitudoMinima, current: currentMagnitudoMinima)
cell.valueChanged = { [unowned self] value in
currentMagnitudoMinima = value
EQNSeismic.shared.magnitudoMinima = value.value
EQNSeismic.shared.saveFilters()
let isFilterInRadiusEnabled = currentFilterType == .inRadius && isLocationAvailable
switch identifier {
case .distanzaMassima:
cell.isDisabled = !isFilterInRadiusEnabled
cell.isUserInteractionEnabled = isFilterInRadiusEnabled
cell.configureSlider(with: dataSourceMaximumDistance, current: currentMaximumDistance)
cell.valueChanged = { [weak self] value in
self?.onChangeMaximumDistance(value)
}
cell.dragEnded = { [unowned self] in
showWarningAlertIfNeeded(for: currentMagnitudoMinima)
}
} else if indexPath.row == RowIdentifier.distanzaMassima.rawValue {
cell.configureSlider(with: dataSourceDistanzaMassima, current: currentDistanzaMassima)
cell.valueChanged = { [unowned self] value in
currentDistanzaMassima = value
EQNSeismic.shared.distanzaMassima = value.value
EQNSeismic.shared.saveFilters()
}
} else if indexPath.row == RowIdentifier.periodoTemporale.rawValue {
cell.configureSlider(with: dataSourcePeriodoTemporale, current: currentPeriodoTemporale)
cell.valueChanged = { [unowned self] value in
currentPeriodoTemporale = value
EQNSeismic.shared.periodoTemporale = value.value
EQNSeismic.shared.saveFilters()
}
} else if indexPath.row == RowIdentifier.sismiFortiDistanza.rawValue {
cell.isDisabled = !currentSismiFortiAbilitati
cell.configureSlider(with: dataSourceSismiForti, current: currentSismiFortiDistanza)
cell.valueChanged = { [unowned self] value in
currentSismiFortiDistanza = value
EQNSeismic.shared.sismiFortiMagnitudo = value.value
EQNSeismic.shared.saveFilters()
case .magnitudoMinima:
cell.isDisabled = !isFilterInRadiusEnabled
cell.isUserInteractionEnabled = isFilterInRadiusEnabled
cell.configureSlider(with: dataSourceMinimumMagnitude, current: currentMinimumMagnitude)
cell.valueChanged = { [weak self] value in
self?.onChangeMinimumMagnitude(value)
}
default:
break
}
return cell
@@ -154,30 +125,31 @@ class SeismicFiltersViewController: UIViewController, UITableViewDelegate, UITab
cell.titleLabel.text = setting.displayTitle
cell.detailTextLabel?.text = setting.subtitle
if indexPath.row == RowIdentifier.sismiFortiAbilita.rawValue {
cell.toggleSwitch.isOn = currentSismiFortiAbilitati
cell.valueChanged = { [unowned self] value in
currentSismiFortiAbilitati = value
EQNSeismic.shared.sismiFortiAbilitati = value
EQNSeismic.shared.saveFilters()
loadDataSource()
tableView.reloadData()
switch identifier {
case .sismiNelRaggio:
let isCurrentFilter = currentFilterType == .inRadius
cell.isDisabled = !isLocationAvailable
cell.toggleSwitch.isOn = isCurrentFilter
cell.valueChanged = { [weak self] enabled in
self?.onChangeFilterOption(enabled, filter: .inRadius)
}
} else if indexPath.row == RowIdentifier.sismiQualsiasiMagnitudo.rawValue {
cell.toggleSwitch.isOn = currentSismiQualsiasiMagnitudo
cell.valueChanged = { [unowned self] value in
currentSismiQualsiasiMagnitudo = value
EQNSeismic.shared.sismiQualsiasiAbilitati = value
EQNSeismic.shared.saveFilters()
cell.errorLabel.text = !isLocationAvailable ? NSLocalizedString("filter_nolocation", comment: "") : nil
case .sismiRilevanti:
let isCurrentFilter = currentFilterType == .positionRelevant
cell.isDisabled = !isLocationAvailable
cell.toggleSwitch.isOn = isCurrentFilter
cell.valueChanged = { [weak self] enabled in
self?.onChangeFilterOption(enabled, filter: .positionRelevant)
}
} else if indexPath.row == RowIdentifier.modificaImpostazioni.rawValue {
cell.toggleSwitch.isOn = currentModificaImpostazioni
cell.valueChanged = { [unowned self] value in
currentModificaImpostazioni = value
EQNSeismic.shared.modificaImpostazioniAbilitato = value
EQNSeismic.shared.saveFilters()
cell.errorLabel.text = !isLocationAvailable ? NSLocalizedString("filter_nolocation", comment: "") : nil
case .sismiTutti:
let isCurrentFilter = currentFilterType == .worldWide
cell.toggleSwitch.isOn = isCurrentFilter
cell.valueChanged = { [weak self] enabled in
self?.onChangeFilterOption(enabled, filter: .worldWide)
}
default:
break
}
return cell
@@ -190,41 +162,34 @@ class SeismicFiltersViewController: UIViewController, UITableViewDelegate, UITab
@IBAction func exitTapped(_ sender: UIButton) {
// data needs to be re-downloaded if (or conditions):
// a) new magnitude is lower than the previous one and new value is less than 2.0
// b) show any near earthquake is active and value is changed
if let initialMagnitude = Float(initialMagnitudoMinima?.value ?? "10.0"), let currentMagnitude = Float(currentMagnitudoMinima.value) {
needsDataUpdate = currentMagnitude < 2.0 && initialMagnitude > currentMagnitude
}
if let initialQualsiasiMagnitudo = initialQualsiasiMagnitudo, currentSismiQualsiasiMagnitudo == true, initialQualsiasiMagnitudo != currentSismiQualsiasiMagnitudo {
needsDataUpdate = true
}
// a) filter type is changed
needsDataUpdate = initialFilterType != currentFilterType
delegate?.seismicFiltersControllerDidUpdateFilters(self)
updateNotificationSettingsIfNeeded()
dismiss(animated: true, completion: nil)
}
// MARK: - Private
private func showWarningAlertIfNeeded(for value: EQNGenericValue) {
guard let magnitude = Double(value.value), magnitude < 2.0 else { return }
private func onChangeFilterOption(_ enabled: Bool, filter: EQNSeismic.FilterType) {
currentFilterType = filter
EQNSeismic.shared.filterOption = filter
EQNSeismic.shared.saveFilters()
let alert = UIAlertController(title: NSLocalizedString("attention", comment: ""), message: NSLocalizedString("options_low_magnitude", comment: ""), preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("main_understood", comment: ""), style: .default, handler: nil))
present(alert, animated: true, completion: nil)
loadDataSource()
tableView.reloadData()
}
private func onChangeMaximumDistance(_ item: EQNGenericValue) {
currentMaximumDistance = item
EQNSeismic.shared.maximumDistance = item.value
EQNSeismic.shared.saveFilters()
}
private func updateNotificationSettingsIfNeeded() {
// if the switch is enabled, update also the settings notification
guard currentModificaImpostazioni == true else { return }
// update notification settings with current filters
EQNNotificheReteSismiche.shared().energiaSisma = EQNSeismic.shared.magnitudoMinima;
EQNNotificheReteSismiche.shared().distanzaPosizione = EQNSeismic.shared.distanzaMassima
EQNNotificheReteSismiche.shared().isAbilitaVicini = EQNSeismic.shared.sismiQualsiasiAbilitati
EQNNotificheReteSismiche.shared().isTerremortiForti = EQNSeismic.shared.sismiFortiAbilitati
EQNNotificheReteSismiche.shared().energiaTerremotiForti = EQNSeismic.shared.sismiFortiMagnitudo
private func onChangeMinimumMagnitude(_ item: EQNGenericValue) {
currentMinimumMagnitude = item
EQNSeismic.shared.minimumMagnitude = item.value
EQNSeismic.shared.saveFilters()
}
}
@@ -11,23 +11,25 @@ import Foundation
struct SeismicNetworkViewModel {
var place: String
var network: String
var isPreliminary: Bool
var magnitude: String
var depth: String
var time: String
var distance: String
var coordinate: String
var population: String
var smartphones: String
var users: String
let place: String
let network: String
let isPreliminary: Bool
let magnitude: String
let rawMagnitude: Double
let depth: String
let time: String
let distance: String
let coordinate: String
let population: String
let smartphones: String
let users: String
// MARK: - Init
init(seismic: EQNSisma) {
self.place = seismic.place
self.network = seismic.provider
self.rawMagnitude = seismic.magnitude.doubleValue
let isPreliminary = seismic.preliminary.intValue > 0
self.isPreliminary = isPreliminary
@@ -38,7 +40,7 @@ struct SeismicNetworkViewModel {
self.depth = ""
} else {
self.magnitude = String(format: "%.1f%@", seismic.magnitude.doubleValue, seismic.magnitudeType)
self.depth = String(format: "%@ %.1f km", NSLocalizedString("official_depth", comment: ""), seismic.depth.doubleValue)
self.depth = String(format: "%.1f km", seismic.depth.doubleValue)
}
// we need to check agains null values, because sometimes WS returns invalid dates
@@ -52,7 +54,7 @@ struct SeismicNetworkViewModel {
// distance
let distanceRounded = Int(round(seismic.userDistance))
self.distance = String(format: NSLocalizedString("timer_message2_other", comment: ""), distanceRounded)
self.distance = String(format: NSLocalizedString("official_distance", comment: ""), distanceRounded)
let coordinateText = EQNUtility.coordinateString(coordinate: seismic.coordinate.coordinate)
self.coordinate = "\(coordinateText)"
@@ -8,6 +8,7 @@
import UIKit
import MapKit
import Shogun
protocol SeismicNetworksMapDetailViewControllerDelegate: AnyObject {
func seismicNetworksMapDetailControllerWillUpdateData(_ controller: SeismicNetworksMapDetailViewController, needsDataUpdate: Bool)
@@ -15,10 +16,27 @@ 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 let eqnSeismic = EQNSeismic.shared
// MARK: - State
override var isCloseButtonVisible: Bool { false }
override var isFilterViewVisible: Bool {
// a custom filter view id displayed
// a custom filter view is displayed
true
}
weak var delegate: SeismicNetworksMapDetailViewControllerDelegate?
@@ -41,10 +59,7 @@ class SeismicNetworksMapDetailViewController: EQNBaseMapViewController {
seismicsFilterLabel.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
seismicsFilterLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 8.0).isActive = true
seismicsFilterLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -8.0).isActive = true
// tap recognizer
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(filtersTapped(_:)))
view.addGestureRecognizer(tapRecognizer)
return view
}()
@@ -75,6 +90,24 @@ class SeismicNetworksMapDetailViewController: EQNBaseMapViewController {
fatalError("init(coder:) is not available, please use init(seismic:allSeismics:)")
}
// MARK: - View Lifecycle
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()
})),
]
}
// MARK: - Public
func updateSeismics(_ seismics: [EQNSisma]) {
@@ -83,7 +116,9 @@ class SeismicNetworksMapDetailViewController: EQNBaseMapViewController {
}
override func registerMapAnnotationViews() {
mapView.register(EQNCustomAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNCustomAnnotationView.DoubleLineIdentifier)
mapView.register(EQNSeismicAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNSeismicAnnotationView.IdentifierFull)
mapView.register(EQNSeismicAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNSeismicAnnotationView.IdentifierLight)
mapView.register(EQNSeismicAnnotationView.self, forAnnotationViewWithReuseIdentifier: EQNSeismicAnnotationView.IdentifierCircle)
}
override func loadDataSource() {
@@ -91,14 +126,14 @@ class SeismicNetworksMapDetailViewController: EQNBaseMapViewController {
updateMap(with: annotations)
// if the given seismic is still in the data source, show circles
// otherwise just remove any other circles already on the map
if allSeismics.contains(seismic) {
addCircles(for: seismic.coordinate)
// if the filter is "in radius",
// show a circle with selected radius
if eqnSeismic.filterOption == .inRadius, let distance = Double(eqnSeismic.maximumDistance) {
addCircle(center: EQNUser.default().lastPosition, radius: distance * 1_000)
} else {
addCircles(for: nil)
addCircle(center: nil, radius: 0)
}
loadFiltersRecap()
}
@@ -107,6 +142,8 @@ class SeismicNetworksMapDetailViewController: EQNBaseMapViewController {
}
override func didTapAnnotation(_ annotation: MKAnnotation) {
mapView.deselectAnnotation(annotation, animated: true)
guard let annotation = annotation as? EQNMapAnnotationSeismic else { return }
let viewModel = SeismicNetworkViewModel(seismic: annotation.seismic)
@@ -124,31 +161,60 @@ class SeismicNetworksMapDetailViewController: EQNBaseMapViewController {
present(alert, animated: true)
}
// MARK: - Private
private func loadFiltersRecap() {
let filters = FiltersViewModel()
let recap = "\(NSLocalizedString("filter_filter", comment: "")): "
+ "M≥\(filters.magnitude) "
+ "D≤\(filters.distance) "
+ "T≤\(filters.timeframe)"
seismicsFilterLabel.text = recap
override func zPriority(for annotation: MKAnnotation) -> MKAnnotationViewZPriority {
guard let annotation = annotation as? EQNMapAnnotationSeismic else {
return .min
}
// il sisma cliccato dall'utente sta sopra a tutti
if annotation.seismic == seismic {
return .max
}
// Ordiniamo le annotazioni in base all amagnitudo, quelle con valore maggiore devono stare sopra.
// La `zPriority` viene calcolata utilizzando la posizione nella lista
let index = mapAnnotations
.compactMap { $0 as? EQNMapAnnotationSeismic }
.sorted(by: { $0.seismic.magnitude.doubleValue < $1.seismic.magnitude.doubleValue })
.firstIndex(where: { $0 == annotation })
guard let index else {
return .min
}
let priority = Float(index) / Float(mapAnnotations.count)
return .init(priority)
}
private func addCircles(for location: CLLocation?) {
// MARK: - Private
private func nextPinStyle() {
pinStyle = pinStyle.next()
reloadMap()
}
private func loadFiltersRecap() {
let filter = EQNSeismic.shared.filterOption
let text = switch filter {
case .inRadius: NSLocalizedString("filter_area", comment: "")
case .positionRelevant: NSLocalizedString("filter_relevant", comment: "")
case .worldWide: NSLocalizedString("filter_all", comment: "")
}
seismicsFilterLabel.text = text
}
private func addCircle(
center location: CLLocation?,
radius: Double
) {
// remove any previous circles
mapView.removeOverlays(mapCircles)
mapCircles.removeAll()
// aggiungiamo 3 cerchi concentrici per il punto specificato (se disponibile)
// li inseriamo a a 50, 100 e 200 km.
guard let location = location else { return }
let circles = [
MKCircle(center: location.coordinate, radius: 25_000),
MKCircle(center: location.coordinate, radius: 100_000),
MKCircle(center: location.coordinate, radius: 200_000)
MKCircle(center: location.coordinate, radius: radius),
]
// !!note: is important to assign here the circles
@@ -159,16 +225,13 @@ class SeismicNetworksMapDetailViewController: EQNBaseMapViewController {
mapView.addOverlays(circles)
}
// MARK: - Actions
@objc override func filtersTapped(_ sender: UIGestureRecognizer) {
let controller = SeismicFiltersViewController.makeController()
controller.delegate = self
controller.modalPresentationStyle = .overCurrentContext
controller.modalTransitionStyle = .crossDissolve
present(controller, animated: true, completion: nil)
private func shareScreenshot() {
let screenshot = createSnapshot()
let controller = UIActivityViewController(activityItems: [screenshot], applicationActivities: [])
present(controller, animated: true)
}
// MARK: - Map
override func setupAnnotationView(for annotation: MKAnnotation, on mapView: MKMapView) -> MKAnnotationView? {
@@ -176,15 +239,23 @@ class SeismicNetworksMapDetailViewController: EQNBaseMapViewController {
return nil
}
let viewModel = SeismicNetworkViewModel(seismic: annotation.seismic)
let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: EQNCustomAnnotationView.DoubleLineIdentifier, for: annotation) as! EQNCustomAnnotationView
annotationView.image = annotation.image
annotationView.title = annotation.title
annotationView.subtitle = viewModel.magnitude
return annotationView
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
}
}
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
@@ -193,8 +264,8 @@ class SeismicNetworksMapDetailViewController: EQNBaseMapViewController {
}
let circle = MKCircleRenderer(overlay: circleOverlay)
circle.strokeColor = AppTheme.Colors.darkGray
circle.lineWidth = 1.0
circle.strokeColor = AppTheme.Colors.red
circle.lineWidth = 2.0
return circle
}
}
@@ -9,6 +9,7 @@
import UIKit
import EventKitUI
import DZNEmptyDataSet
import Shogun
class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
@@ -18,16 +19,10 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
}
private static let SegueIdentifierFilters = "ShowFilters"
private static let SegueIdentifierSettings = "ShowSettings"
private static let SegueIdentifierSeismicNetworks = "ShowSeismicNetworks"
private static let SegueIdentifierCardSettings = "ShowCardSettings"
// MARK: - Internal
@IBOutlet private weak var tableView: UITableView?
@IBOutlet private weak var expandeCollapseButton: UIBarButtonItem!
weak var currentMapController: SeismicNetworksMapDetailViewController?
/// The ad loader
private lazy var adLoader: GADAdLoader = {
let adLoader = GADAdLoader(
@@ -43,23 +38,64 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
private var informations = [SeismicNetworkTableViewCell.InformationType]()
/// Index path of row with map expanded
private var openMapIndexPath: IndexPath?
/// Index path of row with weather expanded
private var openWeatherIndexPath: IndexPath?
/// Push notification opened by the user
private var openedPushNotification: EQNOfficialPushNotification? {
didSet {
scrollToOpenedSeismic = true
}
}
private var scrollToOpenedSeismic = false
/// Current displayed controller with map
private weak var currentMapController: SeismicNetworksMapDetailViewController?
// MARK: - UI
@IBOutlet private weak var expandeCollapseButton: UIBarButtonItem!
@IBOutlet private weak var sortButton: UIBarButtonItem!
private var tableViewTopConstraint: NSLayoutConstraint?
private lazy var tableView: UITableView = {
let tableView = UITableView(frame: .zero, style: .plain)
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.delegate = self
tableView.dataSource = self
return tableView
}()
private lazy var filterChangedView: UIView = {
let view = UIView(frame: .zero)
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = AppTheme.Colors.pureBlue
view.addSubview(filterChangedLabel)
filterChangedLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 8.0).isActive = true
filterChangedLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -8.0).isActive = true
filterChangedLabel.topAnchor.constraint(equalTo: view.topAnchor, constant: 8.0).isActive = true
filterChangedLabel.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -8.0).isActive = true
return view
}()
private lazy var filterChangedLabel: UILabel = {
let label = UILabel(frame: .zero)
label.translatesAutoresizingMaskIntoConstraints = false
label.textColor = .white
label.font = .preferredFont(forTextStyle: .subheadline)
label.numberOfLines = 0
label.textAlignment = .center
return label
}()
// MARK: - View Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
configureUI()
checkForLocation()
refreshUI()
// only the first time, show the popup for country selection
let alreadyPresented = UserDefaults.standard.bool(forKey: EQNUserDefaultKeyOneShotShowCountry)
if !alreadyPresented {
performSegue(withIdentifier: Self.SegueIdentifierSettings, sender: nil)
UserDefaults.standard.setValue(true, forKey: EQNUserDefaultKeyOneShotShowCountry)
}
configureFilterView(isVisible: false)
NotificationCenter.default.addObserver(self, selector: #selector(didReceiveDownloadCompleteNotification(_:)), name: .EQNDownloadDataDidComplete, object: nil)
}
@@ -68,16 +104,95 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
super.viewWillAppear(animated)
loadData(forced: false)
// check for a push to manage
if let notification = EQNOfficialPushNotification.stored() {
manageFilter(for: notification)
self.openedPushNotification = notification
EQNOfficialPushNotification.removeStored()
} else {
configureFilterView(isVisible: false)
self.openedPushNotification = nil
}
tableView.reloadData()
}
// MARK: - Private
private func setupUI() {
view.addSubview(tableView)
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
}
private func setupFilterView(isVisible: Bool) {
if isVisible && filterChangedView.superview == nil {
view.addSubview(filterChangedView)
tableViewTopConstraint?.isActive = false
filterChangedView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
filterChangedView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
filterChangedView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
tableViewTopConstraint = filterChangedView.bottomAnchor.constraint(equalTo: tableView.topAnchor)
tableViewTopConstraint?.isActive = true
} else {
filterChangedView.removeFromSuperview()
tableViewTopConstraint?.isActive = false
tableViewTopConstraint = tableView.topAnchor.constraint(equalTo: view.topAnchor)
tableViewTopConstraint?.isActive = true
}
}
private func configureFilterView(isVisible: Bool) {
setupFilterView(isVisible: isVisible)
if isVisible {
filterChangedLabel.text = NSLocalizedString("official_filter_changed", comment: "")
filterChangedView.backgroundColor = AppTheme.Colors.pureBlue
} else {
filterChangedLabel.text = nil
filterChangedView.backgroundColor = .white
}
}
private func configureUI() {
title = NSLocalizedString("tab_official", comment: "").capitalized
tableView?.estimatedRowHeight = 300.0
tableView?.rowHeight = UITableView.automaticDimension
tableView?.register(SeismicNetworkTableViewCell.self, forCellReuseIdentifier: SeismicNetworkTableViewCell.Identifier)
tableView?.register(SeismicNetworkAdvertiseTableViewCell.self, forCellReuseIdentifier: SeismicNetworkAdvertiseTableViewCell.Identifier)
tableView?.emptyDataSetSource = self
tableView.estimatedRowHeight = 300.0
tableView.rowHeight = UITableView.automaticDimension
tableView.registerCell(for: SeismicNetworkTableViewCell.self)
tableView.registerCell(for: SeismicNetworkAdvertiseTableViewCell.self)
tableView.emptyDataSetSource = self
tableView.separatorStyle = .none
setupSortMenu()
}
private func setupSortMenu() {
let currentSort = EQNSeismic.shared.sort
sortButton.menu = UIMenu(title: "", image: nil, identifier: nil, options: [], children: [
UIAction(title: NSLocalizedString("sort_date", comment: ""), image: UIImage(systemName: "calendar"), state: currentSort == .time ? .on : .off) { [weak self ] _ in
self?.changeSort(to: .time)
},
UIAction(title: NSLocalizedString("sort_position", comment: ""), image: UIImage(systemName: "ruler"), state: currentSort == .position ? .on : .off) { [weak self] _ in
self?.changeSort(to: .position)
},
UIAction(title: NSLocalizedString("sort_magnitude", comment: ""), image: UIImage(systemName: "thermometer"), state: currentSort == .magnitude ? .on : .off) { [weak self] _ in
self?.changeSort(to: .magnitude)
}
])
}
private func checkForLocation() {
// check if a valid location is available,
// otherwise change the filter settings
if !isLocationAvailable() {
EQNSeismic.shared.filterOption = .worldWide
EQNSeismic.shared.saveFilters()
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
@@ -86,14 +201,6 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
if let controller = segue.destination as? SeismicFiltersViewController {
controller.delegate = self
}
case Self.SegueIdentifierSettings:
if let controller = segue.destination as? SeismicSettingsViewController {
controller.delegate = self
}
case Self.SegueIdentifierSeismicNetworks:
if let navController = segue.destination as? UINavigationController, let controller = navController.viewControllers.first as? SeismicSettingsNetworksViewController {
controller.delegate = self
}
case Self.SegueIdentifierCardSettings:
if let controller = segue.destination as? SeismicCardSettingsViewController {
controller.delegate = self
@@ -107,7 +214,8 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
let seismics = getSeismics()
let controller = SeismicNetworksMapDetailViewController(seismic: seismic, allSeismics: seismics)
controller.delegate = self
present(controller, animated: true, completion: nil)
let navController = UINavigationController(rootViewController: controller)
present(navController, animated: true, completion: nil)
self.currentMapController = controller
}
@@ -116,7 +224,6 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
@objc func didReceiveDownloadCompleteNotification(_ sender: Notification) {
self.openMapIndexPath = nil
self.openWeatherIndexPath = nil
DispatchQueue.main.async {
self.refreshUI()
@@ -138,7 +245,12 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
expandeCollapseButton.image = UIImage(named: "navbar-icon-arrow-expand")
}
tableView?.reloadData()
tableView.reloadData()
if scrollToOpenedSeismic, let index = getSeismics().firstIndex(where: { isSeismicToHighlight(seismic: $0) }) {
tableView.scrollToRow(at: IndexPath(row: index, section: 0), at: .middle, animated: true)
scrollToOpenedSeismic = false
}
}
private func loadAd() {
@@ -178,6 +290,276 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
return seismics
}
private func changeSort(to sort: EQNSeismic.Sort) {
EQNSeismic.shared.sort = sort
EQNSeismic.shared.saveFilters()
setupSortMenu()
refreshUI()
}
private func manageFilter(
for notification: EQNOfficialPushNotification
) {
//gestisco i filtri solo se la posizione dell'utente è nota
guard let userPosition = EQNUser.default().lastPosition else {
return
}
var filter_type = EQNSeismic.shared.filterOption
var filter_radius = Double(EQNSeismic.shared.maximumDistance) ?? 0
var filter_min_magnitude = Double(EQNSeismic.shared.minimumMagnitude) ?? 0
var filter_changed = false
//recupero i dati del sisma notificato
let notification_magnitude = notification.magnitude
let notification_latitude = notification.coordinate.coordinate.latitude
let notification_longitude = notification.coordinate.coordinate.longitude
//distanza tra smartphone utente e sisma notificato
let locationNotification = CLLocation(latitude: notification_latitude, longitude: notification_longitude)
let distance = userPosition.distance(from: locationNotification) / 1_000
//verifico se il sisma è significativo in base alla definizione di significativo
var is_significant = true
if notification_magnitude < 7.0 && distance > 2000 {
is_significant = false
} else if notification_magnitude < 6.5 && distance > 1600 {
is_significant = false
} else if notification_magnitude < 6.0 && distance > 1300 {
is_significant = false
} else if notification_magnitude < 5.5 && distance > 1000 {
is_significant = false
} else if notification_magnitude < 5.0 && distance > 700 {
is_significant = false
} else if notification_magnitude < 4.5 && distance > 500 {
is_significant = false
} else if notification_magnitude < 4.0 && distance > 350 {
is_significant = false
} else if notification_magnitude < 3.5 && distance > 200 {
is_significant = false
} else if notification_magnitude < 3.0 && distance > 125 {
is_significant = false
} else if notification_magnitude < 2.5 && distance > 70 {
is_significant = false
} else if notification_magnitude < 2.0 && distance > 35 {
is_significant = false
} else if notification_magnitude < 1.5 && distance > 20 {
is_significant = false
}
//verifico se devo modificare il filtro scelto dall'utente
if filter_type == .inRadius { //filter_type=0 è il filtro basato su raggio e magnitudo
if distance > 2000 && is_significant {
filter_type = .positionRelevant //passo al filtro che mostra i sismi significativi (perché il raggio massimo del filtro basato sul raggio è 2000)
updateFilter(type: filter_type)
filter_changed = true
}
else if distance > 2000 && notification_magnitude >= 2.0 {
filter_type = .worldWide //passo al filtro che mostra tutti i sismi nel mondo
updateFilter(type: filter_type)
filter_changed = true
}
else {
//verifico se devo cambiare il raggio del filtro
if distance > filter_radius {
if distance > 1500 {
filter_radius = 2000
} else if distance > 1000 {
filter_radius = 1500
} else if distance > 750 {
filter_radius = 1000
} else if distance > 500 {
filter_radius = 750
} else if distance > 250 {
filter_radius = 500
} else if distance > 100 {
filter_radius = 250
}
updateFilter(radius: filter_radius)
filter_changed = true
}
//verifico se devo cambiare la mgnitudo del filtro
if notification_magnitude < filter_min_magnitude {
if notification_magnitude < 1.0 {
filter_min_magnitude = 0.0
} else if notification_magnitude < 2.0 {
filter_min_magnitude = 1.0
} else if notification_magnitude < 3.0 {
filter_min_magnitude = 2.0
} else if notification_magnitude < 4.0 {
filter_min_magnitude = 3.0
} else if notification_magnitude < 5.0 {
filter_min_magnitude = 4.0
} else if notification_magnitude < 6.0 {
filter_min_magnitude = 5.0
}
filter_changed = true
updateFilter(magnitude: filter_min_magnitude)
}
}
}
if filter_type == .positionRelevant && !is_significant && distance <= 2000 {
filter_type = .inRadius //passo a filtro basato su raggio e magnitudo
updateFilter(type: filter_type)
if distance > filter_radius {
if distance>1500 {
filter_radius = 2000
}
else if distance > 1000 {
filter_radius = 1500
}
else if distance > 750 {
filter_radius = 1000
}
else if distance > 500 {
filter_radius = 750
}
else if distance > 250 {
filter_radius = 500
}
else if distance > 100 {
filter_radius = 250
}
updateFilter(radius: filter_radius)
}
if notification_magnitude < filter_min_magnitude {
if notification_magnitude < 1.0 {
filter_min_magnitude = 0.0
}
else if notification_magnitude < 2.0 {
filter_min_magnitude = 1.0
}
else if notification_magnitude < 3.0 {
filter_min_magnitude = 2.0
}
else if notification_magnitude < 4.0 {
filter_min_magnitude = 3.0
}
else if notification_magnitude < 5.0 {
filter_min_magnitude = 4.0
}
else if notification_magnitude < 6.0 {
filter_min_magnitude = 5.0
}
updateFilter(magnitude: filter_min_magnitude)
}
filter_changed = true
}
if filter_type == .positionRelevant && !is_significant && distance > 2000 && notification_magnitude >= 2.0 {
filter_type = .worldWide //passo a filtro che mostra tutti i sismi nel mondo
updateFilter(type: filter_type)
filter_changed = true
}
if filter_type == .worldWide && notification_magnitude < 2.0 && is_significant {
filter_type = .positionRelevant //passo a filtro sismi significativi
updateFilter(type: filter_type)
filter_changed = true
}
if filter_type == .worldWide && notification_magnitude < 2.0 && distance <= 2000 && !is_significant {
filter_type = .inRadius
updateFilter(type: filter_type)
if distance > filter_radius {
if distance > 1500 {
filter_radius = 2000
}
else if distance > 1000 {
filter_radius = 1500
}
else if distance > 750 {
filter_radius = 1000
}
else if distance > 500 {
filter_radius = 750
}
else if distance > 250 {
filter_radius = 500
}
else if distance > 100 {
filter_radius = 250
}
updateFilter(radius: filter_radius)
}
if notification_magnitude < filter_min_magnitude {
if notification_magnitude < 1.0 {
filter_min_magnitude = 0.0
}
else if notification_magnitude < 2.0 {
filter_min_magnitude = 1.0
}
else if notification_magnitude < 3.0 {
filter_min_magnitude = 2.0
}
else if notification_magnitude < 4.0 {
filter_min_magnitude = 3.0
}
else if notification_magnitude < 5.0 {
filter_min_magnitude = 4.0
}
else if notification_magnitude < 6.0 {
filter_min_magnitude = 5.0
}
updateFilter(magnitude: filter_min_magnitude)
}
filter_changed = true
}
//mostro all'utente un messaggio per avvisarlo che i filtri sono stati modificati
configureFilterView(isVisible: filter_changed)
if filter_changed {
loadData(forced: true)
}
}
private func updateFilter(
type: EQNSeismic.FilterType? = nil,
radius: Double? = nil,
magnitude: Double? = nil
) {
if let type {
EQNSeismic.shared.filterOption = type
}
if let radius {
EQNSeismic.shared.maximumDistance = String(format: "%.0f", radius)
}
if let magnitude {
EQNSeismic.shared.minimumMagnitude = String(format: "%.1f", magnitude)
}
EQNSeismic.shared.saveFilters()
}
private func isLocationAvailable() -> Bool {
EQNUser.default().lastPosition != nil
}
private func isSeismicToHighlight(seismic: EQNSisma) -> Bool {
guard let notification = openedPushNotification else {
return false
}
guard let seismicDate = seismic.date, let notificationDate = notification.date else { return false }
let deltaTime = abs(seismicDate.timeIntervalSince(notificationDate))
let magnitudeRatio = seismic.magnitude.doubleValue / notification.magnitude
let latitudeDiff = abs(seismic.coordinate.coordinate.latitude - notification.coordinate.coordinate.latitude)
let longitudeDiff = abs(seismic.coordinate.coordinate.longitude - notification.coordinate.coordinate.longitude)
if deltaTime <= 120 && magnitudeRatio > 0.8 && magnitudeRatio < 1.2 && latitudeDiff < 1 && longitudeDiff < 1 { // secondi?
return true
}
return false
}
// MARK: - Actions
@IBAction func refreshDataTapped(_ sender: Any) {
@@ -187,11 +569,7 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
@IBAction func openFilterTapped(_ sender: Any) {
performSegue(withIdentifier: Self.SegueIdentifierFilters, sender: nil)
}
@IBAction func openSettingsTapped(_ sender: Any) {
performSegue(withIdentifier: Self.SegueIdentifierSettings, sender: nil)
}
@IBAction func collapseExpandTapped(_ sender: Any) {
if informations.contains(.buttons) {
informations.removeAll(where: { $0 == .buttons })
@@ -213,20 +591,19 @@ class SeismicNetworksViewController: UIViewController, UITableViewDelegate, UITa
let row = rows[indexPath.row]
switch row {
case .seismic(let seismic):
let cell = tableView.dequeueReusableCell(withIdentifier: SeismicNetworkTableViewCell.Identifier, for: indexPath) as! SeismicNetworkTableViewCell
let cell = tableView.dequeueReusableCell(cellIdentifiable: SeismicNetworkTableViewCell.self, for: indexPath)
var type = SeismicNetworkTableViewCell.DisplayType.normal
if openMapIndexPath == indexPath {
type = .mapExpanded
} else if openWeatherIndexPath == indexPath {
type = .weatherExpanded
}
cell.configure(with: seismic, type: type, informations: informations)
let isPushSelected = isSeismicToHighlight(seismic: seismic)
cell.configure(with: seismic, type: type, informations: informations, isPushSelected: isPushSelected)
cell.delegate = self
return cell
case .advertise(let nativeAd):
let cell = tableView.dequeueReusableCell(withIdentifier: SeismicNetworkAdvertiseTableViewCell.Identifier, for: indexPath) as! SeismicNetworkAdvertiseTableViewCell
let cell = tableView.dequeueReusableCell(cellIdentifiable: SeismicNetworkAdvertiseTableViewCell.self, for: indexPath)
cell.loadNativeAd(nativeAd)
return cell
}
@@ -302,7 +679,7 @@ extension SeismicNetworksViewController: GADNativeAdLoaderDelegate {
let adPosition = min(3, rows.count)
rows.insert(.advertise(nativeAd), at: adPosition)
tableView?.reloadData()
tableView.reloadData()
}
func adLoader(_ adLoader: GADAdLoader, didFailToReceiveAdWithError error: Error) {
@@ -314,10 +691,10 @@ extension SeismicNetworksViewController: GADNativeAdLoaderDelegate {
extension SeismicNetworksViewController: SeismicNetworkTableViewCellDelegate {
func seismicNetworkCellDidTapShare(_ cell: SeismicNetworkTableViewCell) {
guard let index = tableView?.indexPath(for: cell), case let .seismic(seismic) = rows[index.row] else { return }
guard let index = tableView.indexPath(for: cell), case let .seismic(seismic) = rows[index.row] else { return }
// create a snapshot of the cell and share with default share sheet
let snapshot = cell.createSnapshot()
let snapshot = cell.contentView.createSnapshot()
// text to share with the snapshot
let shareHashtag = NSLocalizedString("share_hashtag", comment: "")
@@ -330,43 +707,23 @@ extension SeismicNetworksViewController: SeismicNetworkTableViewCellDelegate {
present(controller, animated: true)
}
func seismicNetworkCellDidTapWeather(_ cell: SeismicNetworkTableViewCell, hasValidWeatherData: Bool) {
guard let index = tableView?.indexPath(for: cell) else { return }
if !hasValidWeatherData {
let alert = UIAlertController(title: NSLocalizedString("attention", comment: ""),
message: NSLocalizedString("weather_nodata", comment: ""),
preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("ok", comment: ""), style: .default))
present(alert, animated: true)
return
}
let indexToReloads = [openMapIndexPath, openWeatherIndexPath, index].compactMap { $0 }
openWeatherIndexPath = index
openMapIndexPath = nil
tableView?.reloadRows(at: indexToReloads, with: .automatic)
}
func seismicNetworkCellDidTapMap(_ cell: SeismicNetworkTableViewCell) {
guard let index = tableView?.indexPath(for: cell) else { return }
guard let index = tableView.indexPath(for: cell) else { return }
let indexToReloads = [openMapIndexPath, openWeatherIndexPath, index].compactMap { $0 }
let indexToReloads = [openMapIndexPath, index].compactMap { $0 }
openMapIndexPath = index
openWeatherIndexPath = nil
tableView?.reloadRows(at: indexToReloads, with: .automatic)
tableView.reloadRows(at: indexToReloads, with: .automatic)
}
func seismicNetworkCellDidTapMapDetail(_ cell: SeismicNetworkTableViewCell) {
guard let index = tableView?.indexPath(for: cell), case let .seismic(seismic) = rows[index.row] else { return }
guard let index = tableView.indexPath(for: cell), case let .seismic(seismic) = rows[index.row] else { return }
showMapDetail(for: seismic)
}
func seismicNetworkCellDidTapCalendar(_ cell: SeismicNetworkTableViewCell) {
guard let index = tableView?.indexPath(for: cell), case let .seismic(seismic) = rows[index.row] else { return }
guard let index = tableView.indexPath(for: cell), case let .seismic(seismic) = rows[index.row] else { return }
openCalendar(for: seismic)
}
@@ -376,13 +733,12 @@ extension SeismicNetworksViewController: SeismicNetworkTableViewCellDelegate {
}
func seismicNetworkCellDidTapClose(_ cell: SeismicNetworkTableViewCell) {
guard let index = tableView?.indexPath(for: cell) else { return }
guard let index = tableView.indexPath(for: cell) else { return }
let indexToReloads = [openMapIndexPath, openWeatherIndexPath, index].compactMap { $0 }
let indexToReloads = [openMapIndexPath, index].compactMap { $0 }
openMapIndexPath = nil
openWeatherIndexPath = nil
tableView?.reloadRows(at: indexToReloads, with: .automatic)
tableView.reloadRows(at: indexToReloads, with: .automatic)
}
}
@@ -400,22 +756,6 @@ extension SeismicNetworksViewController: SeismicFiltersViewControllerDelegate {
}
}
extension SeismicNetworksViewController: SeismicSettingsViewControllerDelegate {
func seismicSettingsControllerDidComplete(_ controller: SeismicSettingsViewController) {
refreshUI()
}
func seismicSettingsControllerWillOpenProviders(_ controller: SeismicSettingsViewController) {
performSegue(withIdentifier: Self.SegueIdentifierSeismicNetworks, sender: nil)
}
}
extension SeismicNetworksViewController: SeismicSettingsNetworksViewControllerDelegate {
func seismicSettingsNetworksControllerDidComplete(_ controller: SeismicSettingsNetworksViewController) {
refreshUI()
}
}
extension SeismicNetworksViewController: SeismicCardSettingsViewControllerDelegate {
func seismicCardSettingsDidComplete(_ controller: SeismicCardSettingsViewController) {
refreshUI()
@@ -423,9 +763,12 @@ extension SeismicNetworksViewController: SeismicCardSettingsViewControllerDelega
}
extension SeismicNetworksViewController: DZNEmptyDataSetSource {
func title(forEmptyDataSet scrollView: UIScrollView!) -> NSAttributedString! {
func title(forEmptyDataSet scrollView: UIScrollView) -> NSAttributedString? {
let text = EQNSeismic.shared.filterOption == .positionRelevant
? NSLocalizedString("filter_empty_relevant", comment: "")
: NSLocalizedString("filter_empty_area", comment: "")
let attributes = [ NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .body) ]
let string = NSAttributedString(string: NSLocalizedString("filter_empty", comment: ""), attributes: attributes)
let string = NSAttributedString(string: text, attributes: attributes)
return string
}
}
@@ -1,112 +0,0 @@
//
// SeismicSettingsNetworksViewController.swift
// Earthquake Network
//
// Created by Busi Andrea on 14/09/2020.
// Copyright © 2020 Earthquake Network. All rights reserved.
//
import UIKit
protocol SeismicSettingsNetworksViewControllerDelegate: AnyObject {
func seismicSettingsNetworksControllerDidComplete(_ controller: SeismicSettingsNetworksViewController)
}
class SeismicSettingsNetworksViewController: UITableViewController {
weak var delegate: SeismicSettingsNetworksViewControllerDelegate?
// MARK: - Private
private var networks = [EQNSeismicNetwork]()
private var savedNetworks = [String]()
// MARK: - View Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(SettingDetailTableViewCell.self, forCellReuseIdentifier: SettingDetailTableViewCell.Identifier)
tableView.register(SettingSectionHeaderView.self, forHeaderFooterViewReuseIdentifier: SettingSectionHeaderView.Identifier)
loadData()
}
// MARK: - Private
private func loadData() {
networks = EQNData.seismicNetworks().sorted(by: { $0.acronym < $1.acronym })
// load saved selected networks or fill with all available networks
if let savedNetworks = UserDefaults.standard.object(forKey: IMPOSTAZIONE_ENTI_RETI_SISMICHEI) as? [String] {
self.savedNetworks = savedNetworks
} else {
self.savedNetworks = EQNData.seismicNetworkAcronyms()
}
}
// MARK: - Table view data source
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: SettingSectionHeaderView.Identifier) as! SettingSectionHeaderView
headerView.titleLabel.text = NSLocalizedString("options_agencies", comment: "");
return headerView
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
CGFloat(SettingSectionHeaderView.Height)
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
networks.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let network = networks[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: SettingDetailTableViewCell.Identifier, for: indexPath) as! SettingDetailTableViewCell
cell.textLabel?.text = "\(network.acronym) (\(network.country))"
if savedNetworks.contains(network.acronym) {
cell.accessoryType = .checkmark
} else {
cell.accessoryType = .none
}
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let network = networks[indexPath.row]
if let index = savedNetworks.firstIndex(of: network.acronym) {
savedNetworks.remove(at: index)
} else {
savedNetworks.append(network.acronym)
}
tableView.reloadRows(at: [indexPath], with: .automatic)
}
// MARK: - Actions
@IBAction func cancelTapped(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
@IBAction func saveTapped(_ sender: Any) {
// save selected networks
UserDefaults.standard.set(savedNetworks, forKey: IMPOSTAZIONE_ENTI_RETI_SISMICHEI)
// se solo un'ente è selezionato, salviamolo anche come nazione
if savedNetworks.count == 1 {
UserDefaults.standard.set(savedNetworks.first!, forKey: IMPOSTAZIONE_NAZIONE_RETI_SISMICHE)
} else {
UserDefaults.standard.removeObject(forKey: IMPOSTAZIONE_NAZIONE_RETI_SISMICHE)
}
delegate?.seismicSettingsNetworksControllerDidComplete(self)
dismiss(animated: true, completion: nil)
}
}
@@ -1,130 +0,0 @@
//
// SeismicSettingsViewController.swift
// Earthquake Network
//
// Created by Busi Andrea on 13/09/2020.
// Copyright © 2020 Earthquake Network. All rights reserved.
//
import Foundation
protocol SeismicSettingsViewControllerDelegate: AnyObject {
func seismicSettingsControllerDidComplete(_ controller: SeismicSettingsViewController)
func seismicSettingsControllerWillOpenProviders(_ controller: SeismicSettingsViewController)
}
class SeismicSettingsViewController: UIViewController {
weak var delegate: SeismicSettingsViewControllerDelegate?
// MARK: - Private
@IBOutlet private weak var containerView: UIView!
@IBOutlet private weak var titleLabel: UILabel!
@IBOutlet private weak var countryTextField: UITextField!
@IBOutlet private weak var confirmButton: UIButton!
@IBOutlet private weak var otherwiseLabel: UILabel!
@IBOutlet private weak var manageNetworksButton: UIButton!
@IBOutlet private weak var cancelButton: UIButton!
private let networks = EQNData.seismicNetworks().sorted(by: { $0.country < $1.country })
private let picker = EQNGenericPickerViewController()
private var selectedNetwork: EQNSeismicNetwork?
// MARK: - View Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
}
// MARK: - Private
private func setupUI() {
containerView.layer.cornerRadius = AppTheme.shared.cardCornerRadius
containerView.layer.masksToBounds = true
// localize
titleLabel.text = NSLocalizedString("official_select_country", comment: "")
countryTextField.placeholder = NSLocalizedString("official_select_country_placeholder", comment: "")
confirmButton.setLocalizedTitle(key: "official_select_confirm", uppercased: false)
otherwiseLabel.text = NSLocalizedString("official_select_or", comment: "")
manageNetworksButton.setLocalizedTitle(key: "official_select_networks", uppercased: false)
cancelButton.setLocalizedTitle(key: "options_cancel", uppercased: false)
// load saved country (if exists)
let savedCountry = UserDefaults.standard.object(forKey: IMPOSTAZIONE_NAZIONE_RETI_SISMICHE) as? String
selectedNetwork = EQNData.seismic(for: savedCountry)
countryTextField.text = selectedNetwork?.country
countryTextField.inputView = picker.view
let selectedIndex: Int? = selectedNetwork != nil ? networks.firstIndex(of: selectedNetwork!) : nil
picker.configure(with: networks, selectedIndex: selectedIndex) { [unowned self] (network) in
guard let network = network as? EQNSeismicNetwork else { return }
self.view.endEditing(true)
self.selectedNetwork = network
self.countryTextField.text = self.selectedNetwork?.country
}
picker.onCancel = { [unowned self] in
self.view.endEditing(true)
}
}
private func performSave(for network: EQNSeismicNetwork) {
// salviamo la sigla dell'ente selezionato
UserDefaults.standard.set(network.acronym, forKey: IMPOSTAZIONE_NAZIONE_RETI_SISMICHE)
// gli enti selezionati conterranno solo l'ente della nazione selezionata
let selectedNetworks = [network.acronym]
UserDefaults.standard.set(selectedNetworks, forKey: IMPOSTAZIONE_ENTI_RETI_SISMICHEI)
// aggiorniamo le impostazioni di notifica
EQNNotificheReteSismiche.shared().listaEnti = selectedNetworks
EQNNotificheReteSismiche.shared().saveUserInfo()
SettingsBaseViewController.saveSettings()
// informiamo il delegato
delegate?.seismicSettingsControllerDidComplete(self)
dismiss(animated: true, completion: nil)
}
// MARK: - Actions
@IBAction func confirmCountryTapped(_ sender: UIButton) {
guard let network = selectedNetwork else {
let alert = UIAlertController(title: NSLocalizedString("attention", comment: ""),
message: NSLocalizedString("official_no_country_selected", comment: ""),
preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("ok", comment: ""), style: .cancel, handler: { [unowned self] (action) in
self.countryTextField.becomeFirstResponder()
}))
present(alert, animated: true, completion: nil)
return
}
// ask confirm to change settings for notifications
let alert = UIAlertController(title: NSLocalizedString("attention", comment: ""),
message: NSLocalizedString("official_select_message", comment: ""),
preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("options_cancel", comment: ""), style: .cancel))
alert.addAction(UIAlertAction(title: NSLocalizedString("official_select_confirm", comment: ""), style: .default, handler: { [unowned self] (action) in
self.performSave(for: network)
}))
present(alert, animated: true, completion: nil)
}
@IBAction func selectNetworksTapped(_ sender: UIButton) {
delegate?.seismicSettingsControllerWillOpenProviders(self)
dismiss(animated: true, completion: nil)
}
@IBAction func cancelTapped(_ sender: UIButton) {
dismiss(animated: true, completion: nil)
}
}
@@ -11,27 +11,27 @@ import Foundation
class SettingDateTableViewCell: UITableViewCell {
@objc static let Identifier = "DateCell"
static let Identifier = "DateCell"
@objc var isDisabled: Bool = false {
var isDisabled: Bool = false {
didSet {
updateUI()
}
}
@objc var isPickerVisible: Bool = false {
var isPickerVisible: Bool = false {
didSet {
if oldValue != isPickerVisible {
updateUI()
}
}
}
@objc private(set) var date = Date()
@objc var valueChanged: ((Date) -> Void)?
private(set) var date = Date()
var valueChanged: ((Date) -> Void)?
// MARK: - Properties
@objc lazy var titleLabel: UILabel = {
lazy var titleLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
@@ -40,7 +40,7 @@ class SettingDateTableViewCell: UITableViewCell {
return label
}()
@objc lazy var valuesLabel: UILabel = {
lazy var valuesLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
@@ -58,7 +58,7 @@ class SettingDateTableViewCell: UITableViewCell {
return picker
}()
@objc lazy var stackView: UIStackView = {
lazy var stackView: UIStackView = {
let stackView = UIStackView()
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical
@@ -81,7 +81,7 @@ class SettingDateTableViewCell: UITableViewCell {
// MARK: - Public
@objc public func updateDate(_ date: Date) {
public func updateDate(_ date: Date) {
self.date = date
datePicker.setDate(date, animated: true)
}
@@ -10,17 +10,16 @@ import UIKit
class SettingDetailTableViewCell: UITableViewCell {
@objc static let Identifier = "DetailCell"
static let Identifier = "DetailCell"
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
@@ -10,10 +10,10 @@ import UIKit
class SettingEnableTableViewCell: UITableViewCell {
@objc static let Identifier = "EnableCell"
static let Identifier = "EnableCell"
@objc var valueChanged: ((Bool) -> Void)?
@objc var isDisabled: Bool = false {
var valueChanged: ((Bool) -> Void)?
var isDisabled: Bool = false {
didSet {
updateUI()
}
@@ -21,7 +21,7 @@ class SettingEnableTableViewCell: UITableViewCell {
// MARK: - Properties
@objc lazy var titleLabel: UILabel = {
lazy var titleLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
@@ -29,7 +29,7 @@ class SettingEnableTableViewCell: UITableViewCell {
return label
}()
@objc lazy var descriptionLabel: UILabel = {
lazy var descriptionLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
@@ -37,7 +37,7 @@ class SettingEnableTableViewCell: UITableViewCell {
return label
}()
@objc lazy var toggleSwitch: UISwitch = {
lazy var toggleSwitch: UISwitch = {
let toggle = UISwitch()
toggle.setContentHuggingPriority(.required, for: .horizontal)
toggle.setContentCompressionResistancePriority(.required, for: .horizontal)
@@ -45,6 +45,15 @@ class SettingEnableTableViewCell: UITableViewCell {
return toggle
}()
lazy var errorLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
label.font = UIFont.preferredFont(forTextStyle: .subheadline)
label.textColor = AppTheme.Colors.red
label.text = nil
return label
}()
// MARK: - Init
@@ -75,6 +84,7 @@ class SettingEnableTableViewCell: UITableViewCell {
contentView.addSubview(stackView)
contentView.addSubview(descriptionLabel)
contentView.addSubview(errorLabel)
stackView.topAnchor.constraint(equalTo: contentView.layoutMarginsGuide.topAnchor).isActive = true
stackView.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor).isActive = true
@@ -83,7 +93,12 @@ class SettingEnableTableViewCell: UITableViewCell {
descriptionLabel.topAnchor.constraint(equalTo: stackView.bottomAnchor, constant: 8).isActive = true
descriptionLabel.trailingAnchor.constraint(equalTo: stackView.trailingAnchor).isActive = true
descriptionLabel.leadingAnchor.constraint(equalTo: stackView.leadingAnchor).isActive = true
descriptionLabel.bottomAnchor.constraint(equalTo: contentView.layoutMarginsGuide.bottomAnchor).isActive = true
//descriptionLabel.bottomAnchor.constraint(equalTo: contentView.layoutMarginsGuide.bottomAnchor).isActive = true
errorLabel.topAnchor.constraint(equalTo: descriptionLabel.bottomAnchor, constant: 8.0).isActive = true
errorLabel.trailingAnchor.constraint(equalTo: descriptionLabel.trailingAnchor).isActive = true
errorLabel.leadingAnchor.constraint(equalTo: descriptionLabel.leadingAnchor).isActive = true
errorLabel.bottomAnchor.constraint(equalTo: contentView.layoutMarginsGuide.bottomAnchor).isActive = true
}
private func updateUI() {
@@ -11,9 +11,9 @@ import Foundation
class SettingMultivaluesTableViewCell: UITableViewCell {
@objc static let Identifier = "MultivaluesCell"
static let Identifier = "MultivaluesCell"
@objc var isDisabled: Bool = false {
var isDisabled: Bool = false {
didSet {
updateUI()
}
@@ -21,7 +21,7 @@ class SettingMultivaluesTableViewCell: UITableViewCell {
// MARK: - Properties
@objc lazy var titleLabel: UILabel = {
lazy var titleLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
@@ -30,7 +30,7 @@ class SettingMultivaluesTableViewCell: UITableViewCell {
return label
}()
@objc lazy var valuesLabel: UILabel = {
lazy var valuesLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
@@ -10,18 +10,17 @@ import UIKit
class SettingSectionHeaderView: UITableViewHeaderFooterView {
@objc static let Identifier = "SectionHeaderView"
@objc static let Height = 50.0
static let Identifier = "SectionHeaderView"
static let Height = 50.0
// MARK: - Properties
@objc lazy var titleLabel: UILabel = {
lazy var titleLabel: UILabel = {
let titleLabel = UILabel()
titleLabel.font = UIFont.preferredFont(forTextStyle: .headline)
titleLabel.textColor = AppTheme.Colors.lightBlue
return titleLabel
}()
// MARK: - Init
@@ -34,7 +33,6 @@ class SettingSectionHeaderView: UITableViewHeaderFooterView {
super.init(coder: coder)
setupUI()
}
// MARK: - Private
@@ -45,6 +43,5 @@ class SettingSectionHeaderView: UITableViewHeaderFooterView {
titleLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
titleLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
titleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
}
}
@@ -11,19 +11,19 @@ import Foundation
class SettingSegmentedTableViewCell: UITableViewCell {
@objc static let Identifier = "SegmentedCell"
static let Identifier = "SegmentedCell"
@objc var isDisabled: Bool = false {
var isDisabled: Bool = false {
didSet {
updateUI()
}
}
@objc var valueChanged: ((EQNGenericValue) -> Void)?
var valueChanged: ((EQNGenericValue) -> Void)?
private var items = [EQNGenericValue]()
// MARK: - Properties
@objc lazy var titleLabel: UILabel = {
lazy var titleLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
@@ -32,7 +32,7 @@ class SettingSegmentedTableViewCell: UITableViewCell {
return label
}()
@objc lazy var segmentedControl: UISegmentedControl = {
lazy var segmentedControl: UISegmentedControl = {
let segmented = UISegmentedControl()
segmented.translatesAutoresizingMaskIntoConstraints = false
segmented.addTarget(self, action: #selector(segmentedControlChanged(_:)), for: .valueChanged)
@@ -10,21 +10,21 @@ import UIKit
class SettingSliderTableViewCell: UITableViewCell {
@objc static let Identifier = "SliderCell"
static let Identifier = "SliderCell"
@objc var isDisabled: Bool = false {
var isDisabled: Bool = false {
didSet {
updateUI()
}
}
@objc var valueChanged: ((EQNGenericValue) -> Void)?
@objc var dragEnded: (() -> Void)?
var valueChanged: ((EQNGenericValue) -> Void)?
var dragEnded: (() -> Void)?
private var items = [EQNGenericValue]()
// MARK: - Properties
@objc lazy var titleLabel: UILabel = {
lazy var titleLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
@@ -33,7 +33,7 @@ class SettingSliderTableViewCell: UITableViewCell {
return label
}()
@objc lazy var valueLabel: UILabel = {
lazy var valueLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
@@ -42,7 +42,7 @@ class SettingSliderTableViewCell: UITableViewCell {
return label
}()
@objc lazy var slider: UISlider = {
lazy var slider: UISlider = {
let slider = UISlider()
slider.isContinuous = true
slider.addTarget(self, action: #selector(sliderChanged(_:)), for: .valueChanged)
@@ -0,0 +1,43 @@
//
// SettingsBaseTableViewController.swift
// Earthquake Network
//
// Created by Andrea Busi on 10/06/24.
// Copyright © 2024 Earthquake Network. All rights reserved.
//
import UIKit
@objc
class SettingsBaseTableViewController: UITableViewController {
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if isMovingFromParent {
Self.saveSettings()
}
}
// MARK: - Class
@objc class func saveSettings() {
saveSettings { _ in
// nope
}
}
@objc class func saveSettings(
completion: @escaping (_ success: Bool) -> Void
) {
let url = EQNGeneratoreURLServer.urlInvioImpostazioniNotifiche()
ServerRequest.default().inviaInformazioniAlServer(with: url, richiesta: .impostazioniNotifiche) { _ in
print("[SETTINGS] Settings saved successfully")
completion(true)
} failure: { error in
print("[SETTINGS] Settings saved failed. Error: \(error?.localizedDescription ?? "n.d.")")
completion(false)
}
}
}
@@ -1,19 +0,0 @@
//
// SettingsBaseViewController.h
// Earthquake Network
//
// Created by Busi Andrea on 30/08/2020.
// Copyright © 2020 Earthquake Network. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface SettingsBaseViewController : UITableViewController
+ (void)saveSettings;
@end
NS_ASSUME_NONNULL_END
@@ -1,42 +0,0 @@
//
// SettingsBaseViewController.m
// Earthquake Network
//
// Created by Busi Andrea on 30/08/2020.
// Copyright © 2020 Earthquake Network. All rights reserved.
//
#import "SettingsBaseViewController.h"
#import "ServerRequest.h"
#import "EQNGeneratoreURLServer.h"
@interface SettingsBaseViewController ()
@end
@implementation SettingsBaseViewController
#pragma mark - View Lifecycle
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
// when controller is dismissed, save settings
if (self.isMovingFromParentViewController) {
[SettingsBaseViewController saveSettings];
}
}
#pragma mark - Private
+ (void)saveSettings
{
[[ServerRequest defaultServerConnectionSingleton] inviaInformazioniAlServerWithURL:[EQNGeneratoreURLServer urlInvioImpostazioniNotifiche] richiesta:EQNTipoChiamataImpostazioniNotifiche success:^(id result){
NSLog(@"Settings saved successfully");
} failure:^(NSError *error){
NSLog(@"Settings saved failed. Error: %@", error.localizedDescription);
}];
}
@end
@@ -1,18 +0,0 @@
//
// AletaSismiTableViewController.h
// Earthquake Network
//
// Refactored by Andrea Busi 25/08/2020.
// Copyright © 2020 Earthquake Network. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "SettingsBaseViewController.h"
NS_ASSUME_NONNULL_BEGIN
@interface SettingsRealTimeAlertsViewController : SettingsBaseViewController
@end
NS_ASSUME_NONNULL_END
@@ -1,308 +0,0 @@
//
// AletaSismiTableViewController.m
// Earthquake Network
//
// Refactored by Andrea Busi 25/08/2020.
// Copyright © 2020 Earthquake Network. All rights reserved.
//
#import "SettingsRealTimeAlertsViewController.h"
#import "EQNAllertaSismica.h"
@import UserNotifications;
@interface SettingsRealTimeAlertsViewController () <UITextFieldDelegate>
@property (nonatomic, strong) NSArray<SettingItem *> *settings;
@property (strong, nonatomic) NSArray<EQNGenericValue *> *dataSourceSismi;
@property (nonatomic, strong) NSArray<EQNGenericValue *> *dataSourceRaggioSisma;
@property (nonatomic, strong) EQNGenericValue *currentSeismicToNotify;
@property (strong, nonatomic) EQNGenericValue *currentLowSeismicRadius;
@property (strong, nonatomic) EQNGenericValue *currentStrongSeismicRadius;
@property (strong, nonatomic) NSDate *currentStartTime;
@property (nonatomic) BOOL isStartTimeExpanded;
@property (strong, nonatomic) NSDate *currentEndTime;
@property (nonatomic) BOOL isEndTimeExpanded;
@property (nonatomic, strong) NSDateFormatter *dateFormatter;
@property (nonatomic, assign) BOOL notificationEnabled;
@property (nonatomic, assign) BOOL criticalAlertsEnabled;
@property (nonatomic, assign) BOOL doNotDisturbEnabled;
@end
@implementation SettingsRealTimeAlertsViewController
typedef NS_ENUM(NSInteger, RowIdentifier) {
RowIdentifierAbilitaNotifiche = 0,
RowIdentifierAbilitaCriticalAlerts,
RowIdentifierSismiDaNotificare,
RowIdentifierRaggioSismiLievi,
RowIndntifierRaggioSismiForti,
RowIdentifierNonDisturbare,
RowIdentifierNonDisturbareOraInizio,
RowIdentifierNonDisturbareOraFine
};
#pragma mark - Accessories
- (NSDateFormatter *)dateFormatter
{
if (!_dateFormatter) {
_dateFormatter = [[NSDateFormatter alloc] init];
[_dateFormatter setDateFormat:@"HH:mm"];
}
return _dateFormatter;
}
#pragma mark - View Lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
[self setupUI];
self.settings = @[
[[SettingItem alloc] initWithType:SettingTypeEnable title:NSLocalizedString(@"options_notification_enable_alarm", @"")],
[[SettingItem alloc] initWithType:SettingTypeEnable title:NSLocalizedString(@"critical_alerts_setting", @"")],
[[SettingItem alloc] initWithType:SettingTypeSegmented title:NSLocalizedString(@"options_notification_eqn_intensity", @"") subtitle:NSLocalizedString(@"", @"")],
[[SettingItem alloc] initWithType:SettingTypeSlider title:NSLocalizedString(@"options_radius_mild", @"")],
[[SettingItem alloc] initWithType:SettingTypeSlider title:NSLocalizedString(@"options_radius_strong", @"")]
];
self.dataSourceSismi = [EQNData seismicToNotify];
self.dataSourceRaggioSisma = [EQNData raggioSismi];
[self loadDataSource];
[self.tableView reloadData];
}
#pragma mark - Private
- (void)setupUI
{
self.navigationItem.largeTitleDisplayMode = UINavigationItemLargeTitleDisplayModeNever;
self.tableView.estimatedRowHeight = 200.0;
self.tableView.rowHeight = UITableViewAutomaticDimension;
[self.tableView registerClass:[SettingSectionHeaderView class] forHeaderFooterViewReuseIdentifier:SettingSectionHeaderView.Identifier];
[self.tableView registerClass:[SettingEnableTableViewCell class] forCellReuseIdentifier:SettingEnableTableViewCell.Identifier];
[self.tableView registerClass:[SettingSliderTableViewCell class] forCellReuseIdentifier:SettingSliderTableViewCell.Identifier];
[self.tableView registerClass:[SettingMultivaluesTableViewCell class] forCellReuseIdentifier:SettingMultivaluesTableViewCell.Identifier];
[self.tableView registerClass:[SettingSegmentedTableViewCell class] forCellReuseIdentifier:SettingSegmentedTableViewCell.Identifier];
[self.tableView registerClass:[SettingDateTableViewCell class] forCellReuseIdentifier:SettingDateTableViewCell.Identifier];
}
- (void)loadDataSource
{
self.notificationEnabled = [EQNAllertaSismica sharedInstance].isAbilitato;
self.criticalAlertsEnabled = [EQNAllertaSismica sharedInstance].isCriticalAlertsEnabled;
self.doNotDisturbEnabled = [EQNAllertaSismica sharedInstance].isintervalloAllarme;
// sismi da notificare
EQNGenericValue *sismiDaNotificare = [EQNData seismicToNotifyFor:[EQNAllertaSismica sharedInstance].sismiDaNotificare];
self.currentSeismicToNotify = sismiDaNotificare;
// raggio sismi lievi
EQNGenericValue *raggioSismiLievi = [EQNData raggioSismaFor:[EQNAllertaSismica sharedInstance].raggioSismiLievi];
self.currentLowSeismicRadius = raggioSismiLievi;
// raggio sismi forti
EQNGenericValue *raggioSismiForti = [EQNData raggioSismaFor:[EQNAllertaSismica sharedInstance].raggioSismiForti];
self.currentStrongSeismicRadius = raggioSismiForti;
// non disturbare, orari
NSDate *startTime = [EQNData doNotDisturbEndDateFrom:[EQNAllertaSismica sharedInstance].oraioInizio];
self.currentStartTime = startTime;
NSDate *endTime = [EQNData doNotDisturbEndDateFrom:[EQNAllertaSismica sharedInstance].orarioFine];
self.currentEndTime = endTime;
[[EQNAllertaSismica sharedInstance] saveUserInfo];
}
#pragma mark - Table view data source
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.settings.count;
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
SettingSectionHeaderView *headerView = [tableView dequeueReusableHeaderFooterViewWithIdentifier:SettingSectionHeaderView.Identifier];
headerView.titleLabel.text = NSLocalizedString(@"options_alarms", @"");
return headerView;
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return SettingSectionHeaderView.Height;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
SettingItem *setting = self.settings[indexPath.row];
if (setting.type == SettingTypeEnable) {
SettingEnableTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:SettingEnableTableViewCell.Identifier forIndexPath:indexPath];
cell.titleLabel.text = setting.displayTitle;
cell.descriptionLabel.text = setting.subtitle;
if (indexPath.row == RowIdentifierAbilitaNotifiche) {
cell.toggleSwitch.on = self.notificationEnabled;
cell.valueChanged = ^(BOOL enabled) {
self.notificationEnabled = enabled;
[EQNAllertaSismica sharedInstance].isAbilitato = self.notificationEnabled;
[[EQNAllertaSismica sharedInstance] saveUserInfo];
[self.tableView reloadData];
};
} else if (indexPath.row == RowIdentifierAbilitaCriticalAlerts) {
cell.toggleSwitch.on = self.criticalAlertsEnabled;
cell.valueChanged = ^(BOOL enabled) {
if (enabled) {
[self askForCriticalAlertsPermission];
}
self.criticalAlertsEnabled = enabled;
[EQNAllertaSismica sharedInstance].isCriticalAlertsEnabled = self.criticalAlertsEnabled;
[[EQNAllertaSismica sharedInstance] saveUserInfo];
[self.tableView reloadData];
};
} else if (indexPath.row == RowIdentifierNonDisturbare) {
cell.toggleSwitch.on = self.doNotDisturbEnabled;
cell.isDisabled = !self.notificationEnabled;
cell.valueChanged = ^(BOOL enabled) {
self.doNotDisturbEnabled = enabled;
[EQNAllertaSismica sharedInstance].isintervalloAllarme = self.doNotDisturbEnabled;
[[EQNAllertaSismica sharedInstance] saveUserInfo];
[self.tableView reloadData];
};
}
return cell;
} else if (setting.type == SettingTypeSegmented) {
SettingSegmentedTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:SettingSegmentedTableViewCell.Identifier forIndexPath:indexPath];
cell.titleLabel.text = setting.displayTitle;
if (indexPath.row == RowIdentifierSismiDaNotificare) {
cell.isDisabled = !self.notificationEnabled;
[cell configureControlWith:self.dataSourceSismi current:self.currentSeismicToNotify];
cell.valueChanged = ^(EQNGenericValue *item) {
[self updateSismicToNotify:item];
};
}
return cell;
} else if (setting.type == SettingTypeSlider) {
SettingSliderTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:SettingSliderTableViewCell.Identifier forIndexPath:indexPath];
cell.titleLabel.text = setting.displayTitle;
if (indexPath.row == RowIdentifierRaggioSismiLievi) {
cell.isDisabled = !self.notificationEnabled;
[cell configureSliderWith:self.dataSourceRaggioSisma current:self.currentLowSeismicRadius];
cell.valueChanged = ^(EQNGenericValue *item) {
[self updateLowSeismicRadius:item];
};
} else if (indexPath.row == RowIndntifierRaggioSismiForti) {
cell.isDisabled = !self.notificationEnabled;
[cell configureSliderWith:self.dataSourceRaggioSisma current:self.currentStrongSeismicRadius];
cell.valueChanged = ^(EQNGenericValue *item) {
[self updateStrongSeismicRadius:item];
};
}
return cell;
} else if (setting.type == SettingTypeDate) {
SettingDateTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:SettingDateTableViewCell.Identifier forIndexPath:indexPath];
cell.isDisabled = !self.doNotDisturbEnabled || !self.notificationEnabled;
cell.userInteractionEnabled = self.doNotDisturbEnabled && self.notificationEnabled;
cell.titleLabel.text = setting.title;
if (indexPath.row == RowIdentifierNonDisturbareOraInizio) {
cell.isPickerVisible = self.isStartTimeExpanded;
[cell updateDate:self.currentStartTime];
cell.valuesLabel.text = [self.dateFormatter stringFromDate:self.currentStartTime];
cell.valueChanged = ^(NSDate *date) {
[self updateStartTime:date];
};
} else if (indexPath.row == RowIdentifierNonDisturbareOraFine) {
cell.isPickerVisible = self.isEndTimeExpanded;
[cell updateDate:self.currentEndTime];
cell.valuesLabel.text = [self.dateFormatter stringFromDate:self.currentEndTime];
cell.valueChanged = ^(NSDate *date) {
[self updateEndTime:date];
};
}
return cell;
}
return nil;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.row == RowIdentifierNonDisturbareOraInizio) {
self.isStartTimeExpanded = !self.isStartTimeExpanded;
} else if (indexPath.row == RowIdentifierNonDisturbareOraFine) {
self.isEndTimeExpanded = !self.isEndTimeExpanded;
}
[tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
}
#pragma mark - Private
- (void)updateSismicToNotify:(EQNGenericValue *)seismic
{
[EQNAllertaSismica sharedInstance].sismiDaNotificare = seismic.value;
[[EQNAllertaSismica sharedInstance] saveUserInfo];
[self loadDataSource];
}
- (void)updateLowSeismicRadius:(EQNGenericValue *)radius
{
[EQNAllertaSismica sharedInstance].raggioSismiLievi = radius.value;
[[EQNAllertaSismica sharedInstance] saveUserInfo];
[self loadDataSource];
}
- (void)updateStrongSeismicRadius:(EQNGenericValue *)radius
{
[EQNAllertaSismica sharedInstance].raggioSismiForti = radius.value;
[[EQNAllertaSismica sharedInstance] saveUserInfo];
[self loadDataSource];
}
- (void)updateStartTime:(NSDate *)date
{
[EQNAllertaSismica sharedInstance].oraioInizio = date;
[[EQNAllertaSismica sharedInstance] saveUserInfo];
[self loadDataSource];
}
- (void)updateEndTime:(NSDate *)date
{
[EQNAllertaSismica sharedInstance].orarioFine = date;
[[EQNAllertaSismica sharedInstance] saveUserInfo];
[self loadDataSource];
}
- (void)askForCriticalAlertsPermission
{
UNAuthorizationOptions authOptions = UNAuthorizationOptionCriticalAlert;
[[UNUserNotificationCenter currentNotificationCenter] requestAuthorizationWithOptions:authOptions completionHandler:^(BOOL granted, NSError *error) {
// nope
}];
}
@end
@@ -0,0 +1,126 @@
//
// SettingsRealTimeAlertsViewController.swift
// Earthquake Network
//
// Created by Andrea Busi on 10/06/24.
// Copyright © 2024 Earthquake Network. All rights reserved.
//
import UIKit
import Shogun
class SettingsRealTimeAlertsViewController: SettingsBaseTableViewController {
private enum RowIdentifier: Int {
case abilitaNotifiche
case abilitaCriticalAlerts
}
private var isNotificationEnabled = false
private var isCriticalAlertsEnabled = false
private let settings: [SettingItem] = [
.init(type: .enable, title: NSLocalizedString("options_notification_enable_alarm", comment: "")),
.init(type: .enable, title: NSLocalizedString("critical_alerts_setting", comment: ""))
]
// MARK: - View Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
tableView.reloadData()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
loadDataSource()
tableView.reloadData()
}
// MARK: - Private
private func setupUI() {
navigationItem.largeTitleDisplayMode = .never
tableView.estimatedRowHeight = 200.0
tableView.rowHeight = UITableView.automaticDimension
tableView.registerHeaderFooterView(for: SettingSectionHeaderView.self)
tableView.registerCell(for: SettingEnableTableViewCell.self)
}
private func loadDataSource() {
let saved = EQNSettingRealTimeAlert.shared
isNotificationEnabled = saved.isAbilitato
isCriticalAlertsEnabled = saved.isCriticalAlertsEnabled
}
// MARK: - Table view delegate and data source
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let view = tableView.dequeueHeaderFooterView(cellIdentifiable: SettingSectionHeaderView.self)
view.titleLabel.text = NSLocalizedString("options_alarms", comment: "")
return view
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
SettingSectionHeaderView.Height
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return settings.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let identifier = RowIdentifier(rawValue: indexPath.row) else {
return UITableViewCell()
}
let setting = settings[indexPath.row]
switch setting.type {
case .enable:
let cell = tableView.dequeueReusableCell(cellIdentifiable: SettingEnableTableViewCell.self, for: indexPath)
cell.titleLabel.text = setting.displayTitle
cell.descriptionLabel.text = setting.subtitle
switch identifier {
case .abilitaNotifiche:
cell.toggleSwitch.isOn = isNotificationEnabled
cell.valueChanged = { [weak self] enabled in
self?.onChangeNotificationEnabled(enabled)
}
case .abilitaCriticalAlerts:
cell.toggleSwitch.isOn = isCriticalAlertsEnabled
cell.valueChanged = { [weak self] enabled in
self?.onChangeCriticalAlertsEnabled(enabled)
}
}
return cell
default:
fatalError()
}
}
// MARK: - Private
private func onChangeNotificationEnabled(_ enabled: Bool) {
isNotificationEnabled = enabled
EQNSettingRealTimeAlert.shared.isAbilitato = isNotificationEnabled
EQNSettingRealTimeAlert.shared.saveUserInfo()
tableView.reloadData()
}
private func onChangeCriticalAlertsEnabled(_ enabled: Bool) {
isCriticalAlertsEnabled = enabled
EQNSettingRealTimeAlert.shared.isCriticalAlertsEnabled = isCriticalAlertsEnabled
EQNSettingRealTimeAlert.shared.saveUserInfo()
tableView.reloadData()
}
}
@@ -1,18 +0,0 @@
//
// SettingsSeismicNetworkAlertsViewController.h
// Earthquake Network
//
// Refactored by Andrea Busi 25/08/2020.
// Copyright © 2020 Earthquake Network. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "SettingsBaseViewController.h"
NS_ASSUME_NONNULL_BEGIN
@interface SettingsSeismicNetworkAlertsViewController : SettingsBaseViewController
@end
NS_ASSUME_NONNULL_END
@@ -1,258 +0,0 @@
//
// SettingsSeismicNetworkAlertsViewController.m
// Earthquake Network
//
// Refactored by Andrea Busi 25/08/2020.
// Copyright © 2020 Earthquake Network. All rights reserved.
//
#import "SettingsSeismicNetworkAlertsViewController.h"
#import "EQNNotificheReteSismiche.h"
@interface SettingsSeismicNetworkAlertsViewController ()
@property (nonatomic, strong) NSArray<SettingItem *> *settings;
@property (nonatomic, strong) NSArray<EQNGenericValue *> *dataSourceRaggioSisma;
@property (nonatomic, strong) NSArray<EQNGenericValue *> *dataSourceMagnitudoDeboli;
@property (nonatomic, strong) NSArray<EQNGenericValue *> *dataSourceMagnitudoForti;
@property (nonatomic, assign) BOOL notificationEnabled;
@property (nonatomic, assign) BOOL notificationNearEarthquakeEnabled;
@property (nonatomic, assign) BOOL notificationStrongEarthquakeEnabled;
@property (nonatomic, strong) EQNGenericValue *currentUserPositionRadius;
@property (nonatomic, strong) EQNGenericValue *currentSeismicEnergy;
@property (nonatomic, strong) EQNGenericValue *currentStrongEarthquakeDistance;
@end
@implementation SettingsSeismicNetworkAlertsViewController
static NSString * const SegueIdentifierListaEnti = @"ShowListaEnti";
typedef NS_ENUM(NSInteger, RowIdentifier) {
RowIdentifierAbilitaNotifiche = 0,
RowIdentifierRetiSismiche,
RowIdentifierRaggioPosizione,
RowIdentifierEnergiaSisma,
RowIdentifierTerremotiVicini,
RowIdentifierTerremotiForti,
RowIdentifierTerremotiFortiDistanza
};
#pragma mark - View Lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
[self setupUI];
self.settings = @[
[[SettingItem alloc] initWithType:SettingTypeEnable title:NSLocalizedString(@"options_notification_enable_official", @"")],
[[SettingItem alloc] initWithType:SettingTypeMultiValues title:NSLocalizedString(@"options_agencies", @"") segue:SegueIdentifierListaEnti],
[[SettingItem alloc] initWithType:SettingTypeSlider title:NSLocalizedString(@"options_radius", @"")],
[[SettingItem alloc] initWithType:SettingTypeSlider title:NSLocalizedString(@"options_energy", @"")],
[[SettingItem alloc] initWithType:SettingTypeEnable title:NSLocalizedString(@"options_near", @"") subtitle:NSLocalizedString(@"options_near_alert", @"")],
[[SettingItem alloc] initWithType:SettingTypeEnable title:NSLocalizedString(@"options_strong", @"") subtitle:NSLocalizedString(@"options_strong_alert", @"")],
[[SettingItem alloc] initWithType:SettingTypeSlider title:NSLocalizedString(@"options_strong_magnitude", @"")]
];
self.dataSourceMagnitudoDeboli = [EQNData magitudoDeboli];
self.dataSourceRaggioSisma = [EQNData raggioSismi];
self.dataSourceMagnitudoForti = [EQNData magitudoForti];
[self loadDataSource];
[self.tableView reloadData];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self loadDataSource];
[self.tableView reloadData];
}
#pragma mark - Private
- (void)setupUI
{
self.navigationItem.largeTitleDisplayMode = UINavigationItemLargeTitleDisplayModeNever;
self.tableView.estimatedRowHeight = 200.0;
self.tableView.rowHeight = UITableViewAutomaticDimension;
[self.tableView registerClass:[SettingSectionHeaderView class] forHeaderFooterViewReuseIdentifier:SettingSectionHeaderView.Identifier];
[self.tableView registerClass:[SettingEnableTableViewCell class] forCellReuseIdentifier:SettingEnableTableViewCell.Identifier];
[self.tableView registerClass:[SettingSliderTableViewCell class] forCellReuseIdentifier:SettingSliderTableViewCell.Identifier];
[self.tableView registerClass:[SettingMultivaluesTableViewCell class] forCellReuseIdentifier:SettingMultivaluesTableViewCell.Identifier];
}
- (void)loadDataSource
{
self.notificationEnabled = [EQNNotificheReteSismiche sharedInstance].isAbilitato;
self.notificationNearEarthquakeEnabled = [EQNNotificheReteSismiche sharedInstance].isAbilitaVicini;
self.notificationStrongEarthquakeEnabled = [EQNNotificheReteSismiche sharedInstance].isTerremortiForti;
// raggio dalla tua posizione
EQNGenericValue *raggioSisma = [EQNData raggioSismaFor:[EQNNotificheReteSismiche sharedInstance].distanzaPosizione];
self.currentUserPositionRadius = raggioSisma;
// energia sisma
EQNGenericValue *energiaSisma = [EQNData magitudoDeboleFor:[EQNNotificheReteSismiche sharedInstance].energiaSisma];
self.currentSeismicEnergy = energiaSisma;
// terremoti forti
EQNGenericValue *terremotiForti = [EQNData magitudoForteFor:[EQNNotificheReteSismiche sharedInstance].energiaTerremotiForti];
self.currentStrongEarthquakeDistance = terremotiForti;
// enti
if (![EQNNotificheReteSismiche sharedInstance].listaEnti) {
[EQNNotificheReteSismiche sharedInstance].listaEnti = [EQNData.seismicNetworkAcronyms copy];
}
[[EQNNotificheReteSismiche sharedInstance] saveUserInfo];
}
#pragma mark - Table view data source
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
SettingSectionHeaderView *headerView = [tableView dequeueReusableHeaderFooterViewWithIdentifier:SettingSectionHeaderView.Identifier];
headerView.titleLabel.text = NSLocalizedString(@"options_notification_official", @"titolo impostazioni notifiche");
return headerView;
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return SettingSectionHeaderView.Height;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.settings.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
SettingItem *setting = self.settings[indexPath.row];
if (setting.type == SettingTypeEnable) {
SettingEnableTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:SettingEnableTableViewCell.Identifier forIndexPath:indexPath];
cell.titleLabel.text = setting.displayTitle;
cell.descriptionLabel.text = setting.subtitle;
if (indexPath.row == RowIdentifierAbilitaNotifiche) {
cell.toggleSwitch.on = self.notificationEnabled;
cell.valueChanged = ^(BOOL enabled) {
self.notificationEnabled = enabled;
[EQNNotificheReteSismiche sharedInstance].isAbilitato = self.notificationEnabled;
[[EQNNotificheReteSismiche sharedInstance] saveUserInfo];
[self.tableView reloadData];
};
} else if (indexPath.row == RowIdentifierTerremotiVicini) {
cell.toggleSwitch.on = self.notificationNearEarthquakeEnabled;
cell.isDisabled = !self.notificationEnabled;
cell.valueChanged = ^(BOOL enabled) {
self.notificationNearEarthquakeEnabled = enabled;
[EQNNotificheReteSismiche sharedInstance].isAbilitaVicini = self.notificationNearEarthquakeEnabled;
[[EQNNotificheReteSismiche sharedInstance] saveUserInfo];
[self.tableView reloadData];
};
} else if (indexPath.row == RowIdentifierTerremotiForti) {
cell.toggleSwitch.on = self.notificationStrongEarthquakeEnabled;
cell.isDisabled = !self.notificationEnabled;
cell.valueChanged = ^(BOOL enabled) {
self.notificationStrongEarthquakeEnabled = enabled;
[EQNNotificheReteSismiche sharedInstance].isTerremortiForti = self.notificationStrongEarthquakeEnabled;
[[EQNNotificheReteSismiche sharedInstance] saveUserInfo];
[self.tableView reloadData];
};
}
return cell;
} else if (setting.type == SettingTypeMultiValues) {
SettingMultivaluesTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:SettingMultivaluesTableViewCell.Identifier forIndexPath:indexPath];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.isDisabled = !self.notificationEnabled;
cell.userInteractionEnabled = self.notificationEnabled;
cell.titleLabel.text = setting.title;
if (indexPath.row == RowIdentifierRetiSismiche) {
cell.valuesLabel.text = [self stringOfSelectedNetworks];
}
return cell;
} else if (setting.type == SettingTypeSlider) {
SettingSliderTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:SettingSliderTableViewCell.Identifier forIndexPath:indexPath];
cell.titleLabel.text = setting.displayTitle;
if (indexPath.row == RowIdentifierRaggioPosizione) {
cell.isDisabled = !self.notificationEnabled;
[cell configureSliderWith:self.dataSourceRaggioSisma current:self.currentUserPositionRadius];
cell.valueChanged = ^(EQNGenericValue *item) {
[self updateUserPositionRadius:item];
};
} else if (indexPath.row == RowIdentifierEnergiaSisma) {
cell.isDisabled = !self.notificationEnabled;
[cell configureSliderWith:self.dataSourceMagnitudoDeboli current:self.currentSeismicEnergy];
cell.valueChanged = ^(EQNGenericValue *item) {
[self updateSeismicEnergy:item];
};
} else if (indexPath.row == RowIdentifierTerremotiFortiDistanza) {
cell.isDisabled = !self.notificationEnabled || !self.notificationStrongEarthquakeEnabled;
[cell configureSliderWith:self.dataSourceMagnitudoForti current:self.currentStrongEarthquakeDistance];
cell.valueChanged = ^(EQNGenericValue *item) {
[self updateStrongEarthquakeEnergy:item];
};
}
return cell;
}
return nil;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
SettingItem *setting = self.settings[indexPath.row];
if (setting.segue != nil) {
[self performSegueWithIdentifier:setting.segue sender:nil];
}
}
#pragma mark - Private
- (void)updateUserPositionRadius:(EQNGenericValue *)radius
{
[EQNNotificheReteSismiche sharedInstance].distanzaPosizione = radius.value;
[[EQNNotificheReteSismiche sharedInstance] saveUserInfo];
[self loadDataSource];
}
- (void)updateSeismicEnergy:(EQNGenericValue *)energy
{
[EQNNotificheReteSismiche sharedInstance].energiaSisma = energy.value;
[[EQNNotificheReteSismiche sharedInstance] saveUserInfo];
[self loadDataSource];
}
- (void)updateStrongEarthquakeEnergy:(EQNGenericValue *)energy
{
[EQNNotificheReteSismiche sharedInstance].energiaTerremotiForti = energy.value;
[[EQNNotificheReteSismiche sharedInstance] saveUserInfo];
[self loadDataSource];
}
- (NSString *)stringOfSelectedNetworks
{
NSArray *networks = [EQNNotificheReteSismiche sharedInstance].listaEnti;
networks = [networks sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
return [networks componentsJoinedByString:@", "];
}
@end
@@ -0,0 +1,166 @@
//
// SettingsSeismicNetworkNotificationsViewController.swift
// Earthquake Network
//
// Created by Andrea Busi on 06/06/24.
// Copyright © 2024 Earthquake Network. All rights reserved.
//
import UIKit
import Shogun
class SettingsSeismicNetworkNotificationsViewController: SettingsBaseTableViewController {
private enum RowIdentifier: Int {
case abilitaNotifiche
case magnitudoMinima
case distanzaMassima
}
private var isNotificationEnabled = false
private var currentMinimumMagnitude = EQNData.DefaultSettingSeismicNetworkNotificationMagitude
private var currentMaximumDistance = EQNData.DefaultSettingSeismicNetworkNotificationRadius
private let dataSourceMinimumMagnitude = EQNData.settingSeismicNetworkNotificationMagnitudes
private let dataSourceMaximumDistance = EQNData.settingSeismicNetworkNotificationRadius
private let settings: [SettingItem] = [
.init(type: .enable, title: NSLocalizedString("options_notification_enable_official", comment: "")),
.init(type: .slider, title: NSLocalizedString("options_official_minmag", comment: "")),
.init(type: .slider, title: NSLocalizedString("options_official_maxdist", comment: ""))
]
// MARK: - View Liefcycle
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
tableView.reloadData()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
loadDataSource()
tableView.reloadData()
}
// MARK: - Private
private func setupUI() {
navigationItem.largeTitleDisplayMode = .never
tableView.estimatedRowHeight = 200.0
tableView.rowHeight = UITableView.automaticDimension
tableView.registerHeaderFooterView(for: SettingSectionHeaderView.self)
tableView.registerCell(for: SettingEnableTableViewCell.self)
tableView.registerCell(for: SettingSliderTableViewCell.self)
tableView.registerCell(for: SettingMultivaluesTableViewCell.self)
}
private func loadDataSource() {
let saved = EQNSettingSeismicNetworkNotification.shared
isNotificationEnabled = saved.isAbilitato
// magnitudo minima
let magnitudoMinima = EQNData.getSettingSeismicNetworkNotificationMagnitudes(for: saved.magnitudoMinima)
currentMinimumMagnitude = magnitudoMinima
// raggio dalla tua posizione
let distanzaMassima = EQNData.getSettingSeismicNetworkNotificationRadius(for: saved.distanzaMassima)
currentMaximumDistance = distanzaMassima
}
// MARK: - Table view data source
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let view = tableView.dequeueHeaderFooterView(cellIdentifiable: SettingSectionHeaderView.self)
view.titleLabel.text = NSLocalizedString("options_notification_official", comment: "")
return view
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
SettingSectionHeaderView.Height
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return settings.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let identifier = RowIdentifier(rawValue: indexPath.row) else {
return UITableViewCell()
}
let setting = settings[indexPath.row]
switch setting.type {
case .enable:
let cell = tableView.dequeueReusableCell(cellIdentifiable: SettingEnableTableViewCell.self, for: indexPath)
cell.titleLabel.text = setting.displayTitle
cell.descriptionLabel.text = setting.subtitle
switch identifier {
case .abilitaNotifiche:
cell.toggleSwitch.isOn = isNotificationEnabled
cell.valueChanged = { [weak self] enabled in
self?.onChangeNotificationEnabled(enabled)
}
default:
break
}
return cell
case .slider:
let cell = tableView.dequeueReusableCell(cellIdentifiable: SettingSliderTableViewCell.self, for: indexPath)
cell.titleLabel.text = setting.displayTitle
let filtersEnabled = isNotificationEnabled
switch identifier {
case .magnitudoMinima:
cell.isDisabled = !filtersEnabled
cell.configureSlider(with: dataSourceMinimumMagnitude, current: currentMinimumMagnitude)
cell.valueChanged = { [weak self] item in
self?.onChangeMinimumMagnitude(item)
}
case .distanzaMassima:
cell.isDisabled = !filtersEnabled
cell.configureSlider(with: dataSourceMaximumDistance, current: currentMaximumDistance)
cell.valueChanged = { [weak self] item in
self?.onChangeMaximumDistance(item)
}
default:
break
}
return cell
default:
fatalError()
}
}
private func onChangeNotificationEnabled(_ enabled: Bool) {
isNotificationEnabled = enabled
EQNSettingSeismicNetworkNotification.shared.isAbilitato = isNotificationEnabled
EQNSettingSeismicNetworkNotification.shared.saveUserInfo()
tableView.reloadData()
}
private func onChangeMinimumMagnitude(_ item: EQNGenericValue) {
EQNSettingSeismicNetworkNotification.shared.magnitudoMinima = item.value
EQNSettingSeismicNetworkNotification.shared.saveUserInfo()
loadDataSource()
}
private func onChangeMaximumDistance(_ item: EQNGenericValue) {
EQNSettingSeismicNetworkNotification.shared.distanzaMassima = item.value
EQNSettingSeismicNetworkNotification.shared.saveUserInfo()
loadDataSource()
}
}
@@ -1,71 +0,0 @@
//
// SettingsSeismicNetworksViewController.swift
// Earthquake Network
//
// Created by Busi Andrea on 26/08/2020.
// Copyright © 2020 Earthquake Network. All rights reserved.
//
import UIKit
class SettingsSeismicNetworksViewController: UITableViewController {
var networks = [EQNSeismicNetwork]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(SettingDetailTableViewCell.self, forCellReuseIdentifier: SettingDetailTableViewCell.Identifier)
tableView.register(SettingSectionHeaderView.self, forHeaderFooterViewReuseIdentifier: SettingSectionHeaderView.Identifier)
networks = EQNData.seismicNetworks().sorted(by: { $0.acronym < $1.acronym })
}
// MARK: - Table view data source
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: SettingSectionHeaderView.Identifier) as! SettingSectionHeaderView
headerView.titleLabel.text = NSLocalizedString("options_agencies", comment: "");
return headerView
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
CGFloat(SettingSectionHeaderView.Height)
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
networks.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let network = networks[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: SettingDetailTableViewCell.Identifier, for: indexPath) as! SettingDetailTableViewCell
cell.textLabel?.text = "\(network.acronym) (\(network.country))"
if EQNNotificheReteSismiche.shared().listaEnti.contains(network.acronym) {
cell.accessoryType = .checkmark
} else {
cell.accessoryType = .none
}
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let network = networks[indexPath.row]
var savedNetworks = EQNNotificheReteSismiche.shared().listaEnti
if let index = savedNetworks.firstIndex(where: { $0 == network.acronym }) {
savedNetworks.remove(at: index)
} else {
savedNetworks.append(network.acronym)
}
EQNNotificheReteSismiche.shared().listaEnti = savedNetworks
EQNNotificheReteSismiche.shared().saveUserInfo()
tableView.reloadData()
}
}
@@ -1,18 +0,0 @@
//
// SettingsUserReportAlertsViewController.h
// Earthquake Network
//
// Refactored by Andrea Busi 25/08/2020.
// Copyright © 2020 Earthquake Network. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "SettingsBaseViewController.h"
NS_ASSUME_NONNULL_BEGIN
@interface SettingsUserReportAlertsViewController : SettingsBaseViewController
@end
NS_ASSUME_NONNULL_END
@@ -1,119 +0,0 @@
//
// SettingsUserReportAlertsViewController.m
// Earthquake Network
//
// Refactored by Andrea Busi 25/08/2020.
// Copyright © 2020 Earthquake Network. All rights reserved.
//
#import "SettingsUserReportAlertsViewController.h"
#import "EQNNotificheSegnalazioniUtente.h"
@interface SettingsUserReportAlertsViewController ()
@property (nonatomic, strong) NSArray<SettingItem *> *settings;
@property (nonatomic, assign) BOOL notificationsEnabled;
@property (nonatomic, strong) EQNGenericValue *currentRadius;
@end
@implementation SettingsUserReportAlertsViewController
#pragma mark - View Lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
[self setupUI];
self.settings = @[
[[SettingItem alloc] initWithType:SettingTypeEnable title:NSLocalizedString(@"options_notification_enable_manual", @"")],
[[SettingItem alloc] initWithType:SettingTypeSlider title:NSLocalizedString(@"options_radius", @"")]
];
[self updateUI];
}
#pragma mark - Private
- (void)setupUI
{
self.navigationItem.largeTitleDisplayMode = UINavigationItemLargeTitleDisplayModeNever;
self.tableView.estimatedRowHeight = 100.0;
self.tableView.rowHeight = UITableViewAutomaticDimension;
[self.tableView registerClass:[SettingSectionHeaderView class] forHeaderFooterViewReuseIdentifier:SettingSectionHeaderView.Identifier];
[self.tableView registerClass:[SettingEnableTableViewCell class] forCellReuseIdentifier:SettingEnableTableViewCell.Identifier];
[self.tableView registerClass:[SettingSliderTableViewCell class] forCellReuseIdentifier:SettingSliderTableViewCell.Identifier];
}
- (void)updateUI
{
self.notificationsEnabled = [EQNNotificheSegnalazioniUtente sharedInstance].isAbilitato;
EQNGenericValue *distanzaPosizione = [EQNData raggioSismaFor:[EQNNotificheSegnalazioniUtente sharedInstance].distanzaPosizione];
self.currentRadius = distanzaPosizione;
[[EQNNotificheSegnalazioniUtente sharedInstance] saveUserInfo];
[self.tableView reloadData];
}
- (void)updateRadius:(EQNGenericValue *)radius
{
self.currentRadius = radius;
[EQNNotificheSegnalazioniUtente sharedInstance].distanzaPosizione = radius.value;
[[EQNNotificheSegnalazioniUtente sharedInstance] saveUserInfo];
[self.tableView reloadData];
}
#pragma mark - Table view data source
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
SettingSectionHeaderView *headerView = [tableView dequeueReusableHeaderFooterViewWithIdentifier:SettingSectionHeaderView.Identifier];
headerView.titleLabel.text = NSLocalizedString(@"options_notification_manual", @"titolo impostazioni notifiche");
return headerView;
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return SettingSectionHeaderView.Height;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.settings.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
SettingItem *setting = self.settings[indexPath.row];
if (setting.type == SettingTypeEnable) {
SettingEnableTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:SettingEnableTableViewCell.Identifier forIndexPath:indexPath];
cell.toggleSwitch.on = self.notificationsEnabled;
cell.titleLabel.text = setting.displayTitle;
cell.descriptionLabel.text = setting.subtitle;
cell.valueChanged = ^(BOOL enabled) {
self.notificationsEnabled = enabled;
[EQNNotificheSegnalazioniUtente sharedInstance].isAbilitato = self.notificationsEnabled;
[[EQNNotificheSegnalazioniUtente sharedInstance] saveUserInfo];
[self.tableView reloadData];
};
return cell;
} else if (setting.type == SettingTypeSlider) {
SettingSliderTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:SettingSliderTableViewCell.Identifier forIndexPath:indexPath];
cell.isDisabled = !self.notificationsEnabled;
cell.titleLabel.text = setting.displayTitle;
[cell configureSliderWith:[EQNData raggioSismi] current:self.currentRadius];
cell.valueChanged = ^(EQNGenericValue *item) {
[self updateRadius:item];
};
return cell;
}
return nil;
}
@end
@@ -0,0 +1,145 @@
//
// SettingsUserReportNotificationsViewController.swift
// Earthquake Network
//
// Created by Andrea Busi on 10/06/24.
// Copyright © 2024 Earthquake Network. All rights reserved.
//
import UIKit
import Shogun
class SettingsUserReportNotificationsViewController: SettingsBaseTableViewController {
private enum RowIdentifier: Int {
case abilitaNotifiche
case distanzaMassima
}
private var isNotificationEnabled = false
private var currentMaximumDistance = EQNData.DefaultSettingUserReportNotificationRadius
private let dataSourceMaximumDistance = EQNData.settingUserReportNotificationRadius
private let settings: [SettingItem] = [
.init(type: .enable, title: NSLocalizedString("options_notification_enable_manual", comment: "")),
.init(type: .slider, title: NSLocalizedString("options_radius", comment: ""))
]
// MARK: - View Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
tableView.reloadData()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
loadDataSource()
tableView.reloadData()
}
// MARK: - Private
private func setupUI() {
navigationItem.largeTitleDisplayMode = .never
tableView.estimatedRowHeight = 200.0
tableView.rowHeight = UITableView.automaticDimension
tableView.registerHeaderFooterView(for: SettingSectionHeaderView.self)
tableView.registerCell(for: SettingEnableTableViewCell.self)
tableView.registerCell(for: SettingSliderTableViewCell.self)
}
private func loadDataSource() {
let saved = EQNSettingUserReportNotification.shared
isNotificationEnabled = saved.isAbilitato
let distanzaMassima = EQNData.getSettingUserReportNotificationRadius(for: saved.distanzaMassima)
currentMaximumDistance = distanzaMassima
}
// MARK: - Table view delegate and data source
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let view = tableView.dequeueHeaderFooterView(cellIdentifiable: SettingSectionHeaderView.self)
view.titleLabel.text = NSLocalizedString("options_notification_manual", comment: "")
return view
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
SettingSectionHeaderView.Height
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return settings.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let identifier = RowIdentifier(rawValue: indexPath.row) else {
return UITableViewCell()
}
let setting = settings[indexPath.row]
switch setting.type {
case .enable:
let cell = tableView.dequeueReusableCell(cellIdentifiable: SettingEnableTableViewCell.self, for: indexPath)
cell.titleLabel.text = setting.displayTitle
cell.descriptionLabel.text = setting.subtitle
switch identifier {
case .abilitaNotifiche:
cell.toggleSwitch.isOn = isNotificationEnabled
cell.valueChanged = { [weak self] enabled in
self?.onChangeNotificationEnabled(enabled)
}
default:
break
}
return cell
case .slider:
let cell = tableView.dequeueReusableCell(cellIdentifiable: SettingSliderTableViewCell.self, for: indexPath)
cell.titleLabel.text = setting.displayTitle
switch identifier {
case .distanzaMassima:
cell.isDisabled = !isNotificationEnabled
cell.configureSlider(with: dataSourceMaximumDistance, current: currentMaximumDistance)
cell.valueChanged = { [weak self] item in
self?.onChangeMaximumDistance(item)
}
default:
break
}
return cell
default:
fatalError()
}
}
// MARK: - Private
private func onChangeNotificationEnabled(_ enabled: Bool) {
isNotificationEnabled = enabled
EQNSettingUserReportNotification.shared.isAbilitato = isNotificationEnabled
EQNSettingUserReportNotification.shared.saveUserInfo()
tableView.reloadData()
}
private func onChangeMaximumDistance(_ item: EQNGenericValue) {
EQNSettingUserReportNotification.shared.distanzaMassima = item.value
EQNSettingUserReportNotification.shared.saveUserInfo()
loadDataSource()
}
}
@@ -27,10 +27,15 @@ class EQNBaseMapViewController: EQNBaseViewController, MKMapViewDelegate {
!availableFilters.isEmpty
}
/// If `true` the close button will be shown on top of the map view
var isCloseButtonVisible: Bool {
true
}
// MARK: - Internal
/// Annotations displayed on the map
private var mapAnnotations = [MKAnnotation]()
private(set) var mapAnnotations = [MKAnnotation]()
/// If `true`, the initial filter has been already evaluated
private var initialFilterEvaluated = false
@@ -99,6 +104,34 @@ class EQNBaseMapViewController: EQNBaseViewController, MKMapViewDelegate {
return label
}()
// app icon and name displayed on the screenshot
private lazy var watermarkView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
let logo = UIImageView(image: .init(named: "eq_icon_transparent"))
logo.translatesAutoresizingMaskIntoConstraints = false
logo.contentMode = .scaleAspectFit
view.addSubview(logo)
logo.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
logo.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
logo.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
logo.widthAnchor.constraint(equalTo: logo.heightAnchor).isActive = true
let title = UILabel()
title.translatesAutoresizingMaskIntoConstraints = false
title.text = NSLocalizedString("app_name", comment: "") + " App"
title.textColor = AppTheme.Colors.red
title.font = .preferredFont(forTextStyle: .title3, weight: .semibold)
view.addSubview(title)
title.leadingAnchor.constraint(equalTo: logo.trailingAnchor, constant: 10.0).isActive = true
title.centerYAnchor.constraint(equalTo: logo.centerYAnchor).isActive = true
title.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
return view
}()
// MARK: - Init
init() {
@@ -116,13 +149,14 @@ class EQNBaseMapViewController: EQNBaseViewController, MKMapViewDelegate {
private func setupUI() {
view.backgroundColor = .white
view.addSubview(mapView)
view.addSubview(closeButton)
view.addSubview(containerView)
if isFilterViewVisible {
view.addSubview(filtersView)
}
closeButton.addDefaultConstraint(to: view)
if isCloseButtonVisible {
view.addSubview(closeButton)
closeButton.addDefaultConstraint(to: view)
}
containerView.bottomAnchor.constraint(equalTo: view.layoutMarginsGuide.bottomAnchor).isActive = true
containerView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
@@ -145,6 +179,20 @@ class EQNBaseMapViewController: EQNBaseViewController, MKMapViewDelegate {
mapView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
mapView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
mapView.bottomAnchor.constraint(equalTo: (isFilterViewVisible ? filtersView : containerView).topAnchor).isActive = true
extraUI()
}
private func addWatermarkView() {
view.addSubview(watermarkView)
watermarkView.topAnchor.constraint(equalTo: mapView.topAnchor, constant: 10.0).isActive = true
watermarkView.leadingAnchor.constraint(equalTo: mapView.leadingAnchor, constant: 10.0).isActive = true
watermarkView.heightAnchor.constraint(equalToConstant: 40.0).isActive = true
}
private func removeWatermarkView() {
watermarkView.removeFromSuperview()
}
// MARK: - View Lifecycle
@@ -152,6 +200,7 @@ class EQNBaseMapViewController: EQNBaseViewController, MKMapViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
configureUI()
registerMapAnnotationViews()
loadDataSource()
}
@@ -167,6 +216,16 @@ class EQNBaseMapViewController: EQNBaseViewController, MKMapViewDelegate {
// MARK: - Public
/// Add extra UI not available in the base class
func extraUI() {
// nope, subclass will implement some logic
}
/// Configure UI after view initialization
func configureUI() {
// nope, subclass will implement some logic
}
/// Load data to display on the map
func loadDataSource() {
// nope, subclass will implement some logic
@@ -219,6 +278,12 @@ class EQNBaseMapViewController: EQNBaseViewController, MKMapViewDelegate {
elaborateMapCenter()
}
func reloadMap() {
// remove and re-add annotations, to force redrawn
mapView.removeAnnotations(mapAnnotations)
mapView.addAnnotations(mapAnnotations)
}
/// Changes the center coordinate of the map to a given location
func setMapCenter(for location: CLLocation, span: MKCoordinateSpan = MKCoordinateSpan(latitudeDelta: 8, longitudeDelta: 8)) {
let region = MKCoordinateRegion(center: location.coordinate, span: span)
@@ -230,6 +295,31 @@ class EQNBaseMapViewController: EQNBaseViewController, MKMapViewDelegate {
// nope, subclass will implement logic
}
func zPriority(for annotation: MKAnnotation) -> MKAnnotationViewZPriority {
// subclass will impelement its own logic to define annotation priority
.min
}
func createSnapshot(
prepare: () -> Void = { },
restore: () -> Void = { }
) -> UIImage {
prepare()
addWatermarkView()
// riduciamo la porzione da salvare alla sola mappa (eliminiamo i filtri)
let size = CGSize(width: view.bounds.width, height: mapView.bounds.maxY)
let renderer = UIGraphicsImageRenderer(size: size)
let image = renderer.image { ctx in
view.drawHierarchy(in: view.bounds, afterScreenUpdates: true)
}
restore()
removeWatermarkView()
return image
}
// MARK: - Private
private func updateUI() {
@@ -249,7 +339,7 @@ class EQNBaseMapViewController: EQNBaseViewController, MKMapViewDelegate {
self.applyFilter(filter)
}))
}
sheet.addAction(UIAlertAction(title: NSLocalizedString("options_cancel", comment: ""), style: .cancel, handler: nil))
sheet.addAction(UIAlertAction(title: NSLocalizedString("status_cancel", comment: ""), style: .cancel, handler: nil))
present(sheet, animated: true, completion: nil)
}
@@ -267,6 +357,7 @@ class EQNBaseMapViewController: EQNBaseViewController, MKMapViewDelegate {
}
let annotationView = setupAnnotationView(for: annotation, on: mapView)
annotationView?.zPriority = zPriority(for: annotation)
return annotationView
}
@@ -277,7 +368,5 @@ class EQNBaseMapViewController: EQNBaseViewController, MKMapViewDelegate {
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
guard let annotation = view.annotation else { return }
didTapAnnotation(annotation)
mapView.deselectAnnotation(annotation, animated: true)
}
}
@@ -8,6 +8,7 @@
import UIKit
import MapKit
import Shogun
class AlertSimulatorViewController: UIViewController, MKMapViewDelegate {
@@ -243,7 +244,7 @@ class AlertSimulatorViewController: UIViewController, MKMapViewDelegate {
}
private func navigateToSubscriptions() {
let controller = SubscriptionsViewController.makeController()
let controller = SubscriptionsViewController()
let navigationController = UINavigationController(rootViewController: controller)
present(navigationController, animated: true)
}
@@ -1,21 +0,0 @@
//
// Costanti+Extensions.swift
// Earthquake Network
//
// Created by Andrea Busi on 06/03/21.
// Copyright © 2021 Earthquake Network. All rights reserved.
//
import Foundation
extension EQNPastquakeIntensity {
var description: String {
switch self {
case .mild: return NSLocalizedString("widget_mild", comment: "")
case .strong: return NSLocalizedString("widget_strong", comment: "")
case .veryStrong: return NSLocalizedString("widget_verystring", comment: "")
}
}
}
+10 -70
View File
@@ -13,6 +13,8 @@
/// Stampa le risposte delle chiamate al server
static BOOL const EQNDebugPrintResponse = NO;
/// Visualizza schermata con info di debug (ex. push token)
static BOOL const EQNEnableDebugView = NO;
#pragma mark - Urls
@@ -31,11 +33,9 @@ static double const EQNMathKelvin = 273.15;
#pragma mark - Server APIs
/// Download reti sismiche
static NSString * const EQNServerUrlDownloadRetiSismiche = @"https://srv.earthquakenetwork.it/distquake_download_automatic18%@.php";
static NSString * const EQNServerUrlDownloadRetiSismiche = @"https://cache.earthquakenetwork.it/distquake_download_automatic21.php%@";
/// Recupera il tempo ancora disponibile per la versione Pro scontata
static NSString * const EQNServerUrlOfferTimeRemaining = @"https://srv.earthquakenetwork.it/distquake_download_offer_time_remaining.php";
/// Recupera il numero di sottoscrizioni ancora disponibili per ogni prodotto
static NSString * const EQNServerUrlAvailableSubscriptionsCounter = @"https://srv.earthquakenetwork.it/distquake_count_top_redis.php";
/// Registra l'abbonamento acquistato dall'utente
static NSString * const EQNServerUrlRegisterSubscription = @"https://srv.earthquakenetwork.it/distquake_upload_subscription.php";
/// Carica le impostazioni delle notifiche definite dall'utente
@@ -47,15 +47,15 @@ static NSString * const EQNServerUrlUserLocation = @"https://srv.earthquakenetwo
static NSString * const EQNServerUrlCalibration = @"https://srv.earthquakenetwork.it/distquake_upload4.php";
// download rete smartphone
static NSString * const EQNServerUrlDownloadSmartphoneNetwork = @"https://srv.earthquakenetwork.it/distquake_count_redis.php";
static NSString * const EQNServerUrlDownloadSmartphoneNetwork = @"https://cache.earthquakenetwork.it/distquake_count_redis3.php";
// download area check
static NSString * const EQNServerUrlDownloadAreaCheck = @"https://srv.earthquakenetwork.it/distquake_download_areacheck.php";
// download pastquakes
static NSString * const EQNServerUrlDownloadPastQuakes = @"https://srv.earthquakenetwork.it/distquake_download_pastquakes.php";
// download segnalazioni
static NSString * const EQNServerUrlDownloadUserReports = @"https://srv.earthquakenetwork.it/distquake_download_manual.php";
static NSString * const EQNServerUrlDownloadUserReports = @"https://cache.earthquakenetwork.it/distquake_download_manual3.php";
// Invio segnalazione
static NSString * const EQNServerUrlSendUserReport = @"https://srv.earthquakenetwork.it/distquake_upload_manual3.php";
static NSString * const EQNServerUrlSendUserReport = @"https://srv.earthquakenetwork.it/distquake_upload_manual4.php";
static NSString * const EQNServerUrlSendUserReportMessage = @"https://srv.earthquakenetwork.it/distquake_upload_manual_message.php";
/// Effettua un test delle notifiche push
static NSString * const EQNServerUrlTestAlarm = @"https://srv.earthquakenetwork.it/distquake_upload_testalarm.php";
@@ -64,23 +64,9 @@ static NSString * const EQNServerUrlAlertSimulator = @"https://srv.earthquakenet
#pragma mark - UserDefaults Keys
static NSString * const EQNUserDefaultAppGroupSuite = @"group.com.finazzi.distquake";
static NSString * const EQNUserDefaultKeyAlertsShowAllCards = @"EQNetwork.AlertsShowAllCards";
static NSString * const EQNUserDefaultKeySesmicInformations = @"EQNetwork.SeismicInformations";
static NSString * const EQNUserDefaultKeyOneShotShowCountry = @"EQNetwork.OneShot.CountrySelection";
static NSString * const EQNUserDefaultLastLocation = @"EQNLast_Location";
static NSString * const EQNUserDefaultSeismicNetworkCards = @"EQNData.RetiSismiche";
/// Numero di aperture dell'app per sbloccare la versione Pro scontata
static NSString * const EQNUserDefaultProDiscountOpenCounter = @"CONTEGGIO_APERTURE_PER_SCONTO";
/// Prezzo scontato per la versione pro scaduto
static NSString * const EQNUserDefaultProDiscountExpired = @"PREZZO_SCONTATO_SCADUTO";
/// Token Firebase dell'utente corrente
static NSString * const EQNUserDefaultUserFirebaseToken = @"EQNToken_User";
/// Server user ID dell'utente corrente
static NSString * const EQNUserDefaultUserId = @"EQNUSER_ID";
/// Token delle notifiche push
static NSString * const EQNUserDefaultPushToken = @"EQNetwork.PushToken";
#pragma mark - NSNotification
@@ -102,10 +88,10 @@ static NSNotificationName const EQNDebugLogWillUpdateNotification = @"EQNDebugLo
#pragma mark - Other constants
static NSTimeInterval const EQNSeismicDataRefreshInterval = 120.0;
/// Tempo di attesa (minuti) tra l'invio di due segnalazioni
static NSTimeInterval const EQNSendReportDelayBetweenMessages = 5.0;
/// Tempo di attesa (minuti) per l'invio di due commenti
static NSTimeInterval const EQNSendReportDelayBetweenComments = 30.0;
/// Tempo di attesa (secondi) tra l'invio di due segnalazioni
static NSTimeInterval const EQNSendReportDelayBetweenMessages = 300.0; // 5 minuti
/// Tempo di attesa (secondi) per l'invio di due commenti
static NSTimeInterval const EQNSendReportDelayBetweenComments = 900.0; // 30 minuti
#ifdef DEBUG
static NSString * const EQNAdMobAppIdAdaptiveBanner = @"ca-app-pub-3940256099942544/2934735716"; // test
@@ -182,53 +168,7 @@ typedef enum : NSInteger {
nonCalibrato
} EQNStatoCal;
//////////////////////////////////////// SEGNALAZIONE MANUALE TERREMOTI ////////////////////////////////////////
#define CODE_MESSAGE_EQN @"CODE_MESSAGE_EQN"
#define DATA_MESSAGE_EQN @"DATA_MESSAGE_EQN"
/////////////////// Segnalazioni Utente ////////////////////////////
#define NOTIFICHE_SU_DISTANZA_POSIZIONE @"NOTIFICHE_SU_DISTANZA_POSIZIONE"
#define NOTIFICHE_SU_ATTIVA_SEGNALAZIONE_UTENTE @"NOTIFICHE_SU_ATTIVA_SEGNALAZIONE_UTENTE"
/////////////////// Reti sismiche ////////////////////////////
#define NOTIFICHE_DISTANZA_POSIZIONE_RETI_SISMICHE @"NOTIFICHE_DISTANZA_POSIZIONE_RETI_SISMICHE"
#define NOTIFICHE_ATTIVA_RETI_SISMICHE @"NOTIFICHE_ATTIVA_RETI_SISMICHE"
#define NOTIFICHE_ATTIVA_RETI_SISMICHE_VICINE @"NOTIFICHE_ATTIVA_RETI_SISMICHE_VICINE"
#define NOTIFICHE_ATTIVA_RETI_TERREMOTI_FORTI @"NOTIFICHE_ATTIVA_RETI_TERREMOTI_FORTI"
#define NOTIFICHE_ATTIVA_RETI_ENERGIA_SISMI @"NOTIFICHE_ATTIVA_RETI_ENERGIA_SISMI"
#define NOTIFICHE_ATTIVA_RETI_ENERGIA_FORTI @"NOTIFICHE_ATTIVA_RETI_ENERGIA_FORTI"
#define NOTIFICHE_ATTIVA_RETI_LISTA_ENTI @"NOTIFICHE_ATTIVA_RETI_LISTA_ENTI"
// Sigla della rete sismica selezionata
#define IMPOSTAZIONE_NAZIONE_RETI_SISMICHE @"IMPOSTAIONE_NAZIONE_RETI_SISMICHE"
#define IMPOSTAZIONE_ENTI_RETI_SISMICHEI @"IMPOSTAZIONE_ENTI_RETI_SISMICHEI"
/////////////////// Allera sismica ////////////////////////////
#define NOTIFICHE_ALLERA_SISMICA_ABILITATO @"NOTIFICHE_ALLERA_SISMICA_ABILITATO"
#define NOTIFICHE_ALLERA_SISMICA_CRITICAL_ALERTS @"NOTIFICHE_ALLERA_SISMICA_CRITICAL_ALERTS"
#define NOTIFICHE_ALLERA_SISMICA_SISMI_DA_NOTIFICARE @"NOTIFICHE_ALLERA_SISMICA_SISMI_DA_NOTIFICARE"
#define NOTIFICHE_ALLERA_SISMICA_RAGGIO_SISMI_LIEVI @"NOTIFICHE_ALLERA_SISMICA_RAGGIO_SISMI_LIEVI"
#define NOTIFICHE_ALLERA_SISMICA_RAGGIO_SISMI_FORTI @"NOTIFICHE_ALLERA_SISMICA_RAGGIO_SISMI_FORTI"
#define NOTIFICHE_ALLERA_SISMICA_IMPOSTA_VOLUME @"NOTIFICHE_ALLERA_SISMICA_IMPOSTA_VOLUME"
#define NOTIFICHE_ALLERA_SISMICA_TESTA_ALLARME @"NOTIFICHE_ALLERA_SISMICA_TESTA_ALLARME"
#define NOTIFICHE_ALLERA_SISMICA_ABILITA_INTERVALLO @"NOTIFICHE_ALLERA_SISMICA_ABILITA_INTERVALLO"
#define NOTIFICHE_ALLERA_SISMICA_ORA_INIZIO @"NOTIFICHE_ALLERA_SISMICA_ORA_INIZIO"
#define NOTIFICHE_ALLERA_SISMICA_ORA_FINE @"NOTIFICHE_ALLERA_SISMICA_ORA_INIZIO"
// NOTIFICHE RETE SMARTPHONE
#define NOTIFICHE_RETE_SMARTPHONE_DATA_NOTIFICA @"NOTIFICHE_RETE_SMARTPHONE_DATA_NOTIFICA"
#define NOTIFICHE_RETE_SMARTPHONE_DIZIONARIO_NOTIFICA @"NOTIFICHE_RETE_SMARTPHONE_DIZIONARIO_NOTIFICA"
#define TEMPO_VISUALIZZAZIONE_NOTIFICA 10800
// FILTRO ENTI
#define EQN_MAGNITUDO_MINIMA @"EQN_MAGNITUDO_MINIMA"
#define EQN_DISTANZA_MASSIMA @"EQN_DISTANZA_MASSIMA"
#define EQN_ETA_MASSIMA @"EQN_ETA_MASSIMA"
#define EQN_SISMI_FORTI_ABILITATI @"EQN_SISMI_FORTI_ABILITATI"
#define EQN_SISMI_FORTI @"EQN_SISMI_FORTI"
#define EQN_SISMI_QUALSIASI_MAGNITUDO @"EQN_SISMI_QUALSIASI_MAGNITUDO"
#define EQN_SISMI_MODIFICA_IMPOSTAZIONI @"EQN_SISMI_MODIFICA_IMPOSTAZIONI"
#endif /* Costanti_h */
@@ -6,16 +6,12 @@
#import "Costanti.h"
#import "EQNUser.h"
#import "EQNManager.h"
#import "EQNNotificheReteSismiche.h"
#import "EQNNotificheSegnalazioniUtente.h"
#import "EQNSisma.h"
#import "EQNBaseViewController.h"
#import "SettingsBaseViewController.h"
#import "EQNGeneratoreURLServer.h"
#import "ServerRequest.h"
#import "EQNSegnalazione.h"
#import "EQNPastquakes.h"
#import "EQNAllertaSismica.h"
#import "GADTTemplateView.h"
#import "GADTMediumTemplateView.h"
@@ -8,6 +8,6 @@
#ifdef __OBJC__
#import "Earthquake_Network-Swift.h"
#import <Earthquake_Network-Swift.h>
#endif
+28 -2
View File
@@ -2,6 +2,10 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>com.finazzi.distquake.update_server_position</string>
</array>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
@@ -18,16 +22,35 @@
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>fb1444404982546319</string>
</array>
</dict>
</array>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>FacebookAdvertiserIDCollectionEnabled</key>
<true/>
<key>FacebookAppID</key>
<string>1444404982546319</string>
<key>FacebookAutoLogAppEventsEnabled</key>
<true/>
<key>FacebookClientToken</key>
<string>46c7a338b2bbd2186b2f1c12865b4004</string>
<key>FacebookDisplayName</key>
<string>Earthquake Network</string>
<key>GADApplicationIdentifier</key>
<string>ca-app-pub-0053870219990922~2021960172</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>googlechromes</string>
<string>comgooglemaps</string>
<string>fbapi</string>
<string>fb-messenger-share-api</string>
</array>
<key>LSRequiresIPhoneOS</key>
<true/>
@@ -43,6 +66,8 @@
<string> Ci occorre la tua posizione per inviare messaggi precisi in caso di terremoto</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>L'accesso alla libreria è richiesto per poter salvare le immagini generate dall'app</string>
<key>NSUserTrackingUsageDescription</key>
<string>Il tracciamento serve a capire se la pubblicità dell'app è efficace</string>
<key>SKAdNetworkItems</key>
<array>
<dict>
@@ -55,6 +80,7 @@
<string>audio</string>
<string>fetch</string>
<string>location</string>
<string>processing</string>
<string>remote-notification</string>
</array>
<key>UILaunchStoryboardName</key>
@@ -1,31 +0,0 @@
//
// Dictionary+EQNExtensions.swift
// Earthquake Network
//
// Created by Andrea Busi on 27/03/21.
// Copyright © 2021 Earthquake Network. All rights reserved.
//
import Foundation
extension Dictionary {
func eqn_intValue(for key: Key) -> Int? {
if let value = self[key] as? Int {
return value
} else if let stringValue = self[key] as? String, let value = Int(stringValue) {
return value
}
return nil
}
func eqn_doubleValue(for key: Key) -> Double? {
if let value = self[key] as? Double {
return value
} else if let stringValue = self[key] as? String, let value = Double(stringValue) {
return value
}
return nil
}
}
@@ -0,0 +1,39 @@
//
// Foundation+Extensions.swift
// Earthquake Network
//
// Created by Andrea Busi on 14/07/23.
// Copyright © 2023 Earthquake Network. All rights reserved.
//
import Foundation
extension Date {
func isBeforeInterval(
_ interval: TimeInterval
) -> Bool {
let now = Date()
return self.addingTimeInterval(interval) >= now
}
}
@objc
extension NSDate {
func isBeforeInterval(
_ interval: TimeInterval
) -> Bool {
return (self as Date).isBeforeInterval(interval)
}
}
extension CGFloat {
var negative: CGFloat {
-self
}
var x2: CGFloat {
self*2.0
}
}

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