├── .gitignore ├── Design Assets ├── FrontRowTrailersIcon.png ├── FrontRowTrailersIcon.pxd │ ├── QuickLook │ │ ├── Icon.tiff │ │ └── Thumbnail.tiff │ ├── data │ │ ├── 078A6F16-62A5-49F3-8504-AF7C459F47B4 │ │ ├── 181C90B3-42F6-497F-A305-F5EF9243DF99 │ │ ├── 3B49F82D-F560-4DFD-88A3-E14073B6CAA3 │ │ ├── 57792EE9-F72F-4DB5-9B51-D94D6F91D96D │ │ ├── 7125E196-D67B-416F-906F-3385B0191E5E │ │ ├── 7BC9B605-12F7-4B11-B220-72C306879F42 │ │ ├── 95E072A3-7433-4426-988A-0D1AD6ACE0E7-PTEffectsImagesKeyPrefix.4D09E887-230F-4550-9FB7-94E65B3C5D63.inputFillImage │ │ ├── 9F658B48-4405-4935-862A-714E053EAF73 │ │ ├── 9FCDD43F-D492-413D-96B4-C0C3B5AFE5A1 │ │ ├── AA93C3D2-27B6-4DEE-A85A-631D701814D7 │ │ ├── C9B8484E-9C56-4016-A419-82D6E1EA21D9 │ │ ├── CF2AE119-0F46-408F-9ADB-4BB083E4F75F │ │ ├── D193F921-674C-4079-8726-47CA702C0E5D │ │ ├── D8AB72EE-7618-4B64-A02B-D0F9F15EF623 │ │ ├── EB32C11D-121F-42E4-8348-4FD4AE5FF7F1 │ │ ├── EEAE44AA-8F71-42A1-B647-6D158BF24E6E │ │ └── F2F03FA8-C11B-4D6F-9EC5-DE0E8ADC54B7 │ └── metadata.info ├── FrontRowTrailersMac.png ├── FrontRowTrailersMac.pxd │ ├── QuickLook │ │ ├── Icon.tiff │ │ └── Thumbnail.tiff │ ├── data │ │ ├── 0C16052B-3605-48EC-98E9-E99C3AAC9412 │ │ ├── 10412498-BFE8-4BAD-83E1-60A57677C890 │ │ ├── 155F743C-190B-4C54-ADDE-D53C443AD367 │ │ ├── 1BCB652B-1ACC-4F27-9451-93535228CC6F │ │ ├── 1C63280D-A29B-41C6-85CA-0B90517484B0 │ │ ├── 278D7451-4F5E-473C-8CC0-26A838C94714-PTEffectsImagesKeyPrefix.2371CCA2-6347-42B5-A9C6-16266B5C1619.inputFillImage │ │ ├── 380F561E-4579-47B8-B147-5261612F4096 │ │ ├── 40558170-7FD2-42A1-9887-D0F63942608D │ │ ├── 41656D3D-ACF1-4196-B277-48B6F6319274 │ │ ├── 46B3811D-56BC-4DE8-8CD1-BF89C544C1AF │ │ ├── 593580E4-05A3-46AB-9999-4F4513A2F538 │ │ ├── 78F3DBFB-D035-4225-ABF0-56AFD35DCBD2 │ │ ├── 83B89697-0CFD-4364-94FB-8F4625DF68C6-PTEffectsImagesKeyPrefix.580D18EB-4EA7-428D-AABF-117402A8BF78.inputFillImage │ │ ├── 84F7DF05-51D2-41F6-97B8-C5DA53871195 │ │ ├── 9824B0A3-9027-4268-80D7-B71C7173653A-PTEffectsImagesKeyPrefix.4D09E887-230F-4550-9FB7-94E65B3C5D63.inputFillImage │ │ ├── 98C9A0DB-D9A8-4017-AD2D-20998B59CCD6 │ │ ├── B0C3816B-74DA-4F0C-923D-F1B8A8305997 │ │ ├── BF712DE6-868A-4800-9D03-E248A2BEB282 │ │ ├── C309F652-917D-47B8-9788-31C7EC95A164 │ │ ├── C66F9A85-E0D4-4A5E-8E8A-02F4A958AF94 │ │ ├── CE9582FB-6813-45C1-A6B8-C497096B9553 │ │ ├── D339FFF1-B47A-4066-93D9-08D9D75A09BB │ │ └── DFCC2063-2BAF-41F5-BF19-22FE34774BA9 │ └── metadata.info ├── moviePosterPlaceholder.pxd │ ├── QuickLook │ │ ├── Icon.tiff │ │ └── Thumbnail.tiff │ ├── data │ │ ├── A646682E-4254-484F-BC4C-551943919ACF │ │ ├── ADC5E32F-B0F4-4AAB-959F-B1EAE7A312AD │ │ ├── D544136C-18F1-4CB4-A60C-B36E90FA3784 │ │ └── originalImportedContentDocumentInfo │ └── metadata.info └── posterMirrorImageGradientMask.pxd │ ├── QuickLook │ ├── Icon.tiff │ └── Thumbnail.tiff │ ├── data │ └── originalImportedContentDocumentInfo │ └── metadata.info ├── LICENSE ├── README.md ├── Shared ├── API Extensions │ ├── CustomDarkAppearance.swift │ ├── DispatchQueue+async.swift │ └── Image+MoviePosterPlaceholder.swift ├── AppError.swift ├── Audio Files │ ├── Exit.aif │ ├── Limit.aif │ ├── Selection.aif │ └── SelectionChange.aif ├── AudioFeedback.swift ├── MovieInfo.swift ├── MovieInfoDataStore.swift ├── MoviePosterView.swift ├── SortingMode.swift └── currentTrailers.bundle │ ├── current.xml │ └── current_720p.xml ├── TMT Mac ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon-Mac.appiconset │ │ ├── Contents.json │ │ ├── icon_128x128.png │ │ ├── icon_128x128@2x.png │ │ ├── icon_16x16.png │ │ ├── icon_16x16@2x.png │ │ ├── icon_256x256.png │ │ ├── icon_256x256@2x.png │ │ ├── icon_32x32.png │ │ ├── icon_32x32@2x.png │ │ ├── icon_512x512.png │ │ └── icon_512x512@2x.png │ ├── Contents.json │ ├── MoviePosterPlaceholder.imageset │ │ ├── Contents.json │ │ └── moviePosterPlaceholder@2x.png │ └── posterMirrorImageGradientMask.imageset │ │ ├── Contents.json │ │ └── posterMirrorImageGradientMask.png ├── ContentView.swift ├── MacFont.swift ├── NSApplication+version+build.swift ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json ├── Settings.swift ├── TMT_Mac.entitlements ├── TMT_MacApp.swift ├── Theatrical-Trailers-Info.plist ├── Utilities │ ├── AppStoreReviewsManager.swift │ └── KeyEventHandling.swift └── Views │ ├── FadeInOutPosterView.swift │ ├── MovieInfoContainerView.swift │ ├── MovieTrailerListView.swift │ ├── SelectionIndicator.swift │ ├── TrailerPlayerView.swift │ └── XButton.swift ├── TheatricalMovieTrailers.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ └── Package.resolved └── xcshareddata │ └── xcschemes │ ├── TMT Mac.xcscheme │ ├── TheatricalMovieTrailers.xcscheme │ └── TrailersWidgetExtension.xcscheme ├── TheatricalMovieTrailers ├── AppDelegate.swift ├── ContentView.swift ├── Info.plist ├── Model │ ├── Settings.swift │ ├── ViewParameters.swift │ └── WindowSceneObject.swift ├── MovieDetailsView.swift ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json ├── SceneDelegate.swift ├── Supporting Files │ ├── API Extensions │ │ ├── AVPlayerViewController+enterFullScreen.swift │ │ ├── UIApplication+version+build.swift │ │ └── UIResponder+currentResponder.swift │ ├── AppStoreReviewsManager.swift │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ ├── 100.png │ │ │ ├── 1024.png │ │ │ ├── 114.png │ │ │ ├── 120.png │ │ │ ├── 144.png │ │ │ ├── 152.png │ │ │ ├── 167.png │ │ │ ├── 180.png │ │ │ ├── 20.png │ │ │ ├── 29.png │ │ │ ├── 40.png │ │ │ ├── 50.png │ │ │ ├── 57.png │ │ │ ├── 58.png │ │ │ ├── 60.png │ │ │ ├── 72.png │ │ │ ├── 76.png │ │ │ ├── 80.png │ │ │ ├── 87.png │ │ │ └── Contents.json │ │ ├── Contents.json │ │ ├── TheatreLogo.imageset │ │ │ ├── 512x512.png │ │ │ └── Contents.json │ │ ├── blackFrame.imageset │ │ │ ├── Contents.json │ │ │ ├── blackFrame@2x.png │ │ │ └── blackFrame@3x.png │ │ ├── frontRowTrailersBackground.colorset │ │ │ └── Contents.json │ │ ├── frontRowTrailersRed.colorset │ │ │ └── Contents.json │ │ ├── moviePosterPlaceholder.imageset │ │ │ ├── Contents.json │ │ │ └── moviePosterPlaceholder@2x.png │ │ ├── posterMirrorImageGradientMask.imageset │ │ │ ├── Contents.json │ │ │ └── posterMirrorImageGradientMask.png │ │ └── watchedCheck.imageset │ │ │ ├── Contents.json │ │ │ ├── watchedCheck@2x.png │ │ │ └── watchedCheck@3x.png │ ├── Base.lproj │ │ └── LaunchScreen.storyboard │ └── ShareSheet.swift ├── TheatricalMovieTrailers.entitlements └── Views │ ├── CoverFlowItemView.swift │ ├── CoverFlowListView.swift │ ├── CoverFlowMovieMetaView.swift │ ├── CoverFlowRotatingView.swift │ ├── CoverFlowScrollView.swift │ ├── ExternalMovieMetaView.swift │ ├── ExternalTrailerView.swift │ ├── ExternalView.swift │ ├── FramedImage.swift │ ├── InlineTrailerPlayerView.swift │ ├── MovieMetaView.swift │ ├── MovieSearchView.swift │ ├── SettingsView.swift │ └── TrailerMetaView.swift ├── TrailersWidget ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ ├── 100.png │ │ ├── 1024.png │ │ ├── 114.png │ │ ├── 120-1.png │ │ ├── 120.png │ │ ├── 144.png │ │ ├── 152.png │ │ ├── 167.png │ │ ├── 180.png │ │ ├── 20.png │ │ ├── 29-1.png │ │ ├── 29.png │ │ ├── 40-1.png │ │ ├── 40-2.png │ │ ├── 40.png │ │ ├── 50.png │ │ ├── 57.png │ │ ├── 58-1.png │ │ ├── 58.png │ │ ├── 60.png │ │ ├── 72.png │ │ ├── 76.png │ │ ├── 80-1.png │ │ ├── 80.png │ │ ├── 87.png │ │ └── Contents.json │ ├── Contents.json │ ├── WidgetBackground.colorset │ │ └── Contents.json │ ├── blackFrame.imageset │ │ ├── Contents.json │ │ ├── blackFrame@2x.png │ │ └── blackFrame@3x.png │ ├── moviePosterPlaceholder.imageset │ │ ├── Contents.json │ │ └── moviePosterPlaceholder@2x.png │ └── posterMirrorImageGradientMask.imageset │ │ ├── Contents.json │ │ └── posterMirrorImageGradientMask.png ├── Info.plist ├── LargeMoviePosterWithTitle.swift ├── LargePosterReflectionWithTitle.swift ├── MoviePosterWithMetadata.swift ├── MoviePosterWithTitle.swift ├── TrailersWidget.intentdefinition ├── TrailersWidget.swift └── TrailersWidgetExtension.entitlements └── docs ├── .gitignore ├── CNAME ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── _config.yml ├── _includes ├── appstoreimages.html ├── features.html ├── footer.html ├── head.html ├── header.html └── screencontent.html ├── _layouts ├── index.html └── page.html ├── _pages └── privacypolicy.md ├── _sass ├── base.scss ├── github-markdown.scss └── layout.scss ├── assets ├── FRT-AirPlay.jpg ├── FRT-CoverFlow.jpg ├── FRT-Mac.jpg ├── FRT-Search.jpg ├── FRT-Widgets.jpg ├── FrontRowTrailers-Mac-Framed.png ├── Theatre.gif ├── appicon.png ├── badge_app_store.svg ├── badge_mac_app_store.svg ├── black.png ├── blue.png ├── coral.png ├── flare2.gif ├── headerimage.jpg ├── screenshot │ └── Theatricals-CoverFlow.jpg ├── squircle.svg ├── squircle120.svg ├── videos │ └── Place-video-files-here.txt ├── white.png └── yellow.png ├── index.html └── main.scss /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | .DS_Store 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | # Package.pins 42 | # Package.resolved 43 | # *.xcodeproj 44 | # 45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 46 | # hence it is not needed unless you have added a package configuration file to your project 47 | # .swiftpm 48 | 49 | .build/ 50 | 51 | # CocoaPods 52 | # 53 | # We recommend against adding the Pods directory to your .gitignore. However 54 | # you should judge for yourself, the pros and cons are mentioned at: 55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 56 | # 57 | # Pods/ 58 | # 59 | # Add this line if you want to avoid checking in source code from the Xcode workspace 60 | # *.xcworkspace 61 | 62 | # Carthage 63 | # 64 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 65 | # Carthage/Checkouts 66 | 67 | Carthage/Build/ 68 | 69 | # Accio dependency management 70 | Dependencies/ 71 | .accio/ 72 | 73 | # fastlane 74 | # 75 | # It is recommended to not store the screenshots in the git repo. 76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 77 | # For more information about the recommended setup visit: 78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 79 | 80 | fastlane/report.xml 81 | fastlane/Preview.html 82 | fastlane/screenshots/**/*.png 83 | fastlane/test_output 84 | 85 | # Code Injection 86 | # 87 | # After new code Injection tools there's a generated folder /iOSInjectionProject 88 | # https://github.com/johnno1962/injectionforxcode 89 | 90 | iOSInjectionProject/ 91 | 92 | Secrets.swift 93 | -------------------------------------------------------------------------------- /Design Assets/FrontRowTrailersIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Design Assets/FrontRowTrailersIcon.png -------------------------------------------------------------------------------- /Design Assets/FrontRowTrailersIcon.pxd/QuickLook/Icon.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Design Assets/FrontRowTrailersIcon.pxd/QuickLook/Icon.tiff -------------------------------------------------------------------------------- /Design Assets/FrontRowTrailersIcon.pxd/QuickLook/Thumbnail.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Design Assets/FrontRowTrailersIcon.pxd/QuickLook/Thumbnail.tiff -------------------------------------------------------------------------------- /Design Assets/FrontRowTrailersIcon.pxd/data/078A6F16-62A5-49F3-8504-AF7C459F47B4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Design Assets/FrontRowTrailersIcon.pxd/data/078A6F16-62A5-49F3-8504-AF7C459F47B4 -------------------------------------------------------------------------------- /Design Assets/FrontRowTrailersIcon.pxd/data/181C90B3-42F6-497F-A305-F5EF9243DF99: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Design Assets/FrontRowTrailersIcon.pxd/data/181C90B3-42F6-497F-A305-F5EF9243DF99 -------------------------------------------------------------------------------- /Design Assets/FrontRowTrailersIcon.pxd/data/3B49F82D-F560-4DFD-88A3-E14073B6CAA3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Design Assets/FrontRowTrailersIcon.pxd/data/3B49F82D-F560-4DFD-88A3-E14073B6CAA3 -------------------------------------------------------------------------------- /Design Assets/FrontRowTrailersIcon.pxd/data/57792EE9-F72F-4DB5-9B51-D94D6F91D96D: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Design Assets/FrontRowTrailersIcon.pxd/data/57792EE9-F72F-4DB5-9B51-D94D6F91D96D -------------------------------------------------------------------------------- /Design Assets/FrontRowTrailersIcon.pxd/data/7125E196-D67B-416F-906F-3385B0191E5E: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Design Assets/FrontRowTrailersIcon.pxd/data/7125E196-D67B-416F-906F-3385B0191E5E -------------------------------------------------------------------------------- /Design Assets/FrontRowTrailersIcon.pxd/data/7BC9B605-12F7-4B11-B220-72C306879F42: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Design Assets/FrontRowTrailersIcon.pxd/data/7BC9B605-12F7-4B11-B220-72C306879F42 -------------------------------------------------------------------------------- /Design Assets/FrontRowTrailersIcon.pxd/data/95E072A3-7433-4426-988A-0D1AD6ACE0E7-PTEffectsImagesKeyPrefix.4D09E887-230F-4550-9FB7-94E65B3C5D63.inputFillImage: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Design Assets/FrontRowTrailersIcon.pxd/data/95E072A3-7433-4426-988A-0D1AD6ACE0E7-PTEffectsImagesKeyPrefix.4D09E887-230F-4550-9FB7-94E65B3C5D63.inputFillImage -------------------------------------------------------------------------------- /Design Assets/FrontRowTrailersIcon.pxd/data/9F658B48-4405-4935-862A-714E053EAF73: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Design Assets/FrontRowTrailersIcon.pxd/data/9F658B48-4405-4935-862A-714E053EAF73 -------------------------------------------------------------------------------- /Design Assets/FrontRowTrailersIcon.pxd/data/9FCDD43F-D492-413D-96B4-C0C3B5AFE5A1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Design Assets/FrontRowTrailersIcon.pxd/data/9FCDD43F-D492-413D-96B4-C0C3B5AFE5A1 -------------------------------------------------------------------------------- /Design Assets/FrontRowTrailersIcon.pxd/data/AA93C3D2-27B6-4DEE-A85A-631D701814D7: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Design Assets/FrontRowTrailersIcon.pxd/data/AA93C3D2-27B6-4DEE-A85A-631D701814D7 -------------------------------------------------------------------------------- /Design Assets/FrontRowTrailersIcon.pxd/data/C9B8484E-9C56-4016-A419-82D6E1EA21D9: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Design Assets/FrontRowTrailersIcon.pxd/data/C9B8484E-9C56-4016-A419-82D6E1EA21D9 -------------------------------------------------------------------------------- /Design Assets/FrontRowTrailersIcon.pxd/data/CF2AE119-0F46-408F-9ADB-4BB083E4F75F: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Design Assets/FrontRowTrailersIcon.pxd/data/CF2AE119-0F46-408F-9ADB-4BB083E4F75F -------------------------------------------------------------------------------- /Design Assets/FrontRowTrailersIcon.pxd/data/D193F921-674C-4079-8726-47CA702C0E5D: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Design Assets/FrontRowTrailersIcon.pxd/data/D193F921-674C-4079-8726-47CA702C0E5D -------------------------------------------------------------------------------- /Design Assets/FrontRowTrailersIcon.pxd/data/D8AB72EE-7618-4B64-A02B-D0F9F15EF623: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Design Assets/FrontRowTrailersIcon.pxd/data/D8AB72EE-7618-4B64-A02B-D0F9F15EF623 -------------------------------------------------------------------------------- /Design Assets/FrontRowTrailersIcon.pxd/data/EB32C11D-121F-42E4-8348-4FD4AE5FF7F1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Design Assets/FrontRowTrailersIcon.pxd/data/EB32C11D-121F-42E4-8348-4FD4AE5FF7F1 -------------------------------------------------------------------------------- /Design Assets/FrontRowTrailersIcon.pxd/data/EEAE44AA-8F71-42A1-B647-6D158BF24E6E: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Design Assets/FrontRowTrailersIcon.pxd/data/EEAE44AA-8F71-42A1-B647-6D158BF24E6E -------------------------------------------------------------------------------- /Design Assets/FrontRowTrailersIcon.pxd/data/F2F03FA8-C11B-4D6F-9EC5-DE0E8ADC54B7: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Design Assets/FrontRowTrailersIcon.pxd/data/F2F03FA8-C11B-4D6F-9EC5-DE0E8ADC54B7 -------------------------------------------------------------------------------- /Design Assets/FrontRowTrailersIcon.pxd/metadata.info: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Design Assets/FrontRowTrailersIcon.pxd/metadata.info -------------------------------------------------------------------------------- /Design Assets/FrontRowTrailersMac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Design Assets/FrontRowTrailersMac.png -------------------------------------------------------------------------------- /Design Assets/FrontRowTrailersMac.pxd/QuickLook/Icon.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Design Assets/FrontRowTrailersMac.pxd/QuickLook/Icon.tiff -------------------------------------------------------------------------------- /Design Assets/FrontRowTrailersMac.pxd/QuickLook/Thumbnail.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Design Assets/FrontRowTrailersMac.pxd/QuickLook/Thumbnail.tiff -------------------------------------------------------------------------------- /Design Assets/FrontRowTrailersMac.pxd/data/0C16052B-3605-48EC-98E9-E99C3AAC9412: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Design Assets/FrontRowTrailersMac.pxd/data/0C16052B-3605-48EC-98E9-E99C3AAC9412 -------------------------------------------------------------------------------- /Design Assets/FrontRowTrailersMac.pxd/data/10412498-BFE8-4BAD-83E1-60A57677C890: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Design Assets/FrontRowTrailersMac.pxd/data/10412498-BFE8-4BAD-83E1-60A57677C890 -------------------------------------------------------------------------------- /Design Assets/FrontRowTrailersMac.pxd/data/155F743C-190B-4C54-ADDE-D53C443AD367: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Design Assets/FrontRowTrailersMac.pxd/data/155F743C-190B-4C54-ADDE-D53C443AD367 -------------------------------------------------------------------------------- /Design Assets/FrontRowTrailersMac.pxd/data/1BCB652B-1ACC-4F27-9451-93535228CC6F: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Design Assets/FrontRowTrailersMac.pxd/data/1BCB652B-1ACC-4F27-9451-93535228CC6F -------------------------------------------------------------------------------- /Design Assets/FrontRowTrailersMac.pxd/data/1C63280D-A29B-41C6-85CA-0B90517484B0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Design Assets/FrontRowTrailersMac.pxd/data/1C63280D-A29B-41C6-85CA-0B90517484B0 -------------------------------------------------------------------------------- /Design Assets/FrontRowTrailersMac.pxd/data/278D7451-4F5E-473C-8CC0-26A838C94714-PTEffectsImagesKeyPrefix.2371CCA2-6347-42B5-A9C6-16266B5C1619.inputFillImage: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Design Assets/FrontRowTrailersMac.pxd/data/278D7451-4F5E-473C-8CC0-26A838C94714-PTEffectsImagesKeyPrefix.2371CCA2-6347-42B5-A9C6-16266B5C1619.inputFillImage -------------------------------------------------------------------------------- /Design Assets/FrontRowTrailersMac.pxd/data/380F561E-4579-47B8-B147-5261612F4096: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Design Assets/FrontRowTrailersMac.pxd/data/380F561E-4579-47B8-B147-5261612F4096 -------------------------------------------------------------------------------- /Design Assets/FrontRowTrailersMac.pxd/data/40558170-7FD2-42A1-9887-D0F63942608D: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Design Assets/FrontRowTrailersMac.pxd/data/40558170-7FD2-42A1-9887-D0F63942608D -------------------------------------------------------------------------------- /Design Assets/FrontRowTrailersMac.pxd/data/41656D3D-ACF1-4196-B277-48B6F6319274: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Design Assets/FrontRowTrailersMac.pxd/data/41656D3D-ACF1-4196-B277-48B6F6319274 -------------------------------------------------------------------------------- /Design Assets/FrontRowTrailersMac.pxd/data/46B3811D-56BC-4DE8-8CD1-BF89C544C1AF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Design Assets/FrontRowTrailersMac.pxd/data/46B3811D-56BC-4DE8-8CD1-BF89C544C1AF -------------------------------------------------------------------------------- /Design Assets/FrontRowTrailersMac.pxd/data/593580E4-05A3-46AB-9999-4F4513A2F538: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Design Assets/FrontRowTrailersMac.pxd/data/593580E4-05A3-46AB-9999-4F4513A2F538 -------------------------------------------------------------------------------- /Design Assets/FrontRowTrailersMac.pxd/data/78F3DBFB-D035-4225-ABF0-56AFD35DCBD2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Design Assets/FrontRowTrailersMac.pxd/data/78F3DBFB-D035-4225-ABF0-56AFD35DCBD2 -------------------------------------------------------------------------------- /Design Assets/FrontRowTrailersMac.pxd/data/83B89697-0CFD-4364-94FB-8F4625DF68C6-PTEffectsImagesKeyPrefix.580D18EB-4EA7-428D-AABF-117402A8BF78.inputFillImage: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Design Assets/FrontRowTrailersMac.pxd/data/83B89697-0CFD-4364-94FB-8F4625DF68C6-PTEffectsImagesKeyPrefix.580D18EB-4EA7-428D-AABF-117402A8BF78.inputFillImage -------------------------------------------------------------------------------- /Design Assets/FrontRowTrailersMac.pxd/data/84F7DF05-51D2-41F6-97B8-C5DA53871195: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Design Assets/FrontRowTrailersMac.pxd/data/84F7DF05-51D2-41F6-97B8-C5DA53871195 -------------------------------------------------------------------------------- /Design Assets/FrontRowTrailersMac.pxd/data/9824B0A3-9027-4268-80D7-B71C7173653A-PTEffectsImagesKeyPrefix.4D09E887-230F-4550-9FB7-94E65B3C5D63.inputFillImage: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Design Assets/FrontRowTrailersMac.pxd/data/9824B0A3-9027-4268-80D7-B71C7173653A-PTEffectsImagesKeyPrefix.4D09E887-230F-4550-9FB7-94E65B3C5D63.inputFillImage -------------------------------------------------------------------------------- /Design Assets/FrontRowTrailersMac.pxd/data/98C9A0DB-D9A8-4017-AD2D-20998B59CCD6: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Design Assets/FrontRowTrailersMac.pxd/data/98C9A0DB-D9A8-4017-AD2D-20998B59CCD6 -------------------------------------------------------------------------------- /Design Assets/FrontRowTrailersMac.pxd/data/B0C3816B-74DA-4F0C-923D-F1B8A8305997: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Design Assets/FrontRowTrailersMac.pxd/data/B0C3816B-74DA-4F0C-923D-F1B8A8305997 -------------------------------------------------------------------------------- /Design Assets/FrontRowTrailersMac.pxd/data/BF712DE6-868A-4800-9D03-E248A2BEB282: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Design Assets/FrontRowTrailersMac.pxd/data/BF712DE6-868A-4800-9D03-E248A2BEB282 -------------------------------------------------------------------------------- /Design Assets/FrontRowTrailersMac.pxd/data/C309F652-917D-47B8-9788-31C7EC95A164: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Design Assets/FrontRowTrailersMac.pxd/data/C309F652-917D-47B8-9788-31C7EC95A164 -------------------------------------------------------------------------------- /Design Assets/FrontRowTrailersMac.pxd/data/C66F9A85-E0D4-4A5E-8E8A-02F4A958AF94: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Design Assets/FrontRowTrailersMac.pxd/data/C66F9A85-E0D4-4A5E-8E8A-02F4A958AF94 -------------------------------------------------------------------------------- /Design Assets/FrontRowTrailersMac.pxd/data/CE9582FB-6813-45C1-A6B8-C497096B9553: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Design Assets/FrontRowTrailersMac.pxd/data/CE9582FB-6813-45C1-A6B8-C497096B9553 -------------------------------------------------------------------------------- /Design Assets/FrontRowTrailersMac.pxd/data/D339FFF1-B47A-4066-93D9-08D9D75A09BB: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Design Assets/FrontRowTrailersMac.pxd/data/D339FFF1-B47A-4066-93D9-08D9D75A09BB -------------------------------------------------------------------------------- /Design Assets/FrontRowTrailersMac.pxd/data/DFCC2063-2BAF-41F5-BF19-22FE34774BA9: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Design Assets/FrontRowTrailersMac.pxd/data/DFCC2063-2BAF-41F5-BF19-22FE34774BA9 -------------------------------------------------------------------------------- /Design Assets/FrontRowTrailersMac.pxd/metadata.info: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Design Assets/FrontRowTrailersMac.pxd/metadata.info -------------------------------------------------------------------------------- /Design Assets/moviePosterPlaceholder.pxd/QuickLook/Icon.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Design Assets/moviePosterPlaceholder.pxd/QuickLook/Icon.tiff -------------------------------------------------------------------------------- /Design Assets/moviePosterPlaceholder.pxd/QuickLook/Thumbnail.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Design Assets/moviePosterPlaceholder.pxd/QuickLook/Thumbnail.tiff -------------------------------------------------------------------------------- /Design Assets/moviePosterPlaceholder.pxd/data/A646682E-4254-484F-BC4C-551943919ACF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Design Assets/moviePosterPlaceholder.pxd/data/A646682E-4254-484F-BC4C-551943919ACF -------------------------------------------------------------------------------- /Design Assets/moviePosterPlaceholder.pxd/data/ADC5E32F-B0F4-4AAB-959F-B1EAE7A312AD: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Design Assets/moviePosterPlaceholder.pxd/data/ADC5E32F-B0F4-4AAB-959F-B1EAE7A312AD -------------------------------------------------------------------------------- /Design Assets/moviePosterPlaceholder.pxd/data/D544136C-18F1-4CB4-A60C-B36E90FA3784: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Design Assets/moviePosterPlaceholder.pxd/data/D544136C-18F1-4CB4-A60C-B36E90FA3784 -------------------------------------------------------------------------------- /Design Assets/moviePosterPlaceholder.pxd/data/originalImportedContentDocumentInfo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Design Assets/moviePosterPlaceholder.pxd/data/originalImportedContentDocumentInfo -------------------------------------------------------------------------------- /Design Assets/moviePosterPlaceholder.pxd/metadata.info: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Design Assets/moviePosterPlaceholder.pxd/metadata.info -------------------------------------------------------------------------------- /Design Assets/posterMirrorImageGradientMask.pxd/QuickLook/Icon.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Design Assets/posterMirrorImageGradientMask.pxd/QuickLook/Icon.tiff -------------------------------------------------------------------------------- /Design Assets/posterMirrorImageGradientMask.pxd/QuickLook/Thumbnail.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Design Assets/posterMirrorImageGradientMask.pxd/QuickLook/Thumbnail.tiff -------------------------------------------------------------------------------- /Design Assets/posterMirrorImageGradientMask.pxd/data/originalImportedContentDocumentInfo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Design Assets/posterMirrorImageGradientMask.pxd/data/originalImportedContentDocumentInfo -------------------------------------------------------------------------------- /Design Assets/posterMirrorImageGradientMask.pxd/metadata.info: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Design Assets/posterMirrorImageGradientMask.pxd/metadata.info -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The Front Row Trailers app icons and related graphics, including screenshots and website raster graphics, are © 2022 Christoph Parstorfer. All rights reserved. 2 | 3 | The Front Row Trailers project source code, including the website source code, is licensed under the MIT License: 4 | 5 | Copyright (c) 2020 CONATH 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | -------------------------------------------------------------------------------- /Shared/API Extensions/CustomDarkAppearance.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomDarkAppearance.swift 3 | // TheatricalMovieTrailers 4 | // 5 | // Created by Chris on 30.06.20. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct CustomDarkAppearance: ViewModifier { 11 | @ObservedObject var settings = Settings.instance 12 | 13 | func body(content: Content) -> some View { 14 | AnyView( 15 | content 16 | .preferredColorScheme(settings.prefersDarkAppearance ? .some(.dark) : .none) 17 | #if os(macOS) 18 | .background(settings.prefersDarkAppearance ? Color.black : nil) 19 | #endif 20 | ) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Shared/API Extensions/DispatchQueue+async.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DispatchQueue+async.swift 3 | // TheatricalMovieTrailers 4 | // 5 | // Created by Christoph Parstorfer on 02.11.20. 6 | // 7 | 8 | import Foundation 9 | 10 | extension DispatchQueue { 11 | func asyncAfter(_ delay: Double, _ block: @escaping () -> Void) { 12 | asyncAfter(deadline: .now() + delay, execute: block) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Shared/API Extensions/Image+MoviePosterPlaceholder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Image+MoviePosterPlaceholder.swift 3 | // TheatricalMovieTrailers 4 | // 5 | // Created by Christoph Parstorfer on 07.01.22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | extension Image { 11 | static var moviePosterPlaceholder: Image { 12 | return Image("moviePosterPlaceholder") 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Shared/AppError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppError.swift 3 | // TheatricalMovieTrailers 4 | // 5 | // Created by Christoph Parstorfer on 22.10.20. 6 | // 7 | 8 | import SwiftUI 9 | 10 | enum AppError: Error, Identifiable { 11 | var id: Int { 12 | switch self { 13 | case .notConnectedToInternet: 14 | return 1 15 | default: 16 | return 0 17 | } 18 | } 19 | 20 | case notConnectedToInternet 21 | case otherError(error: Error?) 22 | 23 | func makeAlert() -> Alert { 24 | let title: Text 25 | let message: Text? 26 | switch self { 27 | case .notConnectedToInternet: 28 | title = Text("Not connected to the internet.") 29 | message = Text("An internet connection is required to watch trailers.") 30 | case .otherError(let error): 31 | title = Text("An unknown error occurred.") 32 | message = error != nil ? Text(error!.localizedDescription) : nil 33 | } 34 | 35 | return Alert(title: title, message: message, dismissButton: .default(Text("Ok"))) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Shared/Audio Files/Exit.aif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Shared/Audio Files/Exit.aif -------------------------------------------------------------------------------- /Shared/Audio Files/Limit.aif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Shared/Audio Files/Limit.aif -------------------------------------------------------------------------------- /Shared/Audio Files/Selection.aif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Shared/Audio Files/Selection.aif -------------------------------------------------------------------------------- /Shared/Audio Files/SelectionChange.aif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/Shared/Audio Files/SelectionChange.aif -------------------------------------------------------------------------------- /Shared/AudioFeedback.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AudioFeedback.swift 3 | // TheatricalMovieTrailers 4 | // 5 | // Created by Christoph Parstorfer on 14.12.21. 6 | // 7 | 8 | import AVKit 9 | 10 | class AudioFeedback { 11 | private let selectAudioPlayer: AVAudioPlayer? 12 | private let limitAudioPlayer: AVAudioPlayer? 13 | private let exitAudioPlayer: AVAudioPlayer? 14 | private let selectionChangeAudioPlayer: AVAudioPlayer? 15 | 16 | init() { 17 | selectAudioPlayer = try? AVAudioPlayer(contentsOf: Bundle.main.url(forResource: "Selection", withExtension: "aif")!) 18 | limitAudioPlayer = try? AVAudioPlayer(contentsOf: Bundle.main.url(forResource: "Limit", withExtension: "aif")!) 19 | exitAudioPlayer = try? AVAudioPlayer(contentsOf: Bundle.main.url(forResource: "Exit", withExtension: "aif")!) 20 | selectionChangeAudioPlayer = try? AVAudioPlayer(contentsOf: Bundle.main.url(forResource: "SelectionChange", withExtension: "aif")!) 21 | #if os(iOS) 22 | setAudioPriority(playingTrailer: false) 23 | #endif 24 | } 25 | 26 | private func tryPlay(_ player:AVAudioPlayer?) { 27 | if Settings.instance.playUISounds, let player = player { 28 | DispatchQueue.global(qos: .userInitiated).async { 29 | player.play() 30 | } 31 | } 32 | } 33 | 34 | public func selection() { 35 | tryPlay(selectAudioPlayer) 36 | } 37 | 38 | public func limit() { 39 | tryPlay(limitAudioPlayer) 40 | } 41 | 42 | public func exit() { 43 | tryPlay(exitAudioPlayer) 44 | } 45 | 46 | public func selectionChange() { 47 | selectionChangeAudioPlayer?.pause() 48 | selectionChangeAudioPlayer?.currentTime = 0 49 | tryPlay(selectionChangeAudioPlayer) 50 | } 51 | 52 | #if os(iOS) 53 | public func setAudioPriority(playingTrailer: Bool) { 54 | try? AVAudioSession.sharedInstance().setCategory(playingTrailer ? .playback : .ambient) 55 | } 56 | #endif 57 | } 58 | -------------------------------------------------------------------------------- /Shared/MoviePosterView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MoviePosterView.swift 3 | // TheatricalMovieTrailers 4 | // 5 | // Created by Christoph Parstorfer on 15.10.20. 6 | // Theatrical Trailers 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct MoviePosterView: View { 12 | private let filmPosterAspectRatio = CGFloat(0.7063020214) 13 | 14 | private var reflectionDistance: CGFloat 15 | @State private var onTap: (() -> ())? 16 | @Binding private var image: Image? 17 | @State private var blurReflection = true 18 | 19 | init(image: Binding, reflectionDistance: CGFloat = -1.0, blurReflection: Bool = true, onTapGesture: (() -> ())? = nil) { 20 | self.reflectionDistance = reflectionDistance 21 | self._blurReflection = State(initialValue: blurReflection) 22 | self._onTap = State<(() -> ())?>(initialValue: onTapGesture) 23 | self._image = image 24 | } 25 | 26 | var body: some View { 27 | let image = self.image ?? .moviePosterPlaceholder 28 | 29 | return GeometryReader { geo in 30 | HStack { 31 | Spacer() 32 | VStack(alignment: .center, spacing: reflectionDistance) { 33 | image 34 | .resizable() 35 | .aspectRatio(contentMode: .fit) 36 | .frame(maxHeight: geo.size.height * 2 / 3) 37 | image 38 | .resizable() 39 | .aspectRatio(contentMode: .fit) 40 | .frame(maxHeight: geo.size.height * 2 / 3) 41 | .opacity(0.7) 42 | .mask( 43 | Image("posterMirrorImageGradientMask") 44 | .resizable() 45 | .aspectRatio(contentMode: .fill) 46 | .luminanceToAlpha() 47 | ) 48 | .rotationEffect(.degrees(180), anchor: .center) 49 | .rotation3DEffect( 50 | .degrees(180), 51 | axis: (x: 0, y: 1, z: 0) 52 | ) 53 | .blur(radius: blurReflection ? 1 : 0) 54 | } 55 | Spacer() 56 | } 57 | .onTapGesture(count: 1, perform: { 58 | onTap?() 59 | }) 60 | .accessibilityHidden(true) 61 | #if os(macOS) 62 | .offset(x: 0, y: geo.size.height * 0.15) 63 | #endif 64 | } 65 | } 66 | } 67 | 68 | #if DEBUG 69 | struct MoviePosterView_Previews: PreviewProvider { 70 | static var previews: some View { 71 | Color.black 72 | .overlay ( 73 | MoviePosterView(image: .constant(.moviePosterPlaceholder)) 74 | .padding(.top, 24) 75 | ) 76 | } 77 | } 78 | #endif 79 | -------------------------------------------------------------------------------- /Shared/SortingMode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SortingMode.swift 3 | // TheatricalMovieTrailers 4 | // 5 | // Created by Christoph Parstorfer on 21.10.20. 6 | // 7 | 8 | import Foundation 9 | 10 | enum SortingMode: String { 11 | case ReleaseAscending = "Release date" 12 | case ReleaseDescending = "Release (reversed)" 13 | case TitleAscending = "Title (A-Z)" 14 | 15 | func nextMode() -> SortingMode { 16 | switch self { 17 | case .ReleaseAscending: 18 | return .ReleaseDescending 19 | case .ReleaseDescending: 20 | return .TitleAscending 21 | default: 22 | return .ReleaseAscending 23 | } 24 | } 25 | 26 | var predicate: ((MovieInfo, MovieInfo) -> Bool) { 27 | get { 28 | switch self { 29 | case .ReleaseAscending: 30 | return { 31 | if $0.releaseDate != nil && $1.releaseDate == nil { 32 | return false 33 | } else if $0.releaseDate == nil && $1.releaseDate != nil { 34 | return true 35 | } else if let r0 = $0.releaseDate, let r1 = $1.releaseDate { 36 | return r0 < r1 37 | } else { 38 | return $0.title < $1.title 39 | } 40 | } 41 | case .ReleaseDescending: 42 | return { 43 | if $0.releaseDate != nil && $1.releaseDate == nil { 44 | return true 45 | } else if $0.releaseDate == nil && $1.releaseDate != nil { 46 | return false 47 | } else if let r0 = $0.releaseDate, let r1 = $1.releaseDate { 48 | return r0 > r1 49 | } else { 50 | return $0.title < $1.title 51 | } 52 | } 53 | default: 54 | return { 55 | return $0.title < $1.title 56 | } 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /TMT Mac/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 | -------------------------------------------------------------------------------- /TMT Mac/Assets.xcassets/AppIcon-Mac.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icon_16x16.png", 5 | "idiom" : "mac", 6 | "scale" : "1x", 7 | "size" : "16x16" 8 | }, 9 | { 10 | "filename" : "icon_16x16@2x.png", 11 | "idiom" : "mac", 12 | "scale" : "2x", 13 | "size" : "16x16" 14 | }, 15 | { 16 | "filename" : "icon_32x32.png", 17 | "idiom" : "mac", 18 | "scale" : "1x", 19 | "size" : "32x32" 20 | }, 21 | { 22 | "filename" : "icon_32x32@2x.png", 23 | "idiom" : "mac", 24 | "scale" : "2x", 25 | "size" : "32x32" 26 | }, 27 | { 28 | "filename" : "icon_128x128.png", 29 | "idiom" : "mac", 30 | "scale" : "1x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "filename" : "icon_128x128@2x.png", 35 | "idiom" : "mac", 36 | "scale" : "2x", 37 | "size" : "128x128" 38 | }, 39 | { 40 | "filename" : "icon_256x256.png", 41 | "idiom" : "mac", 42 | "scale" : "1x", 43 | "size" : "256x256" 44 | }, 45 | { 46 | "filename" : "icon_256x256@2x.png", 47 | "idiom" : "mac", 48 | "scale" : "2x", 49 | "size" : "256x256" 50 | }, 51 | { 52 | "filename" : "icon_512x512.png", 53 | "idiom" : "mac", 54 | "scale" : "1x", 55 | "size" : "512x512" 56 | }, 57 | { 58 | "filename" : "icon_512x512@2x.png", 59 | "idiom" : "mac", 60 | "scale" : "2x", 61 | "size" : "512x512" 62 | } 63 | ], 64 | "info" : { 65 | "author" : "xcode", 66 | "version" : 1 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /TMT Mac/Assets.xcassets/AppIcon-Mac.appiconset/icon_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TMT Mac/Assets.xcassets/AppIcon-Mac.appiconset/icon_128x128.png -------------------------------------------------------------------------------- /TMT Mac/Assets.xcassets/AppIcon-Mac.appiconset/icon_128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TMT Mac/Assets.xcassets/AppIcon-Mac.appiconset/icon_128x128@2x.png -------------------------------------------------------------------------------- /TMT Mac/Assets.xcassets/AppIcon-Mac.appiconset/icon_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TMT Mac/Assets.xcassets/AppIcon-Mac.appiconset/icon_16x16.png -------------------------------------------------------------------------------- /TMT Mac/Assets.xcassets/AppIcon-Mac.appiconset/icon_16x16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TMT Mac/Assets.xcassets/AppIcon-Mac.appiconset/icon_16x16@2x.png -------------------------------------------------------------------------------- /TMT Mac/Assets.xcassets/AppIcon-Mac.appiconset/icon_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TMT Mac/Assets.xcassets/AppIcon-Mac.appiconset/icon_256x256.png -------------------------------------------------------------------------------- /TMT Mac/Assets.xcassets/AppIcon-Mac.appiconset/icon_256x256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TMT Mac/Assets.xcassets/AppIcon-Mac.appiconset/icon_256x256@2x.png -------------------------------------------------------------------------------- /TMT Mac/Assets.xcassets/AppIcon-Mac.appiconset/icon_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TMT Mac/Assets.xcassets/AppIcon-Mac.appiconset/icon_32x32.png -------------------------------------------------------------------------------- /TMT Mac/Assets.xcassets/AppIcon-Mac.appiconset/icon_32x32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TMT Mac/Assets.xcassets/AppIcon-Mac.appiconset/icon_32x32@2x.png -------------------------------------------------------------------------------- /TMT Mac/Assets.xcassets/AppIcon-Mac.appiconset/icon_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TMT Mac/Assets.xcassets/AppIcon-Mac.appiconset/icon_512x512.png -------------------------------------------------------------------------------- /TMT Mac/Assets.xcassets/AppIcon-Mac.appiconset/icon_512x512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TMT Mac/Assets.xcassets/AppIcon-Mac.appiconset/icon_512x512@2x.png -------------------------------------------------------------------------------- /TMT Mac/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /TMT Mac/Assets.xcassets/MoviePosterPlaceholder.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "moviePosterPlaceholder@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /TMT Mac/Assets.xcassets/MoviePosterPlaceholder.imageset/moviePosterPlaceholder@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TMT Mac/Assets.xcassets/MoviePosterPlaceholder.imageset/moviePosterPlaceholder@2x.png -------------------------------------------------------------------------------- /TMT Mac/Assets.xcassets/posterMirrorImageGradientMask.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "posterMirrorImageGradientMask.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /TMT Mac/Assets.xcassets/posterMirrorImageGradientMask.imageset/posterMirrorImageGradientMask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TMT Mac/Assets.xcassets/posterMirrorImageGradientMask.imageset/posterMirrorImageGradientMask.png -------------------------------------------------------------------------------- /TMT Mac/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // TMT Mac 4 | // 5 | // Created by Christoph Parstorfer on 20.11.21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ContentView: View { 11 | @ObservedObject private var settings = Settings.instance 12 | @EnvironmentObject private var dataStore: MovieInfoDataStore 13 | @State private var sortingMode = SortingMode.ReleaseAscending 14 | @State private var loading = false 15 | @State private var fadeInOut = true 16 | 17 | @State private var fadingOutImage: Image? 18 | @State private var fadingInImage: Image? 19 | 20 | private let fadeDuration = 1.0 21 | 22 | static let audioFeedback = AudioFeedback() 23 | 24 | static var listItemHeight: CGFloat { 25 | NSFont.preferredFont(forTextStyle: .title1).pointSize * 1.5 26 | } 27 | 28 | static var selectionRectHeight: CGFloat { 29 | listItemHeight * 1.5 30 | } 31 | 32 | static var selectionRectDeltaY: CGFloat { 33 | selectionRectHeight / ((selectionRectHeight / listItemHeight) * 1.5) 34 | } 35 | 36 | var body: some View { 37 | GeometryReader { frame in 38 | HStack { 39 | MovieInfoContainerView(frame: frame) 40 | .frame(width: 0.5*frame.size.width) 41 | /// Movie titles and selection overlay 42 | MovieTrailerListView(sortingMode: $sortingMode, onQuit: { 43 | withAnimation(.easeIn(duration: fadeDuration)) { 44 | self.fadeInOut = true 45 | } 46 | DispatchQueue.main.asyncAfter(fadeDuration) { 47 | NSApplication.shared.terminate(self) 48 | } 49 | }, frame: frame) 50 | } 51 | } 52 | /// loading overlay 53 | .overlay( 54 | Group { 55 | if loading { 56 | ZStack { 57 | ProgressView("Loading Trailers…") 58 | .frame(width: 200, height: 44) 59 | } 60 | .frame(maxWidth: .infinity, maxHeight: .infinity) 61 | .background(Color.init(NSColor.windowBackgroundColor)) 62 | } 63 | if fadeInOut { 64 | ZStack {} 65 | .frame(maxWidth: .infinity, maxHeight: .infinity) 66 | .background(Color.black) 67 | .transition(.opacity) 68 | } 69 | if dataStore.isPlaying, dataStore.selectedTrailerModel?.trailerURL != nil { 70 | ZStack(alignment: .topLeading) { 71 | TrailerPlayerView(url: dataStore.selectedTrailerModel!.trailerURL!, isShown: $dataStore.isPlaying) 72 | XButton() { 73 | withAnimation { 74 | dataStore.isPlaying = false 75 | } 76 | } 77 | } 78 | .frame(maxWidth: .infinity, maxHeight: .infinity) 79 | .background(Color.black) 80 | .transition(.opacity) 81 | .onDisappear { 82 | /// user played trailer, request review 83 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { 84 | AppStoreReviewsManager.requestReviewIfAppropriate() 85 | } 86 | } 87 | } 88 | } 89 | ) 90 | .alert(item: $dataStore.error, content: { error -> Alert in 91 | error.makeAlert() 92 | }) 93 | .transition(.opacity) 94 | .modifier(CustomDarkAppearance()) 95 | .onChange(of: sortingMode) { sortingMode in 96 | dataStore.model.sort(by: sortingMode.predicate) 97 | } 98 | .onAppear { 99 | if !dataStore.moviesAvailable { 100 | DispatchQueue.main.asyncAfter(0.5) { 101 | withAnimation(.easeIn(duration: fadeDuration)) { 102 | self.fadeInOut = false 103 | } 104 | if !dataStore.moviesAvailable { 105 | withAnimation { 106 | loading = true 107 | } 108 | } 109 | } 110 | } 111 | } 112 | .onChange(of: dataStore.moviesAvailable, perform: { moviesAvailable in 113 | withAnimation { 114 | loading = !moviesAvailable 115 | } 116 | 117 | withAnimation(.easeIn(duration: fadeDuration)) { 118 | self.fadeInOut = false 119 | } 120 | if moviesAvailable { 121 | dataStore.model.sort(by: sortingMode.predicate) 122 | DispatchQueue.main.asyncAfter(0.2) { 123 | if let first = dataStore.model.first { 124 | withAnimation { 125 | dataStore.selectedTrailerModel = first 126 | fadingInImage = imageForMovie(first) 127 | } 128 | } 129 | } 130 | } 131 | }) 132 | } 133 | 134 | private func imageForMovie(_ movieInfo: MovieInfo) -> Image { 135 | return (dataStore.idsAndImages[movieInfo.id] ?? .moviePosterPlaceholder)! 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /TMT Mac/MacFont.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MacFont.swift 3 | // Theatrical Trailers 4 | // 5 | // Created by Christoph Parstorfer on 12.02.22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | extension Font { 11 | static var titleGrande: Font { 12 | get { 13 | Font.custom("Lucida Grande", fixedSize: NSFont.preferredFont(forTextStyle: .title1).pointSize) 14 | .bold() 15 | } 16 | } 17 | static var boldGrande: Font { 18 | get { 19 | Font.custom("Lucida Grande", fixedSize: NSFont.preferredFont(forTextStyle: .body).pointSize * 1.5) 20 | .bold() 21 | } 22 | } 23 | static var bodyGrande: Font { 24 | get { 25 | Font.custom("Lucida Grande", fixedSize: NSFont.preferredFont(forTextStyle: .body).pointSize * 1.5) 26 | } 27 | } 28 | static var smallGrande: Font { 29 | get { 30 | Font.custom("Lucida Grande", fixedSize: NSFont.preferredFont(forTextStyle: .caption1).pointSize) 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /TMT Mac/NSApplication+version+build.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSApplication+version+build.swift 3 | // TMT Mac 4 | // 5 | // Created by Christoph Parstorfer on 20.11.21. 6 | // 7 | 8 | import AppKit 9 | 10 | extension NSApplication { 11 | /// Returns the `CFBundleShortVersionString` from *Info.plist*. 12 | static var version: String { 13 | guard let appVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String else { 14 | fatalError("Couldn't find CFBundleShortVersionString in Info.plist") 15 | } 16 | return appVersion 17 | } 18 | /// Returns the `CFBundleVersion` from *Info.plist*. 19 | static var build: String { 20 | guard let build = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String else { 21 | fatalError("Couldn't find CFBundleVersion in Info.plist") 22 | } 23 | return build 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /TMT Mac/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /TMT Mac/Settings.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Settings.swift 3 | // TheatricalMovieTrailers 4 | // 5 | // Created by Chris on 30.06.20. 6 | // 7 | 8 | import Foundation 9 | import AppKit 10 | 11 | fileprivate extension String { 12 | static let lastBuildNumber = "lastBuildNumber" 13 | static let autoDark = "isAutoDarkAppearance" 14 | static let loadHighDefinition = "loadHighDefinition" 15 | static let firstLaunchedDate = "firstLaunchedDate" 16 | static let lastReviewRequestAppVersion = "lastReviewRequestAppVersion" 17 | static let playUISounds = "playUISounds" 18 | } 19 | 20 | class Settings: ObservableObject { 21 | private let ValueAlwaysDark = 0 22 | private let ValueAutomaticDark = 1 23 | 24 | static let instance = Settings() 25 | 26 | static var userDefaults: UserDefaults { 27 | return UserDefaults(suiteName: "cafe.chrisp.tmt.settings")! 28 | } 29 | 30 | @Published var prefersDarkAppearance = true { 31 | didSet { 32 | let newValue = prefersDarkAppearance ? ValueAlwaysDark : ValueAutomaticDark 33 | let defaults = Self.userDefaults 34 | defaults.setValue(newValue, forKey: .autoDark) 35 | defaults.synchronize() 36 | } 37 | } 38 | @Published var loadHighDefinition = true { 39 | didSet { 40 | let defaults = Self.userDefaults 41 | defaults.setValue(loadHighDefinition, forKey: .loadHighDefinition) 42 | defaults.synchronize() 43 | } 44 | } 45 | /// App Store Reviews Manager metadata 46 | private(set) var firstLaunchedDate: Date 47 | var lastReviewRequestAppVersion: String? = nil { 48 | didSet { 49 | let defaults = Self.userDefaults 50 | defaults.setValue(lastReviewRequestAppVersion, forKey: .lastReviewRequestAppVersion) 51 | defaults.synchronize() 52 | } 53 | } 54 | /// Whether to play FrontRow-inspired UI sounds 55 | @Published var playUISounds = true { 56 | didSet { 57 | let defaults = Self.userDefaults 58 | defaults.setValue(playUISounds, forKey: .playUISounds) 59 | defaults.synchronize() 60 | } 61 | } 62 | 63 | private init() { 64 | let defaults = Self.userDefaults 65 | /// Check for version upgrade 66 | let lastBuild = defaults.string(forKey: .lastBuildNumber) 67 | if lastBuild == nil { 68 | /// never launched, set default values 69 | defaults.setValue(Date(), forKey: .firstLaunchedDate) 70 | defaults.setValue(ValueAlwaysDark, forKey: .autoDark) 71 | defaults.setValue(lastReviewRequestAppVersion, forKey: .lastReviewRequestAppVersion) 72 | defaults.setValue(NSApplication.build, forKey: .lastBuildNumber) 73 | prefersDarkAppearance = true 74 | firstLaunchedDate = Date() 75 | defaults.setValue(Date(), forKey: .firstLaunchedDate) 76 | defaults.setValue(true, forKey: .playUISounds) 77 | } 78 | /// Load settings 79 | let isAutoDark = defaults.integer(forKey: .autoDark) == ValueAutomaticDark 80 | if isAutoDark { 81 | prefersDarkAppearance = false 82 | } 83 | /// App Store Reviews Manager metadata 84 | firstLaunchedDate = defaults.value(forKey: .firstLaunchedDate) as! Date 85 | lastReviewRequestAppVersion = defaults.string(forKey: .lastReviewRequestAppVersion) 86 | /// UI Sounds 87 | playUISounds = defaults.bool(forKey: .playUISounds) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /TMT Mac/TMT_Mac.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.application-groups 8 | U96PJYMZWW.tmt-group 9 | com.apple.security.network.client 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /TMT Mac/TMT_MacApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TMT_MacApp.swift 3 | // TMT Mac 4 | // 5 | // Created by Christoph Parstorfer on 20.11.21. 6 | // 7 | 8 | import SwiftUI 9 | import TelemetryClient 10 | 11 | @main 12 | struct TMT_MacApp: App { 13 | let dataStore = MovieInfoDataStore.shared 14 | var body: some Scene { 15 | WindowGroup { 16 | ContentView() 17 | .environmentObject(dataStore) 18 | .onAppear { 19 | /// initialize telemetry 20 | let configuration = TelemetryManagerConfiguration(appID: TelemetryAppId) 21 | TelemetryManager.initialize(with: configuration) 22 | TelemetryManager.send("appLaunchedRegularly") 23 | /// put window into full screen 24 | DispatchQueue.main.asyncAfter(0.1) { 25 | if let window = NSApplication.shared.windows.last { 26 | window.toggleFullScreen(nil) 27 | /// give it a moment 28 | DispatchQueue.main.asyncAfter(0.1) { 29 | let swiftUIWindow = NSApplication.shared.windows[0] 30 | swiftUIWindow.becomeKey() 31 | } 32 | } 33 | } 34 | } 35 | } 36 | .windowStyle(.hiddenTitleBar) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /TMT Mac/Theatrical-Trailers-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ITSAppUsesNonExemptEncryption 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /TMT Mac/Utilities/AppStoreReviewsManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppStoreReviewManager.swift 3 | // TheatricalMovieTrailers 4 | // 5 | // Created by Christoph Parstorfer on 20.02.22. 6 | // 7 | 8 | import StoreKit 9 | 10 | /** 11 | Shows the App Store review prompt if appropriate. 12 | Appropriate means: 13 | - app launched > 1 minute ago 14 | - has not been asked to review this version 15 | */ 16 | class AppStoreReviewsManager { 17 | static func requestReviewIfAppropriate() { 18 | let settings = Settings.instance 19 | // check if app was first launched more than one minute ago 20 | guard Date().timeIntervalSince(settings.firstLaunchedDate) > 1 * 60/* seconds */ else { 21 | return 22 | } 23 | 24 | // check that no request for review of this app version has been made 25 | let currentVersion = NSApplication.version 26 | let lastVersion = settings.lastReviewRequestAppVersion 27 | guard lastVersion == nil || lastVersion! != currentVersion else { 28 | return 29 | } 30 | 31 | // request for the review popup to be shown 32 | SKStoreReviewController.requestReview() 33 | 34 | // update last requested version number 35 | settings.lastReviewRequestAppVersion = currentVersion 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /TMT Mac/Utilities/KeyEventHandling.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeyEventHandling.swift 3 | // Theatrical Trailers 4 | // 5 | // Created by Christoph Parstorfer on 13.12.21. 6 | // 7 | 8 | import AppKit 9 | import Carbon.HIToolbox 10 | import SwiftUI 11 | 12 | struct KeyEventHandling: NSViewRepresentable { 13 | var onEnter: (() -> ())? = nil 14 | var onUpArrow: (() -> ())? = nil 15 | var onDownArrow: (() -> ())? = nil 16 | var onEsc: (() -> ())? = nil 17 | var onQuit: (() -> ())? = nil 18 | 19 | class Coordinator: NSObject { 20 | var parent: KeyEventHandling 21 | var keyHandler: Any? = nil 22 | 23 | init(_ parent: KeyEventHandling) { 24 | self.parent = parent 25 | super.init() 26 | 27 | keyHandler = NSEvent.addLocalMonitorForEvents(matching: [.keyDown]) { event in 28 | if self.handleKeyDown(with: event) { 29 | /// we handled it, don't send it to the system 30 | return nil 31 | } else { 32 | /// not handled, send back to event system 33 | return event 34 | } 35 | } 36 | } 37 | 38 | deinit { 39 | if let keyHandler = keyHandler { 40 | NSEvent.removeMonitor(keyHandler) 41 | } 42 | keyHandler = nil 43 | } 44 | 45 | func handleKeyDown(with event: NSEvent) -> Bool { 46 | let fixedFlagMask = event.modifierFlags.rawValue & NSEvent.ModifierFlags.deviceIndependentFlagsMask.rawValue 47 | let modifierFlags: NSEvent.ModifierFlags = NSEvent.ModifierFlags(rawValue: fixedFlagMask) 48 | let arrowKeyModifierFlag: NSEvent.ModifierFlags = [.numericPad, .function] 49 | if modifierFlags == [] || modifierFlags == arrowKeyModifierFlag || modifierFlags == .numericPad { 50 | switch Int(event.keyCode) { 51 | case kVK_Return, kVK_ANSI_KeypadEnter: 52 | parent.onEnter?() 53 | break 54 | case kVK_UpArrow: 55 | parent.onUpArrow?() 56 | break 57 | case kVK_DownArrow: 58 | parent.onDownArrow?() 59 | break 60 | case kVK_Escape: 61 | parent.onEsc?() 62 | break 63 | default: 64 | return false 65 | } 66 | return true 67 | } else 68 | /// `cmd-q` ? 69 | if modifierFlags == NSEvent.ModifierFlags.command && 70 | Int(event.keyCode) == kVK_ANSI_Q { 71 | parent.onQuit?() 72 | return true 73 | } else { 74 | return false 75 | } 76 | } 77 | } 78 | 79 | class KeyView: NSView {} 80 | 81 | func makeNSView(context: Context) -> KeyView { 82 | let view = KeyView() 83 | DispatchQueue.main.asyncAfter(1) { // wait till next event cycle 84 | view.window?.makeFirstResponder(view) 85 | } 86 | updateNSView(view, context: context) 87 | return view 88 | } 89 | 90 | func makeCoordinator() -> Coordinator { 91 | Coordinator(self) 92 | } 93 | 94 | func updateNSView(_ nsView: KeyView, context: Context) { 95 | context.coordinator.parent = self 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /TMT Mac/Views/FadeInOutPosterView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FadeInOutPosterView.swift 3 | // Theatrical Trailers 4 | // 5 | // Created by Christoph Parstorfer on 24.12.21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct FadeInOutPosterView: View { 11 | private let animationDuration: Double = 0.3 12 | 13 | @EnvironmentObject private var dataStore: MovieInfoDataStore 14 | @Binding var posterImage: Image? 15 | 16 | @State private var shownImage = Image?.none 17 | @State private var delayImageChange = false 18 | @State private var noImageOpacity: Double = 0 19 | 20 | var body: some View { 21 | Group { 22 | if delayImageChange { 23 | /// Invisible poster view to maintain layout size 24 | MoviePosterView(image: .constant(nil)) 25 | .opacity(0) 26 | } else if shownImage != nil { 27 | /// don't want this MoviePosterView to get `nil` passed in 28 | /// because then its width animates when the image is set again 29 | MoviePosterView(image: .constant(shownImage!)) 30 | .transition(.opacity) 31 | } else { 32 | /// placeholder shown if image is unavailable 33 | MoviePosterView(image: .constant(nil)) 34 | .transition(.opacity) 35 | } 36 | } 37 | /// fade in when view initially appears 38 | .onAppear(perform: { 39 | delayImageChange = true 40 | }) 41 | /// fade out when the poster changes 42 | .onChange(of: posterImage) { [posterImage] newImage in 43 | guard posterImage != newImage else { return } 44 | withAnimation(.easeInOut(duration: animationDuration)) { 45 | delayImageChange = true 46 | } 47 | } 48 | /// fading out or finished fading out 49 | .onChange(of: delayImageChange) { newValue in 50 | if delayImageChange { 51 | DispatchQueue.main.asyncAfter(0.5) { 52 | withAnimation { 53 | delayImageChange = false 54 | } 55 | } 56 | } else { 57 | withAnimation { 58 | shownImage = _posterImage.wrappedValue 59 | } 60 | } 61 | } 62 | } 63 | } 64 | 65 | struct FadeInOutPosterView_Previews: PreviewProvider { 66 | static var previews: some View { 67 | FadeInOutPosterView(posterImage: .constant(nil)) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /TMT Mac/Views/MovieInfoContainerView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MovieInfoContainerView.swift 3 | // Theatrical Trailers 4 | // 5 | // Created by Christoph Parstorfer on 10.01.22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct MovieInfoContainerView: View { 11 | @EnvironmentObject private var dataStore: MovieInfoDataStore 12 | 13 | @State var frame: GeometryProxy 14 | 15 | @State private var animationProgress: CGFloat = 0 16 | @State private var shownMovieInfoMetadata: MovieInfo? = nil 17 | 18 | private var heightFactor: CGFloat { 19 | 1 - 0.2 * animationProgress 20 | } 21 | private var yOffset: CGFloat { 22 | animationProgress * -frame.size.height * 0.15 23 | } 24 | private var angle: CGFloat { 25 | 10 - animationProgress * 10 26 | } 27 | 28 | var body: some View { 29 | ZStack { 30 | /// Movie poster image views with fade out and in transition 31 | FadeInOutPosterView(posterImage: $dataStore.posterImage) 32 | .frame(height: frame.size.height * heightFactor) 33 | .offset(x: 0, y: yOffset) 34 | .rotation3DEffect(Angle(degrees: angle), axis: (x: 0, y: 0.5, z: 0)) 35 | .onChange(of: dataStore.posterImage) { image in 36 | withAnimation { 37 | shownMovieInfoMetadata = nil 38 | } 39 | DispatchQueue.main.asyncAfter(0.3) { 40 | guard dataStore.posterImage == image else { return } 41 | animationProgress = 0 42 | DispatchQueue.main.asyncAfter(1.2) { 43 | guard dataStore.posterImage == image else { return } 44 | withAnimation(.easeInOut(duration: 0.5)) { 45 | animationProgress = 1 46 | shownMovieInfoMetadata = dataStore.selectedTrailerModel 47 | } 48 | } 49 | } 50 | } 51 | if let info = shownMovieInfoMetadata { 52 | VStack(alignment: .leading) { 53 | Spacer() 54 | .frame(maxWidth: .infinity, maxHeight: frame.size.height * 0.5) 55 | // Title 56 | HStack { 57 | Text(info.title) 58 | .font(.titleGrande) 59 | Spacer() 60 | Text(info.trailerLength) 61 | .font(.titleGrande) 62 | } 63 | Divider() 64 | // Synopsis 65 | Text(info.synopsis) 66 | .font(.bodyGrande) 67 | .lineLimit(5) 68 | Divider() 69 | Group { 70 | // Cast 71 | HLabelAndContent(label: "Cast:", content: info.actors.joined(separator: ", "), frame: frame) 72 | // Director 73 | HLabelAndContent(label: "Director:", content: info.director, frame: frame) 74 | // Genre 75 | HLabelAndContent(label: "Genre:", content: info.genres.joined(separator: ", "), frame: frame) 76 | // Release Date 77 | HLabelAndContent(label: "Release:", content: info.releaseDateString, frame: frame) 78 | } 79 | Divider() 80 | // Copyright 81 | Text(info.copyright) 82 | .font(.smallGrande) 83 | Spacer() 84 | } 85 | .padding() 86 | .padding() 87 | .transition(.opacity) 88 | } 89 | } 90 | } 91 | 92 | struct HLabelAndContent: View { 93 | @State var label: String 94 | @State var content: String 95 | @State var frame: GeometryProxy 96 | 97 | var body: some View { 98 | HStack(alignment: .top) { 99 | ZStack(alignment: .trailing) { 100 | Text("DirectorD") 101 | .font(.bodyGrande) 102 | .foregroundColor(.clear) 103 | .accessibilityHidden(true) 104 | Text(label) 105 | .font(.bodyGrande) 106 | } 107 | Text(content) 108 | .font(.boldGrande) 109 | } 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /TMT Mac/Views/SelectionIndicator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SelectionIndicator.swift 3 | // Theatrical Trailers 4 | // 5 | // Created by Christoph Parstorfer on 13.12.21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct SelectionIndicator: View { 11 | var frame: GeometryProxy 12 | 13 | var body: some View { 14 | /// the text "Layout placeholder" makes it so the text view is laid out with the proper font size 15 | Text("Layout placeholder") 16 | .lineLimit(1) 17 | .truncationMode(.tail) 18 | .foregroundColor(.clear) 19 | .accessibilityHidden(true) 20 | .font(Font.title.bold()) 21 | .frame(width: 0.4*frame.size.width, height: ContentView.selectionRectHeight) 22 | .padding(15) 23 | .background( 24 | ZStack(alignment: .leading) { 25 | /// blue shine 26 | Rectangle() 27 | .foregroundColor(.clear) 28 | .frame(maxWidth: .infinity, maxHeight: .infinity) 29 | .border(Color.blue, width: 10) 30 | .blur(radius: 4) 31 | .padding(4) 32 | .mask { 33 | Rectangle() 34 | .foregroundColor(.clear) 35 | .frame(maxWidth: .infinity, maxHeight: .infinity) 36 | .border(Color.white, width: 7) 37 | } 38 | /// black border 39 | Rectangle() 40 | .foregroundColor(.clear) 41 | .frame(maxWidth: .infinity, maxHeight: .infinity) 42 | .border(Color.black, width: 2) 43 | .padding(6) 44 | /// gloss shine 45 | Rectangle() 46 | .frame(maxWidth: .infinity, maxHeight: .infinity) 47 | .foregroundColor(.clear) 48 | .overlay( 49 | LinearGradient(colors: [.init(white: 1, opacity: 0.5), .init(white: 1, opacity: 0.2), .clear, .clear, .clear, .clear], startPoint: .top, endPoint: .bottom)) 50 | .padding(8) 51 | } 52 | .frame(height: ContentView.selectionRectHeight) 53 | ) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /TMT Mac/Views/TrailerPlayerView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TrailerPlayerView.swift 3 | // TheatricalMovieTrailers 4 | // 5 | // Created by Christoph Parstorfer on 14.10.20. 6 | // 7 | 8 | import AVFoundation 9 | import AVKit 10 | import SwiftUI 11 | 12 | struct TrailerPlayerView: NSViewRepresentable { 13 | typealias NSViewType = AVPlayerView 14 | 15 | @State private var avPlayer: AVPlayer 16 | @Binding var isShown: Bool 17 | 18 | init(url: URL, isShown: Binding) { 19 | _avPlayer = State(initialValue: AVPlayer(url: url)) 20 | _isShown = isShown 21 | } 22 | 23 | func makeNSView(context: Context) -> AVPlayerView { 24 | let avVC = AVPlayerView() 25 | avVC.player = avPlayer 26 | avPlayer.play() 27 | return avVC 28 | } 29 | 30 | func updateNSView(_ nsView: AVPlayerView, context: Context) { 31 | nsView.player = avPlayer 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /TMT Mac/Views/XButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XButton.swift 3 | // Front Row Trailers 4 | // 5 | // Created by Christoph Parstorfer on 13.02.22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct XButton: View { 11 | var action: () -> () 12 | 13 | @State private var increaseOpacity = true 14 | 15 | var body: some View { 16 | Button() { 17 | action() 18 | } label: { 19 | Image(systemName: "xmark.circle.fill") 20 | .font(.largeTitle) 21 | } 22 | .buttonStyle(PlainButtonStyle()) 23 | .frame(width: ContentView.listItemHeight, height: ContentView.listItemHeight) 24 | .clipShape(Circle()) 25 | .opacity(increaseOpacity ? 0.8 : 0.3) 26 | .padding() 27 | .onAppear { 28 | withAnimation(.linear(duration: 3)) { 29 | increaseOpacity = false 30 | } 31 | } 32 | .onHover { hovering in 33 | increaseOpacity = hovering 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /TheatricalMovieTrailers.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /TheatricalMovieTrailers.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /TheatricalMovieTrailers.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "TelemetryClient", 6 | "repositoryURL": "https://github.com/AppTelemetry/SwiftClient", 7 | "state": { 8 | "branch": null, 9 | "revision": "fb518b5acb02aa6ca41be7018bb1619aaef3dc13", 10 | "version": "1.1.6" 11 | } 12 | } 13 | ] 14 | }, 15 | "version": 1 16 | } 17 | -------------------------------------------------------------------------------- /TheatricalMovieTrailers.xcodeproj/xcshareddata/xcschemes/TMT Mac.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /TheatricalMovieTrailers.xcodeproj/xcshareddata/xcschemes/TheatricalMovieTrailers.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /TheatricalMovieTrailers.xcodeproj/xcshareddata/xcschemes/TrailersWidgetExtension.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 6 | 9 | 10 | 16 | 22 | 23 | 24 | 30 | 36 | 37 | 38 | 39 | 40 | 45 | 46 | 47 | 48 | 60 | 63 | 69 | 70 | 71 | 72 | 78 | 79 | 80 | 81 | 85 | 86 | 90 | 91 | 95 | 96 | 97 | 98 | 106 | 108 | 114 | 115 | 116 | 117 | 119 | 120 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /TheatricalMovieTrailers/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // TheatricalMovieTrailers 4 | // 5 | // Created by Chris on 26.06.20. 6 | // 7 | 8 | import UIKit 9 | import SwiftUI 10 | import TelemetryClient 11 | 12 | @UIApplicationMain 13 | class AppDelegate: UIResponder, UIApplicationDelegate, ObservableObject { 14 | @Published var isExternalScreenConnected = false 15 | 16 | var externalWindow: UIWindow? 17 | var externalVC: UIViewController? 18 | 19 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 20 | // Override point for customization after application launch. 21 | let configuration = TelemetryManagerConfiguration(appID: TelemetryAppId) 22 | TelemetryManager.initialize(with: configuration) 23 | TelemetryManager.send("appLaunchedRegularly") 24 | 25 | NotificationCenter.default.addObserver(forName: UIScreen.didDisconnectNotification, object: nil, queue: nil) { _ in 26 | DispatchQueue.main.async { 27 | self.isExternalScreenConnected = false 28 | self.externalVC = nil 29 | self.externalWindow = nil 30 | } 31 | } 32 | return true 33 | } 34 | 35 | private func setupExternalScreen(session: UISceneSession, options: UIScene.ConnectionOptions) { 36 | let newWindow = UIWindow() 37 | let windowScene = UIWindowScene(session: session, connectionOptions: options) 38 | newWindow.windowScene = windowScene 39 | externalWindow = newWindow 40 | 41 | //newWindow.layer.frame = newWindow.layer.frame.applying(CGAffineTransform.identity.rotated(by: rotation)) 42 | 43 | let externalView = ExternalView() 44 | let hostingController = UIHostingController(rootView: externalView) 45 | hostingController.overrideUserInterfaceStyle = .dark 46 | newWindow.rootViewController = hostingController 47 | newWindow.isHidden = false 48 | externalVC = hostingController 49 | } 50 | 51 | // MARK: UISceneSession Lifecycle 52 | 53 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 54 | // is external display? 55 | if connectingSceneSession.configuration.role == UISceneSession.Role.windowExternalDisplay { 56 | setupExternalScreen(session: connectingSceneSession, options: options) 57 | isExternalScreenConnected = true 58 | return UISceneConfiguration(name: "External screen", sessionRole: .windowExternalDisplay) 59 | } else { 60 | // Called when a new scene session is being created. 61 | // Use this method to select a configuration to create the new scene with. 62 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 63 | } 64 | } 65 | } 66 | 67 | -------------------------------------------------------------------------------- /TheatricalMovieTrailers/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // TheatricalMovieTrailers 4 | // 5 | // Created by Chris on 26.06.20. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ContentView: View { 11 | @ObservedObject var settings = Settings.instance 12 | @EnvironmentObject var dataStore: MovieInfoDataStore 13 | @State var sortingMode = SortingMode.ReleaseAscending 14 | @State private var loading = false 15 | 16 | var body: some View { 17 | CoverFlowScrollView(model: $dataStore.model, sortingMode: $sortingMode) 18 | .overlay( 19 | Group { 20 | if loading { 21 | ZStack { 22 | ProgressView("Loading Trailers…") 23 | .frame(width: 200, height: 44) 24 | } 25 | .frame(maxWidth: .infinity, maxHeight: .infinity) 26 | .background(Color.init(UIColor.systemBackground)) 27 | .edgesIgnoringSafeArea(.all) 28 | } 29 | } 30 | ) 31 | .alert(item: $dataStore.error, content: { error -> Alert in 32 | error.makeAlert() 33 | }) 34 | .transition(.opacity) 35 | .modifier(CustomDarkAppearance()) 36 | .statusBar(hidden: true) 37 | .onChange(of: sortingMode) { sortingMode in 38 | dataStore.model.sort(by: sortingMode.predicate) 39 | } 40 | .onAppear { 41 | if !dataStore.moviesAvailable { 42 | DispatchQueue.main.asyncAfter(0.5) { 43 | if !dataStore.moviesAvailable { 44 | withAnimation { 45 | loading = true 46 | } 47 | } 48 | } 49 | } 50 | } 51 | .onChange(of: dataStore.moviesAvailable, perform: { moviesAvailable in 52 | withAnimation { 53 | loading = !moviesAvailable 54 | } 55 | }) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /TheatricalMovieTrailers/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | Front Row 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 | CFBundleURLTypes 22 | 23 | 24 | CFBundleTypeRole 25 | Viewer 26 | CFBundleURLIconFile 27 | 28 | CFBundleURLName 29 | cafe.chrisp.tmt.TheatricalsTrailer 30 | CFBundleURLSchemes 31 | 32 | theatricals 33 | 34 | 35 | 36 | CFBundleVersion 37 | $(CURRENT_PROJECT_VERSION) 38 | ITSAppUsesNonExemptEncryption 39 | 40 | LSRequiresIPhoneOS 41 | 42 | NSUserActivityTypes 43 | 44 | ConfigurationIntent 45 | 46 | UIApplicationSceneManifest 47 | 48 | UIApplicationSupportsMultipleScenes 49 | 50 | UISceneConfigurations 51 | 52 | UIWindowSceneSessionRoleApplication 53 | 54 | 55 | UISceneConfigurationName 56 | Default Configuration 57 | UISceneDelegateClassName 58 | $(PRODUCT_MODULE_NAME).SceneDelegate 59 | 60 | 61 | 62 | 63 | UIApplicationSupportsIndirectInputEvents 64 | 65 | UILaunchStoryboardName 66 | LaunchScreen 67 | UIRequiredDeviceCapabilities 68 | 69 | armv7 70 | 71 | UIStatusBarHidden 72 | 73 | UISupportedInterfaceOrientations 74 | 75 | UIInterfaceOrientationPortrait 76 | UIInterfaceOrientationLandscapeLeft 77 | UIInterfaceOrientationLandscapeRight 78 | 79 | UISupportedInterfaceOrientations~ipad 80 | 81 | UIInterfaceOrientationLandscapeLeft 82 | UIInterfaceOrientationLandscapeRight 83 | UIInterfaceOrientationPortrait 84 | UIInterfaceOrientationPortraitUpsideDown 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /TheatricalMovieTrailers/Model/ViewParameters.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewParameters.swift 3 | // TheatricalMovieTrailers 4 | // 5 | // Created by Christoph Parstorfer on 01.11.20. 6 | // 7 | 8 | import Foundation 9 | 10 | class ViewParameters: ObservableObject { 11 | @Published var showTrailerID: Int? 12 | } 13 | -------------------------------------------------------------------------------- /TheatricalMovieTrailers/Model/WindowSceneObject.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WindowSceneObject.swift 3 | // TheatricalMovieTrailers 4 | // 5 | // Created by Christoph Parstorfer on 30.10.20. 6 | // 7 | 8 | import Combine 9 | import UIKit 10 | 11 | /// Wraps `UIWindowScene` as an `ObservableObject` to be passed to SwiftUI views. 12 | class WindowSceneObject: ObservableObject { 13 | @Published var windowScene: UIWindowScene? 14 | 15 | init(_ windowScene: UIWindowScene?) { 16 | self.windowScene = windowScene 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /TheatricalMovieTrailers/MovieDetailsView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MovieDetailsView.swift 3 | // TheatricalMovieTrailers 4 | // 5 | // Created by Chris on 26.06.20. 6 | // 7 | 8 | import AVKit 9 | import SwiftUI 10 | 11 | struct MovieTrailerView: View { 12 | @State var model: TrailerModel 13 | @ObservedObject var appDelegate = UIApplication.shared.delegate as! AppDelegate 14 | @State var isPlaying: Bool 15 | 16 | let avPlayer: AVPlayer? 17 | let keyValueObservation: NSKeyValueObservation? 18 | 19 | init(model: TrailerModel = .Empty, isPlaying: Bool = false, changeHandler: @escaping ((AVPlayer, NSKeyValueObservedChange) -> ())) { 20 | self._model = State(initialValue: model) 21 | self._isPlaying = State(initialValue: isPlaying) 22 | if let url = URL(string: model.trailerURL) { 23 | let avPlayer: AVPlayer? = AVPlayer(url: url) 24 | self.avPlayer = avPlayer 25 | keyValueObservation = avPlayer?.observe(\.rate, options: [.new], changeHandler: changeHandler) 26 | } else { 27 | avPlayer = nil 28 | keyValueObservation = nil 29 | } 30 | } 31 | 32 | var body: some View { 33 | return GeometryReader { geo in 34 | VStack { 35 | TrailerMetaView(model: model) 36 | // Trailer Video 37 | VideoPlayer(player: avPlayer, videoOverlay: { 38 | Spacer() 39 | }) 40 | .onAppear { 41 | if appDelegate.isExternalScreenConnected { 42 | avPlayer?.volume = 0 43 | } 44 | } 45 | .onChange(of: appDelegate.isExternalScreenConnected) { isConnected in 46 | avPlayer?.volume = isConnected ? 0 : 1 47 | } 48 | .frame(width: geo.size.width, height: geo.size.width * (9 / 16), alignment: .center) 49 | 50 | Text(model.synopsis) 51 | .lineLimit(10) 52 | .font(.body) 53 | .padding([.leading, .trailing]) 54 | 55 | Divider() 56 | 57 | VStack(alignment: .leading) { 58 | HStack(alignment: .top) { 59 | HStack { 60 | Spacer() 61 | Text("Director") 62 | .font(.headline) 63 | } 64 | .frame(width: geo.size.width * 0.22) 65 | .padding(.trailing, 5) 66 | Text(model.director) 67 | .font(.body) 68 | Spacer() 69 | } 70 | .padding(.bottom, 5) 71 | 72 | HStack(alignment: .top) { 73 | HStack { 74 | Spacer() 75 | Text("Actors") 76 | .font(.headline) 77 | } 78 | .frame(width: geo.size.width * 0.22) 79 | .padding(.trailing, 5) 80 | Text(model.actors) 81 | .font(.body) 82 | Spacer() 83 | } 84 | .padding(.bottom, 5) 85 | HStack(alignment: .top) { 86 | HStack { 87 | Spacer() 88 | Text("Genre") 89 | .font(.headline) 90 | } 91 | .frame(width: geo.size.width * 0.22) 92 | .padding(.trailing, 5) 93 | Text(model.genres.joined(separator: ", ")) 94 | .font(.body) 95 | Spacer() 96 | } 97 | .padding(.bottom, 5) 98 | HStack(alignment: .top) { 99 | HStack { 100 | Spacer() 101 | Text("Run Time") 102 | .font(.headline) 103 | } 104 | .frame(width: geo.size.width * 0.22) 105 | .padding(.trailing, 5) 106 | Text(model.runTime) 107 | .font(.body) 108 | Spacer() 109 | } 110 | .padding(.bottom, 5) 111 | } 112 | .padding(.all, 8) 113 | 114 | Spacer() 115 | } 116 | .colorScheme(.dark) 117 | .background(Color.black) 118 | .statusBar(hidden: true) 119 | } 120 | } 121 | } 122 | 123 | struct MovieDetailsView_Previews: PreviewProvider { 124 | static var previews: some View { 125 | MovieTrailerView(model: TrailerModel.Example.MoneyPlane, changeHandler: { _,_ in }) 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /TheatricalMovieTrailers/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /TheatricalMovieTrailers/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // TheatricalMovieTrailers 4 | // 5 | // Created by Chris on 26.06.20. 6 | // 7 | 8 | import UIKit 9 | import SwiftUI 10 | 11 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 12 | 13 | var window: UIWindow? 14 | let viewParameters = ViewParameters() 15 | 16 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 17 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 18 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 19 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 20 | 21 | // Create the SwiftUI view that provides the window contents. 22 | let view = ContentView() 23 | let dataStore = MovieInfoDataStore.shared 24 | let contentView = view.environmentObject(dataStore).environmentObject(viewParameters) 25 | 26 | // Was the scene opened from a URL call (e. g. widget) 27 | if let urlContext = connectionOptions.urlContexts.first, urlContext.url.absoluteString.hasPrefix(MovieInfoDataStore.urlScheme) { 28 | let url = urlContext.url.absoluteString 29 | guard let id = Int(url.suffix(from: url.firstIndex(of: "=")!).dropFirst()) else { 30 | assertionFailure("Malformed id URL parameter: expected integer") 31 | return 32 | } 33 | /// App needs a bit to catch up 34 | DispatchQueue.main.asyncAfter(0.5) { [self] in 35 | viewParameters.showTrailerID = Int(id) 36 | } 37 | } 38 | 39 | // Use a UIHostingController as window root view controller. 40 | if let windowScene = scene as? UIWindowScene { 41 | let windowSceneObject = WindowSceneObject(windowScene) 42 | let contentView = contentView.environmentObject(windowSceneObject) 43 | let window = UIWindow(windowScene: windowScene) 44 | window.rootViewController = UIHostingController(rootView: contentView) 45 | self.window = window 46 | window.makeKeyAndVisible() 47 | } 48 | } 49 | 50 | func scene(_ scene: UIScene, openURLContexts urlContexts: Set) { 51 | // Was the scene opened from a URL call (e. g. widget) 52 | if let urlContext = urlContexts.first, urlContext.url.absoluteString.hasPrefix(MovieInfoDataStore.urlScheme) { 53 | let url = urlContext.url.absoluteString 54 | guard let id = Int(url.suffix(from: url.firstIndex(of: "=")!).dropFirst()) else { 55 | assertionFailure("Malformed id URL parameter: expected integer") 56 | return 57 | } 58 | /// App needs a bit to catch up 59 | DispatchQueue.main.async { [self] in 60 | viewParameters.showTrailerID = id 61 | } 62 | } 63 | } 64 | 65 | func sceneDidDisconnect(_ scene: UIScene) { 66 | // Called as the scene is being released by the system. 67 | // This occurs shortly after the scene enters the background, or when its session is discarded. 68 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 69 | // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). 70 | } 71 | 72 | func sceneDidBecomeActive(_ scene: UIScene) { 73 | // Called when the scene has moved from an inactive state to an active state. 74 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 75 | } 76 | 77 | func sceneWillResignActive(_ scene: UIScene) { 78 | // Called when the scene will move from an active state to an inactive state. 79 | // This may occur due to temporary interruptions (ex. an incoming phone call). 80 | } 81 | 82 | func sceneWillEnterForeground(_ scene: UIScene) { 83 | // Called as the scene transitions from the background to the foreground. 84 | // Use this method to undo the changes made on entering the background. 85 | } 86 | 87 | func sceneDidEnterBackground(_ scene: UIScene) { 88 | // Called as the scene transitions from the foreground to the background. 89 | // Use this method to save data, release shared resources, and store enough scene-specific state information 90 | // to restore the scene back to its current state. 91 | } 92 | 93 | 94 | 95 | } 96 | 97 | -------------------------------------------------------------------------------- /TheatricalMovieTrailers/Supporting Files/API Extensions/AVPlayerViewController+enterFullScreen.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AVPlayerViewController+enterFullScreen.swift 3 | // TheatricalMovieTrailers 4 | // 5 | // Created by Christoph Parstorfer on 21.10.20. 6 | // 7 | 8 | import AVKit 9 | 10 | /// Exposes private API to enter and exit full screen programmatically 11 | /// https://github.com/SparkDev97/iOS14-Runtime-Headers/blob/master/Frameworks/AVKit.framework/AVPlayerViewController.h#L249 12 | extension AVPlayerViewController { 13 | func enterFullScreen(animated: Bool) { 14 | perform(NSSelectorFromString("enterFullScreenAnimated:completionHandler:"), with: animated, with: nil) 15 | } 16 | func exitFullScreen(animated: Bool) { 17 | perform(NSSelectorFromString("exitFullScreenAnimated:completionHandler:"), with: animated, with: nil) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /TheatricalMovieTrailers/Supporting Files/API Extensions/UIApplication+version+build.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIApplication+version+build.swift 3 | // PresentAnything 4 | // 5 | // Created by Christoph Parstorfer on 22.08.20. 6 | // Copyright © 2020 Christoph Parstorfer. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIApplication { 12 | /// Returns the `CFBundleShortVersionString` from *Info.plist*. 13 | static var version: String { 14 | guard let appVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String else { 15 | fatalError("Couldn't find CFBundleShortVersionString in Info.plist") 16 | } 17 | return appVersion 18 | } 19 | /// Returns the `CFBundleVersion` from *Info.plist*. 20 | static var build: String { 21 | guard let build = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String else { 22 | fatalError("Couldn't find CFBundleVersion in Info.plist") 23 | } 24 | return build 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /TheatricalMovieTrailers/Supporting Files/API Extensions/UIResponder+currentResponder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIResponder+currentResponder.swift 3 | // TheatricalMovieTrailers 4 | // 5 | // Created by Christoph Parstorfer on 23.10.20. 6 | // 7 | 8 | import UIKit 9 | 10 | /// From https://stackoverflow.com/a/14135456/6870041 11 | /// via https://www.vadimbulavin.com/how-to-move-swiftui-view-when-keyboard-covers-text-field/ 12 | extension UIResponder { 13 | static var currentFirstResponder: UIResponder? { 14 | _currentFirstResponder = nil 15 | UIApplication.shared.sendAction(#selector(UIResponder.findFirstResponder(_:)), to: nil, from: nil, for: nil) 16 | return _currentFirstResponder 17 | } 18 | 19 | private static weak var _currentFirstResponder: UIResponder? 20 | 21 | @objc private func findFirstResponder(_ sender: Any) { 22 | UIResponder._currentFirstResponder = self 23 | } 24 | 25 | var globalFrame: CGRect? { 26 | guard let view = self as? UIView else { return nil } 27 | return view.superview?.convert(view.frame, to: nil) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /TheatricalMovieTrailers/Supporting Files/AppStoreReviewsManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppStoreReviewManager.swift 3 | // TheatricalMovieTrailers 4 | // 5 | // Created by Chris on 27.10.20. 6 | // 7 | 8 | import StoreKit 9 | 10 | /** 11 | Shows the App Store review prompt if appropriate. 12 | Appropriate means: 13 | - app launched > 1 minute ago 14 | - has watched at least one trailer 15 | */ 16 | class AppStoreReviewsManager { 17 | static func requestReviewIfAppropriate(in windowScene: UIWindowScene) { 18 | let settings = Settings.instance 19 | // check if app was first launched more than one minute ago 20 | guard Date().timeIntervalSince(settings.firstLaunchedDate) > 1 * 60/* seconds */ else { 21 | return 22 | } 23 | // further checks, e.g. has watched at least one trailer 24 | 25 | // check that a no request for review of this app version has been made 26 | let currentVersion = UIApplication.version 27 | let lastVersion = settings.lastReviewRequestAppVersion 28 | guard lastVersion == nil || lastVersion! != currentVersion else { 29 | return 30 | } 31 | 32 | // request for the review popup to be shown 33 | SKStoreReviewController.requestReview(in: windowScene) 34 | 35 | // update last requested version number 36 | settings.lastReviewRequestAppVersion = currentVersion 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /TheatricalMovieTrailers/Supporting Files/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 | -------------------------------------------------------------------------------- /TheatricalMovieTrailers/Supporting Files/Assets.xcassets/AppIcon.appiconset/100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TheatricalMovieTrailers/Supporting Files/Assets.xcassets/AppIcon.appiconset/100.png -------------------------------------------------------------------------------- /TheatricalMovieTrailers/Supporting Files/Assets.xcassets/AppIcon.appiconset/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TheatricalMovieTrailers/Supporting Files/Assets.xcassets/AppIcon.appiconset/1024.png -------------------------------------------------------------------------------- /TheatricalMovieTrailers/Supporting Files/Assets.xcassets/AppIcon.appiconset/114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TheatricalMovieTrailers/Supporting Files/Assets.xcassets/AppIcon.appiconset/114.png -------------------------------------------------------------------------------- /TheatricalMovieTrailers/Supporting Files/Assets.xcassets/AppIcon.appiconset/120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TheatricalMovieTrailers/Supporting Files/Assets.xcassets/AppIcon.appiconset/120.png -------------------------------------------------------------------------------- /TheatricalMovieTrailers/Supporting Files/Assets.xcassets/AppIcon.appiconset/144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TheatricalMovieTrailers/Supporting Files/Assets.xcassets/AppIcon.appiconset/144.png -------------------------------------------------------------------------------- /TheatricalMovieTrailers/Supporting Files/Assets.xcassets/AppIcon.appiconset/152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TheatricalMovieTrailers/Supporting Files/Assets.xcassets/AppIcon.appiconset/152.png -------------------------------------------------------------------------------- /TheatricalMovieTrailers/Supporting Files/Assets.xcassets/AppIcon.appiconset/167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TheatricalMovieTrailers/Supporting Files/Assets.xcassets/AppIcon.appiconset/167.png -------------------------------------------------------------------------------- /TheatricalMovieTrailers/Supporting Files/Assets.xcassets/AppIcon.appiconset/180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TheatricalMovieTrailers/Supporting Files/Assets.xcassets/AppIcon.appiconset/180.png -------------------------------------------------------------------------------- /TheatricalMovieTrailers/Supporting Files/Assets.xcassets/AppIcon.appiconset/20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TheatricalMovieTrailers/Supporting Files/Assets.xcassets/AppIcon.appiconset/20.png -------------------------------------------------------------------------------- /TheatricalMovieTrailers/Supporting Files/Assets.xcassets/AppIcon.appiconset/29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TheatricalMovieTrailers/Supporting Files/Assets.xcassets/AppIcon.appiconset/29.png -------------------------------------------------------------------------------- /TheatricalMovieTrailers/Supporting Files/Assets.xcassets/AppIcon.appiconset/40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TheatricalMovieTrailers/Supporting Files/Assets.xcassets/AppIcon.appiconset/40.png -------------------------------------------------------------------------------- /TheatricalMovieTrailers/Supporting Files/Assets.xcassets/AppIcon.appiconset/50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TheatricalMovieTrailers/Supporting Files/Assets.xcassets/AppIcon.appiconset/50.png -------------------------------------------------------------------------------- /TheatricalMovieTrailers/Supporting Files/Assets.xcassets/AppIcon.appiconset/57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TheatricalMovieTrailers/Supporting Files/Assets.xcassets/AppIcon.appiconset/57.png -------------------------------------------------------------------------------- /TheatricalMovieTrailers/Supporting Files/Assets.xcassets/AppIcon.appiconset/58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TheatricalMovieTrailers/Supporting Files/Assets.xcassets/AppIcon.appiconset/58.png -------------------------------------------------------------------------------- /TheatricalMovieTrailers/Supporting Files/Assets.xcassets/AppIcon.appiconset/60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TheatricalMovieTrailers/Supporting Files/Assets.xcassets/AppIcon.appiconset/60.png -------------------------------------------------------------------------------- /TheatricalMovieTrailers/Supporting Files/Assets.xcassets/AppIcon.appiconset/72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TheatricalMovieTrailers/Supporting Files/Assets.xcassets/AppIcon.appiconset/72.png -------------------------------------------------------------------------------- /TheatricalMovieTrailers/Supporting Files/Assets.xcassets/AppIcon.appiconset/76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TheatricalMovieTrailers/Supporting Files/Assets.xcassets/AppIcon.appiconset/76.png -------------------------------------------------------------------------------- /TheatricalMovieTrailers/Supporting Files/Assets.xcassets/AppIcon.appiconset/80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TheatricalMovieTrailers/Supporting Files/Assets.xcassets/AppIcon.appiconset/80.png -------------------------------------------------------------------------------- /TheatricalMovieTrailers/Supporting Files/Assets.xcassets/AppIcon.appiconset/87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TheatricalMovieTrailers/Supporting Files/Assets.xcassets/AppIcon.appiconset/87.png -------------------------------------------------------------------------------- /TheatricalMovieTrailers/Supporting Files/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | {"images":[{"size":"60x60","expected-size":"180","filename":"180.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"40x40","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"60x60","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"57x57","expected-size":"57","filename":"57.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"87","filename":"87.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"57x57","expected-size":"114","filename":"114.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"60","filename":"60.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"1024x1024","filename":"1024.png","expected-size":"1024","idiom":"ios-marketing","folder":"Assets.xcassets/AppIcon.appiconset/","scale":"1x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"72x72","expected-size":"72","filename":"72.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"76x76","expected-size":"152","filename":"152.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"50x50","expected-size":"100","filename":"100.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"76x76","expected-size":"76","filename":"76.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"50x50","expected-size":"50","filename":"50.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"72x72","expected-size":"144","filename":"144.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"40x40","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"83.5x83.5","expected-size":"167","filename":"167.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"20x20","expected-size":"20","filename":"20.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"}]} -------------------------------------------------------------------------------- /TheatricalMovieTrailers/Supporting Files/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /TheatricalMovieTrailers/Supporting Files/Assets.xcassets/TheatreLogo.imageset/512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TheatricalMovieTrailers/Supporting Files/Assets.xcassets/TheatreLogo.imageset/512x512.png -------------------------------------------------------------------------------- /TheatricalMovieTrailers/Supporting Files/Assets.xcassets/TheatreLogo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "512x512.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /TheatricalMovieTrailers/Supporting Files/Assets.xcassets/blackFrame.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "blackFrame@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "blackFrame@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /TheatricalMovieTrailers/Supporting Files/Assets.xcassets/blackFrame.imageset/blackFrame@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TheatricalMovieTrailers/Supporting Files/Assets.xcassets/blackFrame.imageset/blackFrame@2x.png -------------------------------------------------------------------------------- /TheatricalMovieTrailers/Supporting Files/Assets.xcassets/blackFrame.imageset/blackFrame@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TheatricalMovieTrailers/Supporting Files/Assets.xcassets/blackFrame.imageset/blackFrame@3x.png -------------------------------------------------------------------------------- /TheatricalMovieTrailers/Supporting Files/Assets.xcassets/frontRowTrailersBackground.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 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" : "1.000", 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 | -------------------------------------------------------------------------------- /TheatricalMovieTrailers/Supporting Files/Assets.xcassets/frontRowTrailersRed.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "4", 9 | "green" : "11", 10 | "red" : "194" 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" : "1.000", 26 | "blue" : "0.016", 27 | "green" : "0.043", 28 | "red" : "0.761" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /TheatricalMovieTrailers/Supporting Files/Assets.xcassets/moviePosterPlaceholder.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "moviePosterPlaceholder@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /TheatricalMovieTrailers/Supporting Files/Assets.xcassets/moviePosterPlaceholder.imageset/moviePosterPlaceholder@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TheatricalMovieTrailers/Supporting Files/Assets.xcassets/moviePosterPlaceholder.imageset/moviePosterPlaceholder@2x.png -------------------------------------------------------------------------------- /TheatricalMovieTrailers/Supporting Files/Assets.xcassets/posterMirrorImageGradientMask.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "posterMirrorImageGradientMask.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /TheatricalMovieTrailers/Supporting Files/Assets.xcassets/posterMirrorImageGradientMask.imageset/posterMirrorImageGradientMask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TheatricalMovieTrailers/Supporting Files/Assets.xcassets/posterMirrorImageGradientMask.imageset/posterMirrorImageGradientMask.png -------------------------------------------------------------------------------- /TheatricalMovieTrailers/Supporting Files/Assets.xcassets/watchedCheck.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "watchedCheck@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "watchedCheck@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /TheatricalMovieTrailers/Supporting Files/Assets.xcassets/watchedCheck.imageset/watchedCheck@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TheatricalMovieTrailers/Supporting Files/Assets.xcassets/watchedCheck.imageset/watchedCheck@2x.png -------------------------------------------------------------------------------- /TheatricalMovieTrailers/Supporting Files/Assets.xcassets/watchedCheck.imageset/watchedCheck@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TheatricalMovieTrailers/Supporting Files/Assets.xcassets/watchedCheck.imageset/watchedCheck@3x.png -------------------------------------------------------------------------------- /TheatricalMovieTrailers/Supporting Files/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 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /TheatricalMovieTrailers/Supporting Files/ShareSheet.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShareSheet.swift 3 | // TheatricalMovieTrailers 4 | // 5 | // Created by Chris on 27.10.20. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ShareSheet: UIViewControllerRepresentable { 11 | typealias Callback = (_ activityType: UIActivity.ActivityType?, _ completed: Bool, _ returnedItems: [Any]?, _ error: Error?) -> Void 12 | 13 | let activityItems: [Any] 14 | let applicationActivities: [UIActivity]? = nil 15 | let excludedActivityTypes: [UIActivity.ActivityType]? = nil 16 | let callback: Callback? = nil 17 | 18 | func makeUIViewController(context: Context) -> UIActivityViewController { 19 | let controller = UIActivityViewController( 20 | activityItems: activityItems, 21 | applicationActivities: applicationActivities) 22 | controller.excludedActivityTypes = excludedActivityTypes 23 | /// dismiss when user completed sharing 24 | controller.completionWithItemsHandler = { [self] (_ activityType: UIActivity.ActivityType?, _ completed: Bool, _ returnedItems: [Any]?, _ error: Error?) in 25 | callback?(activityType, completed, returnedItems, error) 26 | controller.dismiss(animated: true, completion: nil) 27 | } 28 | return controller 29 | } 30 | 31 | func updateUIViewController(_ uiViewController: UIActivityViewController, context: Context) { 32 | // nothing to do here 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /TheatricalMovieTrailers/TheatricalMovieTrailers.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.application-groups 6 | 7 | group.cafe.chrisp.tmt 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /TheatricalMovieTrailers/Views/CoverFlowItemView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CoverFlowListItem.swift 3 | // TheatricalMovieTrailers 4 | // 5 | // Created by Chris on 19.10.20. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct CoverFlowItemView: View { 11 | @State var frame: GeometryProxy 12 | @State var model: MovieInfo 13 | @State var onSelected: (Bool) -> () 14 | @State var onCentered: (Bool) -> () 15 | @State var isCentered = false 16 | @ObservedObject private var dataStore = MovieInfoDataStore.shared 17 | @State private var image: Image? = nil 18 | 19 | var body: some View { 20 | GeometryReader { movGeo in 21 | ZStack { 22 | CoverFlowRotatingView(envGeo: frame, content: 23 | MoviePosterView(image: $image) { 24 | onSelected(isCentered) 25 | } 26 | ) 27 | VStack { 28 | Spacer() 29 | .frame(height: movGeo.size.height / 2) 30 | 31 | Spacer() 32 | 33 | Text(model.title) 34 | .font(.largeTitle) 35 | .lineLimit(4) 36 | .multilineTextAlignment(.center) 37 | .padding(.init(top: 0, leading: 16, bottom: 32, trailing: 16)) 38 | 39 | Spacer() 40 | } 41 | .opacity(isCenteredX(container: frame, movGeo) ? 1 : 0) 42 | .animation(Animation.easeIn) 43 | } 44 | .onChange(of: movGeo.frame(in: .global).midX) { (midX) in 45 | if isCenteredX(container: frame, movGeo) { 46 | // centered now 47 | if !isCentered { 48 | // moved to center 49 | onCentered(true) 50 | isCentered = true 51 | } 52 | } else { 53 | // off center now 54 | if isCentered { 55 | // moved off center 56 | onCentered(false) 57 | isCentered = false 58 | } 59 | } 60 | } 61 | .onChange(of: dataStore.idsAndImages, perform: { _ in 62 | DispatchQueue.main.async { 63 | self.image = dataStore.idsAndImages[model.id, default: nil] 64 | } 65 | }) 66 | } 67 | } 68 | 69 | private func isCenteredX(container frame: GeometryProxy, _ geo: GeometryProxy, allowance: CGFloat = 0.1) -> Bool { 70 | let outerCenter = frame.frame(in: .local).midX 71 | let center = geo.frame(in: .global).midX 72 | return abs(outerCenter - center) < frame.size.width * allowance 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /TheatricalMovieTrailers/Views/CoverFlowListView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CoverFlowListView.swift 3 | // TheatricalMovieTrailers 4 | // 5 | // Created by Christoph Parstorfer on 14.10.20. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct CoverFlowListView: View { 11 | @State var frame: GeometryProxy 12 | @Binding var model: [MovieInfo] 13 | @State var onSelected: (MovieInfo, Bool) -> () 14 | @State var onCenteredItemChanged: (MovieInfo?) -> () 15 | 16 | var body: some View { 17 | VStack { 18 | Spacer().frame(height: frame.size.height * 0.1) 19 | HStack(alignment: .center, spacing: 0) { 20 | Spacer() 21 | .frame(width: itemWidth(frame) * 0.5, height: 100) 22 | ForEach(model) { info in 23 | CoverFlowItemView(frame: frame, model: info, onSelected: { (isCentered: Bool) in 24 | onSelected(info, isCentered) 25 | }, onCentered: { isCentered in 26 | onCenteredItemChanged(isCentered ? info : nil) 27 | }) 28 | .frame(width: itemWidth(frame), height: frame.size.height * 0.8) 29 | .id(info.id) 30 | } 31 | } 32 | } 33 | } 34 | 35 | private func itemWidth(_ geo: GeometryProxy) -> CGFloat { 36 | return min(geo.size.width * 0.5, 450) 37 | } 38 | 39 | private func isCenteredX(container frame: GeometryProxy, _ geo: GeometryProxy, allowance: CGFloat = 0.1) -> Bool { 40 | let outerCenter = frame.frame(in: .local).midX 41 | let center = geo.frame(in: .global).midX 42 | return abs(outerCenter - center) < frame.size.width * allowance 43 | } 44 | } 45 | 46 | #if DEBUG 47 | struct CoverFlowListView_Previews: PreviewProvider { 48 | static var previews: some View { 49 | GeometryReader { frame in 50 | CoverFlowListView(frame: frame, model: .constant([MovieInfo.Example.AQuietPlaceII]), onSelected: { _,_ in }, onCenteredItemChanged: { _ in }) 51 | } 52 | } 53 | } 54 | #endif 55 | -------------------------------------------------------------------------------- /TheatricalMovieTrailers/Views/CoverFlowRotatingView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CoverFlowRotatingView.swift 3 | // TheatricalMovieTrailers 4 | // 5 | // Created by Chris on 16.10.20. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct CoverFlowRotatingView: View { 11 | @State var envGeo: GeometryProxy 12 | @State var content: Content 13 | 14 | var body: some View { 15 | GeometryReader { myGeo in 16 | content 17 | .offset(x: getOffsetX(envGeo, myGeo), y: getOffsetY(envGeo, myGeo)) 18 | .rotation3DEffect(getRotationAngle(envGeo, myGeo), axis: (x: 0, y: 0.5, z: 0)) 19 | } 20 | } 21 | 22 | private func getOffsetX(_ geo: GeometryProxy, _ myGeo: GeometryProxy) -> CGFloat { 23 | let centerX = myGeo.frame(in: .global).midX 24 | let halfW = geo.frame(in: .local).midX 25 | // maps from -50% to 50% 26 | let percentW = (centerX - halfW) / geo.size.width 27 | let baseValue: CGFloat = 10 28 | return baseValue * percentW 29 | } 30 | 31 | private func getOffsetY(_ geo: GeometryProxy, _ myGeo: GeometryProxy) -> CGFloat { 32 | let centerX = myGeo.frame(in: .global).midX 33 | let halfW = geo.frame(in: .local).midX 34 | // maps from -50% to 50% 35 | let percentW = (centerX - halfW) / geo.size.width 36 | let baseValue: CGFloat = -10 37 | return baseValue * abs(percentW) 38 | } 39 | 40 | private func getRotationAngle(_ geo: GeometryProxy, _ myGeo: GeometryProxy) -> Angle { 41 | let centerX = myGeo.frame(in: .global).midX 42 | let halfW = geo.frame(in: .local).midX 43 | // maps from -50% to 50% 44 | let percentW = Double((centerX - halfW) / geo.size.width) 45 | let baseAngle: Double = -90 46 | return .degrees(percentW * baseAngle) 47 | } 48 | } 49 | 50 | #if DEBUG 51 | struct CoverFlowRotatingView_Previews: PreviewProvider { 52 | static var previews: some View { 53 | GeometryReader { geo in 54 | ScrollView(.horizontal, showsIndicators: true, content: { 55 | ForEach(0..<3) { _ in 56 | GeometryReader { myGeo in 57 | CoverFlowRotatingView(envGeo: geo, content: 58 | EmptyView() 59 | ) 60 | } 61 | } 62 | }) 63 | } 64 | } 65 | } 66 | #endif 67 | -------------------------------------------------------------------------------- /TheatricalMovieTrailers/Views/ExternalMovieMetaView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MovieMetaView.swift 3 | // TheatricalMovieTrailers 4 | // 5 | // Created by Chris on 26.06.20. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ExternalMovieMetaView: View { 11 | @Binding var model: MovieInfo 12 | 13 | var body: some View { 14 | GeometryReader { geo in 15 | let metaWidth = geo.size.width * 0.23 16 | let metaHeadlineFont = Font.system(size: 25, weight: .medium, design: .default) 17 | let metaBodyFont = Font.system(size: 25) 18 | 19 | VStack(alignment: .center) { 20 | HStack(alignment: .top) { 21 | VStack { 22 | ExtMovieMetaRow(title: "Director", value: model.director, width: metaWidth / 3, font1: metaHeadlineFont, font2: metaBodyFont) 23 | 24 | ExtMovieMetaRow(title: "Actors", value: model.actors.joined(separator: ", "), width: metaWidth / 3, font1: metaHeadlineFont, font2: metaBodyFont) 25 | 26 | ExtMovieMetaRow(title: "Genre", value: model.genres.joined(separator: "ExtMovieMetaRow "), width: metaWidth / 3, font1: metaHeadlineFont, font2: metaBodyFont) 27 | 28 | ExtMovieMetaRow(title: "Release", value: model.releaseDateString, width: metaWidth / 3, font1: metaHeadlineFont, font2: metaBodyFont) 29 | } 30 | .frame(width: metaWidth) 31 | 32 | Text(model.synopsis) 33 | .lineLimit(6) 34 | .font(.system(size: 25)) 35 | .padding([.leading, .trailing]) 36 | Spacer() 37 | 38 | } 39 | 40 | Text(model.copyright) 41 | .font(.system(size: 15)) 42 | } 43 | .padding(.all, 8) 44 | 45 | Spacer() 46 | } 47 | } 48 | } 49 | 50 | #if DEBUG 51 | struct ExternalMovieMetaView_Previews: PreviewProvider { 52 | static var previews: some View { 53 | Group { 54 | ExternalMovieMetaView(model: .constant(MovieInfo.Example.AQuietPlaceII)) 55 | .colorScheme(.dark) 56 | .background(Color.black) 57 | .previewLayout(.fixed(width: 1280, height: 720/3)) 58 | } 59 | } 60 | } 61 | #endif 62 | 63 | fileprivate struct ExtMovieMetaRow: View { 64 | let title: String 65 | let value: String 66 | let width: CGFloat 67 | let font1: Font 68 | let font2: Font 69 | 70 | var body: some View { 71 | HStack(alignment: .top) { 72 | HStack { 73 | Spacer() 74 | Text(title) 75 | .font(font1) 76 | } 77 | .frame(width: width) 78 | .padding(.trailing, 5) 79 | Text(value) 80 | .font(font2) 81 | Spacer() 82 | } 83 | .padding(.bottom, 5) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /TheatricalMovieTrailers/Views/ExternalTrailerView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExternalTrailerView.swift 3 | // MovieTrailers 4 | // 5 | // Created by Chris on 25.06.20. 6 | // 7 | 8 | import SwiftUI 9 | import AVKit 10 | 11 | struct ExternalTrailerView: View { 12 | @State var model: MovieInfo 13 | @ObservedObject private var dataStore = MovieInfoDataStore.shared 14 | @Binding var posterImage: Image? 15 | 16 | @State private var avPlayer: AVPlayer? 17 | 18 | var body: some View { 19 | GeometryReader { geo in 20 | VStack(alignment: .leading) { 21 | HStack(alignment: .top) { 22 | VStack { 23 | // Title, subtitle, trailer duration 24 | TrailerMetaView(model: $model, largeTitle: true) 25 | if let image = posterImage { 26 | HStack() { 27 | image 28 | .resizable() 29 | .aspectRatio(contentMode: .fit) 30 | .padding(.all, 5) 31 | } 32 | Spacer() 33 | } 34 | } 35 | .padding(.leading) 36 | .frame(width: geo.size.width * 0.23) 37 | // Trailer Video Player 38 | VideoPlayer(player: avPlayer) 39 | .onChange(of: dataStore.isPlaying) { isPlaying in 40 | if isPlaying { 41 | avPlayer?.play() 42 | } else { 43 | avPlayer?.pause() 44 | } 45 | } 46 | .frame(minHeight: geo.size.height * 0.7, maxHeight: .infinity) 47 | }.frame(minWidth: geo.size.width * (2/3), minHeight: geo.size.height * 0.7, maxHeight: .infinity) 48 | 49 | // Meta details 50 | ExternalMovieMetaView(model: $model) 51 | } 52 | .colorScheme(.dark) 53 | .background(Color.black) 54 | .statusBar(hidden: true) 55 | .onAppear(perform: { 56 | if let url = model.trailerURL { 57 | let avPlayer: AVPlayer? = AVPlayer(url: url) 58 | self.avPlayer = avPlayer 59 | } 60 | }) 61 | .onChange(of: model, perform: { model in 62 | self.avPlayer?.pause() 63 | if let url = model.trailerURL { 64 | let avPlayer: AVPlayer? = AVPlayer(url: url) 65 | self.avPlayer = avPlayer 66 | self.avPlayer?.play() 67 | } 68 | }) 69 | .onChange(of: dataStore.isPlaying) { isPlaying in 70 | if isPlaying { 71 | self.avPlayer?.play() 72 | } else { 73 | self.avPlayer?.pause() 74 | } 75 | } 76 | } 77 | } 78 | } 79 | 80 | #if DEBUG 81 | struct ExternalTrailerView_Previews: PreviewProvider { 82 | static var previews: some View { 83 | ExternalTrailerView(model: MovieInfo.Example.AQuietPlaceII, posterImage: .constant(.moviePosterPlaceholder)) 84 | .previewLayout(.fixed(width: 1280, height: 720)) 85 | } 86 | } 87 | #endif 88 | -------------------------------------------------------------------------------- /TheatricalMovieTrailers/Views/FramedImage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FramedImage.swift 3 | // TheatricalMovieTrailers 4 | // 5 | // Created by Christoph Parstorfer on 15.10.20. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct FramedImage: View { 11 | private let inset: CGFloat = 16 12 | @State var image: Image 13 | 14 | var body: some View { 15 | GeometryReader { geo in 16 | ZStack { 17 | image 18 | .resizable() 19 | .aspectRatio(contentMode: .fill) 20 | .frame(width: geo.size.width - inset/2, height: geo.size.height - inset/2) 21 | .clipped() 22 | Image("blackFrame") 23 | .resizable(capInsets: .init(top: inset, leading: inset, bottom: inset, trailing: inset), resizingMode: .tile) 24 | } 25 | } 26 | } 27 | } 28 | 29 | struct FramedImage_Previews: PreviewProvider { 30 | static var previews: some View { 31 | FramedImage(image: .moviePosterPlaceholder) 32 | .previewLayout(.fixed(width: 540, height: 840)) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /TheatricalMovieTrailers/Views/InlineTrailerPlayerView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TrailerPlayerView.swift 3 | // TheatricalMovieTrailers 4 | // 5 | // Created by Christoph Parstorfer on 14.10.20. 6 | // 7 | 8 | import AVKit 9 | import SwiftUI 10 | import UIKit 11 | 12 | struct InlineTrailerPlayerView: UIViewControllerRepresentable { 13 | @ObservedObject private var appDelegate = UIApplication.shared.delegate as! AppDelegate 14 | @EnvironmentObject private var windowSceneObject: WindowSceneObject 15 | 16 | func makeCoordinator() -> Coordinator { 17 | Coordinator(self, windowSceneObject) 18 | } 19 | 20 | typealias UIViewControllerType = AVPlayerViewController 21 | 22 | @State var avPlayer: AVPlayer 23 | @State var isPlaying = false 24 | let enterFullScreenOnAppear: Bool 25 | 26 | init(url: URL, enterFullScreenOnAppear: Bool = false) { 27 | _avPlayer = State(initialValue: AVPlayer(url: url)) 28 | self.enterFullScreenOnAppear = enterFullScreenOnAppear 29 | } 30 | 31 | func makeUIViewController(context: UIViewControllerRepresentableContext) -> AVPlayerViewController { 32 | let avVC = AVPlayerViewController() 33 | avVC.player = avPlayer 34 | avVC.delegate = context.coordinator 35 | avVC.entersFullScreenWhenPlaybackBegins = enterFullScreenOnAppear 36 | avVC.exitsFullScreenWhenPlaybackEnds = true 37 | if enterFullScreenOnAppear { 38 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { 39 | /// enter full screen is private API for some reason 40 | avVC.enterFullScreen(animated: true) 41 | } 42 | } 43 | return avVC 44 | } 45 | 46 | func updateUIViewController(_ uiViewController: AVPlayerViewController, context: 47 | UIViewControllerRepresentableContext) { 48 | context.coordinator.trailerPlayerView = self 49 | context.coordinator.windowSceneObject = windowSceneObject 50 | if isPlaying { 51 | avPlayer.play() 52 | } else { 53 | avPlayer.pause() 54 | } 55 | uiViewController.player = avPlayer 56 | if appDelegate.isExternalScreenConnected { 57 | DispatchQueue.main.async { 58 | uiViewController.dismiss(animated: true, completion: nil) 59 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { 60 | // user played trailer, request review 61 | if let windowScene = windowSceneObject.windowScene { 62 | AppStoreReviewsManager.requestReviewIfAppropriate(in: windowScene) 63 | } 64 | } 65 | } 66 | } 67 | } 68 | 69 | // MARK: Coordinator is AVPlayerViewControllerDelegate 70 | class Coordinator: NSObject, AVPlayerViewControllerDelegate { 71 | var trailerPlayerView: InlineTrailerPlayerView 72 | var windowSceneObject: WindowSceneObject 73 | 74 | init(_ trailerPlayerView: InlineTrailerPlayerView, _ windowSceneObject: WindowSceneObject) { 75 | self.trailerPlayerView = trailerPlayerView 76 | self.windowSceneObject = windowSceneObject 77 | super.init() 78 | } 79 | 80 | func playerViewController(_ playerViewController: AVPlayerViewController, willBeginFullScreenPresentationWithAnimationCoordinator coordinator: UIViewControllerTransitionCoordinator) { 81 | playerViewController.player?.play() 82 | trailerPlayerView.isPlaying = true 83 | } 84 | 85 | func playerViewController(_ playerViewController: AVPlayerViewController, willEndFullScreenPresentationWithAnimationCoordinator coordinator: UIViewControllerTransitionCoordinator) { 86 | playerViewController.player?.pause() 87 | trailerPlayerView.isPlaying = false 88 | playerViewController.dismiss(animated: true, completion: nil) 89 | DispatchQueue.main.async { [self] in 90 | if let windowScene = windowSceneObject.windowScene { 91 | AppStoreReviewsManager.requestReviewIfAppropriate(in: windowScene) 92 | } 93 | } 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /TheatricalMovieTrailers/Views/MovieMetaView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MovieMetaView.swift 3 | // TheatricalMovieTrailers 4 | // 5 | // Created by Chris on 26.06.20. 6 | // 7 | 8 | import SwiftUI 9 | 10 | fileprivate let labelWidth: CGFloat = 0.25 11 | 12 | struct MovieMetaView: View { 13 | @Environment(\.horizontalSizeClass) var horizontalSizeClass 14 | 15 | @Binding var model: MovieInfo 16 | 17 | var body: some View { 18 | GeometryReader { geo in 19 | let titleWidth = geo.size.width * labelWidth 20 | let metaWidth = titleWidth / 3 21 | 22 | VStack(alignment: .leading) { 23 | if horizontalSizeClass == .compact { 24 | ScrollView(.vertical, showsIndicators: true, content: { 25 | Text(model.synopsis) 26 | .lineLimit(.max) 27 | .font(.body) 28 | .padding([.leading, .trailing]) 29 | }) 30 | .frame(minHeight: 100, maxHeight: 150) 31 | 32 | Divider() 33 | MovieMetaRow(title: "Director", value: model.director, labelWidth: titleWidth) 34 | 35 | MovieMetaRow(title: "Actors", value: model.actors.joined(separator: ", "), labelWidth: titleWidth) 36 | 37 | MovieMetaRow(title: "Genre", value: model.genres.joined(separator: ", "), labelWidth: titleWidth) 38 | 39 | MovieMetaRow(title: "Release", value: model.releaseDateString, labelWidth: titleWidth) 40 | } else { 41 | HStack(alignment: .top) { 42 | VStack { 43 | MovieMetaRow(title: "Director", value: model.director, labelWidth: metaWidth) 44 | 45 | MovieMetaRow(title: "Actors", value: model.actors.joined(separator: ", "), labelWidth: metaWidth) 46 | 47 | MovieMetaRow(title: "Genre", value: model.genres.joined(separator: ", "), labelWidth: metaWidth) 48 | 49 | MovieMetaRow(title: "Release", value: model.releaseDateString, labelWidth: metaWidth) 50 | } 51 | .frame(width: geo.size.width / 3) 52 | 53 | ScrollView(.vertical, showsIndicators: true, content: { 54 | Text(model.synopsis) 55 | .lineLimit(.max) 56 | .font(.body) 57 | .padding([.leading, .trailing]) 58 | }) 59 | .frame(minHeight: 100, maxHeight: 150) 60 | } 61 | } 62 | 63 | HStack(alignment: .top) { 64 | Spacer() 65 | Text(model.copyright) 66 | .font(.caption) 67 | Spacer() 68 | } 69 | } 70 | .padding(.all, 8) 71 | } 72 | } 73 | } 74 | #if DEBUG 75 | struct MovieMetaView_Previews: PreviewProvider { 76 | static var previews: some View { 77 | Group { 78 | MovieMetaView(model: .constant(MovieInfo.Example.AQuietPlaceII)) 79 | .colorScheme(.dark) 80 | .background(Color.black) 81 | .previewLayout(.sizeThatFits) 82 | MovieMetaView(model: .constant(MovieInfo.Example.AQuietPlaceII)) 83 | .colorScheme(.dark) 84 | .background(Color.black) 85 | .previewLayout(.fixed(width: 1024, height: 350)) 86 | } 87 | } 88 | } 89 | #endif 90 | 91 | struct MovieMetaRow: View { 92 | let title: String 93 | let value: String 94 | let labelWidth: CGFloat 95 | 96 | var body: some View { 97 | HStack(alignment: .top) { 98 | HStack { 99 | Spacer() 100 | Text(title) 101 | .font(.headline) 102 | } 103 | .frame(width: labelWidth) 104 | .padding(.trailing, 5) 105 | Text(value) 106 | .font(.body) 107 | Spacer() 108 | } 109 | .padding(.bottom, 5) 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /TheatricalMovieTrailers/Views/SettingsView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SettingsView.swift 3 | // TheatricalMovieTrailers 4 | // 5 | // Created by Chris on 30.06.20. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct SettingsView: View { 11 | @Environment(\.presentationMode) var presentationMode 12 | @ObservedObject var settings = Settings.instance 13 | @ObservedObject private var dataStore = MovieInfoDataStore.shared 14 | 15 | var body: some View { 16 | NavigationView { 17 | VStack(alignment: .leading) { 18 | Toggle("Always use dark mode", isOn: $settings.prefersDarkAppearance) 19 | if settings.prefersDarkAppearance { 20 | Text("The app will always be in dark mode.") 21 | .font(.subheadline) 22 | } else { 23 | Text("The app will match your system appearance.") 24 | .font(.subheadline) 25 | } 26 | Spacer().frame(height: 22) 27 | 28 | Toggle("Interaction sounds", isOn: $settings.playUISounds) 29 | if settings.playUISounds { 30 | Text("Subtle sounds will play for some interactions.") 31 | .font(.subheadline) 32 | } else { 33 | Text("No sounds will be played for any interactions.") 34 | .font(.subheadline) 35 | } 36 | Spacer().frame(height: 22) 37 | 38 | if dataStore.moviesAvailable { 39 | Button("Update list of Trailers") { 40 | dataStore.update() 41 | } 42 | .disabled(!dataStore.streamingAvailable) 43 | Text(getLastUpdatedString()) 44 | .font(.subheadline) 45 | } else { 46 | ProgressView("Updating …") 47 | } 48 | Spacer() 49 | } 50 | .padding([.leading, .trailing], 16) 51 | .navigationTitle("Settings") 52 | } 53 | .modifier(CustomDarkAppearance()) 54 | } 55 | 56 | private func getLastUpdatedString() -> String { 57 | if let date = dataStore.lastUpdated { 58 | let dateFormatter = DateFormatter() 59 | dateFormatter.dateStyle = .short 60 | dateFormatter.doesRelativeDateFormatting = true 61 | dateFormatter.timeStyle = .none 62 | let str = NSLocalizedString("Last updated: %@", comment: "Label that tells the user when the list of trailers was last updated. A date will be inserted.") 63 | return String(format: str, dateFormatter.string(from: date)) 64 | } else { 65 | return NSLocalizedString("Trailers have never been updated.", comment: "") 66 | } 67 | } 68 | } 69 | 70 | #if DEBUG 71 | struct SettingsView_Previews: PreviewProvider { 72 | static var previews: some View { 73 | SettingsView() 74 | } 75 | } 76 | #endif 77 | -------------------------------------------------------------------------------- /TheatricalMovieTrailers/Views/TrailerMetaView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MovieMetaView.swift 3 | // TheatricalMovieTrailers 4 | // 5 | // Created by Chris on 26.06.20. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct TrailerMetaView: View { 11 | @Binding var model: MovieInfo 12 | @State var largeTitle: Bool 13 | 14 | var body: some View { 15 | VStack(alignment: .leading) { 16 | // Title 17 | Text(model.title) 18 | .font(largeTitle ? .largeTitle : .title) 19 | .bold() 20 | .padding([.top, .trailing]) 21 | HStack { 22 | // Studio 23 | Text(model.studio) 24 | .font(largeTitle ? .title3 : .callout) 25 | Spacer() 26 | // Trailer length 27 | Text("Trailer: \(model.trailerLength)") 28 | .font(largeTitle ? .title3 : .callout) 29 | } 30 | .padding(.bottom, 4) 31 | } 32 | } 33 | } 34 | 35 | #if DEBUG 36 | struct TrailerMetaView_Previews: PreviewProvider { 37 | static var previews: some View { 38 | TrailerMetaView(model: .constant(MovieInfo.Example.AQuietPlaceII), largeTitle: false) 39 | .colorScheme(.dark) 40 | .background(Color.black) 41 | .previewLayout(.sizeThatFits) 42 | } 43 | } 44 | #endif 45 | -------------------------------------------------------------------------------- /TrailersWidget/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 | -------------------------------------------------------------------------------- /TrailersWidget/Assets.xcassets/AppIcon.appiconset/100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TrailersWidget/Assets.xcassets/AppIcon.appiconset/100.png -------------------------------------------------------------------------------- /TrailersWidget/Assets.xcassets/AppIcon.appiconset/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TrailersWidget/Assets.xcassets/AppIcon.appiconset/1024.png -------------------------------------------------------------------------------- /TrailersWidget/Assets.xcassets/AppIcon.appiconset/114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TrailersWidget/Assets.xcassets/AppIcon.appiconset/114.png -------------------------------------------------------------------------------- /TrailersWidget/Assets.xcassets/AppIcon.appiconset/120-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TrailersWidget/Assets.xcassets/AppIcon.appiconset/120-1.png -------------------------------------------------------------------------------- /TrailersWidget/Assets.xcassets/AppIcon.appiconset/120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TrailersWidget/Assets.xcassets/AppIcon.appiconset/120.png -------------------------------------------------------------------------------- /TrailersWidget/Assets.xcassets/AppIcon.appiconset/144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TrailersWidget/Assets.xcassets/AppIcon.appiconset/144.png -------------------------------------------------------------------------------- /TrailersWidget/Assets.xcassets/AppIcon.appiconset/152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TrailersWidget/Assets.xcassets/AppIcon.appiconset/152.png -------------------------------------------------------------------------------- /TrailersWidget/Assets.xcassets/AppIcon.appiconset/167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TrailersWidget/Assets.xcassets/AppIcon.appiconset/167.png -------------------------------------------------------------------------------- /TrailersWidget/Assets.xcassets/AppIcon.appiconset/180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TrailersWidget/Assets.xcassets/AppIcon.appiconset/180.png -------------------------------------------------------------------------------- /TrailersWidget/Assets.xcassets/AppIcon.appiconset/20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TrailersWidget/Assets.xcassets/AppIcon.appiconset/20.png -------------------------------------------------------------------------------- /TrailersWidget/Assets.xcassets/AppIcon.appiconset/29-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TrailersWidget/Assets.xcassets/AppIcon.appiconset/29-1.png -------------------------------------------------------------------------------- /TrailersWidget/Assets.xcassets/AppIcon.appiconset/29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TrailersWidget/Assets.xcassets/AppIcon.appiconset/29.png -------------------------------------------------------------------------------- /TrailersWidget/Assets.xcassets/AppIcon.appiconset/40-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TrailersWidget/Assets.xcassets/AppIcon.appiconset/40-1.png -------------------------------------------------------------------------------- /TrailersWidget/Assets.xcassets/AppIcon.appiconset/40-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TrailersWidget/Assets.xcassets/AppIcon.appiconset/40-2.png -------------------------------------------------------------------------------- /TrailersWidget/Assets.xcassets/AppIcon.appiconset/40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TrailersWidget/Assets.xcassets/AppIcon.appiconset/40.png -------------------------------------------------------------------------------- /TrailersWidget/Assets.xcassets/AppIcon.appiconset/50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TrailersWidget/Assets.xcassets/AppIcon.appiconset/50.png -------------------------------------------------------------------------------- /TrailersWidget/Assets.xcassets/AppIcon.appiconset/57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TrailersWidget/Assets.xcassets/AppIcon.appiconset/57.png -------------------------------------------------------------------------------- /TrailersWidget/Assets.xcassets/AppIcon.appiconset/58-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TrailersWidget/Assets.xcassets/AppIcon.appiconset/58-1.png -------------------------------------------------------------------------------- /TrailersWidget/Assets.xcassets/AppIcon.appiconset/58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TrailersWidget/Assets.xcassets/AppIcon.appiconset/58.png -------------------------------------------------------------------------------- /TrailersWidget/Assets.xcassets/AppIcon.appiconset/60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TrailersWidget/Assets.xcassets/AppIcon.appiconset/60.png -------------------------------------------------------------------------------- /TrailersWidget/Assets.xcassets/AppIcon.appiconset/72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TrailersWidget/Assets.xcassets/AppIcon.appiconset/72.png -------------------------------------------------------------------------------- /TrailersWidget/Assets.xcassets/AppIcon.appiconset/76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TrailersWidget/Assets.xcassets/AppIcon.appiconset/76.png -------------------------------------------------------------------------------- /TrailersWidget/Assets.xcassets/AppIcon.appiconset/80-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TrailersWidget/Assets.xcassets/AppIcon.appiconset/80-1.png -------------------------------------------------------------------------------- /TrailersWidget/Assets.xcassets/AppIcon.appiconset/80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TrailersWidget/Assets.xcassets/AppIcon.appiconset/80.png -------------------------------------------------------------------------------- /TrailersWidget/Assets.xcassets/AppIcon.appiconset/87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TrailersWidget/Assets.xcassets/AppIcon.appiconset/87.png -------------------------------------------------------------------------------- /TrailersWidget/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "40.png", 5 | "idiom" : "iphone", 6 | "scale" : "2x", 7 | "size" : "20x20" 8 | }, 9 | { 10 | "filename" : "60.png", 11 | "idiom" : "iphone", 12 | "scale" : "3x", 13 | "size" : "20x20" 14 | }, 15 | { 16 | "filename" : "29-1.png", 17 | "idiom" : "iphone", 18 | "scale" : "1x", 19 | "size" : "29x29" 20 | }, 21 | { 22 | "filename" : "58.png", 23 | "idiom" : "iphone", 24 | "scale" : "2x", 25 | "size" : "29x29" 26 | }, 27 | { 28 | "filename" : "87.png", 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "29x29" 32 | }, 33 | { 34 | "filename" : "80.png", 35 | "idiom" : "iphone", 36 | "scale" : "2x", 37 | "size" : "40x40" 38 | }, 39 | { 40 | "filename" : "120.png", 41 | "idiom" : "iphone", 42 | "scale" : "3x", 43 | "size" : "40x40" 44 | }, 45 | { 46 | "filename" : "57.png", 47 | "idiom" : "iphone", 48 | "scale" : "1x", 49 | "size" : "57x57" 50 | }, 51 | { 52 | "filename" : "114.png", 53 | "idiom" : "iphone", 54 | "scale" : "2x", 55 | "size" : "57x57" 56 | }, 57 | { 58 | "filename" : "120-1.png", 59 | "idiom" : "iphone", 60 | "scale" : "2x", 61 | "size" : "60x60" 62 | }, 63 | { 64 | "filename" : "180.png", 65 | "idiom" : "iphone", 66 | "scale" : "3x", 67 | "size" : "60x60" 68 | }, 69 | { 70 | "filename" : "20.png", 71 | "idiom" : "ipad", 72 | "scale" : "1x", 73 | "size" : "20x20" 74 | }, 75 | { 76 | "filename" : "40-1.png", 77 | "idiom" : "ipad", 78 | "scale" : "2x", 79 | "size" : "20x20" 80 | }, 81 | { 82 | "filename" : "29.png", 83 | "idiom" : "ipad", 84 | "scale" : "1x", 85 | "size" : "29x29" 86 | }, 87 | { 88 | "filename" : "58-1.png", 89 | "idiom" : "ipad", 90 | "scale" : "2x", 91 | "size" : "29x29" 92 | }, 93 | { 94 | "filename" : "40-2.png", 95 | "idiom" : "ipad", 96 | "scale" : "1x", 97 | "size" : "40x40" 98 | }, 99 | { 100 | "filename" : "80-1.png", 101 | "idiom" : "ipad", 102 | "scale" : "2x", 103 | "size" : "40x40" 104 | }, 105 | { 106 | "filename" : "50.png", 107 | "idiom" : "ipad", 108 | "scale" : "1x", 109 | "size" : "50x50" 110 | }, 111 | { 112 | "filename" : "100.png", 113 | "idiom" : "ipad", 114 | "scale" : "2x", 115 | "size" : "50x50" 116 | }, 117 | { 118 | "filename" : "72.png", 119 | "idiom" : "ipad", 120 | "scale" : "1x", 121 | "size" : "72x72" 122 | }, 123 | { 124 | "filename" : "144.png", 125 | "idiom" : "ipad", 126 | "scale" : "2x", 127 | "size" : "72x72" 128 | }, 129 | { 130 | "filename" : "76.png", 131 | "idiom" : "ipad", 132 | "scale" : "1x", 133 | "size" : "76x76" 134 | }, 135 | { 136 | "filename" : "152.png", 137 | "idiom" : "ipad", 138 | "scale" : "2x", 139 | "size" : "76x76" 140 | }, 141 | { 142 | "filename" : "167.png", 143 | "idiom" : "ipad", 144 | "scale" : "2x", 145 | "size" : "83.5x83.5" 146 | }, 147 | { 148 | "filename" : "1024.png", 149 | "idiom" : "ios-marketing", 150 | "scale" : "1x", 151 | "size" : "1024x1024" 152 | } 153 | ], 154 | "info" : { 155 | "author" : "xcode", 156 | "version" : 1 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /TrailersWidget/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /TrailersWidget/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 | -------------------------------------------------------------------------------- /TrailersWidget/Assets.xcassets/blackFrame.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "blackFrame@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "blackFrame@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /TrailersWidget/Assets.xcassets/blackFrame.imageset/blackFrame@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TrailersWidget/Assets.xcassets/blackFrame.imageset/blackFrame@2x.png -------------------------------------------------------------------------------- /TrailersWidget/Assets.xcassets/blackFrame.imageset/blackFrame@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TrailersWidget/Assets.xcassets/blackFrame.imageset/blackFrame@3x.png -------------------------------------------------------------------------------- /TrailersWidget/Assets.xcassets/moviePosterPlaceholder.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "moviePosterPlaceholder@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /TrailersWidget/Assets.xcassets/moviePosterPlaceholder.imageset/moviePosterPlaceholder@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TrailersWidget/Assets.xcassets/moviePosterPlaceholder.imageset/moviePosterPlaceholder@2x.png -------------------------------------------------------------------------------- /TrailersWidget/Assets.xcassets/posterMirrorImageGradientMask.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "posterMirrorImageGradientMask.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /TrailersWidget/Assets.xcassets/posterMirrorImageGradientMask.imageset/posterMirrorImageGradientMask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/TrailersWidget/Assets.xcassets/posterMirrorImageGradientMask.imageset/posterMirrorImageGradientMask.png -------------------------------------------------------------------------------- /TrailersWidget/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | TrailersWidget 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 | -------------------------------------------------------------------------------- /TrailersWidget/LargeMoviePosterWithTitle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MoviePosterWIthMetadata.swift 3 | // TrailersWidgetExtension 4 | // 5 | // Created by Christoph Parstorfer on 30.10.20. 6 | // 7 | 8 | import SwiftUI 9 | import WidgetKit 10 | 11 | struct LargeMoviePosterWithTitle: View { 12 | private let filmPosterAspectRatio = CGFloat(0.7063020214) 13 | 14 | @State var info: MovieInfo 15 | @State var image: Image 16 | 17 | var body: some View { 18 | GeometryReader { frame in 19 | VStack(alignment: .center) { 20 | image 21 | .resizable() 22 | .aspectRatio(filmPosterAspectRatio, contentMode: .fit) 23 | .clipped() 24 | 25 | Text(info.title) 26 | .font(.headline) 27 | .multilineTextAlignment(.center) 28 | .frame(maxWidth: .infinity) 29 | 30 | Text(info.studio) 31 | .font(.caption) 32 | .frame(maxWidth: .infinity) 33 | } 34 | .padding(8) 35 | } 36 | .foregroundColor(.white) 37 | .background(Color.black) 38 | } 39 | } 40 | 41 | #if DEBUG 42 | struct LargeMoviePosterWithTitle_Previews: PreviewProvider { 43 | static var previews: some View { 44 | LargeMoviePosterWithTitle(info: MovieInfo.Example.AQuietPlaceII, image: Image.moviePosterPlaceholder) 45 | .previewContext(WidgetPreviewContext(family: .systemLarge)) 46 | } 47 | } 48 | #endif 49 | -------------------------------------------------------------------------------- /TrailersWidget/LargePosterReflectionWithTitle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MoviePosterWIthMetadata.swift 3 | // TrailersWidgetExtension 4 | // 5 | // Created by Christoph Parstorfer on 30.10.20. 6 | // 7 | 8 | import SwiftUI 9 | import WidgetKit 10 | 11 | struct LargePosterReflectionWithTitle: View { 12 | private let filmPosterAspectRatio = CGFloat(0.7063020214) 13 | 14 | @State var info: MovieInfo 15 | @State var image: Image 16 | @State var showMeta = false 17 | 18 | var body: some View { 19 | GeometryReader { frame in 20 | ZStack(alignment: Alignment(horizontal: .center, vertical: .top)) { 21 | HStack(alignment: .top, spacing: -frame.size.width * 0.5) { 22 | MoviePosterView(image: .constant(image)) 23 | } 24 | .frame(height: frame.size.height * (showMeta ? 1.7 : 1.875)) 25 | .offset(y: frame.size.height / 32) 26 | 27 | if showMeta { 28 | VStack { 29 | Spacer().frame(height: frame.size.height * 0.7) 30 | 31 | VStack { 32 | Spacer() 33 | Text(info.title) 34 | .font(.headline) 35 | .multilineTextAlignment(.center) 36 | .frame(maxWidth: frame.size.width) 37 | 38 | Text(info.studio) 39 | .font(.caption) 40 | .frame(maxWidth: frame.size.width) 41 | } 42 | .frame(height: frame.size.height * 0.3 - 16) 43 | .padding(frame.size.height / 64) 44 | } 45 | } 46 | } 47 | } 48 | .foregroundColor(.white) 49 | .background(Color.black) 50 | } 51 | } 52 | 53 | #if DEBUG 54 | struct LargePosterReflectionWithTitle_Previews: PreviewProvider { 55 | static var previews: some View { 56 | Group { 57 | LargePosterReflectionWithTitle(info: MovieInfo.Example.AQuietPlaceII, image: .moviePosterPlaceholder) 58 | .previewContext(WidgetPreviewContext(family: .systemLarge)) 59 | 60 | LargePosterReflectionWithTitle(info: MovieInfo.Example.AQuietPlaceII, image: .moviePosterPlaceholder, showMeta: true) 61 | .previewContext(WidgetPreviewContext(family: .systemLarge)) 62 | } 63 | } 64 | } 65 | #endif 66 | -------------------------------------------------------------------------------- /TrailersWidget/MoviePosterWithMetadata.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MoviePosterWIthMetadata.swift 3 | // TrailersWidgetExtension 4 | // 5 | // Created by Christoph Parstorfer on 30.10.20. 6 | // 7 | 8 | import SwiftUI 9 | import WidgetKit 10 | 11 | struct MoviePosterWithMetadata: View { 12 | private let filmPosterAspectRatio = CGFloat(0.7063020214) 13 | 14 | @State var info: MovieInfo 15 | @State var image: Image 16 | 17 | var body: some View { 18 | GeometryReader { frame in 19 | VStack { 20 | Text(info.studio) 21 | .multilineTextAlignment(.center) 22 | .frame(maxWidth: .infinity) 23 | .padding([.leading,.top, .trailing]) 24 | Text(info.title) 25 | .font(.title) 26 | .multilineTextAlignment(.center) 27 | .frame(maxWidth: .infinity) 28 | .padding([.leading, .trailing]) 29 | 30 | HStack(alignment: .top) { 31 | FramedImage(image: image) 32 | .frame(width: frame.size.width / 2 - 16) 33 | .aspectRatio(filmPosterAspectRatio, contentMode: .fit) 34 | .clipped() 35 | .padding(.leading, 8) 36 | 37 | VStack(alignment: .leading) { 38 | Text("Director") 39 | .font(.headline) 40 | Text(info.director) 41 | .lineLimit(2) 42 | .font(.body) 43 | .padding(.bottom, 2) 44 | Text("Actors") 45 | .font(.headline) 46 | Text(info.actors.joined(separator: ", ")) 47 | .lineLimit(3) 48 | .font(.body) 49 | .padding(.bottom, 2) 50 | Text("Genre") 51 | .font(.headline) 52 | Text(info.genres.joined(separator: ", ")) 53 | .lineLimit(2) 54 | .font(.body) 55 | } 56 | .padding(.trailing, 8) 57 | } 58 | 59 | Text("Release: \(info.releaseDateString)") 60 | .font(.caption) 61 | .padding(.bottom) 62 | } 63 | } 64 | } 65 | } 66 | 67 | #if DEBUG 68 | struct MoviePosterWithMetadata_Previews: PreviewProvider { 69 | static var previews: some View { 70 | MoviePosterWithMetadata(info: MovieInfo.Example.AQuietPlaceII, image: Image.moviePosterPlaceholder) 71 | .previewContext(WidgetPreviewContext(family: .systemLarge)) 72 | } 73 | } 74 | #endif 75 | -------------------------------------------------------------------------------- /TrailersWidget/MoviePosterWithTitle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MoviePosterWithTitle.swift 3 | // TrailersWidgetExtension 4 | // 5 | // Created by Christoph Parstorfer on 30.10.20. 6 | // 7 | 8 | import SwiftUI 9 | import WidgetKit 10 | 11 | struct MoviePosterWithTitle: View { 12 | 13 | private let filmPosterAspectRatio = CGFloat(0.7063020214) 14 | 15 | @State var info: MovieInfo 16 | @State var image: Image 17 | 18 | var body: some View { 19 | GeometryReader { frame in 20 | HStack { 21 | FramedImage(image: image) 22 | .aspectRatio(filmPosterAspectRatio, contentMode: .fit) 23 | .frame(maxWidth: frame.size.width / 2) 24 | .clipped() 25 | .padding([.leading, .bottom, .top], 8) 26 | VStack(alignment: .leading) { 27 | Text(info.studio) 28 | .lineLimit(2) 29 | .font(.subheadline) 30 | Text(info.title) 31 | .lineLimit(2) 32 | .font(.headline) 33 | HStack(alignment: .center, spacing: 0) { 34 | Text("Director: ") 35 | .font(.subheadline) 36 | Text(info.director) 37 | .font(.subheadline) 38 | .lineLimit(2) 39 | } 40 | .padding(.vertical, 2) 41 | Text("Release: \(info.releaseDateString)") 42 | .lineLimit(1) 43 | .font(.caption) 44 | } 45 | } 46 | } 47 | } 48 | } 49 | 50 | #if DEBUG 51 | struct MoviePosterWithTitle_Previews: PreviewProvider { 52 | static var previews: some View { 53 | MoviePosterWithTitle(info: MovieInfo.Example.AQuietPlaceII, image: Image.moviePosterPlaceholder) 54 | .previewContext(WidgetPreviewContext(family: .systemMedium)) 55 | } 56 | } 57 | #endif 58 | -------------------------------------------------------------------------------- /TrailersWidget/TrailersWidgetExtension.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.application-groups 6 | 7 | group.cafe.chrisp.tmt 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | _site/ 2 | .sass-cache/ 3 | .jekyll-cache/ 4 | .jekyll-metadata 5 | # Ignore folders generated by Bundler 6 | .bundle/ 7 | vendor/ 8 | -------------------------------------------------------------------------------- /docs/CNAME: -------------------------------------------------------------------------------- 1 | frontrow-trailers.app -------------------------------------------------------------------------------- /docs/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | gem 'github-pages' 3 | gem "webrick", "~> 1.7" 4 | -------------------------------------------------------------------------------- /docs/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Emil Baehr 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /docs/_includes/appstoreimages.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {% if site.ios_app_id %} 4 | 5 | 70 | 71 | {% endif %} -------------------------------------------------------------------------------- /docs/_includes/features.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | {% for feature in site.features %} 4 | 5 | {% if feature.title %} 6 |
7 |
8 | 9 | 10 | 11 | 12 |
13 |
14 |

