├── MacMagazine ├── mmlive.json ├── sound.wav ├── Assets.xcassets │ ├── Contents.json │ ├── Colors │ │ ├── Contents.json │ │ ├── MMGray.colorset │ │ │ └── Contents.json │ │ ├── MMWhiteGrey6.colorset │ │ │ └── Contents.json │ │ ├── MMBlack90.colorset │ │ │ └── Contents.json │ │ ├── MMGrayWhite.colorset │ │ │ └── Contents.json │ │ ├── MMDarkGreyWhite.colorset │ │ │ └── Contents.json │ │ ├── MMLessDarkGreyWhite.colorset │ │ │ └── Contents.json │ │ ├── MMBlue.colorset │ │ │ └── Contents.json │ │ └── MMBlueWhite.colorset │ │ │ └── Contents.json │ ├── settings-icons │ │ ├── Contents.json │ │ ├── normal.imageset │ │ │ ├── phone_1.png │ │ │ └── Contents.json │ │ └── alternative.imageset │ │ │ ├── phone_2.png │ │ │ └── Contents.json │ ├── logo.imageset │ │ ├── logo.pdf │ │ └── Contents.json │ ├── chrome.imageset │ │ ├── chrome.pdf │ │ └── Contents.json │ ├── splash.imageset │ │ ├── logo.pdf │ │ └── Contents.json │ ├── image_logo.imageset │ │ ├── image_logo.pdf │ │ ├── image_logo_dark.pdf │ │ └── Contents.json │ ├── image_logo_feature.imageset │ │ ├── image_logo_feature.pdf │ │ ├── image_logo_feature_dark.pdf │ │ └── Contents.json │ ├── line.3.horizontal.circle.symbolset │ │ └── Contents.json │ ├── line.3.horizontal.decrease.circle.symbolset │ │ └── Contents.json │ ├── line.3.horizontal.decrease.circle.fill.symbolset │ │ └── Contents.json │ └── AppIcon.appiconset │ │ └── Contents.json ├── Icons │ ├── mm_icon.icon │ │ ├── Assets │ │ │ ├── layer_1.png │ │ │ ├── layer_1.svg │ │ │ ├── layer_2.svg │ │ │ └── layer_3.svg │ │ └── icon.json │ ├── mm_icon_normal.icon │ │ ├── Assets │ │ │ ├── layer_1.png │ │ │ ├── layer_1.svg │ │ │ ├── layer_2.svg │ │ │ └── layer_3.svg │ │ └── icon.json │ └── mm_icon_inverted.icon │ │ ├── Assets │ │ ├── layer_3 2.png │ │ ├── layer_0.svg │ │ ├── layer_2.svg │ │ └── layer_1.svg │ │ └── icon.json ├── Theme │ ├── DarkTheme.swift │ ├── LightTheme.swift │ ├── Extensions │ │ ├── UIWindowTheme.swift │ │ └── With.swift │ └── Theme.swift ├── macmagazine.xcdatamodeld │ ├── .xccurrentversion │ ├── macmagazine.xcdatamodel │ │ └── contents │ ├── macmagazine 2.xcdatamodel │ │ └── contents │ ├── macmagazine 3.xcdatamodel │ │ └── contents │ ├── macmagazine 6.xcdatamodel │ │ └── contents │ ├── macmagazine 4.xcdatamodel │ │ └── contents │ ├── macmagazine 7.xcdatamodel │ │ └── contents │ ├── macmagazine 8.xcdatamodel │ │ └── contents │ ├── macmagazine 5.xcdatamodel │ │ └── contents │ ├── macmagazine 11.xcdatamodel │ │ └── contents │ ├── macmagazine 9.xcdatamodel │ │ └── contents │ └── macmagazine 10.xcdatamodel │ │ └── contents ├── Config.xcconfig ├── SFSafariViewControllerExtensions.swift ├── UISplitViewControllerExtension.swift ├── MacMagazine.entitlements ├── ArrayExtensions.swift ├── Enums │ └── ShortcutActions.swift ├── DateExtensions.swift ├── FavoriteAction │ └── Favorite.swift ├── WhatsNew │ └── WhatsNewTableViewController.swift ├── PostData.swift ├── UILabelExtension.swift ├── PlayerViewController.swift ├── UIViewExtensions.swift ├── UIActivityExtensions.swift ├── AppDelegate.swift ├── Helper.swift ├── Webview │ ├── MMLiveViewController.swift │ ├── Cookies.swift │ └── YouTubePlayer.swift ├── Base.lproj │ ├── LaunchScreen.storyboard │ ├── Splash.storyboard │ └── SettingsHeaderCell.xib ├── Obfuscator.swift ├── WatchSessionManager.swift ├── ShareAction │ └── Share.swift ├── SplashViewController.swift ├── Configuration.storekit ├── Network.swift ├── KeywordsTableViewController.swift ├── SettingsDisclaimerFooter.xib ├── TabBarController.swift ├── SettingsTermsFooter.xib └── CoreDataStackExtension.swift ├── MacMagazineWatchExtension ├── Assets.xcassets │ ├── Contents.json │ └── Complication.complicationset │ │ ├── Circular.imageset │ │ └── Contents.json │ │ ├── Extra Large.imageset │ │ └── Contents.json │ │ ├── Modular.imageset │ │ └── Contents.json │ │ ├── Utilitarian.imageset │ │ └── Contents.json │ │ ├── Graphic Bezel.imageset │ │ └── Contents.json │ │ ├── Graphic Circular.imageset │ │ └── Contents.json │ │ ├── Graphic Corner.imageset │ │ └── Contents.json │ │ ├── Graphic Extra Large.imageset │ │ └── Contents.json │ │ ├── Graphic Large Rectangular.imageset │ │ └── Contents.json │ │ └── Contents.json ├── MacMagazineWatchExtension.entitlements ├── DetailInterfaceController.swift ├── Info.plist └── MainInterfaceController.swift ├── MacMagazineWidgetExtension ├── Assets.xcassets │ ├── Contents.json │ ├── logo.imageset │ │ ├── logo.png │ │ ├── logo@2x.png │ │ ├── logo@3x.png │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── image_logo_feature.imageset │ │ ├── image_logo_feature.png │ │ ├── image_logo_feature@2x.png │ │ ├── image_logo_feature@3x.png │ │ ├── image_logo_feature copy.png │ │ ├── image_logo_feature copy@2x.png │ │ ├── image_logo_feature copy@3x.png │ │ └── Contents.json │ ├── AccentColor.colorset │ │ └── Contents.json │ └── WidgetBackground.colorset │ │ └── Contents.json ├── MacMagazineWidgetExtensionExtension.entitlements ├── RecentPosts │ ├── RecentPostsEntry.swift │ ├── RecentPostsProvider.swift │ └── RecentPostsWidget.swift ├── PostDataExtension.swift ├── Info.plist └── MacMagazineWidgetExtension.swift ├── InAppPurchase.xcframework ├── ios-arm64 │ └── InAppPurchase.framework │ │ ├── Info.plist │ │ ├── InAppPurchase │ │ ├── AppleIncRootCertificate.cer │ │ ├── StoreKitTestCertificate.cer │ │ ├── Modules │ │ ├── InAppPurchase.swiftmodule │ │ │ ├── arm64.swiftdoc │ │ │ ├── arm64-apple-ios.swiftdoc │ │ │ └── Project │ │ │ │ ├── arm64.swiftsourceinfo │ │ │ │ └── arm64-apple-ios.swiftsourceinfo │ │ └── module.modulemap │ │ └── Headers │ │ └── InAppPurchase.h ├── ios-arm64_x86_64-simulator │ └── InAppPurchase.framework │ │ ├── Info.plist │ │ ├── InAppPurchase │ │ ├── AppleIncRootCertificate.cer │ │ ├── StoreKitTestCertificate.cer │ │ ├── Modules │ │ ├── InAppPurchase.swiftmodule │ │ │ ├── arm64.swiftdoc │ │ │ ├── x86_64.swiftdoc │ │ │ ├── Project │ │ │ │ ├── arm64.swiftsourceinfo │ │ │ │ ├── x86_64.swiftsourceinfo │ │ │ │ ├── arm64-apple-ios-simulator.swiftsourceinfo │ │ │ │ └── x86_64-apple-ios-simulator.swiftsourceinfo │ │ │ ├── arm64-apple-ios-simulator.swiftdoc │ │ │ └── x86_64-apple-ios-simulator.swiftdoc │ │ └── module.modulemap │ │ └── Headers │ │ └── InAppPurchase.h └── Info.plist ├── MacMagazine.xcodeproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ ├── IDEWorkspaceChecks.plist │ └── swiftpm │ └── Package.resolved ├── .gitignore ├── MacMagazineNotificationServiceExtension ├── MacMagazineNotificationServiceExtension.entitlements ├── Info.plist └── NotificationService.swift ├── Support ├── PushNotificationAPNSTest │ └── push.apns └── Scripts │ └── updateBuildVersion.sh ├── MacMagazineTests ├── Info.plist └── ParsedObjectTests.swift ├── ExportOptions.plist ├── MacMagazineWatch └── Info.plist └── README.md /MacMagazine/mmlive.json: -------------------------------------------------------------------------------- 1 | { 2 | "inicio": 1619535300, 3 | "fim": 1619535600 4 | } 5 | -------------------------------------------------------------------------------- /MacMagazine/sound.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MacMagazine/app-iOS/HEAD/MacMagazine/sound.wav -------------------------------------------------------------------------------- /MacMagazine/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /MacMagazine/Assets.xcassets/Colors/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /MacMagazineWatchExtension/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /MacMagazine/Assets.xcassets/settings-icons/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /MacMagazineWidgetExtension/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /MacMagazine/Icons/mm_icon.icon/Assets/layer_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MacMagazine/app-iOS/HEAD/MacMagazine/Icons/mm_icon.icon/Assets/layer_1.png -------------------------------------------------------------------------------- /MacMagazine/Assets.xcassets/logo.imageset/logo.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MacMagazine/app-iOS/HEAD/MacMagazine/Assets.xcassets/logo.imageset/logo.pdf -------------------------------------------------------------------------------- /MacMagazine/Assets.xcassets/chrome.imageset/chrome.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MacMagazine/app-iOS/HEAD/MacMagazine/Assets.xcassets/chrome.imageset/chrome.pdf -------------------------------------------------------------------------------- /MacMagazine/Assets.xcassets/splash.imageset/logo.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MacMagazine/app-iOS/HEAD/MacMagazine/Assets.xcassets/splash.imageset/logo.pdf -------------------------------------------------------------------------------- /MacMagazine/Icons/mm_icon_normal.icon/Assets/layer_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MacMagazine/app-iOS/HEAD/MacMagazine/Icons/mm_icon_normal.icon/Assets/layer_1.png -------------------------------------------------------------------------------- /MacMagazine/Icons/mm_icon_inverted.icon/Assets/layer_3 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MacMagazine/app-iOS/HEAD/MacMagazine/Icons/mm_icon_inverted.icon/Assets/layer_3 2.png -------------------------------------------------------------------------------- /MacMagazine/Assets.xcassets/image_logo.imageset/image_logo.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MacMagazine/app-iOS/HEAD/MacMagazine/Assets.xcassets/image_logo.imageset/image_logo.pdf -------------------------------------------------------------------------------- /MacMagazineWidgetExtension/Assets.xcassets/logo.imageset/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MacMagazine/app-iOS/HEAD/MacMagazineWidgetExtension/Assets.xcassets/logo.imageset/logo.png -------------------------------------------------------------------------------- /MacMagazine/Assets.xcassets/image_logo.imageset/image_logo_dark.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MacMagazine/app-iOS/HEAD/MacMagazine/Assets.xcassets/image_logo.imageset/image_logo_dark.pdf -------------------------------------------------------------------------------- /MacMagazineWidgetExtension/Assets.xcassets/logo.imageset/logo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MacMagazine/app-iOS/HEAD/MacMagazineWidgetExtension/Assets.xcassets/logo.imageset/logo@2x.png -------------------------------------------------------------------------------- /MacMagazineWidgetExtension/Assets.xcassets/logo.imageset/logo@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MacMagazine/app-iOS/HEAD/MacMagazineWidgetExtension/Assets.xcassets/logo.imageset/logo@3x.png -------------------------------------------------------------------------------- /InAppPurchase.xcframework/ios-arm64/InAppPurchase.framework/Info.plist: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MacMagazine/app-iOS/HEAD/InAppPurchase.xcframework/ios-arm64/InAppPurchase.framework/Info.plist -------------------------------------------------------------------------------- /MacMagazine/Assets.xcassets/settings-icons/normal.imageset/phone_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MacMagazine/app-iOS/HEAD/MacMagazine/Assets.xcassets/settings-icons/normal.imageset/phone_1.png -------------------------------------------------------------------------------- /InAppPurchase.xcframework/ios-arm64/InAppPurchase.framework/InAppPurchase: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MacMagazine/app-iOS/HEAD/InAppPurchase.xcframework/ios-arm64/InAppPurchase.framework/InAppPurchase -------------------------------------------------------------------------------- /MacMagazine/Assets.xcassets/settings-icons/alternative.imageset/phone_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MacMagazine/app-iOS/HEAD/MacMagazine/Assets.xcassets/settings-icons/alternative.imageset/phone_2.png -------------------------------------------------------------------------------- /MacMagazineWidgetExtension/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | 4 | ], 5 | "info" : { 6 | "author" : "xcode", 7 | "version" : 1 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /MacMagazine/Assets.xcassets/image_logo_feature.imageset/image_logo_feature.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MacMagazine/app-iOS/HEAD/MacMagazine/Assets.xcassets/image_logo_feature.imageset/image_logo_feature.pdf -------------------------------------------------------------------------------- /MacMagazine/Assets.xcassets/image_logo_feature.imageset/image_logo_feature_dark.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MacMagazine/app-iOS/HEAD/MacMagazine/Assets.xcassets/image_logo_feature.imageset/image_logo_feature_dark.pdf -------------------------------------------------------------------------------- /InAppPurchase.xcframework/ios-arm64/InAppPurchase.framework/AppleIncRootCertificate.cer: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MacMagazine/app-iOS/HEAD/InAppPurchase.xcframework/ios-arm64/InAppPurchase.framework/AppleIncRootCertificate.cer -------------------------------------------------------------------------------- /InAppPurchase.xcframework/ios-arm64/InAppPurchase.framework/StoreKitTestCertificate.cer: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MacMagazine/app-iOS/HEAD/InAppPurchase.xcframework/ios-arm64/InAppPurchase.framework/StoreKitTestCertificate.cer -------------------------------------------------------------------------------- /InAppPurchase.xcframework/ios-arm64_x86_64-simulator/InAppPurchase.framework/Info.plist: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MacMagazine/app-iOS/HEAD/InAppPurchase.xcframework/ios-arm64_x86_64-simulator/InAppPurchase.framework/Info.plist -------------------------------------------------------------------------------- /MacMagazine.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /InAppPurchase.xcframework/ios-arm64_x86_64-simulator/InAppPurchase.framework/InAppPurchase: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MacMagazine/app-iOS/HEAD/InAppPurchase.xcframework/ios-arm64_x86_64-simulator/InAppPurchase.framework/InAppPurchase -------------------------------------------------------------------------------- /MacMagazineWidgetExtension/Assets.xcassets/image_logo_feature.imageset/image_logo_feature.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MacMagazine/app-iOS/HEAD/MacMagazineWidgetExtension/Assets.xcassets/image_logo_feature.imageset/image_logo_feature.png -------------------------------------------------------------------------------- /MacMagazineWidgetExtension/Assets.xcassets/image_logo_feature.imageset/image_logo_feature@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MacMagazine/app-iOS/HEAD/MacMagazineWidgetExtension/Assets.xcassets/image_logo_feature.imageset/image_logo_feature@2x.png -------------------------------------------------------------------------------- /MacMagazineWidgetExtension/Assets.xcassets/image_logo_feature.imageset/image_logo_feature@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MacMagazine/app-iOS/HEAD/MacMagazineWidgetExtension/Assets.xcassets/image_logo_feature.imageset/image_logo_feature@3x.png -------------------------------------------------------------------------------- /MacMagazineWidgetExtension/Assets.xcassets/image_logo_feature.imageset/image_logo_feature copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MacMagazine/app-iOS/HEAD/MacMagazineWidgetExtension/Assets.xcassets/image_logo_feature.imageset/image_logo_feature copy.png -------------------------------------------------------------------------------- /MacMagazineWidgetExtension/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /MacMagazineWidgetExtension/Assets.xcassets/image_logo_feature.imageset/image_logo_feature copy@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MacMagazine/app-iOS/HEAD/MacMagazineWidgetExtension/Assets.xcassets/image_logo_feature.imageset/image_logo_feature copy@2x.png -------------------------------------------------------------------------------- /MacMagazineWidgetExtension/Assets.xcassets/image_logo_feature.imageset/image_logo_feature copy@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MacMagazine/app-iOS/HEAD/MacMagazineWidgetExtension/Assets.xcassets/image_logo_feature.imageset/image_logo_feature copy@3x.png -------------------------------------------------------------------------------- /InAppPurchase.xcframework/ios-arm64_x86_64-simulator/InAppPurchase.framework/AppleIncRootCertificate.cer: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MacMagazine/app-iOS/HEAD/InAppPurchase.xcframework/ios-arm64_x86_64-simulator/InAppPurchase.framework/AppleIncRootCertificate.cer -------------------------------------------------------------------------------- /InAppPurchase.xcframework/ios-arm64_x86_64-simulator/InAppPurchase.framework/StoreKitTestCertificate.cer: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MacMagazine/app-iOS/HEAD/InAppPurchase.xcframework/ios-arm64_x86_64-simulator/InAppPurchase.framework/StoreKitTestCertificate.cer -------------------------------------------------------------------------------- /MacMagazineWidgetExtension/Assets.xcassets/WidgetBackground.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /MacMagazineWatchExtension/MacMagazineWatchExtension.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /InAppPurchase.xcframework/ios-arm64/InAppPurchase.framework/Modules/InAppPurchase.swiftmodule/arm64.swiftdoc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MacMagazine/app-iOS/HEAD/InAppPurchase.xcframework/ios-arm64/InAppPurchase.framework/Modules/InAppPurchase.swiftmodule/arm64.swiftdoc -------------------------------------------------------------------------------- /MacMagazine/Assets.xcassets/logo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "logo.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /MacMagazine/Assets.xcassets/splash.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "logo.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /MacMagazine/Assets.xcassets/chrome.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "chrome.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /InAppPurchase.xcframework/ios-arm64/InAppPurchase.framework/Modules/InAppPurchase.swiftmodule/arm64-apple-ios.swiftdoc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MacMagazine/app-iOS/HEAD/InAppPurchase.xcframework/ios-arm64/InAppPurchase.framework/Modules/InAppPurchase.swiftmodule/arm64-apple-ios.swiftdoc -------------------------------------------------------------------------------- /MacMagazine/Assets.xcassets/settings-icons/normal.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "phone_1.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /MacMagazine/Assets.xcassets/settings-icons/alternative.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "phone_2.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /InAppPurchase.xcframework/ios-arm64/InAppPurchase.framework/Modules/InAppPurchase.swiftmodule/Project/arm64.swiftsourceinfo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MacMagazine/app-iOS/HEAD/InAppPurchase.xcframework/ios-arm64/InAppPurchase.framework/Modules/InAppPurchase.swiftmodule/Project/arm64.swiftsourceinfo -------------------------------------------------------------------------------- /InAppPurchase.xcframework/ios-arm64_x86_64-simulator/InAppPurchase.framework/Modules/InAppPurchase.swiftmodule/arm64.swiftdoc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MacMagazine/app-iOS/HEAD/InAppPurchase.xcframework/ios-arm64_x86_64-simulator/InAppPurchase.framework/Modules/InAppPurchase.swiftmodule/arm64.swiftdoc -------------------------------------------------------------------------------- /InAppPurchase.xcframework/ios-arm64_x86_64-simulator/InAppPurchase.framework/Modules/InAppPurchase.swiftmodule/x86_64.swiftdoc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MacMagazine/app-iOS/HEAD/InAppPurchase.xcframework/ios-arm64_x86_64-simulator/InAppPurchase.framework/Modules/InAppPurchase.swiftmodule/x86_64.swiftdoc -------------------------------------------------------------------------------- /MacMagazine/Assets.xcassets/line.3.horizontal.circle.symbolset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | }, 6 | "symbols" : [ 7 | { 8 | "filename" : "line.3.horizontal.circle.svg", 9 | "idiom" : "universal" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /InAppPurchase.xcframework/ios-arm64/InAppPurchase.framework/Modules/InAppPurchase.swiftmodule/Project/arm64-apple-ios.swiftsourceinfo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MacMagazine/app-iOS/HEAD/InAppPurchase.xcframework/ios-arm64/InAppPurchase.framework/Modules/InAppPurchase.swiftmodule/Project/arm64-apple-ios.swiftsourceinfo -------------------------------------------------------------------------------- /InAppPurchase.xcframework/ios-arm64/InAppPurchase.framework/Modules/module.modulemap: -------------------------------------------------------------------------------- 1 | framework module InAppPurchase { 2 | umbrella header "InAppPurchase.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | 8 | module InAppPurchase.Swift { 9 | header "InAppPurchase-Swift.h" 10 | requires objc 11 | } 12 | -------------------------------------------------------------------------------- /MacMagazine/Assets.xcassets/line.3.horizontal.decrease.circle.symbolset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | }, 6 | "symbols" : [ 7 | { 8 | "filename" : "line.3.horizontal.decrease.circle.svg", 9 | "idiom" : "universal" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /InAppPurchase.xcframework/ios-arm64_x86_64-simulator/InAppPurchase.framework/Modules/InAppPurchase.swiftmodule/Project/arm64.swiftsourceinfo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MacMagazine/app-iOS/HEAD/InAppPurchase.xcframework/ios-arm64_x86_64-simulator/InAppPurchase.framework/Modules/InAppPurchase.swiftmodule/Project/arm64.swiftsourceinfo -------------------------------------------------------------------------------- /InAppPurchase.xcframework/ios-arm64_x86_64-simulator/InAppPurchase.framework/Modules/InAppPurchase.swiftmodule/Project/x86_64.swiftsourceinfo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MacMagazine/app-iOS/HEAD/InAppPurchase.xcframework/ios-arm64_x86_64-simulator/InAppPurchase.framework/Modules/InAppPurchase.swiftmodule/Project/x86_64.swiftsourceinfo -------------------------------------------------------------------------------- /MacMagazine/Assets.xcassets/line.3.horizontal.decrease.circle.fill.symbolset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | }, 6 | "symbols" : [ 7 | { 8 | "filename" : "line.3.horizontal.decrease.circle.fill.svg", 9 | "idiom" : "universal" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /InAppPurchase.xcframework/ios-arm64_x86_64-simulator/InAppPurchase.framework/Modules/InAppPurchase.swiftmodule/arm64-apple-ios-simulator.swiftdoc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MacMagazine/app-iOS/HEAD/InAppPurchase.xcframework/ios-arm64_x86_64-simulator/InAppPurchase.framework/Modules/InAppPurchase.swiftmodule/arm64-apple-ios-simulator.swiftdoc -------------------------------------------------------------------------------- /InAppPurchase.xcframework/ios-arm64_x86_64-simulator/InAppPurchase.framework/Modules/InAppPurchase.swiftmodule/x86_64-apple-ios-simulator.swiftdoc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MacMagazine/app-iOS/HEAD/InAppPurchase.xcframework/ios-arm64_x86_64-simulator/InAppPurchase.framework/Modules/InAppPurchase.swiftmodule/x86_64-apple-ios-simulator.swiftdoc -------------------------------------------------------------------------------- /InAppPurchase.xcframework/ios-arm64_x86_64-simulator/InAppPurchase.framework/Modules/module.modulemap: -------------------------------------------------------------------------------- 1 | framework module InAppPurchase { 2 | umbrella header "InAppPurchase.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | 8 | module InAppPurchase.Swift { 9 | header "InAppPurchase-Swift.h" 10 | requires objc 11 | } 12 | -------------------------------------------------------------------------------- /MacMagazine.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /MacMagazine/Theme/DarkTheme.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DarkTheme.swift 3 | // MacMagazine 4 | // 5 | // Created by Cassio Rossi on 28/02/2019. 6 | // Copyright © 2019 MacMagazine. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | struct DarkTheme: Theme { 12 | let tint: UIColor = UIColor(named: "MMBlueWhite") ?? .white 13 | } 14 | -------------------------------------------------------------------------------- /MacMagazine/macmagazine.xcdatamodeld/.xccurrentversion: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | _XCCurrentVersionName 6 | macmagazine 11.xcdatamodel 7 | 8 | 9 | -------------------------------------------------------------------------------- /MacMagazine/Theme/LightTheme.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LightTheme.swift 3 | // MacMagazine 4 | // 5 | // Created by Cassio Rossi on 28/02/2019. 6 | // Copyright © 2019 MacMagazine. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | struct LightTheme: Theme { 12 | let tint: UIColor = UIColor(named: "MMBlueWhite") ?? .systemBlue 13 | } 14 | -------------------------------------------------------------------------------- /InAppPurchase.xcframework/ios-arm64_x86_64-simulator/InAppPurchase.framework/Modules/InAppPurchase.swiftmodule/Project/arm64-apple-ios-simulator.swiftsourceinfo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MacMagazine/app-iOS/HEAD/InAppPurchase.xcframework/ios-arm64_x86_64-simulator/InAppPurchase.framework/Modules/InAppPurchase.swiftmodule/Project/arm64-apple-ios-simulator.swiftsourceinfo -------------------------------------------------------------------------------- /InAppPurchase.xcframework/ios-arm64_x86_64-simulator/InAppPurchase.framework/Modules/InAppPurchase.swiftmodule/Project/x86_64-apple-ios-simulator.swiftsourceinfo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MacMagazine/app-iOS/HEAD/InAppPurchase.xcframework/ios-arm64_x86_64-simulator/InAppPurchase.framework/Modules/InAppPurchase.swiftmodule/Project/x86_64-apple-ios-simulator.swiftsourceinfo -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Carthage/ 2 | Docs/ 3 | .DS_store 4 | 5 | ## Xcode Patch 6 | *.xcodeproj/* 7 | !*.xcodeproj/project.pbxproj 8 | !*.xcodeproj/xcshareddata/ 9 | !*.xcworkspace/contents.xcworkspacedata 10 | !*.xcodeproj/project.xcworkspace/contents.xcworkspacedata 11 | !*.xcodeproj/project.xcworkspace/xcshareddata 12 | 13 | Package.resolved 14 | !*.xcodeproj/**/Package.resolved 15 | -------------------------------------------------------------------------------- /MacMagazine/Config.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // Config.xcconfig 3 | // MacMagazine 4 | // 5 | // Created by Cassio Rossi on 28/08/20. 6 | // Copyright © 2020 MacMagazine. All rights reserved. 7 | // 8 | 9 | // Configuration settings file format documentation can be found at: 10 | // https://help.apple.com/xcode/#/dev745c5c974 11 | 12 | $(MARKETING_VERSION) = 4.5.2 13 | $(CURRENT_PROJECT_VERSION) = 4.5.2.0 14 | -------------------------------------------------------------------------------- /MacMagazineWidgetExtension/MacMagazineWidgetExtensionExtension.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.application-groups 6 | 7 | group.com.brit.macmagazine.data 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /MacMagazineWidgetExtension/RecentPosts/RecentPostsEntry.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RecentPostsEntry.swift 3 | // MacMagazineWidgetExtensionExtension 4 | // 5 | // Created by Ailton Vieira Pinto Filho on 16/01/21. 6 | // Copyright © 2021 MacMagazine. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import WidgetKit 11 | 12 | struct RecentPostsEntry: TimelineEntry { 13 | let date: Date 14 | let posts: [PostData] 15 | } 16 | -------------------------------------------------------------------------------- /MacMagazineNotificationServiceExtension/MacMagazineNotificationServiceExtension.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.application-groups 6 | 7 | group.com.brit.macmagazine.onesignal.push 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /MacMagazine/SFSafariViewControllerExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SFSafariViewControllerExtensions.swift 3 | // MacMagazine 4 | // 5 | // Created by Cassio Rossi on 29/11/19. 6 | // Copyright © 2019 MacMagazine. All rights reserved. 7 | // 8 | 9 | import SafariServices 10 | 11 | extension SFSafariViewController { 12 | func setup() { 13 | self.dismissButtonStyle = .close 14 | self.modalPresentationStyle = .overFullScreen 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /MacMagazine/UISplitViewControllerExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UISplitViewControllerExtension.swift 3 | // MacMagazine 4 | // 5 | // Created by Cassio Rossi on 28/02/2019. 6 | // Copyright © 2019 MacMagazine. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UISplitViewController { 12 | override open var preferredStatusBarStyle: UIStatusBarStyle { 13 | return Settings().isDarkMode ? .lightContent : .darkContent 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Support/PushNotificationAPNSTest/push.apns: -------------------------------------------------------------------------------- 1 | { 2 | "Simulator Target Bundle": "com.brit.macmagazine", 3 | "aps": { 4 | "alert" : { 5 | "title" : "MacMagazine", 6 | "body" : "Final Cut Pro poderá migrar para modelo de assinatura", 7 | }, 8 | "sound": "default", 9 | "badge": 1 10 | }, 11 | "additionalData": { 12 | "url": "https://macmagazine.com.br/post/2021/02/24/final-cut-pro-podera-migrar-para-modelo-de-assinatura/" 13 | } 14 | } -------------------------------------------------------------------------------- /MacMagazine/MacMagazine.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aps-environment 6 | production 7 | com.apple.security.application-groups 8 | 9 | group.com.brit.macmagazine.onesignal.push 10 | group.com.brit.macmagazine.data 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /MacMagazine/Assets.xcassets/image_logo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "image_logo.pdf", 5 | "idiom" : "universal" 6 | }, 7 | { 8 | "appearances" : [ 9 | { 10 | "appearance" : "luminosity", 11 | "value" : "dark" 12 | } 13 | ], 14 | "filename" : "image_logo_dark.pdf", 15 | "idiom" : "universal" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /MacMagazine/Assets.xcassets/image_logo_feature.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "image_logo_feature.pdf", 5 | "idiom" : "universal" 6 | }, 7 | { 8 | "appearances" : [ 9 | { 10 | "appearance" : "luminosity", 11 | "value" : "dark" 12 | } 13 | ], 14 | "filename" : "image_logo_feature_dark.pdf", 15 | "idiom" : "universal" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /MacMagazineWidgetExtension/Assets.xcassets/logo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "logo.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "logo@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "logo@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /MacMagazine/ArrayExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ArrayExtensions.swift 3 | // MacMagazine 4 | // 5 | // Created by Cassio Rossi on 26/11/19. 6 | // Copyright © 2019 MacMagazine. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Array where Element: Hashable { 12 | func removingDuplicates() -> [Element] { 13 | var addedDict = [Element: Bool]() 14 | 15 | return filter { 16 | addedDict.updateValue(true, forKey: $0) == nil 17 | } 18 | } 19 | 20 | mutating func removeDuplicates() { 21 | self = self.removingDuplicates() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /InAppPurchase.xcframework/ios-arm64/InAppPurchase.framework/Headers/InAppPurchase.h: -------------------------------------------------------------------------------- 1 | // 2 | // InAppPurchase.h 3 | // InAppPurchase 4 | // 5 | // Created by Cassio Rossi on 05/03/21. 6 | // 7 | 8 | #import 9 | 10 | //! Project version number for InAppPurchase. 11 | FOUNDATION_EXPORT double InAppPurchaseVersionNumber; 12 | 13 | //! Project version string for InAppPurchase. 14 | FOUNDATION_EXPORT const unsigned char InAppPurchaseVersionString[]; 15 | 16 | // In this header, you should import all the public headers of your framework using statements like #import 17 | -------------------------------------------------------------------------------- /InAppPurchase.xcframework/ios-arm64_x86_64-simulator/InAppPurchase.framework/Headers/InAppPurchase.h: -------------------------------------------------------------------------------- 1 | // 2 | // InAppPurchase.h 3 | // InAppPurchase 4 | // 5 | // Created by Cassio Rossi on 05/03/21. 6 | // 7 | 8 | #import 9 | 10 | //! Project version number for InAppPurchase. 11 | FOUNDATION_EXPORT double InAppPurchaseVersionNumber; 12 | 13 | //! Project version string for InAppPurchase. 14 | FOUNDATION_EXPORT const unsigned char InAppPurchaseVersionString[]; 15 | 16 | // In this header, you should import all the public headers of your framework using statements like #import 17 | -------------------------------------------------------------------------------- /MacMagazineWatchExtension/Assets.xcassets/Complication.complicationset/Circular.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "watch", 5 | "scale" : "2x", 6 | "screen-width" : "<=145" 7 | }, 8 | { 9 | "idiom" : "watch", 10 | "scale" : "2x", 11 | "screen-width" : ">161" 12 | }, 13 | { 14 | "idiom" : "watch", 15 | "scale" : "2x", 16 | "screen-width" : ">145" 17 | }, 18 | { 19 | "idiom" : "watch", 20 | "scale" : "2x", 21 | "screen-width" : ">183" 22 | } 23 | ], 24 | "info" : { 25 | "version" : 1, 26 | "author" : "xcode" 27 | } 28 | } -------------------------------------------------------------------------------- /MacMagazineWatchExtension/Assets.xcassets/Complication.complicationset/Extra Large.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "watch", 5 | "scale" : "2x", 6 | "screen-width" : "<=145" 7 | }, 8 | { 9 | "idiom" : "watch", 10 | "scale" : "2x", 11 | "screen-width" : ">161" 12 | }, 13 | { 14 | "idiom" : "watch", 15 | "scale" : "2x", 16 | "screen-width" : ">145" 17 | }, 18 | { 19 | "idiom" : "watch", 20 | "scale" : "2x", 21 | "screen-width" : ">183" 22 | } 23 | ], 24 | "info" : { 25 | "version" : 1, 26 | "author" : "xcode" 27 | } 28 | } -------------------------------------------------------------------------------- /MacMagazineWatchExtension/Assets.xcassets/Complication.complicationset/Modular.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "watch", 5 | "scale" : "2x", 6 | "screen-width" : "<=145" 7 | }, 8 | { 9 | "idiom" : "watch", 10 | "scale" : "2x", 11 | "screen-width" : ">161" 12 | }, 13 | { 14 | "idiom" : "watch", 15 | "scale" : "2x", 16 | "screen-width" : ">145" 17 | }, 18 | { 19 | "idiom" : "watch", 20 | "scale" : "2x", 21 | "screen-width" : ">183" 22 | } 23 | ], 24 | "info" : { 25 | "version" : 1, 26 | "author" : "xcode" 27 | } 28 | } -------------------------------------------------------------------------------- /MacMagazineWatchExtension/Assets.xcassets/Complication.complicationset/Utilitarian.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "watch", 5 | "scale" : "2x", 6 | "screen-width" : "<=145" 7 | }, 8 | { 9 | "idiom" : "watch", 10 | "scale" : "2x", 11 | "screen-width" : ">161" 12 | }, 13 | { 14 | "idiom" : "watch", 15 | "scale" : "2x", 16 | "screen-width" : ">145" 17 | }, 18 | { 19 | "idiom" : "watch", 20 | "scale" : "2x", 21 | "screen-width" : ">183" 22 | } 23 | ], 24 | "info" : { 25 | "version" : 1, 26 | "author" : "xcode" 27 | } 28 | } -------------------------------------------------------------------------------- /MacMagazineWatchExtension/Assets.xcassets/Complication.complicationset/Graphic Bezel.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "watch", 5 | "scale" : "2x", 6 | "screen-width" : "<=145" 7 | }, 8 | { 9 | "idiom" : "watch", 10 | "scale" : "2x", 11 | "screen-width" : ">161" 12 | }, 13 | { 14 | "idiom" : "watch", 15 | "scale" : "2x", 16 | "screen-width" : ">145" 17 | }, 18 | { 19 | "idiom" : "watch", 20 | "scale" : "2x", 21 | "screen-width" : ">183" 22 | } 23 | ], 24 | "info" : { 25 | "version" : 1, 26 | "author" : "xcode" 27 | } 28 | } -------------------------------------------------------------------------------- /MacMagazineWatchExtension/Assets.xcassets/Complication.complicationset/Graphic Circular.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "watch", 5 | "scale" : "2x", 6 | "screen-width" : "<=145" 7 | }, 8 | { 9 | "idiom" : "watch", 10 | "scale" : "2x", 11 | "screen-width" : ">161" 12 | }, 13 | { 14 | "idiom" : "watch", 15 | "scale" : "2x", 16 | "screen-width" : ">145" 17 | }, 18 | { 19 | "idiom" : "watch", 20 | "scale" : "2x", 21 | "screen-width" : ">183" 22 | } 23 | ], 24 | "info" : { 25 | "version" : 1, 26 | "author" : "xcode" 27 | } 28 | } -------------------------------------------------------------------------------- /MacMagazineWatchExtension/Assets.xcassets/Complication.complicationset/Graphic Corner.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "watch", 5 | "scale" : "2x", 6 | "screen-width" : "<=145" 7 | }, 8 | { 9 | "idiom" : "watch", 10 | "scale" : "2x", 11 | "screen-width" : ">161" 12 | }, 13 | { 14 | "idiom" : "watch", 15 | "scale" : "2x", 16 | "screen-width" : ">145" 17 | }, 18 | { 19 | "idiom" : "watch", 20 | "scale" : "2x", 21 | "screen-width" : ">183" 22 | } 23 | ], 24 | "info" : { 25 | "version" : 1, 26 | "author" : "xcode" 27 | } 28 | } -------------------------------------------------------------------------------- /MacMagazineWatchExtension/Assets.xcassets/Complication.complicationset/Graphic Extra Large.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "watch", 5 | "scale" : "2x", 6 | "screen-width" : "<=145" 7 | }, 8 | { 9 | "idiom" : "watch", 10 | "scale" : "2x", 11 | "screen-width" : ">161" 12 | }, 13 | { 14 | "idiom" : "watch", 15 | "scale" : "2x", 16 | "screen-width" : ">145" 17 | }, 18 | { 19 | "idiom" : "watch", 20 | "scale" : "2x", 21 | "screen-width" : ">183" 22 | } 23 | ], 24 | "info" : { 25 | "author" : "xcode", 26 | "version" : 1 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /MacMagazineWatchExtension/Assets.xcassets/Complication.complicationset/Graphic Large Rectangular.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "watch", 5 | "scale" : "2x", 6 | "screen-width" : "<=145" 7 | }, 8 | { 9 | "idiom" : "watch", 10 | "scale" : "2x", 11 | "screen-width" : ">161" 12 | }, 13 | { 14 | "idiom" : "watch", 15 | "scale" : "2x", 16 | "screen-width" : ">145" 17 | }, 18 | { 19 | "idiom" : "watch", 20 | "scale" : "2x", 21 | "screen-width" : ">183" 22 | } 23 | ], 24 | "info" : { 25 | "version" : 1, 26 | "author" : "xcode" 27 | } 28 | } -------------------------------------------------------------------------------- /MacMagazine/Enums/ShortcutActions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShortcutActions.swift 3 | // MacMagazine 4 | // 5 | // Created by Marcos Ferreira on 31/07/25. 6 | // Copyright © 2025 MacMagazine. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum ShortcutActions: String { 12 | case openLastSeenPost 13 | case openMostRecentPost 14 | case openSearchPost 15 | case none = "" 16 | 17 | var notificationName: Notification.Name? { 18 | switch self { 19 | case .openLastSeenPost: return .shortcutActionLastPost 20 | case .openMostRecentPost: return .shortcutActionRecentPost 21 | case .openSearchPost: return .shortcutActionSearchPost 22 | case .none: return nil 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /MacMagazine/Assets.xcassets/Colors/MMGray.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "display-p3", 6 | "components" : { 7 | "alpha" : "0.700", 8 | "blue" : "0xDF", 9 | "green" : "0xDC", 10 | "red" : "0xDB" 11 | } 12 | }, 13 | "idiom" : "iphone" 14 | }, 15 | { 16 | "color" : { 17 | "color-space" : "srgb", 18 | "components" : { 19 | "alpha" : "0.700", 20 | "blue" : "0xDF", 21 | "green" : "0xDC", 22 | "red" : "0xDB" 23 | } 24 | }, 25 | "idiom" : "ipad" 26 | } 27 | ], 28 | "info" : { 29 | "author" : "xcode", 30 | "version" : 1 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /MacMagazine/Assets.xcassets/Colors/MMWhiteGrey6.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "1.000", 9 | "green" : "1.000", 10 | "red" : "1.000" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "platform" : "ios", 24 | "reference" : "systemGray6Color" 25 | }, 26 | "idiom" : "universal" 27 | } 28 | ], 29 | "info" : { 30 | "author" : "xcode", 31 | "version" : 1 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /MacMagazineTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 10 21 | 22 | 23 | -------------------------------------------------------------------------------- /MacMagazine/Theme/Extensions/UIWindowTheme.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIWindowTheme.swift 3 | // MacMagazine 4 | // 5 | // Created by Cassio Rossi on 28/02/2019. 6 | // Copyright © 2019 MacMagazine. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public extension UIWindow { 12 | /// Unload all views and add back. 13 | /// Useful for applying `UIAppearance` changes to existing views. 14 | func reload() { 15 | subviews.forEach { view in 16 | view.removeFromSuperview() 17 | addSubview(view) 18 | } 19 | } 20 | } 21 | 22 | public extension Array where Element == UIWindow { 23 | /// Unload all views for each `UIWindow` and add back. 24 | /// Useful for applying `UIAppearance` changes to existing views. 25 | func reload() { 26 | forEach { $0.reload() } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /MacMagazine.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "originHash" : "255298906b5e0fe45154cee505684b0a368e9c7ddaac83f76ceb11094cd38ea6", 3 | "pins" : [ 4 | { 5 | "identity" : "kingfisher", 6 | "kind" : "remoteSourceControl", 7 | "location" : "https://github.com/onevcat/Kingfisher.git", 8 | "state" : { 9 | "revision" : "b450a3b30e0767277a6eed7e02dbdebd7d173da0", 10 | "version" : "8.3.0" 11 | } 12 | }, 13 | { 14 | "identity" : "onesignal-xcframework", 15 | "kind" : "remoteSourceControl", 16 | "location" : "https://github.com/OneSignal/OneSignal-XCFramework", 17 | "state" : { 18 | "revision" : "a7df0fa1cb25ae2920b6ee01fe703fec50639256", 19 | "version" : "5.2.10" 20 | } 21 | } 22 | ], 23 | "version" : 3 24 | } 25 | -------------------------------------------------------------------------------- /MacMagazine/Theme/Extensions/With.swift: -------------------------------------------------------------------------------- 1 | // 2 | // With.swift 3 | // MacMagazine 4 | // 5 | // Created by Cassio Rossi on 28/02/2019. 6 | // Copyright © 2019 MacMagazine. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public protocol With {} 12 | 13 | public extension With where Self: Any { 14 | 15 | /// Makes it available to set properties with closures just after initializing. 16 | /// 17 | /// let label = UILabel().with { 18 | /// $0.textAlignment = .center 19 | /// $0.textColor = UIColor.black 20 | /// $0.text = "Hello, World!" 21 | /// } 22 | @discardableResult 23 | func with(_ block: (Self) -> Void) -> Self { 24 | // https://github.com/devxoul/Then 25 | block(self) 26 | return self 27 | } 28 | } 29 | 30 | extension NSObject: With {} 31 | -------------------------------------------------------------------------------- /MacMagazine/Assets.xcassets/Colors/MMBlack90.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "0.900", 8 | "blue" : "0.000", 9 | "green" : "0.000", 10 | "red" : "0.000" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "0.900", 26 | "blue" : "0.000", 27 | "green" : "0.000", 28 | "red" : "0.000" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /MacMagazine/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | }, 8 | { 9 | "appearances" : [ 10 | { 11 | "appearance" : "luminosity", 12 | "value" : "dark" 13 | } 14 | ], 15 | "idiom" : "universal", 16 | "platform" : "ios", 17 | "size" : "1024x1024" 18 | }, 19 | { 20 | "appearances" : [ 21 | { 22 | "appearance" : "luminosity", 23 | "value" : "tinted" 24 | } 25 | ], 26 | "idiom" : "universal", 27 | "platform" : "ios", 28 | "size" : "1024x1024" 29 | }, 30 | { 31 | "idiom" : "universal", 32 | "platform" : "watchos", 33 | "size" : "1024x1024" 34 | } 35 | ], 36 | "info" : { 37 | "author" : "xcode", 38 | "version" : 1 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /MacMagazine/Icons/mm_icon_inverted.icon/Assets/layer_0.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | AppIcon-2 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /MacMagazine/DateExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DateExtensions.swift 3 | // MacMagazine 4 | // 5 | // Created by Cassio Rossi on 27/02/2019. 6 | // Copyright © 2019 MacMagazine. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Date { 12 | 13 | func cellDate() -> String { 14 | let dateFormatter = DateFormatter() 15 | dateFormatter.dateFormat = "dd/MM/yyyy" 16 | dateFormatter.timeZone = TimeZone(abbreviation: "BRT") 17 | return dateFormatter.string(from: self) 18 | } 19 | 20 | func sortedDate() -> String { 21 | let dateFormatter = DateFormatter() 22 | dateFormatter.dateFormat = "yyyyMMdd" 23 | dateFormatter.timeZone = TimeZone(abbreviation: "BRT") 24 | return dateFormatter.string(from: self) 25 | } 26 | 27 | func watchDate() -> String { 28 | let dateFormatter = DateFormatter() 29 | dateFormatter.dateFormat = "dd/MM/yyyy HH:mm" 30 | dateFormatter.timeZone = TimeZone(abbreviation: "BRT") 31 | return dateFormatter.string(from: self) 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /MacMagazine/FavoriteAction/Favorite.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Favorite.swift 3 | // MacMagazine 4 | // 5 | // Created by Cassio Rossi on 15/04/2019. 6 | // Copyright © 2019 MacMagazine. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct Favorite { 12 | 13 | func updatePostStatus(using link: String, _ completion: ((Bool) -> Void)? = nil) { 14 | CoreDataStack.shared.get(link: link) { (items: [Post]) in 15 | if !items.isEmpty { 16 | items[0].favorite = !items[0].favorite 17 | CoreDataStack.shared.save() 18 | completion?(items[0].favorite) 19 | } 20 | } 21 | } 22 | 23 | func updateVideoStatus(using videoId: String?, _ completion: ((Bool) -> Void)? = nil) { 24 | guard let videoId = videoId else { 25 | completion?(false) 26 | return 27 | } 28 | CoreDataStack.shared.get(video: videoId) { items in 29 | if !items.isEmpty { 30 | items[0].favorite = !items[0].favorite 31 | CoreDataStack.shared.save() 32 | completion?(items[0].favorite) 33 | } 34 | } 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /MacMagazine/WhatsNew/WhatsNewTableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WhatsNewTableViewController.swift 3 | // MacMagazine 4 | // 5 | // Created by Cassio Rossi on 03/10/20. 6 | // Copyright © 2020 MacMagazine. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class WhatsNewTableViewController: UIViewController { 12 | 13 | // MARK: - Properties - 14 | 15 | @IBOutlet private var version: UILabel! 16 | @IBOutlet private var whatsnewLabel: UILabel! 17 | 18 | // MARK: - View lifecycle - 19 | 20 | override func viewWillAppear(_ animated: Bool) { 21 | super.viewWillAppear(animated) 22 | 23 | whatsnewLabel?.font = Settings().getMetricBoldFont(forTextStyle: .title2) 24 | 25 | version.text = "versão \(Settings().appVersion)" 26 | } 27 | 28 | // MARK: - Methods - 29 | 30 | @IBAction private func close(_ sender: Any) { 31 | var settings = Settings() 32 | settings.whatsNew = Settings().appVersion 33 | 34 | self.dismiss(animated: true, completion: nil) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /MacMagazineWidgetExtension/PostDataExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PostData.swift 3 | // MacMagazineWidgetExtensionExtension 4 | // 5 | // Created by Ailton Vieira Pinto Filho on 16/01/21. 6 | // Copyright © 2021 MacMagazine. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension PostData { 12 | static var placeholder = PostData(title: "Acompanhe as últimas notícias do universo Apple.", 13 | link: "", 14 | thumbnail: "", 15 | favorito: false, 16 | pubDate: "", 17 | excerpt: "Adicione o widget à tela inicial do seu dispositivo.", 18 | postId: "", 19 | shortURL: "") 20 | 21 | var url: URL { 22 | return URL(staticString: link ?? "") 23 | } 24 | } 25 | 26 | extension URL { 27 | init(staticString string: String) { 28 | self = URL(string: "\(string)") ?? URL(staticString: "widget://") 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /MacMagazineWidgetExtension/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | MacMagazineWidgetExtension 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | $(MARKETING_VERSION) 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSExtension 24 | 25 | NSExtensionPointIdentifier 26 | com.apple.widgetkit-extension 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /MacMagazine/PostData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PostData.swift 3 | // MacMagazine 4 | // 5 | // Created by Cassio Rossi on 08/04/2019. 6 | // Copyright © 2019 MacMagazine. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct PostData: Codable, Hashable { 12 | var title: String? 13 | var link: String? 14 | var thumbnail: String? 15 | var favorito: Bool = false 16 | var pubDate: String? 17 | var excerpt: String? 18 | var postId: String? 19 | var shortURL: String? 20 | var playable: Bool = false 21 | var fullContent: String? 22 | 23 | init(title: String?, 24 | link: String?, 25 | thumbnail: String?, 26 | favorito: Bool, 27 | pubDate: String? = "", 28 | excerpt: String? = "", 29 | fullContent: String? = "", 30 | postId: String? = "", 31 | shortURL: String? = "", 32 | playable: Bool = false) { 33 | 34 | self.title = title 35 | self.link = link 36 | self.thumbnail = thumbnail 37 | self.favorito = favorito 38 | self.pubDate = pubDate 39 | self.excerpt = excerpt 40 | self.fullContent = fullContent 41 | self.postId = postId 42 | self.shortURL = shortURL 43 | self.playable = playable 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /MacMagazine/UILabelExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UILabelExtension.swift 3 | // MacMagazine 4 | // 5 | // Created by Cassio Rossi on 22/11/19. 6 | // Copyright © 2019 MacMagazine. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @IBDesignable class PaddingLabel: UILabel { 12 | 13 | @IBInspectable var topInset: CGFloat = 5.0 14 | @IBInspectable var bottomInset: CGFloat = 5.0 15 | @IBInspectable var leftInset: CGFloat = 7.0 16 | @IBInspectable var rightInset: CGFloat = 7.0 17 | 18 | override func drawText(in rect: CGRect) { 19 | let insets = UIEdgeInsets(top: topInset, left: leftInset, bottom: bottomInset, right: rightInset) 20 | super.drawText(in: rect.inset(by: insets)) 21 | } 22 | 23 | override var intrinsicContentSize: CGSize { 24 | let size = super.intrinsicContentSize 25 | return CGSize(width: size.width + leftInset + rightInset, 26 | height: size.height + topInset + bottomInset) 27 | } 28 | } 29 | 30 | extension UILabel { 31 | func textHeight(withWidth width: CGFloat) -> CGFloat { 32 | guard let text = text else { 33 | return 0 34 | } 35 | return text.height(withWidth: width, font: font) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /InAppPurchase.xcframework/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | AvailableLibraries 6 | 7 | 8 | LibraryIdentifier 9 | ios-arm64_x86_64-simulator 10 | LibraryPath 11 | InAppPurchase.framework 12 | SupportedArchitectures 13 | 14 | arm64 15 | x86_64 16 | 17 | SupportedPlatform 18 | ios 19 | SupportedPlatformVariant 20 | simulator 21 | 22 | 23 | LibraryIdentifier 24 | ios-arm64 25 | LibraryPath 26 | InAppPurchase.framework 27 | SupportedArchitectures 28 | 29 | arm64 30 | 31 | SupportedPlatform 32 | ios 33 | 34 | 35 | CFBundlePackageType 36 | XFWK 37 | XCFrameworkFormatVersion 38 | 1.0 39 | 40 | 41 | -------------------------------------------------------------------------------- /ExportOptions.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | destination 6 | export 7 | manageAppVersionAndBuildNumber 8 | 9 | method 10 | app-store 11 | provisioningProfiles 12 | 13 | com.brit.macmagazine 14 | MacMagazine 15 | com.brit.macmagazine.MacMagazinePushNotificationServiceExtension 16 | MacMagazine Notification Extension 17 | com.brit.macmagazine.MacMagazineWidgetExtension 18 | MacMagazine Widget 19 | com.brit.macmagazine.watchkitapp 20 | MacMagazine Wildcard 21 | com.brit.macmagazine.watchkitapp.watchkitExtension 22 | MacMagazine Wildcard 23 | 24 | signingCertificate 25 | Apple Distribution 26 | signingStyle 27 | manual 28 | stripSwiftSymbols 29 | 30 | teamID 31 | A5VW9QUF9L 32 | uploadSymbols 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /MacMagazine/PlayerViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PlayerViewController.swift 3 | // MacMagazine 4 | // 5 | // Created by Cassio Rossi on 03/04/2019. 6 | // Copyright © 2019 MacMagazine. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | struct Podcast { 12 | var title: String? 13 | var duration: String? 14 | var url: String? 15 | var frame: String? 16 | } 17 | 18 | class PlayerViewController: UIViewController { 19 | 20 | // MARK: - Properties - 21 | 22 | @IBOutlet private weak var scPlayer: SoundcloudPlayer! 23 | 24 | var show: ((Bool) -> Void)? 25 | var isHidden = true 26 | var frame: String? 27 | 28 | var podcast: Podcast? { 29 | didSet { 30 | scPlayer.soundEffect = nil 31 | 32 | if podcast?.frame == frame { 33 | if isHidden { 34 | show?(true) 35 | scPlayer?.play() 36 | } else { 37 | show?(false) 38 | scPlayer?.pause() 39 | } 40 | isHidden = !isHidden 41 | } else { 42 | if isHidden { 43 | show?(true) 44 | isHidden = false 45 | } 46 | frame = podcast?.frame 47 | loadPlayer() 48 | } 49 | } 50 | } 51 | 52 | // MARK: - Methods - 53 | 54 | fileprivate func loadPlayer() { 55 | if !self.isHidden { 56 | scPlayer.iFrame = podcast?.frame 57 | } 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /MacMagazineWatchExtension/DetailInterfaceController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailInterfaceController.swift 3 | // MacMagazine 4 | // 5 | // Created by Cassio Rossi on 14/09/2025. 6 | // Copyright © 2025 MacMagazine. All rights reserved. 7 | // 8 | 9 | import WatchKit 10 | 11 | class DetailInterfaceController: WKInterfaceController { 12 | 13 | // MARK: - Properties - 14 | 15 | @IBOutlet private weak var content: WKInterfaceLabel! 16 | 17 | var object: [String: Any]? 18 | 19 | // MARK: - App lifecycle - 20 | 21 | override func awake(withContext context: Any?) { 22 | super.awake(withContext: context) 23 | 24 | // Configure interface objects here. 25 | guard let object = context as? [String: Any] else { 26 | return 27 | } 28 | self.object = object 29 | self.setTitle("Fechar") 30 | } 31 | 32 | override func willActivate() { 33 | super.willActivate() 34 | 35 | if let object = self.object, 36 | let item = object["post"] as? PostData { 37 | 38 | let style = NSMutableParagraphStyle() 39 | style.paragraphSpacing = 12 40 | 41 | let string = NSMutableAttributedString(string: item.fullContent ?? "") 42 | string.addAttribute(.paragraphStyle, 43 | value: style, 44 | range: NSRange(location: 0, length: string.length)) 45 | 46 | content.setAttributedText(string) 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /MacMagazineNotificationServiceExtension/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | MacMagazineNotificationServiceExtension 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | XPC! 19 | CFBundleShortVersionString 20 | $(MARKETING_VERSION) 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSExtension 24 | 25 | NSExtensionPointIdentifier 26 | com.apple.usernotifications.service 27 | NSExtensionPrincipalClass 28 | $(PRODUCT_MODULE_NAME).NotificationService 29 | 30 | OneSignal_app_groups_key 31 | group.com.brit.macmagazine.onesignal.push 32 | 33 | 34 | -------------------------------------------------------------------------------- /MacMagazine/UIViewExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewExtensions.swift 3 | // MacMagazine 4 | // 5 | // Created by Cassio Rossi on 28/02/2019. 6 | // Copyright © 2019 MacMagazine. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public extension UIView { 12 | 13 | /// Border color of view; also inspectable from Storyboard. 14 | @IBInspectable var borderColor: UIColor? { 15 | get { 16 | guard let color = layer.borderColor else { 17 | return nil 18 | } 19 | return UIColor(cgColor: color) 20 | } 21 | 22 | set { 23 | guard let color = newValue else { 24 | layer.borderColor = nil 25 | return 26 | } 27 | layer.borderColor = color.cgColor 28 | } 29 | } 30 | 31 | /// Border width of view; also inspectable from Storyboard. 32 | @IBInspectable var borderWidth: CGFloat { 33 | get { return layer.borderWidth } 34 | set { layer.borderWidth = newValue } 35 | } 36 | 37 | /// Corner radius of view; also inspectable from Storyboard. 38 | @IBInspectable var cornerRadius: CGFloat { 39 | get { return layer.cornerRadius } 40 | 41 | set { 42 | layer.masksToBounds = true 43 | layer.cornerRadius = newValue 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /MacMagazine/Assets.xcassets/Colors/MMGrayWhite.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "platform" : "ios", 6 | "reference" : "systemGray2Color" 7 | }, 8 | "idiom" : "iphone" 9 | }, 10 | { 11 | "appearances" : [ 12 | { 13 | "appearance" : "luminosity", 14 | "value" : "dark" 15 | } 16 | ], 17 | "color" : { 18 | "color-space" : "srgb", 19 | "components" : { 20 | "alpha" : "1.000", 21 | "blue" : "1.000", 22 | "green" : "1.000", 23 | "red" : "1.000" 24 | } 25 | }, 26 | "idiom" : "iphone" 27 | }, 28 | { 29 | "color" : { 30 | "platform" : "ios", 31 | "reference" : "systemGray2Color" 32 | }, 33 | "idiom" : "ipad" 34 | }, 35 | { 36 | "appearances" : [ 37 | { 38 | "appearance" : "luminosity", 39 | "value" : "dark" 40 | } 41 | ], 42 | "color" : { 43 | "color-space" : "srgb", 44 | "components" : { 45 | "alpha" : "1.000", 46 | "blue" : "1.000", 47 | "green" : "1.000", 48 | "red" : "1.000" 49 | } 50 | }, 51 | "idiom" : "ipad" 52 | } 53 | ], 54 | "info" : { 55 | "author" : "xcode", 56 | "version" : 1 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /MacMagazineWatchExtension/Assets.xcassets/Complication.complicationset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "assets" : [ 3 | { 4 | "filename" : "Circular.imageset", 5 | "idiom" : "watch", 6 | "role" : "circular" 7 | }, 8 | { 9 | "filename" : "Extra Large.imageset", 10 | "idiom" : "watch", 11 | "role" : "extra-large" 12 | }, 13 | { 14 | "filename" : "Graphic Bezel.imageset", 15 | "idiom" : "watch", 16 | "role" : "graphic-bezel" 17 | }, 18 | { 19 | "filename" : "Graphic Circular.imageset", 20 | "idiom" : "watch", 21 | "role" : "graphic-circular" 22 | }, 23 | { 24 | "filename" : "Graphic Corner.imageset", 25 | "idiom" : "watch", 26 | "role" : "graphic-corner" 27 | }, 28 | { 29 | "filename" : "Graphic Extra Large.imageset", 30 | "idiom" : "watch", 31 | "role" : "graphic-extra-large" 32 | }, 33 | { 34 | "filename" : "Graphic Large Rectangular.imageset", 35 | "idiom" : "watch", 36 | "role" : "graphic-large-rectangular" 37 | }, 38 | { 39 | "filename" : "Modular.imageset", 40 | "idiom" : "watch", 41 | "role" : "modular" 42 | }, 43 | { 44 | "filename" : "Utilitarian.imageset", 45 | "idiom" : "watch", 46 | "role" : "utilitarian" 47 | } 48 | ], 49 | "info" : { 50 | "author" : "xcode", 51 | "version" : 1 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /MacMagazineWidgetExtension/Assets.xcassets/image_logo_feature.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "image_logo_feature.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "appearances" : [ 10 | { 11 | "appearance" : "luminosity", 12 | "value" : "dark" 13 | } 14 | ], 15 | "filename" : "image_logo_feature copy.png", 16 | "idiom" : "universal", 17 | "scale" : "1x" 18 | }, 19 | { 20 | "filename" : "image_logo_feature@2x.png", 21 | "idiom" : "universal", 22 | "scale" : "2x" 23 | }, 24 | { 25 | "appearances" : [ 26 | { 27 | "appearance" : "luminosity", 28 | "value" : "dark" 29 | } 30 | ], 31 | "filename" : "image_logo_feature copy@2x.png", 32 | "idiom" : "universal", 33 | "scale" : "2x" 34 | }, 35 | { 36 | "filename" : "image_logo_feature@3x.png", 37 | "idiom" : "universal", 38 | "scale" : "3x" 39 | }, 40 | { 41 | "appearances" : [ 42 | { 43 | "appearance" : "luminosity", 44 | "value" : "dark" 45 | } 46 | ], 47 | "filename" : "image_logo_feature copy@3x.png", 48 | "idiom" : "universal", 49 | "scale" : "3x" 50 | } 51 | ], 52 | "info" : { 53 | "author" : "xcode", 54 | "version" : 1 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /MacMagazine/UIActivityExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIActivityExtensions.swift 3 | // MacMagazine 4 | // 5 | // Created by Cassio Rossi on 30/03/2019. 6 | // Copyright © 2019 MacMagazine. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class UIActivityExtensions: UIActivity { 12 | 13 | // MARK: - Properties - 14 | 15 | var localActivityTitle: String 16 | var localActivityImage: UIImage? 17 | var activityItems = [Any]() 18 | var action: ([Any]) -> Void 19 | 20 | init(title: String, image: UIImage?, performAction: @escaping ([Any]) -> Void) { 21 | localActivityTitle = title 22 | localActivityImage = image 23 | action = performAction 24 | super.init() 25 | } 26 | 27 | // MARK: - UIActivity override - 28 | 29 | override var activityTitle: String? { 30 | return localActivityTitle 31 | } 32 | 33 | override var activityImage: UIImage? { 34 | return localActivityImage 35 | } 36 | 37 | override var activityType: UIActivity.ActivityType? { 38 | return UIActivity.ActivityType(rawValue: "com.brit.macmagazine.activity") 39 | } 40 | 41 | override class var activityCategory: UIActivity.Category { 42 | return .action 43 | } 44 | 45 | override func canPerform(withActivityItems activityItems: [Any]) -> Bool { 46 | return true 47 | } 48 | 49 | override func prepare(withActivityItems activityItems: [Any]) { 50 | self.activityItems = activityItems 51 | } 52 | 53 | override func perform() { 54 | action(activityItems) 55 | activityDidFinish(true) 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /MacMagazine/Assets.xcassets/Colors/MMDarkGreyWhite.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "extended-gray", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "white" : "0.250" 9 | } 10 | }, 11 | "idiom" : "iphone" 12 | }, 13 | { 14 | "appearances" : [ 15 | { 16 | "appearance" : "luminosity", 17 | "value" : "dark" 18 | } 19 | ], 20 | "color" : { 21 | "color-space" : "srgb", 22 | "components" : { 23 | "alpha" : "1.000", 24 | "blue" : "1.000", 25 | "green" : "1.000", 26 | "red" : "1.000" 27 | } 28 | }, 29 | "idiom" : "iphone" 30 | }, 31 | { 32 | "color" : { 33 | "color-space" : "extended-gray", 34 | "components" : { 35 | "alpha" : "1.000", 36 | "white" : "0.250" 37 | } 38 | }, 39 | "idiom" : "ipad" 40 | }, 41 | { 42 | "appearances" : [ 43 | { 44 | "appearance" : "luminosity", 45 | "value" : "dark" 46 | } 47 | ], 48 | "color" : { 49 | "color-space" : "srgb", 50 | "components" : { 51 | "alpha" : "1.000", 52 | "blue" : "1.000", 53 | "green" : "1.000", 54 | "red" : "1.000" 55 | } 56 | }, 57 | "idiom" : "ipad" 58 | } 59 | ], 60 | "info" : { 61 | "author" : "xcode", 62 | "version" : 1 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /MacMagazine/Assets.xcassets/Colors/MMLessDarkGreyWhite.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "extended-gray", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "white" : "0.350" 9 | } 10 | }, 11 | "idiom" : "iphone" 12 | }, 13 | { 14 | "appearances" : [ 15 | { 16 | "appearance" : "luminosity", 17 | "value" : "dark" 18 | } 19 | ], 20 | "color" : { 21 | "color-space" : "srgb", 22 | "components" : { 23 | "alpha" : "1.000", 24 | "blue" : "1.000", 25 | "green" : "1.000", 26 | "red" : "1.000" 27 | } 28 | }, 29 | "idiom" : "iphone" 30 | }, 31 | { 32 | "color" : { 33 | "color-space" : "extended-gray", 34 | "components" : { 35 | "alpha" : "1.000", 36 | "white" : "0.350" 37 | } 38 | }, 39 | "idiom" : "ipad" 40 | }, 41 | { 42 | "appearances" : [ 43 | { 44 | "appearance" : "luminosity", 45 | "value" : "dark" 46 | } 47 | ], 48 | "color" : { 49 | "color-space" : "srgb", 50 | "components" : { 51 | "alpha" : "1.000", 52 | "blue" : "1.000", 53 | "green" : "1.000", 54 | "red" : "1.000" 55 | } 56 | }, 57 | "idiom" : "ipad" 58 | } 59 | ], 60 | "info" : { 61 | "author" : "xcode", 62 | "version" : 1 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /MacMagazine/Theme/Theme.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Theme.swift 3 | // MacMagazine 4 | // 5 | // Created by Cassio Rossi on 28/02/2019. 6 | // Copyright © 2019 MacMagazine. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import WebKit 11 | 12 | class AppCollectionViewCell: UICollectionViewCell {} 13 | 14 | protocol Theme { 15 | var tint: UIColor { get } 16 | 17 | func apply(for application: UIApplication) 18 | func extend(_ application: UIApplication) 19 | } 20 | 21 | extension Theme { 22 | 23 | func apply(for application: UIApplication) { 24 | extend(application) 25 | 26 | // Ensure existing views render with new theme 27 | // https://developer.apple.com/documentation/uikit/uiappearance 28 | (application.connectedScenes.first as? UIWindowScene)?.windows.first?.reload() 29 | } 30 | 31 | // Optionally extend theme 32 | func extend(_ application: UIApplication) { 33 | 34 | // WINDOW 35 | let window = (application.connectedScenes.first as? UIWindowScene)?.windows.first 36 | window?.tintColor = tint 37 | 38 | let selectedMode: UIUserInterfaceStyle = Settings().isDarkMode ? .dark : .light 39 | let overrideInterfaceStyle: UIUserInterfaceStyle = Settings().appearance == .native ? .unspecified : selectedMode 40 | window?.overrideUserInterfaceStyle = overrideInterfaceStyle 41 | 42 | // SEGMENTCONTROL 43 | 44 | UILabel.appearance(whenContainedInInstancesOf: [UISegmentedControl.self]).with { 45 | $0.textColor = tint 46 | } 47 | 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /MacMagazine/Assets.xcassets/Colors/MMBlue.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.831", 9 | "green" : "0.592", 10 | "red" : "0.000" 11 | } 12 | }, 13 | "idiom" : "iphone" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.831", 27 | "green" : "0.592", 28 | "red" : "0.000" 29 | } 30 | }, 31 | "idiom" : "iphone" 32 | }, 33 | { 34 | "color" : { 35 | "color-space" : "srgb", 36 | "components" : { 37 | "alpha" : "1.000", 38 | "blue" : "0.831", 39 | "green" : "0.592", 40 | "red" : "0.000" 41 | } 42 | }, 43 | "idiom" : "ipad" 44 | }, 45 | { 46 | "appearances" : [ 47 | { 48 | "appearance" : "luminosity", 49 | "value" : "dark" 50 | } 51 | ], 52 | "color" : { 53 | "color-space" : "srgb", 54 | "components" : { 55 | "alpha" : "1.000", 56 | "blue" : "0.831", 57 | "green" : "0.592", 58 | "red" : "0.000" 59 | } 60 | }, 61 | "idiom" : "ipad" 62 | } 63 | ], 64 | "info" : { 65 | "author" : "xcode", 66 | "version" : 1 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /MacMagazine/Assets.xcassets/Colors/MMBlueWhite.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0xD3", 9 | "green" : "0x96", 10 | "red" : "0x00" 11 | } 12 | }, 13 | "idiom" : "iphone" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "1.000", 27 | "green" : "1.000", 28 | "red" : "1.000" 29 | } 30 | }, 31 | "idiom" : "iphone" 32 | }, 33 | { 34 | "color" : { 35 | "color-space" : "srgb", 36 | "components" : { 37 | "alpha" : "1.000", 38 | "blue" : "0.831", 39 | "green" : "0.592", 40 | "red" : "0.000" 41 | } 42 | }, 43 | "idiom" : "ipad" 44 | }, 45 | { 46 | "appearances" : [ 47 | { 48 | "appearance" : "luminosity", 49 | "value" : "dark" 50 | } 51 | ], 52 | "color" : { 53 | "color-space" : "srgb", 54 | "components" : { 55 | "alpha" : "1.000", 56 | "blue" : "1.000", 57 | "green" : "1.000", 58 | "red" : "1.000" 59 | } 60 | }, 61 | "idiom" : "ipad" 62 | } 63 | ], 64 | "info" : { 65 | "author" : "xcode", 66 | "version" : 1 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /MacMagazine/macmagazine.xcdatamodeld/macmagazine.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /MacMagazine/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // MacMagazine 4 | // 5 | // Created by Cassio Rossi on 18/08/17. 6 | // Copyright © 2017 MacMagazine. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | // MARK: - Properties - 15 | 16 | var window: UIWindow? 17 | var previousController: UIViewController? 18 | var supportedInterfaceOrientation: UIInterfaceOrientationMask = .all 19 | var isMMLive: Bool = false 20 | var pushNotification: PushNotification? 21 | var tabBarController: UITabBarController? 22 | 23 | // MARK: - Window lifecycle - 24 | 25 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 26 | // Override point for customization after application launch. 27 | setup(launchOptions) 28 | return true 29 | } 30 | 31 | func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask { 32 | return supportedInterfaceOrientation 33 | } 34 | 35 | // MARK: - UISceneSession Lifecycle - 36 | 37 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 38 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 39 | } 40 | 41 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) {} 42 | } 43 | -------------------------------------------------------------------------------- /MacMagazine/Helper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Helper.swift 3 | // MacMagazine 4 | // 5 | // Created by Cassio Rossi on 24/09/2022. 6 | // Copyright © 2022 MacMagazine. All rights reserved. 7 | // 8 | 9 | import OneSignalExtension 10 | import UIKit 11 | 12 | // MARK: - 13 | 14 | enum Definitions { 15 | static let darkMode = "darkMode" 16 | static let fontSize = "font-size-settings" 17 | static let icon = "icon" 18 | static let watch = "watch" 19 | static let askForReview = "askForReview" 20 | static let allPostsPushes = "all_posts_pushes" 21 | static let pushPreferences = "pushPreferences" 22 | static let transparency = "transparency" 23 | static let mmPatrao = "mm_patrao" 24 | static let purchased = "purchased" 25 | static let whatsnew = "whatsnew" 26 | static let deleteAllCookies = "deleteAllCookies" 27 | static let mmLive = "mmLive" 28 | static let badge = "badge" 29 | } 30 | 31 | struct Helper { 32 | var badgeCount: Int = { CoreDataStack.shared.numberOfUnreadPosts() }() 33 | } 34 | 35 | #if !WIDGET 36 | extension Helper { 37 | func showBadge() { 38 | delay(1) { 39 | if UserDefaults.standard.bool(forKey: Definitions.badge) { 40 | UNUserNotificationCenter.current().setBadgeCount(badgeCount) 41 | updateOneSignal(counter: badgeCount) 42 | } else { 43 | // Icon badge should be set to -1 to disappear but keep history of notifications 44 | UNUserNotificationCenter.current().setBadgeCount(0) 45 | updateOneSignal(counter: 0) 46 | } 47 | } 48 | } 49 | 50 | fileprivate func updateOneSignal(counter: Int) { 51 | OneSignalExtensionBadgeHandler.updateCachedBadgeValue(counter) 52 | } 53 | } 54 | #endif 55 | -------------------------------------------------------------------------------- /MacMagazine/Webview/MMLiveViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MMLiveViewController.swift 3 | // MacMagazine 4 | // 5 | // Created by Cassio Rossi on 08/03/21. 6 | // Copyright © 2021 MacMagazine. All rights reserved. 7 | // 8 | 9 | import SafariServices 10 | import UIKit 11 | import WebKit 12 | 13 | class MMLiveViewController: WebViewController { 14 | 15 | let liveURL = "https://macmagazine.com.br/live" 16 | 17 | // MARK: - View lifecycle - 18 | 19 | override func viewDidLoad() { 20 | super.viewDidLoad() 21 | 22 | // Do any additional setup after loading the view. 23 | postURL = URL(string: liveURL) 24 | } 25 | 26 | override func viewDidAppear(_ animated: Bool) { 27 | super.viewDidAppear(animated) 28 | 29 | if Settings.widgetSpotlightPost != nil { 30 | TabBarController.shared.selectIndex(1) 31 | } 32 | } 33 | 34 | override func willTransition(to newCollection: UITraitCollection, with coordinator: UIViewControllerTransitionCoordinator) { 35 | super.willTransition(to: newCollection, with: coordinator) 36 | 37 | coordinator.animate(alongsideTransition: nil) { [weak self] _ in 38 | delay(0.8) { 39 | guard let self = self else { return } 40 | self.postURL = URL(string: self.liveURL) 41 | NotificationCenter.default.post(name: .reloadWeb, object: nil) 42 | } 43 | } 44 | } 45 | 46 | @IBAction private func refresh(_ sender: Any) { 47 | reload() 48 | } 49 | 50 | // MARK: - Override - 51 | 52 | override func setRightButtomItems(_ buttons: [RightButtons]) {} 53 | override func webView(_ webView: WKWebView, didFinish navigation: WKNavigation) {} 54 | } 55 | -------------------------------------------------------------------------------- /MacMagazine/macmagazine.xcdatamodeld/macmagazine 2.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /MacMagazineNotificationServiceExtension/NotificationService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NotificationService.swift 3 | // MacMagazineNotificationServiceExtension 4 | // 5 | // Created by Cassio Rossi on 20/04/2019. 6 | // Copyright © 2019 MacMagazine. All rights reserved. 7 | // 8 | 9 | import OneSignalExtension 10 | import UserNotifications 11 | import WidgetKit 12 | 13 | class NotificationService: UNNotificationServiceExtension { 14 | 15 | var contentHandler: ((UNNotificationContent) -> Void)? 16 | var receivedRequest: UNNotificationRequest? 17 | var bestAttemptContent: UNMutableNotificationContent? 18 | 19 | override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { 20 | self.receivedRequest = request 21 | self.contentHandler = contentHandler 22 | bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) 23 | 24 | WidgetCenter.shared.reloadAllTimelines() 25 | 26 | if let bestAttemptContent = bestAttemptContent, 27 | let receivedRequest = receivedRequest { 28 | OneSignalExtension.didReceiveNotificationExtensionRequest(receivedRequest, with: bestAttemptContent, withContentHandler: contentHandler) 29 | } 30 | } 31 | 32 | override func serviceExtensionTimeWillExpire() { 33 | // Called just before the extension will be terminated by the system. 34 | // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used. 35 | if let contentHandler = contentHandler, 36 | let bestAttemptContent = bestAttemptContent, 37 | let receivedRequest = receivedRequest { 38 | OneSignalExtension.serviceExtensionTimeWillExpireRequest(receivedRequest, with: bestAttemptContent) 39 | contentHandler(bestAttemptContent) 40 | } 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /MacMagazine/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /MacMagazineWatch/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | MacMagazine 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(MARKETING_VERSION) 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | CLKComplicationPrincipalClass 24 | $(PRODUCT_MODULE_NAME).ComplicationController 25 | CLKComplicationSupportedFamilies 26 | 27 | CLKComplicationFamilyGraphicRectangular 28 | CLKComplicationFamilyModularLarge 29 | CLKComplicationFamilyUtilitarianLarge 30 | 31 | LSApplicationCategoryType 32 | public.app-category.news 33 | NSAppTransportSecurity 34 | 35 | NSAllowsArbitraryLoads 36 | 37 | 38 | UISupportedInterfaceOrientations 39 | 40 | UIInterfaceOrientationPortrait 41 | UIInterfaceOrientationPortraitUpsideDown 42 | 43 | WKApplication 44 | 45 | WKCompanionAppBundleIdentifier 46 | com.brit.macmagazine 47 | WKRunsIndependentlyOfCompanionApp 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /MacMagazineWatchExtension/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | MacMagazineWatchExtension 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | XPC! 19 | CFBundleShortVersionString 20 | $(MARKETING_VERSION) 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | CLKComplicationPrincipalClass 24 | $(PRODUCT_MODULE_NAME).ComplicationController 25 | CLKComplicationSupportedFamilies 26 | 27 | CLKComplicationFamilyGraphicRectangular 28 | CLKComplicationFamilyModularLarge 29 | CLKComplicationFamilyUtilitarianLarge 30 | 31 | NSAppTransportSecurity 32 | 33 | NSAllowsArbitraryLoads 34 | 35 | 36 | NSExtension 37 | 38 | NSExtensionAttributes 39 | 40 | WKAppBundleIdentifier 41 | com.brit.macmagazine.watchkitapp 42 | 43 | NSExtensionPointIdentifier 44 | com.apple.watchkit 45 | 46 | WKExtensionDelegateClassName 47 | $(PRODUCT_MODULE_NAME).ExtensionDelegate 48 | WKRunsIndependentlyOfCompanionApp 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /MacMagazine/macmagazine.xcdatamodeld/macmagazine 3.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /MacMagazineWidgetExtension/RecentPosts/RecentPostsProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RecentPostsProvider.swift 3 | // MacMagazineWidgetExtensionExtension 4 | // 5 | // Created by Ailton Vieira Pinto Filho on 16/01/21. 6 | // Copyright © 2021 MacMagazine. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Kingfisher 11 | import WidgetKit 12 | 13 | struct RecentPostsProvider: TimelineProvider { 14 | func placeholder(in context: Context) -> RecentPostsEntry { 15 | RecentPostsEntry(date: Date(), posts: [.placeholder, .placeholder, .placeholder]) 16 | } 17 | 18 | func getSnapshot(in context: Context, completion: @escaping (RecentPostsEntry) -> Void) { 19 | getWidgetContent { posts in 20 | let entry = RecentPostsEntry(date: Date(), posts: posts) 21 | completion(entry) 22 | } 23 | } 24 | 25 | func getTimeline(in context: Context, completion: @escaping (Timeline) -> Void) { 26 | getWidgetContent { posts in 27 | let timeline = Timeline(entries: [RecentPostsEntry(date: Date(), posts: posts)], policy: .atEnd) 28 | completion(timeline) 29 | } 30 | } 31 | } 32 | 33 | extension RecentPostsProvider { 34 | fileprivate func getWidgetContent(onCompletion: @escaping (([PostData]) -> Void)) { 35 | var posts = [PostData]() 36 | 37 | API().getPosts { xmlPost in 38 | guard let xmlPost else { 39 | let urls = posts.compactMap { $0.thumbnail }.compactMap { URL(string: $0) } 40 | ImagePrefetcher(urls: urls, completionHandler: { _, _, _ in 41 | onCompletion(posts) 42 | }).start() 43 | 44 | return 45 | } 46 | let post = PostData(title: xmlPost.title, 47 | link: xmlPost.link, 48 | thumbnail: xmlPost.artworkURL, 49 | favorito: false) 50 | posts.append(post) 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /MacMagazineWatchExtension/MainInterfaceController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainInterfaceController.swift 3 | // MacMagazineWatch Extension 4 | // 5 | // Created by Cassio Rossi on 08/04/2019. 6 | // Copyright © 2019 MacMagazine. All rights reserved. 7 | // 8 | 9 | import Kingfisher 10 | import WatchKit 11 | 12 | class MainInterfaceController: WKInterfaceController { 13 | 14 | // MARK: - Properties - 15 | 16 | @IBOutlet private weak var image: WKInterfaceImage! 17 | @IBOutlet private weak var titleLabel: WKInterfaceLabel! 18 | @IBOutlet private weak var dateLabel: WKInterfaceLabel! 19 | @IBOutlet private weak var content: WKInterfaceLabel! 20 | 21 | var object: [String: Any]? 22 | 23 | // MARK: - App lifecycle - 24 | 25 | override func awake(withContext context: Any?) { 26 | super.awake(withContext: context) 27 | 28 | // Configure interface objects here. 29 | guard let object = context as? [String: Any] else { 30 | return 31 | } 32 | self.object = object 33 | self.setTitle("MacMagazine") 34 | } 35 | 36 | override func willActivate() { 37 | super.willActivate() 38 | 39 | if let object = self.object, 40 | let title = object["title"] as? String, 41 | let item = object["post"] as? PostData { 42 | 43 | self.setTitle(title) 44 | 45 | titleLabel.setText(item.title) 46 | dateLabel.setText(item.pubDate) 47 | content.setText(item.excerpt) 48 | 49 | guard let thumbnail = item.thumbnail, 50 | let url = URL(string: thumbnail) else { 51 | return 52 | } 53 | image.kf.setImage(with: url, completionHandler: { _ in }) 54 | } 55 | } 56 | 57 | @IBAction private func reload() { 58 | WKInterfaceController.reloadRootPageControllers(withNames: ["loading"], contexts: nil, orientation: .horizontal, pageIndex: 0) 59 | } 60 | 61 | @IBAction private func clearCache() { 62 | reload() 63 | } 64 | 65 | @IBAction private func showMore() { 66 | self.presentController(withName: "DetailController", context: object) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /MacMagazine/Obfuscator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Obfuscator.swift 3 | // MacMagazine 4 | // 5 | // Created by Cassio Rossi on 12/04/2019. 6 | // Copyright © 2019 MacMagazine. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class Obfuscator { 12 | 13 | // MARK: - Variables 14 | 15 | /// The salt used to obfuscate and reveal the string. 16 | private var salt: String 17 | 18 | // MARK: - Initialization 19 | 20 | init() { 21 | self.salt = "\(String(describing: "AppDelegate"))\(String(describing: NSObject.self))" 22 | } 23 | 24 | init(with salt: String) { 25 | self.salt = salt 26 | } 27 | 28 | // MARK: - Instance Methods 29 | 30 | /** 31 | This method obfuscates the string passed in using the salt 32 | that was used when the Obfuscator was initialized. 33 | 34 | - parameter string: the string to obfuscate 35 | 36 | - returns: the obfuscated string in a byte array 37 | */ 38 | func bytesByObfuscatingString(string: String) -> [UInt8] { 39 | let text = [UInt8](string.utf8) 40 | let cipher = [UInt8](self.salt.utf8) 41 | let length = cipher.count 42 | 43 | var encrypted = [UInt8]() 44 | 45 | for temp in text.enumerated() { 46 | encrypted.append(temp.element ^ cipher[temp.offset % length]) 47 | } 48 | 49 | #if DEBUG 50 | print("Salt used: \(self.salt)\n") 51 | print("Swift Code:\n************") 52 | print("// Original \"\(string)\"") 53 | print("let key: [UInt8] = \(encrypted)\n") 54 | #endif 55 | 56 | return encrypted 57 | } 58 | 59 | /** 60 | This method reveals the original string from the obfuscated 61 | byte array passed in. The salt must be the same as the one 62 | used to encrypt it in the first place. 63 | 64 | - parameter key: the byte array to reveal 65 | 66 | - returns: the original string 67 | */ 68 | func reveal(key: [UInt8]) -> String { 69 | let cipher = [UInt8](self.salt.utf8) 70 | let length = cipher.count 71 | 72 | var decrypted = [UInt8]() 73 | 74 | for localKey in key.enumerated() { 75 | decrypted.append(localKey.element ^ cipher[localKey.offset % length]) 76 | } 77 | 78 | guard let revealedString = String(bytes: decrypted, encoding: .utf8) else { 79 | return "" 80 | } 81 | return revealedString 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /MacMagazine/WatchSessionManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WatchSessionManager.swift 3 | // MacMagazine 4 | // 5 | // Created by Cassio Rossi on 08/04/2019. 6 | // Copyright © 2019 MacMagazine. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Kingfisher 11 | import WatchConnectivity 12 | 13 | class WatchSessionManager: NSObject { 14 | 15 | // MARK: - Properties - 16 | 17 | static let shared = WatchSessionManager() 18 | 19 | // MARK: - Init - 20 | 21 | func startSession() { 22 | if WCSession.isSupported() { 23 | WCSession.default.delegate = self 24 | WCSession.default.activate() 25 | } 26 | } 27 | 28 | } 29 | 30 | extension WatchSessionManager: WCSessionDelegate { 31 | 32 | func sessionDidBecomeInactive(_ session: WCSession) {} 33 | 34 | func sessionDidDeactivate(_ session: WCSession) { 35 | WCSession.default.activate() 36 | } 37 | 38 | func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {} 39 | 40 | func session(_ session: WCSession, didReceiveMessage message: [String: Any], replyHandler: @escaping ([String: Any]) -> Void) { 41 | if message["request"] as? String == "posts" { 42 | retrieve(replyHandler: replyHandler) 43 | } 44 | } 45 | 46 | } 47 | 48 | extension WatchSessionManager { 49 | func retrieve(replyHandler: @escaping ([String: Any]) -> Void) { 50 | CoreDataStack.shared.getPostsForWatch { [weak self] watchPosts in 51 | do { 52 | let jsonData = try JSONEncoder().encode(watchPosts) 53 | replyHandler(["posts": jsonData]) 54 | } catch { 55 | logE(error.localizedDescription) 56 | self?.load(replyHandler: replyHandler) 57 | } 58 | } 59 | } 60 | 61 | func load(replyHandler: @escaping ([String: Any]) -> Void) { 62 | var images: [String] = [] 63 | 64 | API().getWatchPosts { [weak self] post in 65 | DispatchQueue.main.async { 66 | guard let post = post else { 67 | // Prefetch images to be able to sent to Apple Watch 68 | let urls = images.compactMap { URL(string: $0) } 69 | let prefetcher = ImagePrefetcher(urls: urls) 70 | prefetcher.start() 71 | 72 | self?.retrieve(replyHandler: replyHandler) 73 | return 74 | } 75 | images.append(post.artworkURL) 76 | CoreDataStack.shared.save(post: post) 77 | } 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # App do MacMagazine para iOS 2 | ![Build Status](https://app.bitrise.io/app/b04bb172ee4330fd/status.svg?token=hWsWH4V5VQAavaZAQZMEhA&branch=release/v4) 3 | 4 | O aplicativo do MacMagazine agora é um projeto de código aberto (_open source_), para que a enorme comunidade de desenvolvedores/leitores do site possa colaborar e construir um app cada vez melhor e mais completo. 5 | 6 | ## Funcionalidades existentes 7 | - Posts com imagens dos artigos 8 | - Compartilhamento de posts 9 | - Favoritar posts 10 | - Podcasts, com compartilhamento e favoritar 11 | - Videos, com compartilhamento e favoritar 12 | - Buscas em Posts, Podcasts e Videos 13 | - Notificações _push_ de todos os posts ou apenas de destaques 14 | - `WKWebView` para leitura dos artigos e visualização dos comentários 15 | - Modo Escuro 16 | - Fontes dinâmicas para melhor visualização 17 | - Leitura dos posts em fullscreen no iPad 18 | - App para `watchOS` 19 | - Widgets, tanto na Lock Screen como na Home screen 20 | 21 | ## Bug Reporting e Feature request 22 | Use as [Issues](https://github.com/MacMagazine/app-iOS/issues) para cadastrar problemas encontrados ou features desejadas. 23 | 24 | ## Instruções para colaboração 25 | Optamos pela não utilização de Gerenciadores de Dependências, como Cocoapods ou Carthage, para permitir um melhor entendimento do projeto, além de servir como estudo de Swift. Porém se tiver uma biblioteca que realmente faça a diferença no projeto, use Swift Package Manager - ou nos escreva para discutirmos a melhor opção. 26 | 27 | Tenha sempre seu Xcode e Swift atualizado na última versão e a versão de iOS suportada é 13+. 28 | 29 | Antes de iniciar seu desenvolvimento, o código-fonte está disponível aqui mesmo neste repositório, na branch `release/v4`. 30 | 31 | Instale o utitlitário [swiftlint](https://github.com/realm/SwiftLint) e observe o [code style](https://github.com/raywenderlich/swift-style-guide) para manter o padrão no desenvolvimento. 32 | 33 | Para cada bug/nova funcionalidade que for desenvolver, crie uma nova branch, no formato `hotfix/[descricao]` (no título, mencione o número do issue, usando hashtag (ex: branch: `hotfix/Fix_91_TableView_bug` e título: `Correção #91 TableView bug`)) ou `feature/[descricao]` para nova funcionalidade e utilize [Pull Requests](https://github.com/MacMagazine/app-iOS/pulls) para enviar o código para aprovação do nosso time de revisores. 34 | 35 | Bom desenvolvimento. 36 | 37 | Equipe MM :-) 38 | -------------------------------------------------------------------------------- /MacMagazine/Webview/Cookies.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Cookies.swift 3 | // MacMagazine 4 | // 5 | // Created by Cassio Rossi on 08/10/20. 6 | // Copyright © 2020 MacMagazine. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct Cookies { 12 | 13 | let disqus = API.APIParams.disqus 14 | let mmDomain = API.APIParams.mmDomain 15 | 16 | func getCookies(_ domain: String? = nil) -> [HTTPCookie]? { 17 | let cookies = HTTPCookieStorage.shared.cookies 18 | 19 | guard let domain = domain else { 20 | return cookies 21 | } 22 | 23 | return cookies?.filter { 24 | return $0.domain.contains(domain) 25 | } 26 | } 27 | 28 | func cleanCookies() { 29 | for cookie in getCookies() ?? [] { 30 | if !cookie.domain.contains(disqus) && 31 | !cookie.domain.contains(mmDomain) { 32 | HTTPCookieStorage.shared.deleteCookie(cookie) 33 | } 34 | } 35 | } 36 | 37 | func createDarkModeCookie(_ value: String) -> HTTPCookie? { 38 | return HTTPCookie(properties: [ 39 | .domain: mmDomain, 40 | .path: "/", 41 | .name: "darkmode", 42 | .value: value, 43 | .secure: "true", 44 | .expires: NSDate(timeIntervalSinceNow: 60) 45 | ]) 46 | } 47 | 48 | func createFonteCookie(_ value: String) -> HTTPCookie? { 49 | return HTTPCookie(properties: [ 50 | .domain: mmDomain, 51 | .path: "/", 52 | .name: "fonte", 53 | .value: value, 54 | .secure: "true", 55 | .expires: NSDate(timeIntervalSinceNow: 60) 56 | ]) 57 | } 58 | 59 | func createVersionCookie(_ value: String) -> HTTPCookie? { 60 | return HTTPCookie(properties: [ 61 | .domain: mmDomain, 62 | .path: "/", 63 | .name: "version", 64 | .value: value, 65 | .secure: "true", 66 | .expires: NSDate(timeIntervalSinceNow: 60) 67 | ]) 68 | } 69 | 70 | func createPurchasedCookie(_ value: String) -> HTTPCookie? { 71 | return HTTPCookie(properties: [ 72 | .domain: mmDomain, 73 | .path: "/", 74 | .name: "patr", 75 | .value: value, 76 | .secure: "true", 77 | .expires: NSDate(timeIntervalSinceNow: 60) 78 | ]) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /MacMagazine/ShareAction/Share.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Share.swift 3 | // MacMagazine 4 | // 5 | // Created by Cassio Rossi on 15/04/2019. 6 | // Copyright © 2019 MacMagazine. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | struct Share { 12 | func present(at location: T?, using items: [Any], activities: [UIActivityExtensions]? = nil) { 13 | let safari = UIActivityExtensions(title: "Abrir no Safari", image: UIImage(systemName: "safari")) { items in 14 | for item in items { 15 | guard let url = URL(string: "\(item)") else { 16 | continue 17 | } 18 | if UIApplication.shared.canOpenURL(url) { 19 | UIApplication.shared.open(url, options: [:], completionHandler: nil) 20 | } 21 | } 22 | } 23 | 24 | let chrome = UIActivityExtensions(title: "Abrir no Chrome", image: UIImage(named: "chrome")) { items in 25 | for item in items { 26 | guard let url = URL(string: "\(item)".replacingOccurrences(of: "http", with: "googlechrome")) else { 27 | continue 28 | } 29 | if UIApplication.shared.canOpenURL(url) { 30 | UIApplication.shared.open(url, options: [:], completionHandler: nil) 31 | } 32 | } 33 | } 34 | var applicationActivities = [UIActivityExtensions]() 35 | if let activities = activities { 36 | applicationActivities.append(contentsOf: activities) 37 | } 38 | applicationActivities.append(safari) 39 | if let url = URL(string: "googlechrome://"), 40 | UIApplication.shared.canOpenURL(url) { 41 | applicationActivities.append(chrome) 42 | } 43 | 44 | let customCopy = UIActivityExtensions(title: "Copiar Link", image: UIImage(systemName: "link")) { items in 45 | for item in items { 46 | guard let url = URL(string: "\(item)") else { 47 | continue 48 | } 49 | UIPasteboard.general.url = url 50 | } 51 | } 52 | applicationActivities.append(customCopy) 53 | 54 | let activityVC = UIActivityViewController(activityItems: items, applicationActivities: applicationActivities) 55 | if let ppc = activityVC.popoverPresentationController { 56 | if location != nil { 57 | if let view = location as? UIView { 58 | ppc.sourceView = view 59 | } 60 | if let button = location as? UIBarButtonItem { 61 | ppc.barButtonItem = button 62 | } 63 | } 64 | } 65 | 66 | Settings.rootViewController?.present(activityVC, animated: true) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /MacMagazineWidgetExtension/MacMagazineWidgetExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MacMagazineWidgetExtension.swift 3 | // MacMagazineWidgetExtension 4 | // 5 | // Created by Ailton Vieira Pinto Filho on 16/01/21. 6 | // Copyright © 2021 MacMagazine. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import WidgetKit 11 | 12 | @main 13 | struct MacMagazineWidgetExtension: Widget { 14 | let kind: String = "MacMagazineRecentPostsWidget" 15 | 16 | private var supportedFamilies: [WidgetFamily] { 17 | #if os(iOS) 18 | return [.accessoryRectangular, 19 | .accessoryInline, 20 | .accessoryCircular, 21 | .systemSmall, 22 | .systemMedium, 23 | .systemLarge] 24 | #else 25 | return [.systemSmall, 26 | .systemMedium] 27 | #endif 28 | } 29 | 30 | var body: some WidgetConfiguration { 31 | StaticConfiguration(kind: kind, provider: RecentPostsProvider()) { entry in 32 | RecentPostsWidget(entry: entry) 33 | } 34 | .configurationDisplayName("MacMagazine") 35 | .description("Confira nossos últimos posts!") 36 | .supportedFamilies(supportedFamilies) 37 | .contentMarginsDisabled() 38 | } 39 | } 40 | 41 | #Preview("Large", as: .systemLarge) { 42 | MacMagazineWidgetExtension() 43 | } timeline: { 44 | RecentPostsEntry(date: Date(), 45 | posts: [.placeholder, .placeholder, .placeholder]) 46 | } 47 | 48 | #Preview("Rectangular", as: .accessoryRectangular) { 49 | MacMagazineWidgetExtension() 50 | } timeline: { 51 | RecentPostsEntry(date: Date(), 52 | posts: [.placeholder]) 53 | } 54 | 55 | #Preview("Inline", as: .accessoryInline) { 56 | MacMagazineWidgetExtension() 57 | } timeline: { 58 | RecentPostsEntry(date: Date(), 59 | posts: [.placeholder]) 60 | } 61 | 62 | #Preview("Circular", as: .accessoryCircular) { 63 | MacMagazineWidgetExtension() 64 | } timeline: { 65 | RecentPostsEntry(date: Date(), 66 | posts: [.placeholder]) 67 | } 68 | 69 | #Preview("Small", as: .systemSmall) { 70 | MacMagazineWidgetExtension() 71 | } timeline: { 72 | RecentPostsEntry(date: Date(), 73 | posts: [.placeholder]) 74 | } 75 | 76 | #Preview("Medium", as: .systemMedium) { 77 | MacMagazineWidgetExtension() 78 | } timeline: { 79 | RecentPostsEntry(date: Date(), 80 | posts: [.placeholder, .placeholder]) 81 | } 82 | -------------------------------------------------------------------------------- /MacMagazine/SplashViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SplashViewController.swift 3 | // MacMagazine 4 | // 5 | // Created by Cassio Rossi on 08/06/2019. 6 | // Copyright © 2019 MacMagazine. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SplashViewController: UIViewController { 12 | 13 | override func viewDidAppear(_ animated: Bool) { 14 | super.viewDidAppear(animated) 15 | 16 | view.backgroundColor = Settings().isDarkMode ? .black : .white 17 | 18 | // Check subscriptions and update status 19 | Subscriptions.shared.checkSubscriptions { response in 20 | var settings = Settings() 21 | settings.purchased = response 22 | } 23 | 24 | Settings().isMMLive { isLive in 25 | DispatchQueue.main.async { 26 | (UIApplication.shared.delegate as? AppDelegate)?.isMMLive = isLive 27 | } 28 | 29 | delay(0.2) { 30 | let storyboard = UIStoryboard(name: "Main", bundle: nil) 31 | 32 | guard let controller = storyboard.instantiateViewController(withIdentifier: "main") as? UITabBarController, 33 | let appDelegate = UIApplication.shared.delegate as? AppDelegate, 34 | let splitViewController = controller.viewControllers?[1] as? UISplitViewController, 35 | let window = (UIApplication.shared.connectedScenes.first as? UIWindowScene)?.windows.first else { 36 | return 37 | } 38 | 39 | appDelegate.tabBarController = controller 40 | 41 | splitViewController.delegate = appDelegate 42 | splitViewController.preferredDisplayMode = UISplitViewController.DisplayMode.oneBesideSecondary 43 | splitViewController.preferredPrimaryColumnWidthFraction = 0.33 44 | 45 | UIView.transition(with: window, 46 | duration: 0.2, 47 | options: .transitionCrossDissolve, 48 | animations: { 49 | window.rootViewController = controller 50 | if !isLive { 51 | TabBarController.shared.removeIndexes([0]) 52 | } 53 | }, 54 | completion: { finished in 55 | if finished && 56 | Settings.widgetSpotlightPost != nil { 57 | TabBarController.shared.selectIndex(isLive ? 1 : 0) 58 | } 59 | }) 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /MacMagazine/Configuration.storekit: -------------------------------------------------------------------------------- 1 | { 2 | "identifier" : "B267CFDB", 3 | "nonRenewingSubscriptions" : [ 4 | 5 | ], 6 | "products" : [ 7 | 8 | ], 9 | "settings" : { 10 | "_askToBuyEnabled" : false, 11 | "_locale" : "pt_BR", 12 | "_storefront" : "BRA", 13 | "_timeRate" : 5 14 | }, 15 | "subscriptionGroups" : [ 16 | { 17 | "id" : "0A60022E", 18 | "localizations" : [ 19 | 20 | ], 21 | "name" : "Assinaturas", 22 | "subscriptions" : [ 23 | { 24 | "adHocOffers" : [ 25 | 26 | ], 27 | "codeOffers" : [ 28 | 29 | ], 30 | "displayPrice" : "10.90", 31 | "familyShareable" : false, 32 | "groupNumber" : 1, 33 | "internalID" : "33E27B74", 34 | "introductoryOffer" : null, 35 | "localizations" : [ 36 | { 37 | "description" : "Assinatura mensal", 38 | "displayName" : "Remover propagandas", 39 | "locale" : "pt_BR" 40 | } 41 | ], 42 | "productID" : "MMASSINATURAMENSAL", 43 | "recurringSubscriptionPeriod" : "P1M", 44 | "referenceName" : "Remover propagandas (mensal)", 45 | "subscriptionGroupID" : "0A60022E", 46 | "type" : "RecurringSubscription" 47 | }, 48 | { 49 | "adHocOffers" : [ 50 | 51 | ], 52 | "codeOffers" : [ 53 | 54 | ], 55 | "displayPrice" : "99.90", 56 | "familyShareable" : false, 57 | "groupNumber" : 1, 58 | "internalID" : "EC489E7F", 59 | "introductoryOffer" : null, 60 | "localizations" : [ 61 | { 62 | "description" : "Assinatura anual", 63 | "displayName" : "Remover propagandas", 64 | "locale" : "pt_BR" 65 | } 66 | ], 67 | "productID" : "MMASSINATURAANUAL", 68 | "recurringSubscriptionPeriod" : "P1Y", 69 | "referenceName" : "Remover propagandas (anual)", 70 | "subscriptionGroupID" : "0A60022E", 71 | "type" : "RecurringSubscription" 72 | } 73 | ] 74 | } 75 | ], 76 | "subscriptionOffersKeyPair" : { 77 | "id" : "CA6C26D9", 78 | "privateKey" : "MF8CAQEEGFNdmXwbztKRElvBEQRj1CQDG7Zr+BCz1KAKBggqhkjOPQMBAaE0AzIA\nBBNChsXDXfzhsrIodoIHyP3B1CiiB/WphGoPJmcfQePk1ZUw5ws7eLGqVN45qEUA\nkw==", 79 | "publicKey" : "MEkwEwYHKoZIzj0CAQYIKoZIzj0DAQEDMgAEE0KGxcNd/OGysih2ggfI/cHUKKIH\n9amEag8mZx9B4+TVlTDnCzt4sapU3jmoRQCT" 80 | }, 81 | "version" : { 82 | "major" : 2, 83 | "minor" : 0 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /MacMagazine/macmagazine.xcdatamodeld/macmagazine 6.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /MacMagazine/Icons/mm_icon.icon/icon.json: -------------------------------------------------------------------------------- 1 | { 2 | "fill" : { 3 | "solid" : "extended-gray:1.00000,1.00000" 4 | }, 5 | "groups" : [ 6 | { 7 | "blur-material" : null, 8 | "hidden" : false, 9 | "layers" : [ 10 | { 11 | "glass" : true, 12 | "hidden" : false, 13 | "image-name" : "layer_3.svg", 14 | "name" : "letra_MM" 15 | }, 16 | { 17 | "glass" : false, 18 | "hidden" : false, 19 | "image-name" : "layer_2.svg", 20 | "name" : "sombra_MM" 21 | } 22 | ], 23 | "lighting" : "individual", 24 | "name" : "MM", 25 | "shadow" : { 26 | "kind" : "none", 27 | "opacity" : 0.5 28 | }, 29 | "specular" : false, 30 | "translucency" : { 31 | "enabled" : false, 32 | "value" : 0.5 33 | } 34 | }, 35 | { 36 | "hidden" : false, 37 | "layers" : [ 38 | { 39 | "hidden" : false, 40 | "image-name" : "layer_1.svg", 41 | "name" : "base-pasta" 42 | }, 43 | { 44 | "blend-mode-specializations" : [ 45 | { 46 | "value" : "normal" 47 | }, 48 | { 49 | "appearance" : "dark", 50 | "value" : "normal" 51 | } 52 | ], 53 | "fill-specializations" : [ 54 | { 55 | "value" : "none" 56 | }, 57 | { 58 | "appearance" : "tinted", 59 | "value" : "automatic" 60 | } 61 | ], 62 | "glass" : false, 63 | "hidden-specializations" : [ 64 | { 65 | "value" : false 66 | }, 67 | { 68 | "appearance" : "tinted", 69 | "value" : true 70 | } 71 | ], 72 | "image-name" : "layer_1.png", 73 | "name" : "sombra_pasta", 74 | "position" : { 75 | "scale" : 1, 76 | "translation-in-points" : [ 77 | 0, 78 | 0 79 | ] 80 | } 81 | } 82 | ], 83 | "name" : "pasta", 84 | "shadow" : { 85 | "kind" : "neutral", 86 | "opacity" : 0.5 87 | }, 88 | "specular" : true, 89 | "translucency" : { 90 | "enabled" : false, 91 | "value" : 0.5 92 | } 93 | } 94 | ], 95 | "supported-platforms" : { 96 | "circles" : [ 97 | "watchOS" 98 | ], 99 | "squares" : "shared" 100 | } 101 | } -------------------------------------------------------------------------------- /MacMagazine/macmagazine.xcdatamodeld/macmagazine 4.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /MacMagazine/Icons/mm_icon_normal.icon/icon.json: -------------------------------------------------------------------------------- 1 | { 2 | "fill" : { 3 | "solid" : "extended-gray:1.00000,1.00000" 4 | }, 5 | "groups" : [ 6 | { 7 | "blur-material" : null, 8 | "hidden" : false, 9 | "layers" : [ 10 | { 11 | "glass" : true, 12 | "hidden" : false, 13 | "image-name" : "layer_3.svg", 14 | "name" : "letra_MM" 15 | }, 16 | { 17 | "glass" : false, 18 | "hidden" : false, 19 | "image-name" : "layer_2.svg", 20 | "name" : "sombra_MM" 21 | } 22 | ], 23 | "lighting" : "individual", 24 | "name" : "MM", 25 | "shadow" : { 26 | "kind" : "none", 27 | "opacity" : 0.5 28 | }, 29 | "specular" : false, 30 | "translucency" : { 31 | "enabled" : false, 32 | "value" : 0.5 33 | } 34 | }, 35 | { 36 | "hidden" : false, 37 | "layers" : [ 38 | { 39 | "hidden" : false, 40 | "image-name" : "layer_1.svg", 41 | "name" : "base-pasta" 42 | }, 43 | { 44 | "blend-mode-specializations" : [ 45 | { 46 | "value" : "normal" 47 | }, 48 | { 49 | "appearance" : "dark", 50 | "value" : "normal" 51 | } 52 | ], 53 | "fill-specializations" : [ 54 | { 55 | "value" : "none" 56 | }, 57 | { 58 | "appearance" : "tinted", 59 | "value" : "automatic" 60 | } 61 | ], 62 | "glass" : false, 63 | "hidden-specializations" : [ 64 | { 65 | "value" : false 66 | }, 67 | { 68 | "appearance" : "tinted", 69 | "value" : true 70 | } 71 | ], 72 | "image-name" : "layer_1.png", 73 | "name" : "sombra_pasta", 74 | "position" : { 75 | "scale" : 1, 76 | "translation-in-points" : [ 77 | 0, 78 | 0 79 | ] 80 | } 81 | } 82 | ], 83 | "name" : "pasta", 84 | "shadow" : { 85 | "kind" : "neutral", 86 | "opacity" : 0.5 87 | }, 88 | "specular" : true, 89 | "translucency" : { 90 | "enabled" : false, 91 | "value" : 0.5 92 | } 93 | } 94 | ], 95 | "supported-platforms" : { 96 | "circles" : [ 97 | "watchOS" 98 | ], 99 | "squares" : "shared" 100 | } 101 | } -------------------------------------------------------------------------------- /MacMagazine/macmagazine.xcdatamodeld/macmagazine 7.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /MacMagazineTests/ParsedObjectTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ParsedObjectTests.swift 3 | // MacMagazineTests 4 | // 5 | // Created by Cassio Rossi on 26/05/2019. 6 | // Copyright © 2019 MacMagazine. All rights reserved. 7 | // 8 | 9 | @testable import MacMagazine 10 | import XCTest 11 | 12 | // Tests to be performed: 13 | // 1) Create a mock test 14 | // 2) Test for the content of the mock data, adding to the XMLPost class 15 | 16 | class ParsedObjectTests: XCTestCase { 17 | 18 | var postExample: Data? 19 | let examplePost = ExamplePost() 20 | 21 | override func setUp() { 22 | // Put setup code here. This method is called before the invocation of each test method in the class. 23 | guard let post = self.examplePost.getExamplePost() else { 24 | return 25 | } 26 | postExample = post 27 | } 28 | 29 | override func tearDown() { 30 | // Put teardown code here. This method is called after the invocation of each test method in the class. 31 | } 32 | 33 | func testGetPost() { 34 | XCTAssertNotNil(postExample, "Example post should exist") 35 | } 36 | 37 | func testParseExample() { 38 | guard let post = postExample else { 39 | XCTFail("Example post should exist") 40 | return 41 | } 42 | 43 | let expectation = self.expectation(description: "Testing API for a valid XML...") 44 | expectation.expectedFulfillmentCount = 2 45 | 46 | let onCompletion = { (post: XMLPost?) in 47 | guard let post = post else { 48 | expectation.fulfill() 49 | return 50 | } 51 | 52 | XCTAssertEqual(post.title, self.examplePost.getValidTitle(), "API response title must match") 53 | XCTAssertEqual(post.link, self.examplePost.getValidLink(), "API response link must match") 54 | XCTAssertEqual(post.pubDate, self.examplePost.getValidPubdate(), "API response date should must match") 55 | XCTAssertEqual(post.artworkURL, self.examplePost.getValidArtworkURL(), "API response artworkURL must match") 56 | XCTAssertEqual(post.podcastURL, "", "API response podcastURL must match") 57 | XCTAssertEqual(post.podcast, "", "API response podcast must match") 58 | XCTAssertEqual(post.duration, "", "API response duration must match") 59 | XCTAssertEqual(post.podcastFrame, "", "API response podcastFrame must match") 60 | XCTAssertEqual(post.excerpt, self.examplePost.getValidExcerpt(), "API response excerpt must match") 61 | XCTAssertEqual(post.categories, self.examplePost.getValidCategories(), "API response categories must match") 62 | 63 | expectation.fulfill() 64 | } 65 | 66 | API().parse(post, onCompletion: onCompletion, numberOfPosts: 1) 67 | 68 | waitForExpectations(timeout: 30) { error in 69 | XCTAssertNil(error, "Error occurred: \(String(describing: error))") 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /MacMagazine/Network.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Network.swift 3 | // MacMagazine 4 | // 5 | // Created by Cassio Rossi on 28/11/16. 6 | // Copyright © 2016 Cassio Rossi. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum RestAPIError: Error { 12 | case network 13 | case decoding 14 | 15 | var reason: String { 16 | switch self { 17 | case .network: 18 | return "An error occurred while fetching data" 19 | case .decoding: 20 | return "An error occurred while decoding data" 21 | } 22 | } 23 | } 24 | 25 | class Network { 26 | 27 | class func get(url: URL, 28 | completion: @escaping (Result) -> Void) { 29 | 30 | let request = self.setHeaders(url: url, method: "GET") 31 | 32 | let configuration = URLSessionConfiguration.ephemeral 33 | configuration.requestCachePolicy = .reloadIgnoringLocalAndRemoteCacheData 34 | 35 | let defaultSession = URLSession(configuration: configuration) 36 | defaultSession.dataTask(with: request) { data, response, _ in 37 | 38 | guard let httpResponse = response as? HTTPURLResponse, 39 | httpResponse.hasSuccessStatusCode, 40 | let data = data as? T else { 41 | completion(Result.failure(RestAPIError.network)) 42 | return 43 | } 44 | completion(Result.success(data)) 45 | 46 | }.resume() 47 | } 48 | 49 | class func getVdeos(url: URL, completion: @escaping (Result) -> Void) { 50 | 51 | var request = URLRequest(url: url) 52 | request.httpMethod = "GET" 53 | 54 | let configuration = URLSessionConfiguration.ephemeral 55 | configuration.requestCachePolicy = .reloadIgnoringLocalAndRemoteCacheData 56 | 57 | let defaultSession = URLSession(configuration: configuration) 58 | defaultSession.dataTask(with: request) { data, response, _ in 59 | 60 | guard let httpResponse = response as? HTTPURLResponse, 61 | httpResponse.hasSuccessStatusCode, 62 | let data = data as? T else { 63 | completion(Result.failure(RestAPIError.network)) 64 | return 65 | } 66 | completion(Result.success(data)) 67 | 68 | }.resume() 69 | } 70 | } 71 | 72 | extension Network { 73 | static func setHeaders(url: URL, method: String) -> URLRequest { 74 | var request = URLRequest(url: url) 75 | request.httpMethod = method 76 | request.setValue("Feedburner", forHTTPHeaderField: "User-Agent") 77 | return request 78 | } 79 | } 80 | 81 | extension HTTPURLResponse { 82 | var hasSuccessStatusCode: Bool { 83 | return 200...299 ~= statusCode 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /MacMagazine/KeywordsTableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeywordsTableViewController.swift 3 | // MacMagazine 4 | // 5 | // Created by Cassio Rossi on 17/12/2021. 6 | // Copyright © 2021 MacMagazine. All rights reserved. 7 | // 8 | 9 | import Combine 10 | import UIKit 11 | 12 | class KeywordsTableViewController: UITableViewController, ObservableObject { 13 | 14 | // MARK: - Properties - 15 | 16 | var keywords = [String]() 17 | var filter = [String]() 18 | @Published var selection = [String]() 19 | 20 | // MARK: - View Lifecycle - 21 | 22 | override func viewDidLoad() { 23 | super.viewDidLoad() 24 | 25 | CoreDataStack.shared.getCategories { [weak self] categories in 26 | self?.keywords = categories 27 | self?.filter = categories 28 | self?.tableView.reloadData() 29 | } 30 | } 31 | } 32 | 33 | // MARK: - Table view data source - 34 | 35 | extension KeywordsTableViewController { 36 | override func numberOfSections(in tableView: UITableView) -> Int { 37 | return 1 38 | } 39 | 40 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 41 | return filter.count 42 | } 43 | 44 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 45 | let cell = tableView.dequeueReusableCell(withIdentifier: "keyword", for: indexPath) 46 | 47 | // Configure the cell... 48 | cell.textLabel?.text = filter[indexPath.row] 49 | 50 | return cell 51 | } 52 | } 53 | 54 | // MARK: - Table view delegate - 55 | 56 | extension KeywordsTableViewController { 57 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 58 | selection.append(filter[indexPath.row]) 59 | } 60 | 61 | override func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) { 62 | selection = selection.filter { $0 != filter[indexPath.row] } 63 | } 64 | } 65 | 66 | // MARK: - Actions - 67 | 68 | extension KeywordsTableViewController { 69 | @IBAction private func close(_ sender: Any) { 70 | self.dismiss(animated: true, completion: { self.selection = [] }) 71 | } 72 | 73 | @IBAction private func search(_ sender: Any) { 74 | self.dismiss(animated: true, completion: nil) 75 | } 76 | } 77 | 78 | // MARK: - UISearchBarDelegate - 79 | 80 | extension KeywordsTableViewController: UISearchBarDelegate { 81 | func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { 82 | filter = keywords.filter { $0.contains(searchText) } 83 | 84 | if searchText.isEmpty { 85 | filter = keywords 86 | } 87 | 88 | tableView.reloadData() 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /MacMagazineWidgetExtension/RecentPosts/RecentPostsWidget.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RecentPostsWidget.swift 3 | // MacMagazineWidgetExtensionExtension 4 | // 5 | // Created by Ailton Vieira Pinto Filho on 16/01/21. 6 | // Copyright © 2021 MacMagazine. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import WidgetKit 11 | 12 | struct RecentPostsWidget: View { 13 | @Environment(\.widgetFamily) var widgetFamily 14 | var entry: RecentPostsEntry 15 | var content: [PostData] { entry.posts } 16 | 17 | var body: some View { 18 | Group { 19 | if content.isEmpty { 20 | Text("Nenhum conteúdo disponível.") 21 | .font(.headline) 22 | } else { 23 | switch widgetFamily { 24 | case .systemSmall: 25 | PostCell(post: content[0]) 26 | 27 | case .systemMedium: 28 | VStack(spacing: 1) { 29 | ForEach(0 ..< min(2, content.count), 30 | id: \.self) { index in 31 | PostCell(post: content[index]) 32 | } 33 | } 34 | 35 | case .systemLarge: 36 | VStack(spacing: 1) { 37 | ForEach(0 ..< min(3, content.count), 38 | id: \.self) { index in 39 | PostCell(post: content[index]) 40 | } 41 | } 42 | 43 | case .accessoryRectangular, 44 | .accessoryInline, 45 | .accessoryCircular: 46 | PostCell(post: content[0]) 47 | 48 | case .systemExtraLarge: 49 | Text("Tamanho incompatível.") 50 | 51 | @unknown default: 52 | Text("Tamanho incompatível.") 53 | } 54 | } 55 | } 56 | } 57 | } 58 | 59 | struct RecentPostsWidget_Previews: PreviewProvider { 60 | static var previews: some View { 61 | Group { 62 | RecentPostsWidget(entry: RecentPostsEntry(date: Date(), posts: [.placeholder])) 63 | .previewContext(WidgetPreviewContext(family: .accessoryRectangular)) 64 | .previewDisplayName("Rectangular") 65 | RecentPostsWidget(entry: RecentPostsEntry(date: Date(), posts: [.placeholder])) 66 | .previewContext(WidgetPreviewContext(family: .accessoryInline)) 67 | .previewDisplayName("Inline") 68 | RecentPostsWidget(entry: RecentPostsEntry(date: Date(), posts: [.placeholder])) 69 | .previewContext(WidgetPreviewContext(family: .accessoryCircular)) 70 | .previewDisplayName("Circular") 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /MacMagazine/macmagazine.xcdatamodeld/macmagazine 8.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /MacMagazine/macmagazine.xcdatamodeld/macmagazine 5.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /MacMagazine/Icons/mm_icon_inverted.icon/icon.json: -------------------------------------------------------------------------------- 1 | { 2 | "fill" : "automatic", 3 | "groups" : [ 4 | { 5 | "blur-material" : null, 6 | "layers" : [ 7 | { 8 | "hidden" : false, 9 | "image-name" : "layer_3 2.png", 10 | "name" : "MM" 11 | } 12 | ], 13 | "lighting" : "individual", 14 | "name" : "MM", 15 | "shadow" : { 16 | "kind" : "none", 17 | "opacity" : 0.5 18 | }, 19 | "specular" : false, 20 | "translucency" : { 21 | "enabled" : false, 22 | "value" : 0.5 23 | } 24 | }, 25 | { 26 | "blur-material" : null, 27 | "layers" : [ 28 | { 29 | "glass" : false, 30 | "image-name" : "layer_2.svg", 31 | "name" : "pasta" 32 | } 33 | ], 34 | "name" : "pasta", 35 | "shadow" : { 36 | "kind" : "neutral", 37 | "opacity" : 0.5 38 | }, 39 | "specular" : true, 40 | "translucency" : { 41 | "enabled" : true, 42 | "value" : 0.5 43 | } 44 | }, 45 | { 46 | "blend-mode" : "darken", 47 | "blur-material" : null, 48 | "layers" : [ 49 | { 50 | "glass" : false, 51 | "image-name" : "layer_1.svg", 52 | "name" : "sombra" 53 | }, 54 | { 55 | "glass" : false, 56 | "hidden-specializations" : [ 57 | { 58 | "value" : false 59 | }, 60 | { 61 | "appearance" : "tinted", 62 | "value" : true 63 | } 64 | ], 65 | "image-name" : "layer_0.svg", 66 | "name" : "gradiente", 67 | "position-specializations" : [ 68 | { 69 | "value" : { 70 | "scale" : 1, 71 | "translation-in-points" : [ 72 | 0, 73 | 0 74 | ] 75 | } 76 | }, 77 | { 78 | "idiom" : "watchOS", 79 | "value" : { 80 | "scale" : 1.07, 81 | "translation-in-points" : [ 82 | 0, 83 | 0 84 | ] 85 | } 86 | } 87 | ] 88 | } 89 | ], 90 | "lighting" : "individual", 91 | "name" : "fundo", 92 | "shadow" : { 93 | "kind" : "neutral", 94 | "opacity" : 0.5 95 | }, 96 | "specular" : false, 97 | "translucency" : { 98 | "enabled" : false, 99 | "value" : 0.5 100 | } 101 | } 102 | ], 103 | "supported-platforms" : { 104 | "circles" : [ 105 | "watchOS" 106 | ], 107 | "squares" : "shared" 108 | } 109 | } -------------------------------------------------------------------------------- /MacMagazine/macmagazine.xcdatamodeld/macmagazine 11.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /MacMagazine/macmagazine.xcdatamodeld/macmagazine 9.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /MacMagazine/macmagazine.xcdatamodeld/macmagazine 10.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /MacMagazine/SettingsDisclaimerFooter.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /MacMagazine/Base.lproj/Splash.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /MacMagazine/Webview/YouTubePlayer.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import WebKit 3 | 4 | enum YouTubePlayerState: Int { 5 | case unstarted = -1 6 | case ended = 0 7 | case playing = 1 8 | case paused = 2 9 | case buffering = 3 10 | case videoCued = 5 11 | case unknown = 6 12 | 13 | var description: String { 14 | switch self { 15 | case .unstarted: return "unstarted" 16 | case .ended: return "ended" 17 | case .playing: return "playing..." 18 | case .paused: return "paused" 19 | case .buffering: return "buffering" 20 | case .videoCued: return "video_cued" 21 | case .unknown: return "unknown" 22 | } 23 | } 24 | } 25 | 26 | class YouTubePlayer: WKWebView { 27 | 28 | // MARK: - Properties - 29 | 30 | var embedVideoHtml: String { 31 | return """ 32 | 33 | 34 |
35 | 70 | 71 | """ 72 | } 73 | 74 | var autoPlay = true 75 | var time: Double = 0 76 | var videoId: String? 77 | 78 | func play() { 79 | self.evaluateJavaScript("player.playVideo()") 80 | } 81 | 82 | func play(time: Double) { 83 | if time > 0 { 84 | self.time = time 85 | self.evaluateJavaScript("player.seekTo(\(time))") 86 | } 87 | play() 88 | } 89 | 90 | func load(_ video: String) { 91 | videoId = video 92 | self.navigationDelegate = self 93 | self.loadHTMLString(self.embedVideoHtml, baseURL: URL(string: "https://com.brit.macmagazine")) 94 | } 95 | 96 | func cue(_ video: String, time: Double = 0) { 97 | self.evaluateJavaScript("player.cueVideoById('\(video)',\(time));") 98 | } 99 | 100 | func state(_ state: Int) -> YouTubePlayerState { 101 | YouTubePlayerState(rawValue: state) ?? .unknown 102 | } 103 | } 104 | 105 | extension YouTubePlayer: WKNavigationDelegate { 106 | func webView(_ webView: WKWebView, didFinish navigation: WKNavigation) { 107 | if autoPlay { 108 | delay(0.1) { 109 | self.play(time: self.time) 110 | } 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /MacMagazine/Icons/mm_icon.icon/Assets/layer_1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | AppIcon-1 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | -------------------------------------------------------------------------------- /MacMagazine/Icons/mm_icon_inverted.icon/Assets/layer_2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | AppIcon-2 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /MacMagazine/Icons/mm_icon_normal.icon/Assets/layer_1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | AppIcon-1 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | -------------------------------------------------------------------------------- /MacMagazine/Icons/mm_icon_inverted.icon/Assets/layer_1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | AppIcon-2 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /MacMagazine/Base.lproj/SettingsHeaderCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /MacMagazine/Icons/mm_icon.icon/Assets/layer_2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | AppIcon-1 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /MacMagazine/TabBarController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class TabBarController: NSObject, UITabBarControllerDelegate { 4 | 5 | static let shared = TabBarController() 6 | 7 | var previousController: UIViewController? 8 | var controllers: [UIViewController]? 9 | 10 | override init() { 11 | guard let tabController = Settings.rootViewController as? UITabBarController else { 12 | return 13 | } 14 | controllers = tabController.viewControllers 15 | } 16 | 17 | // MARK: - Indexes - 18 | 19 | func selectIndex(_ index: Int) { 20 | guard let tabController = Settings.rootViewController as? UITabBarController else { 21 | return 22 | } 23 | if index < tabController.viewControllers?.count ?? 0 { 24 | tabController.selectedIndex = index 25 | } 26 | } 27 | 28 | func removeIndexes(_ indexex: [Int]) { 29 | guard let tabController = Settings.rootViewController as? UITabBarController else { 30 | return 31 | } 32 | var controllers = self.controllers 33 | indexex.sorted(by: { $0 > $1 }).forEach { index in 34 | if index < controllers?.count ?? 0 { 35 | controllers?.remove(at: index) 36 | tabController.viewControllers = controllers 37 | } 38 | } 39 | } 40 | 41 | func resetTabs() { 42 | guard let tabController = Settings.rootViewController as? UITabBarController else { 43 | return 44 | } 45 | tabController.viewControllers = self.controllers 46 | } 47 | 48 | // MARK: - Delegate - 49 | 50 | // Tap 2x to Top 51 | func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) { 52 | if let navVC = viewController as? UINavigationController, 53 | let viewController = navVC.children[0] as? UITableViewController { 54 | if previousController == viewController { 55 | viewController.tableView.selectRow(at: IndexPath(row: 0, section: 0), animated: true, scrollPosition: .bottom) 56 | viewController.tableView.deselectRow(at: IndexPath(row: 0, section: 0), animated: false) 57 | } 58 | previousController = viewController 59 | } else if let navVC = viewController as? UINavigationController, 60 | let viewController = navVC.children[0] as? PodcastViewController { 61 | if previousController == viewController { 62 | NotificationCenter.default.post(name: .scrollToTop, object: nil) 63 | } 64 | previousController = viewController 65 | } else if let splitVC = viewController as? UISplitViewController, 66 | let navVC = splitVC.children[0] as? UINavigationController, 67 | let viewController = navVC.children[0] as? PostsMasterViewController { 68 | if previousController == viewController || previousController == nil { 69 | viewController.tableView.selectRow(at: IndexPath(row: 0, section: 0), animated: true, scrollPosition: .bottom) 70 | viewController.tableView.deselectRow(at: IndexPath(row: 0, section: 0), animated: false) 71 | navVC.popViewController(animated: true) 72 | } 73 | previousController = viewController 74 | } else if let navVC = viewController as? UINavigationController, 75 | let viewController = navVC.children[0] as? VideoCollectionViewController { 76 | if previousController == viewController { 77 | viewController.collectionView.scrollToItem(at: IndexPath(item: 0, section: 0), at: .bottom, animated: true) 78 | } 79 | previousController = viewController 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /MacMagazine/Icons/mm_icon_normal.icon/Assets/layer_2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | AppIcon-1 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /Support/Scripts/updateBuildVersion.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # exit if a command fails 4 | set -e 5 | 6 | # ---- READ PARAMS ---- 7 | while getopts ":p:i:b:v:" opt; do 8 | case $opt in 9 | p) project_name="$OPTARG";; 10 | i) info_plist_file="$OPTARG";; 11 | b) build_version="$OPTARG";; 12 | v) app_version="$OPTARG";; 13 | \?) echo "Invalid option -$OPTARG" >&2;; 14 | esac 15 | done 16 | 17 | echo "" 18 | 19 | # ---- PROJECT FILE ---- 20 | if [ -z "${project_name}" ] ; then 21 | for file in *.xcodeproj 22 | do 23 | project_name="${file%.*}" 24 | done 25 | fi 26 | if [ -z "${project_name}" ] ; then 27 | echo -e "\x1B[31m✗ PROJECT FILE (.xcodeproj) DOESN'T EXIST AT PATH: $PWD\x1B[0m" 28 | echo -e "\x1B[31m[✗] Exiting...\x1B[0m" 29 | echo "" 30 | exit 1 31 | fi 32 | 33 | # ---- PLIST FILE ---- 34 | if [ ! -f "${info_plist_file}" ] ; then 35 | echo -e "\x1B[31m[✗] File Info.plist doesn't exist at specified path: ${info_plist_file}\x1B[0m" 36 | echo -e "\x1B[31m[✗] Exiting...\x1B[0m" 37 | echo "" 38 | exit 1 39 | fi 40 | 41 | echo "" 42 | echo "(i) Replacing version for ${info_plist_file} ..." 43 | 44 | # ---- ORIGINAL APP VERSION - 1.0.0 ---- 45 | ORIGINAL_APP_VERSION=`sed -n '/MARKETING_VERSION/{s/MARKETING_VERSION = //;s/;//;s/^[[:space:]]*//;p;q;}' ./${project_name}.xcodeproj/project.pbxproj` 46 | if [ -z "${ORIGINAL_APP_VERSION}" ] ; then 47 | ORIGINAL_APP_VERSION=$(/usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" "${info_plist_file}") 48 | fi 49 | if [ -z "${ORIGINAL_APP_VERSION}" ] ; then 50 | echo -e "\x1B[31m[✗] No App Version String (app_version) specified!\x1B[0m" 51 | echo -e "\x1B[31m[✗] Exiting...\x1B[0m" 52 | exit 1 53 | fi 54 | echo " Original App Version String: $ORIGINAL_APP_VERSION" 55 | 56 | bundle_app_version=`echo $ORIGINAL_APP_VERSION | awk -F "." '{print "'$app_version'" }'` 57 | 58 | echo -e " (\x1B[32m✓\x1B[0m) Provided App Version: ${bundle_app_version}" 59 | 60 | # ---- UPDATE PLIST ---- 61 | 62 | echo "" 63 | /usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString ${bundle_app_version}" "${info_plist_file}" 64 | 65 | # ---- ORIGINAL BUILD VERSION - 1 ---- 66 | ORIGINAL_BUNDLE_VERSION=`sed -n '/CURRENT_PROJECT_VERSION/{s/CURRENT_PROJECT_VERSION = //;s/;//;s/^[[:space:]]*//;p;q;}' ./${project_name}.xcodeproj/project.pbxproj` 67 | if [ -z "${ORIGINAL_BUNDLE_VERSION}" ] ; then 68 | ORIGINAL_BUNDLE_VERSION=$(/usr/libexec/PlistBuddy -c "Print CFBundleVersion" "${info_plist_file}") 69 | fi 70 | if [ -z "${ORIGINAL_BUNDLE_VERSION}" ] ; then 71 | echo -e "\x1B[31m[✗] No Bundle Version String (bundle_version) specified!\x1B[0m" 72 | echo -e "\x1B[31m[✗] Exiting...\x1B[0m" 73 | exit 1 74 | fi 75 | echo " Original Bundle Version String: $ORIGINAL_BUNDLE_VERSION" 76 | 77 | bundle_version=`echo $ORIGINAL_BUNDLE_VERSION | awk -F "." '{print "'$app_version'.'$build_version'" }'` 78 | 79 | echo -e " (\x1B[32m✓\x1B[0m) Provided Bundle Version: ${bundle_version}" 80 | 81 | # ---- UPDATE PLIST ---- 82 | 83 | echo "" 84 | /usr/libexec/PlistBuddy -c "Set :CFBundleVersion ${bundle_version}" "${info_plist_file}" 85 | 86 | # ---- REPLACED BUILD VERSION - 1 ---- 87 | REPLACED_BUNDLE_VERSION=`sed -n '/CURRENT_PROJECT_VERSION/{s/CURRENT_PROJECT_VERSION = //;s/;//;s/^[[:space:]]*//;p;q;}' ./${project_name}.xcodeproj/project.pbxproj` 88 | if [ "${REPLACED_BUNDLE_VERSION}" != "${bundle_version}" ] ; then 89 | REPLACED_BUNDLE_VERSION=$(/usr/libexec/PlistBuddy -c "Print CFBundleVersion" "${info_plist_file}") 90 | fi 91 | if [ "${REPLACED_BUNDLE_VERSION}" != "${bundle_version}" ] ; then 92 | echo -e "\x1B[31m[✗] Error on update Bundle Version Short String: ${REPLACED_BUNDLE_VERSION} should be ${bundle_version}\x1B[0m" 93 | echo -e "\x1B[31m[✗] Exiting...\x1B[0m" 94 | exit 1 95 | fi 96 | echo -e "(\x1B[32m✓\x1B[0m) Replaced Bundle Short Version String: ${REPLACED_BUNDLE_VERSION}" 97 | -------------------------------------------------------------------------------- /MacMagazine/SettingsTermsFooter.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 27 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /MacMagazine/CoreDataStackExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CoreDataStack.swift 3 | // MacMagazine 4 | // 5 | // Created by Cassio Rossi on 23/03/2019. 6 | // Copyright © 2019 MacMagazine. All rights reserved. 7 | // 8 | 9 | import CoreData 10 | import Foundation 11 | 12 | // MARK: - Entity Video - 13 | 14 | extension CoreDataStack { 15 | 16 | func get(video videoId: String, completion: @escaping ([Video]) -> Void) { 17 | let request = NSFetchRequest(entityName: videoEntityName) 18 | request.predicate = NSPredicate(format: "videoId == %@", videoId) 19 | 20 | // Creates `asynchronousFetchRequest` with the fetch request and the completion closure 21 | let asynchronousFetchRequest = NSAsynchronousFetchRequest(fetchRequest: request) { asynchronousFetchResult in 22 | guard let result = asynchronousFetchResult.finalResult as? [Video] else { 23 | completion([]) 24 | return 25 | } 26 | completion(result) 27 | } 28 | 29 | do { 30 | try viewContext.execute(asynchronousFetchRequest) 31 | } catch let error { 32 | logE(error.localizedDescription) 33 | } 34 | } 35 | 36 | func save(playlist: YouTube, statistics: [Item]) { 37 | // Cannot duplicate videos 38 | guard let videos = playlist.items else { 39 | return 40 | } 41 | 42 | let mappedVideos: [JSONVideo] = videos.compactMap { 43 | guard let title = $0.snippet?.title, 44 | let videoId = $0.snippet?.resourceId?.videoId 45 | else { 46 | return nil 47 | } 48 | 49 | var likes = "" 50 | var views = "" 51 | var duration = "" 52 | let stat = statistics.filter { 53 | $0.id == videoId 54 | } 55 | if !stat.isEmpty { 56 | views = stat[0].statistics?.viewCount ?? "" 57 | likes = stat[0].statistics?.likeCount ?? "" 58 | duration = stat[0].contentDetails?.duration ?? "" 59 | } 60 | 61 | let artworkURL = $0.snippet?.thumbnails?.maxres?.url ?? $0.snippet?.thumbnails?.high?.url ?? "" 62 | 63 | // swiftlint:disable vertical_parameter_alignment_on_call 64 | return JSONVideo(title: title, 65 | videoId: videoId, 66 | pubDate: $0.contentDetails?.videoPublishedAt ?? $0.snippet?.publishedAt ?? "", 67 | artworkURL: artworkURL, 68 | views: views, 69 | likes: likes, 70 | duration: duration) 71 | // swiftlint:enable vertical_parameter_alignment_on_call 72 | } 73 | 74 | mappedVideos.forEach { video in 75 | get(video: video.videoId) { items in 76 | if items.isEmpty { 77 | self.insert(video: video) 78 | } else { 79 | self.update(video: items[0], with: video) 80 | } 81 | self.save() 82 | } 83 | } 84 | } 85 | 86 | func insert(video: JSONVideo) { 87 | let newItem = Video(context: viewContext) 88 | newItem.favorite = false 89 | newItem.title = video.title 90 | newItem.artworkURL = video.artworkURL.escape() 91 | newItem.pubDate = video.pubDate.toDate(Format.youtube) 92 | newItem.videoId = video.videoId 93 | newItem.likes = video.likes 94 | newItem.views = video.views 95 | newItem.duration = video.duration 96 | } 97 | 98 | func update(video: Video, with item: JSONVideo) { 99 | video.title = item.title 100 | video.artworkURL = item.artworkURL.escape() 101 | video.pubDate = item.pubDate.toDate(Format.youtube) 102 | video.videoId = item.videoId 103 | video.likes = item.likes 104 | video.views = item.views 105 | video.duration = item.duration 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /MacMagazine/Icons/mm_icon.icon/Assets/layer_3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | AppIcon-1 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /MacMagazine/Icons/mm_icon_normal.icon/Assets/layer_3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | AppIcon-1 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | --------------------------------------------------------------------------------