├── 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 |
--------------------------------------------------------------------------------
/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 | 
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 |
--------------------------------------------------------------------------------
/MacMagazine/Icons/mm_icon_inverted.icon/Assets/layer_2.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/MacMagazine/Icons/mm_icon_normal.icon/Assets/layer_1.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/MacMagazine/Icons/mm_icon_inverted.icon/Assets/layer_1.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/MacMagazine/Icons/mm_icon_normal.icon/Assets/layer_3.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------