15 | {{ feature.title }} 16 |

17 |

18 | {{ feature.description }} 19 |

20 |
21 |
22 | {% endif %} 23 | 24 | {% endfor %} 25 | 26 |
-------------------------------------------------------------------------------- /docs/_includes/footer.html: -------------------------------------------------------------------------------- 1 | 62 | -------------------------------------------------------------------------------- /docs/_includes/head.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {{ site.page_title }} 8 | 9 | 10 | 11 | 12 | 13 | {% if site.enable_smart_app_banner %} 14 | 15 | {% endif %} 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /docs/_includes/header.html: -------------------------------------------------------------------------------- 1 |
2 | 19 | 31 |
32 | -------------------------------------------------------------------------------- /docs/_includes/screencontent.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {% for file in site.static_files %} 4 | {% if file.path contains 'assets/screenshot/' %} 5 | 12 | {% elsif file.path contains 'assets/videos/' %} 13 | {% unless file.path contains 'assets/videos/Place-video-files-here.txt' %} 14 | 20 | {% endunless %} 21 | {% if file.extname == ".mov" or file.extname == ".mp4" %} 22 | 29 | {% elsif file.extname == ".ogg" %} 30 | 37 | {% elsif file.extname == ".webm" %} 38 | 45 | {% endif %} 46 | {% endif %} 47 | {% endfor %} -------------------------------------------------------------------------------- /docs/_layouts/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {% include head.html %} 5 | 6 | 7 |
8 |
9 |
10 | {% include header.html %} 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 25 | 26 | {{site.ios_screenshot_alt}} 27 | 28 |
29 |
30 |
31 |
32 |
33 |
34 |

35 | {{ site.app_name }} 36 |

37 |

38 | {{ site.app_description }} 39 |

40 |
41 |
42 | Download on the App Store 43 | 44 | {% if site.app_price %} 45 |

46 | {{ site.app_price }} 47 |

48 | {% endif %} 49 |
50 |
51 | {% if site.mac_screenshot_url %} 52 |
53 | {% if site.mac_lead %} 54 |

55 | {{ site.mac_lead }} 56 |

57 |
58 | {% endif %} 59 | {% if site.mac_heading %} 60 |

61 | {{ site.mac_heading }} 62 |

63 | {% endif %} 64 | {% if site.show_mac_app_store %} 65 | Download on the Mac App Store 66 | {% endif %} 67 |
68 |
69 | {{site.mac_screenshot_alt}} 70 |
71 |
72 | {% endif %} 73 | {% include features.html %} 74 | {% include footer.html %} 75 | 76 |
77 |
78 |
79 | 80 | 81 | -------------------------------------------------------------------------------- /docs/_layouts/page.html: -------------------------------------------------------------------------------- 1 | --- 2 | # Front matter comment to ensure Jekyll properly reads file. 3 | --- 4 | 5 | 6 | 7 | 8 | {% include head.html %} 9 | 10 | 11 |
12 |
13 | {% include header.html %} 14 |
15 | {{ content }} 16 |
17 |
18 | {% include appstoreimages.html %} 19 |
20 | 21 | 22 | -------------------------------------------------------------------------------- /docs/_pages/privacypolicy.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Privacy Policy 4 | include_in_header: false 5 | --- 6 | 7 | # Front Row Trailers Privacy Policy 8 | 9 | This website does not collect any data. 10 | 11 | Front Row Trailers (the app) downloads movie information and trailers from iTunes Trailers. In this process no cookies or other tracking technology is used, meaning your trailer watching habits are not recorded by the backend. 12 | 13 | The app collects no personally identifiable information. It collects statistical data about how often users use certain features, and which trailers users watch. These data never include any personally identifiable information, and can't be linked to you. In other words, we can see how many times a certain trailer was watched but now who watched it. The same goes for all other features in the app. 14 | 15 | Front Row Trailers is built on technologies provided by Apple Inc. See the [Apple Privacy Policy](https://www.apple.com/legal/privacy/en-ww/) and [Apple Platform Security](https://support.apple.com/guide/security/welcome/web) website for more information. 16 | 17 | iTunes is a trademark of Apple Inc., registered in the U.S. and other countries. 18 | 19 | This page was last updated on February 15th, 2022. 20 | -------------------------------------------------------------------------------- /docs/_sass/base.scss: -------------------------------------------------------------------------------- 1 | // Layout and grids 2 | $content-width: 1170px; 3 | 4 | // Fonts and sizes 5 | $font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 6 | $primary-text-color: $text-color; 7 | 8 | html { 9 | font-size: 62.5%; 10 | font-family: $font; 11 | line-height: 1; 12 | } 13 | 14 | body { 15 | font-size: 2rem; 16 | background-color: $body-color; 17 | } 18 | 19 | .subPageBody { 20 | background-color: $body-color; 21 | color: $text-color; 22 | } 23 | 24 | h1 { 25 | font-size: 3rem; 26 | } 27 | 28 | h2 { 29 | font-size: 2rem; 30 | } 31 | 32 | h3 { 33 | font-size: 2rem; 34 | } 35 | 36 | // Better font rendering 37 | body { 38 | -webkit-font-smoothing: antialiased; 39 | -moz-osx-font-smoothing: grayscale; 40 | background-color: $body-color; 41 | } 42 | 43 | a:link, 44 | a:hover, 45 | a:visited, 46 | a:active { 47 | color: $accent-color; 48 | text-decoration: none; 49 | } 50 | 51 | // Shadows 52 | $drop-shadow: drop-shadow(0px 5px 10px rgba(#000, 0.1)) 53 | drop-shadow(0px 1px 1px rgba(#000, 0.2) 54 | ); 55 | 56 | // Various resets 57 | *, 58 | *::before, 59 | *::after { 60 | -webkit-box-sizing: border-box; 61 | -moz-box-sizing: border-box; 62 | box-sizing: border-box; 63 | 64 | margin: 0; 65 | padding: 0; 66 | } 67 | -------------------------------------------------------------------------------- /docs/assets/FRT-AirPlay.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/docs/assets/FRT-AirPlay.jpg -------------------------------------------------------------------------------- /docs/assets/FRT-CoverFlow.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/docs/assets/FRT-CoverFlow.jpg -------------------------------------------------------------------------------- /docs/assets/FRT-Mac.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/docs/assets/FRT-Mac.jpg -------------------------------------------------------------------------------- /docs/assets/FRT-Search.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/docs/assets/FRT-Search.jpg -------------------------------------------------------------------------------- /docs/assets/FRT-Widgets.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/docs/assets/FRT-Widgets.jpg -------------------------------------------------------------------------------- /docs/assets/FrontRowTrailers-Mac-Framed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/docs/assets/FrontRowTrailers-Mac-Framed.png -------------------------------------------------------------------------------- /docs/assets/Theatre.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/docs/assets/Theatre.gif -------------------------------------------------------------------------------- /docs/assets/appicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/docs/assets/appicon.png -------------------------------------------------------------------------------- /docs/assets/black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/docs/assets/black.png -------------------------------------------------------------------------------- /docs/assets/blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/docs/assets/blue.png -------------------------------------------------------------------------------- /docs/assets/coral.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/docs/assets/coral.png -------------------------------------------------------------------------------- /docs/assets/flare2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/docs/assets/flare2.gif -------------------------------------------------------------------------------- /docs/assets/headerimage.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/docs/assets/headerimage.jpg -------------------------------------------------------------------------------- /docs/assets/screenshot/Theatricals-CoverFlow.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/docs/assets/screenshot/Theatricals-CoverFlow.jpg -------------------------------------------------------------------------------- /docs/assets/squircle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /docs/assets/squircle120.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /docs/assets/videos/Place-video-files-here.txt: -------------------------------------------------------------------------------- 1 | Place video files in this folder. 2 | 3 | Formats for Chrome & Firefox: 4 | .webm 5 | .ogg 6 | 7 | Formats for Safari: 8 | .mp4 9 | .mov 10 | 11 | Optimal video resolutions: 12 | 828x1792 13 | 1125x2436 14 | 1242x2688 15 | -------------------------------------------------------------------------------- /docs/assets/white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/docs/assets/white.png -------------------------------------------------------------------------------- /docs/assets/yellow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/conath/FrontRowTrailers/812420b4681b33faa057e86643ddcb1eee0f4191/docs/assets/yellow.png -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: index 3 | --- -------------------------------------------------------------------------------- /docs/main.scss: -------------------------------------------------------------------------------- 1 | --- 2 | # Front matter comment to ensure Jekyll properly reads file. 3 | --- 4 | 5 | $body-color: {{ site.body_background_color }}; 6 | $text-color: {{ site.text_color }}; 7 | 8 | $header-image: "{{ site.cover_image }}"; 9 | $device-color: "assets/{{ site.device_color }}.png"; 10 | 11 | $accent-color: {{ site.link_color }}; 12 | 13 | $header-title-color: {{ site.topbar_title_color }}; 14 | $app-title-color: {{ site.app_title_color }}; 15 | $app-price-color: {{ site.app_price_color }}; 16 | $app-description-color: {{ site.app_description_color }}; 17 | 18 | $feature-title-color: {{ site.feature_title_color }}; 19 | $feature-text-color: {{ site.feature_text_color }}; 20 | $footer-text-color: {{ site.footer_text_color }}; 21 | 22 | $header_color: {{ site.topbar_color }}; 23 | $header_transparency: {{ site.topbar_transparency }}; 24 | 25 | $image-overlay-color: {{ site.cover_overlay_color }}; 26 | $image-overlay-transparency: {{ site.cover_overlay_transparency }}; 27 | 28 | $feature-icons-foreground-color: {{ site.feature_icons_foreground_color }}; 29 | $feature-icons-background-color: {{ site.feature_icons_background_color }}; 30 | 31 | $social-icons-foreground-color: {{ site.social_icons_foreground_color }}; 32 | $social-icons-background-color: {{ site.social_icons_background_color }}; 33 | 34 | @import 35 | "base", 36 | "layout", 37 | "github-markdown" 38 | --------------------------------------------------------------------------------