diff --git a/Cocopods-Homebrew-and-Ruby-Install-Guide.md b/Cocopods-Homebrew-and-Ruby-Install-Guide.md deleted file mode 100644 index 2654e85a..00000000 --- a/Cocopods-Homebrew-and-Ruby-Install-Guide.md +++ /dev/null @@ -1,143 +0,0 @@ -# Cocopods Homebrew and Ruby Install Guide - -You'll use Hombrew (`brew`) to install Cocoapods in order to build the Firebase project. - -Homebrew is built on Ruby, and you'll want to configure Ruby (programming language) so that it's update to date. - -With the system installed version in macOS, you don't have the ability to update the libraries that come with Ruby (`gem update`). - -Gem is the tool to update Ruby packages, which is a prerequisite for Homebrew (`brew`). - -## Enter `rbenv` - -To help with managing different versions of Ruby, there is a tool called `rbenv`. - -You can use it to install a different version of Ruby, which you can then update, and then install hombrew, and then install Cocoapods. - -# When Ruby gem update fails (and how to install Cocoapods) -​ -You can't update the system version of Ruby, and you shouldn't do that anyway, since Apple manages it. Forcing it to change can cause issues with other tools that Apple ships with each verison of macOS. - -Running the command in Terminal may fail. - -`gem update` - -If you see this error, then you'll want to setup `rbenv` to manage your versions of Ruby (programming langauge). - -`Updating installed gems -Updating CFPropertyList -ERROR: While executing gem ... (Gem::FilePermissionError) - You don't have write permissions for the /Library/Ruby/Gems/2.6.0 directory.` - - -## Install `rbenv` - -Follow the [rbenv install instructors](https://github.com/rbenv/rbenv) to setup your `.zshrc` file for `rbenv` - -### Terminal setup (zsh/bash for rbenv) - -To follow the instructions you'll either need to edit your zsh setup file, or bash. In Catalina (10.15) Apple made `zsh` the new default shell in Terminal (instead of `bash`). - -`open ~/.zshrc` - -Will allow you to open and edit your .zshrc file (in your home directory) that is used by the shell `zsh` in Catalina (If you're still using bash, edit the `~/.bashrc` file) - -## Use `rbenv` to see what versions are installed - -Run the command: - -`rbenv versions` - -``` -* system (set by /Users/paulsolt/.rbenv/version) - 2.6.2 - 2.6.3 -``` - -And you can see the current "global" version of Ruby is the "system"" default (what Apple provides, and it's read-only) - -## Install a new Ruby version that will read-write - -Run the command: - -`rbenv install 2.6.3` - -## Switch Ruby Versions - -After you install a new version of Ruby, you can switch to it as the default. - -`rbenv global 2.6.3` - -Will change Ruby across the system, unless you've specified a local version (inside a directory). - -NOTE: Sometimes you need to switch back to system with `rbenv global system` if Apple related commands are failing, but generally you'll want to stay on your version) - -## Update Ruby Gems (Package Manager for Ruby) - -Gems are either applications or libraries that you can use within Ruby projects or apps. - -Run the command to keep your gems for the current version of Ruby up to date. - -`gem update` - -## Install Homebrew - -To install [Homebrew](https://brew.sh) Run - -``` -/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" -``` - -## Install Cocoapods (if you don't have it installed) - -```brew install cocoapods``` - -## (Optional) Update Cocoapods - -Update your current cocoapods version to keep it up to date after it's installed: - -``` -brew upgrade cocoapods -``` - -## Test pod - -Type the command test pod is installed, you should see normal output. - -``` -Usage: - - $ pod COMMAND - - CocoaPods, the Cocoa library package manager. - ... -``` - - -## (Extra) Occasionally you get errors running pod / gem / etc. - -Errors are one of the challenges you'll need to overcome as a developer. Maintaining your Mac is part of the job. - -I Google these errors or I follow the instructions provided in the error (read the message). Usually they have a hint. - -On my system I had overwritten an install of Ruby 2.6.3 that I believe is causing this issue. It's warning me every time I try to use the command `pod` (not good) - -``` -pod - -Ignoring json-2.3.0 because its extensions are not built. Try: gem pristine json --version 2.3.0 -Ignoring json-2.3.0 because its extensions are not built. Try: gem pristine json --version 2.3.0 -... - -``` - -Run a command to install gems with the current version you installed (2.6.3 in this case). - -Keep in mind that ruby gems are install per install of Ruby, so you might need to do this again if there's a new version of Ruby to install. - -``` -rbenv global 2.6.3 -gem pristine --all -``` - -After running `gem pristine` (and waiting 5-10 minutes) the errors went away. diff --git a/ImageFilterEditor/ImageFilterEditor.xcodeproj/project.pbxproj b/ImageFilterEditor/ImageFilterEditor.xcodeproj/project.pbxproj new file mode 100644 index 00000000..c7746f55 --- /dev/null +++ b/ImageFilterEditor/ImageFilterEditor.xcodeproj/project.pbxproj @@ -0,0 +1,350 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + E41CA4A5254A5B570053699D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41CA4A4254A5B570053699D /* AppDelegate.swift */; }; + E41CA4A7254A5B570053699D /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41CA4A6254A5B570053699D /* SceneDelegate.swift */; }; + E41CA4AC254A5B570053699D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E41CA4AA254A5B570053699D /* Main.storyboard */; }; + E41CA4AE254A5B580053699D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E41CA4AD254A5B580053699D /* Assets.xcassets */; }; + E41CA4B1254A5B580053699D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E41CA4AF254A5B580053699D /* LaunchScreen.storyboard */; }; + E45CAC9E254A5BC900CAFE8D /* ImagePostViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E45CAC9D254A5BC900CAFE8D /* ImagePostViewController.swift */; }; + E45CACA1254A5EB100CAFE8D /* UIImage+Scaling.swift in Sources */ = {isa = PBXBuildFile; fileRef = E45CACA0254A5EB100CAFE8D /* UIImage+Scaling.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + E41CA4A1254A5B570053699D /* ImageFilterEditor.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ImageFilterEditor.app; sourceTree = BUILT_PRODUCTS_DIR; }; + E41CA4A4254A5B570053699D /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + E41CA4A6254A5B570053699D /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + E41CA4AB254A5B570053699D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + E41CA4AD254A5B580053699D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + E41CA4B0254A5B580053699D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + E41CA4B2254A5B580053699D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + E45CAC9D254A5BC900CAFE8D /* ImagePostViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePostViewController.swift; sourceTree = ""; }; + E45CACA0254A5EB100CAFE8D /* UIImage+Scaling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Scaling.swift"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + E41CA49E254A5B570053699D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + E41CA498254A5B570053699D = { + isa = PBXGroup; + children = ( + E41CA4A3254A5B570053699D /* ImageFilterEditor */, + E41CA4A2254A5B570053699D /* Products */, + ); + sourceTree = ""; + }; + E41CA4A2254A5B570053699D /* Products */ = { + isa = PBXGroup; + children = ( + E41CA4A1254A5B570053699D /* ImageFilterEditor.app */, + ); + name = Products; + sourceTree = ""; + }; + E41CA4A3254A5B570053699D /* ImageFilterEditor */ = { + isa = PBXGroup; + children = ( + E41CA4A4254A5B570053699D /* AppDelegate.swift */, + E41CA4A6254A5B570053699D /* SceneDelegate.swift */, + E41CA4AA254A5B570053699D /* Main.storyboard */, + E41CA4AD254A5B580053699D /* Assets.xcassets */, + E41CA4AF254A5B580053699D /* LaunchScreen.storyboard */, + E41CA4B2254A5B580053699D /* Info.plist */, + E45CAC9D254A5BC900CAFE8D /* ImagePostViewController.swift */, + E45CACA0254A5EB100CAFE8D /* UIImage+Scaling.swift */, + ); + path = ImageFilterEditor; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + E41CA4A0254A5B570053699D /* ImageFilterEditor */ = { + isa = PBXNativeTarget; + buildConfigurationList = E41CA4B5254A5B580053699D /* Build configuration list for PBXNativeTarget "ImageFilterEditor" */; + buildPhases = ( + E41CA49D254A5B570053699D /* Sources */, + E41CA49E254A5B570053699D /* Frameworks */, + E41CA49F254A5B570053699D /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = ImageFilterEditor; + productName = ImageFilterEditor; + productReference = E41CA4A1254A5B570053699D /* ImageFilterEditor.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + E41CA499254A5B570053699D /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1200; + LastUpgradeCheck = 1200; + TargetAttributes = { + E41CA4A0254A5B570053699D = { + CreatedOnToolsVersion = 12.0.1; + }; + }; + }; + buildConfigurationList = E41CA49C254A5B570053699D /* Build configuration list for PBXProject "ImageFilterEditor" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = E41CA498254A5B570053699D; + productRefGroup = E41CA4A2254A5B570053699D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + E41CA4A0254A5B570053699D /* ImageFilterEditor */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + E41CA49F254A5B570053699D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + E41CA4B1254A5B580053699D /* LaunchScreen.storyboard in Resources */, + E41CA4AE254A5B580053699D /* Assets.xcassets in Resources */, + E41CA4AC254A5B570053699D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + E41CA49D254A5B570053699D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + E41CA4A5254A5B570053699D /* AppDelegate.swift in Sources */, + E45CACA1254A5EB100CAFE8D /* UIImage+Scaling.swift in Sources */, + E45CAC9E254A5BC900CAFE8D /* ImagePostViewController.swift in Sources */, + E41CA4A7254A5B570053699D /* SceneDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + E41CA4AA254A5B570053699D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + E41CA4AB254A5B570053699D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + E41CA4AF254A5B580053699D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + E41CA4B0254A5B580053699D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + E41CA4B3254A5B580053699D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + E41CA4B4254A5B580053699D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + E41CA4B6254A5B580053699D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 4487ZJU795; + INFOPLIST_FILE = ImageFilterEditor/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.robvance.ImageFilterEditor; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + E41CA4B7254A5B580053699D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 4487ZJU795; + INFOPLIST_FILE = ImageFilterEditor/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.robvance.ImageFilterEditor; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + E41CA49C254A5B570053699D /* Build configuration list for PBXProject "ImageFilterEditor" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + E41CA4B3254A5B580053699D /* Debug */, + E41CA4B4254A5B580053699D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + E41CA4B5254A5B580053699D /* Build configuration list for PBXNativeTarget "ImageFilterEditor" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + E41CA4B6254A5B580053699D /* Debug */, + E41CA4B7254A5B580053699D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = E41CA499254A5B570053699D /* Project object */; +} diff --git a/ImageFilterEditor/ImageFilterEditor/AppDelegate.swift b/ImageFilterEditor/ImageFilterEditor/AppDelegate.swift new file mode 100644 index 00000000..e3916d4d --- /dev/null +++ b/ImageFilterEditor/ImageFilterEditor/AppDelegate.swift @@ -0,0 +1,36 @@ +// +// AppDelegate.swift +// ImageFilterEditor +// +// Created by Rob Vance on 10/28/20. +// + +import UIKit + +@main +class AppDelegate: UIResponder, UIApplicationDelegate { + + + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + // Override point for customization after application launch. + return true + } + + // MARK: UISceneSession Lifecycle + + func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { + // Called when a new scene session is being created. + // Use this method to select a configuration to create the new scene with. + return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) + } + + func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { + // Called when the user discards a scene session. + // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. + // Use this method to release any resources that were specific to the discarded scenes, as they will not return. + } + + +} + diff --git a/ImageFilterEditor/ImageFilterEditor/Assets.xcassets/AccentColor.colorset/Contents.json b/ImageFilterEditor/ImageFilterEditor/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 00000000..eb878970 --- /dev/null +++ b/ImageFilterEditor/ImageFilterEditor/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ImageFilterEditor/ImageFilterEditor/Assets.xcassets/AppIcon.appiconset/Contents.json b/ImageFilterEditor/ImageFilterEditor/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..9221b9bb --- /dev/null +++ b/ImageFilterEditor/ImageFilterEditor/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,98 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ImageFilterEditor/ImageFilterEditor/Assets.xcassets/Contents.json b/ImageFilterEditor/ImageFilterEditor/Assets.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/ImageFilterEditor/ImageFilterEditor/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ImageFilterEditor/ImageFilterEditor/Base.lproj/LaunchScreen.storyboard b/ImageFilterEditor/ImageFilterEditor/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 00000000..865e9329 --- /dev/null +++ b/ImageFilterEditor/ImageFilterEditor/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ImageFilterEditor/ImageFilterEditor/Base.lproj/Main.storyboard b/ImageFilterEditor/ImageFilterEditor/Base.lproj/Main.storyboard new file mode 100644 index 00000000..5ea3b610 --- /dev/null +++ b/ImageFilterEditor/ImageFilterEditor/Base.lproj/Main.storyboard @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ImageFilterEditor/ImageFilterEditor/ImagePostViewController.swift b/ImageFilterEditor/ImageFilterEditor/ImagePostViewController.swift new file mode 100644 index 00000000..a6d0b567 --- /dev/null +++ b/ImageFilterEditor/ImageFilterEditor/ImagePostViewController.swift @@ -0,0 +1,153 @@ +// +// ImagePostViewController.swift +// ImageFilterEditor +// +// Created by Rob Vance on 10/28/20. +// + +import UIKit +import CoreImage +import CoreImage.CIFilterBuiltins +import Photos + +class ImagePostViewController: UIViewController { + + // MARK: - IBOutlets - + @IBOutlet weak var brightnessSlider: UISlider! + @IBOutlet weak var contrastSlider: UISlider! + @IBOutlet weak var blurSlider: UISlider! + @IBOutlet weak var saturationSlider: UISlider! + @IBOutlet weak var vignetteSlider: UISlider! + @IBOutlet weak var choosePhotoButton: UIButton! + @IBOutlet weak var imageView: UIImageView! + + // Mark: - Properties - + + private var orignalImage: UIImage? { + didSet { + guard let orignalImage = orignalImage else { + scaledImage = nil + return + } + + var scaledSize = imageView.bounds.size + let scale = imageView.contentScaleFactor + + scaledSize.width *= scale + scaledSize.height *= scale + guard let scaledUIImage = orignalImage.imageByScaling(toSize: scaledSize) else { + scaledImage = nil + return + } + scaledImage = CIImage(image: scaledUIImage) + } + } + private var scaledImage: CIImage? { + didSet { + updateImage() + } + } + private let context = CIContext() + private let colorControlsFilter = CIFilter.colorControls() + private let blurFilter = CIFilter.gaussianBlur() + + + override func viewDidLoad() { + super.viewDidLoad() + orignalImage = imageView.image + } + + + private func image(byFiltering image: CIImage) -> UIImage? { + let inputImage = image + + colorControlsFilter.inputImage = inputImage + colorControlsFilter.saturation = saturationSlider.value + colorControlsFilter.brightness = brightnessSlider.value + colorControlsFilter.contrast = contrastSlider.value + + // blur filter + let blurFilter = CIFilter.gaussianBlur() + blurFilter.inputImage = colorControlsFilter.outputImage?.clampedToExtent() + blurFilter.radius = blurSlider.value + + // vignette filter + let vignetteFilter = CIFilter.vignette() + vignetteFilter.inputImage = blurFilter.outputImage?.clampedToExtent() + vignetteFilter.intensity = vignetteSlider.value + vignetteFilter.radius = vignetteSlider.value + + guard let outputImage = blurFilter.outputImage else { return nil } + + guard let renderedCGImage = context.createCGImage(outputImage, from: inputImage.extent) else { return nil } + + return UIImage(cgImage: renderedCGImage) + } + + private func updateImage() { + if let scaledImage = scaledImage { + imageView.image = image(byFiltering: scaledImage) + } else { + imageView.image = nil + } + } + + // MARK: - IBActions - + + @IBAction func choosePhotoTapped(_ sender: Any) { + guard UIImagePickerController.isSourceTypeAvailable(.photoLibrary) else { + print("Photo library is not available.") + return + } + let imagePicker = UIImagePickerController() + imagePicker.sourceType = .photoLibrary + imagePicker.delegate = self + + present(imagePicker, animated: true) + } + + @IBAction func savePhotoTapped(_ sender: Any) { + + } + + // MARK: - Sliders - + @IBAction func brightnessChanged(_ sender: Any) { + updateImage() + } + + @IBAction func contrastChanged(_ sender: Any) { + updateImage() + } + + @IBAction func saturationChanged(_ sender: Any) { + updateImage() + } + + @IBAction func blurChanged(_ sender: Any) { + updateImage() + } + + @IBAction func vignetteChanged(_ sender: Any) { + updateImage() + } + +} + +extension ImagePostViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate { + + func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { + + if let image = info[.editedImage] as? UIImage { + orignalImage = image + } else if let image = info[.originalImage] as? UIImage { + orignalImage = image + } + + picker.dismiss(animated: true, completion: nil) + } + + func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { + picker.dismiss(animated: true, completion: nil) + } + +} diff --git a/ImageFilterEditor/ImageFilterEditor/Info.plist b/ImageFilterEditor/ImageFilterEditor/Info.plist new file mode 100644 index 00000000..d0141709 --- /dev/null +++ b/ImageFilterEditor/ImageFilterEditor/Info.plist @@ -0,0 +1,70 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + NSPhotoLibraryUsageDescription + $(PRODUCT_NAME) needs access to your library to filter images + NSPhotoLibraryAddUsageDescription + $(PRODUCT_NAME)needs access to your library to save filtered images + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + + + UIApplicationSupportsIndirectInputEvents + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/ImageFilterEditor/ImageFilterEditor/SceneDelegate.swift b/ImageFilterEditor/ImageFilterEditor/SceneDelegate.swift new file mode 100644 index 00000000..7f093900 --- /dev/null +++ b/ImageFilterEditor/ImageFilterEditor/SceneDelegate.swift @@ -0,0 +1,52 @@ +// +// SceneDelegate.swift +// ImageFilterEditor +// +// Created by Rob Vance on 10/28/20. +// + +import UIKit + +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + + var window: UIWindow? + + + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. + // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. + // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). + guard let _ = (scene as? UIWindowScene) else { return } + } + + func sceneDidDisconnect(_ scene: UIScene) { + // Called as the scene is being released by the system. + // This occurs shortly after the scene enters the background, or when its session is discarded. + // Release any resources associated with this scene that can be re-created the next time the scene connects. + // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). + } + + func sceneDidBecomeActive(_ scene: UIScene) { + // Called when the scene has moved from an inactive state to an active state. + // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. + } + + func sceneWillResignActive(_ scene: UIScene) { + // Called when the scene will move from an active state to an inactive state. + // This may occur due to temporary interruptions (ex. an incoming phone call). + } + + func sceneWillEnterForeground(_ scene: UIScene) { + // Called as the scene transitions from the background to the foreground. + // Use this method to undo the changes made on entering the background. + } + + func sceneDidEnterBackground(_ scene: UIScene) { + // Called as the scene transitions from the foreground to the background. + // Use this method to save data, release shared resources, and store enough scene-specific state information + // to restore the scene back to its current state. + } + + +} + diff --git a/ImageFilterEditor/ImageFilterEditor/UIImage+Scaling.swift b/ImageFilterEditor/ImageFilterEditor/UIImage+Scaling.swift new file mode 100644 index 00000000..9cc56207 --- /dev/null +++ b/ImageFilterEditor/ImageFilterEditor/UIImage+Scaling.swift @@ -0,0 +1,37 @@ +// +// UIImage+Scaling.swift +// ImageFilterEditor +// +// Created by Rob Vance on 10/28/20. +// + +import UIKit + +extension UIImage { + + /// Resize the image to a max dimension from size parameter + func imageByScaling(toSize size: CGSize) -> UIImage? { + guard size.width > 0 && size.height > 0 else { return nil } + + let originalAspectRatio = self.size.width/self.size.height + var correctedSize = size + + if correctedSize.width > correctedSize.width*originalAspectRatio { + correctedSize.width = correctedSize.width*originalAspectRatio + } else { + correctedSize.height = correctedSize.height/originalAspectRatio + } + + return UIGraphicsImageRenderer(size: correctedSize, format: imageRendererFormat).image { context in + draw(in: CGRect(origin: .zero, size: correctedSize)) + } + } + + /// Renders the image if the pixel data was rotated due to orientation of camera + var flattened: UIImage { + if imageOrientation == .up { return self } + return UIGraphicsImageRenderer(size: size, format: imageRendererFormat).image { context in + draw(at: .zero) + } + } +} diff --git a/LambdaTimeline.xcodeproj/project.pbxproj b/LambdaTimeline.xcodeproj/project.pbxproj index 95ea1e3d..b2af5414 100644 --- a/LambdaTimeline.xcodeproj/project.pbxproj +++ b/LambdaTimeline.xcodeproj/project.pbxproj @@ -3,63 +3,45 @@ archiveVersion = 1; classes = { }; - objectVersion = 51; + objectVersion = 50; objects = { /* Begin PBXBuildFile section */ 4646377C216FDE4B00E7FF73 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4646377B216FDE4B00E7FF73 /* AppDelegate.swift */; }; - 46463781216FDE4B00E7FF73 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4646377F216FDE4B00E7FF73 /* Main.storyboard */; }; 46463783216FDE4C00E7FF73 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 46463782216FDE4C00E7FF73 /* Assets.xcassets */; }; 46463786216FDE4C00E7FF73 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 46463784216FDE4C00E7FF73 /* LaunchScreen.storyboard */; }; 46463790216FFD1B00E7FF73 /* Post.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4646378F216FFD1B00E7FF73 /* Post.swift */; }; 46463792216FFDD900E7FF73 /* Comment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46463791216FFDD900E7FF73 /* Comment.swift */; }; - 464637992170048900E7FF73 /* FirebaseConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 464637982170048900E7FF73 /* FirebaseConvertible.swift */; }; 4646379C2170091A00E7FF73 /* PostController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4646379B2170091A00E7FF73 /* PostController.swift */; }; 46A0366A21700F5100E7FF73 /* PostsCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46A0366921700F5100E7FF73 /* PostsCollectionViewController.swift */; }; 46A0366D2170158900E7FF73 /* SignInViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46A0366C2170158900E7FF73 /* SignInViewController.swift */; }; - 46CFE6F32170757F00E7FF73 /* User+DictionaryRepresentation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46CFE6F22170757F00E7FF73 /* User+DictionaryRepresentation.swift */; }; 46CFE6F521707D0000E7FF73 /* ImagePostViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46CFE6F421707D0000E7FF73 /* ImagePostViewController.swift */; }; 46CFE6F721707FA600E7FF73 /* UIViewController+InformationalAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46CFE6F621707FA600E7FF73 /* UIViewController+InformationalAlert.swift */; }; 46CFE6F92170862C00E7FF73 /* ShiftableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46CFE6F82170862C00E7FF73 /* ShiftableViewController.swift */; }; - 46CFE6FB21714E6100E7FF73 /* Author.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46CFE6FA21714E6100E7FF73 /* Author.swift */; }; - 46CFE6FD217154F500E7FF73 /* Networking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46CFE6FC217154F500E7FF73 /* Networking.swift */; }; - 46CFE6FF2171556D00E7FF73 /* ConcurrentOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46CFE6FE2171556D00E7FF73 /* ConcurrentOperation.swift */; }; - 46CFE7012171559500E7FF73 /* FetchMediaOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46CFE7002171559500E7FF73 /* FetchMediaOperation.swift */; }; 46CFE7032171572600E7FF73 /* ImagePostCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46CFE7022171572600E7FF73 /* ImagePostCollectionViewCell.swift */; }; - 46D571F32172D43B00E7FF73 /* Cache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46D571F22172D43B00E7FF73 /* Cache.swift */; }; + 46D1A48924FF0BC4008D1CA7 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 46D1A48824FF0BC4008D1CA7 /* Main.storyboard */; }; 46D571F52173CF3E00E7FF73 /* UIImage+Ratio.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46D571F42173CF3E00E7FF73 /* UIImage+Ratio.swift */; }; 46D571F82173FC2700E7FF73 /* ImagePostDetailTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46D571F72173FC2700E7FF73 /* ImagePostDetailTableViewController.swift */; }; - E453457955DF907FCBD117F1 /* Pods_LambdaTimeline.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA39CACB677861CF84322FB5 /* Pods_LambdaTimeline.framework */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ - 33A2B89DE9E21538434BD640 /* Pods-LambdaTimeline.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LambdaTimeline.debug.xcconfig"; path = "Pods/Target Support Files/Pods-LambdaTimeline/Pods-LambdaTimeline.debug.xcconfig"; sourceTree = ""; }; 46463778216FDE4B00E7FF73 /* LambdaTimeline.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LambdaTimeline.app; sourceTree = BUILT_PRODUCTS_DIR; }; 4646377B216FDE4B00E7FF73 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 46463780216FDE4B00E7FF73 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 46463782216FDE4C00E7FF73 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 46463785216FDE4C00E7FF73 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 46463787216FDE4C00E7FF73 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 4646378F216FFD1B00E7FF73 /* Post.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Post.swift; sourceTree = ""; }; 46463791216FFDD900E7FF73 /* Comment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Comment.swift; sourceTree = ""; }; - 464637982170048900E7FF73 /* FirebaseConvertible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirebaseConvertible.swift; sourceTree = ""; }; 4646379B2170091A00E7FF73 /* PostController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostController.swift; sourceTree = ""; }; 46A0366921700F5100E7FF73 /* PostsCollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostsCollectionViewController.swift; sourceTree = ""; }; 46A0366C2170158900E7FF73 /* SignInViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInViewController.swift; sourceTree = ""; }; - 46CFE6F22170757F00E7FF73 /* User+DictionaryRepresentation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "User+DictionaryRepresentation.swift"; sourceTree = ""; }; 46CFE6F421707D0000E7FF73 /* ImagePostViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePostViewController.swift; sourceTree = ""; }; 46CFE6F621707FA600E7FF73 /* UIViewController+InformationalAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+InformationalAlert.swift"; sourceTree = ""; }; 46CFE6F82170862C00E7FF73 /* ShiftableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShiftableViewController.swift; sourceTree = ""; }; - 46CFE6FA21714E6100E7FF73 /* Author.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Author.swift; sourceTree = ""; }; - 46CFE6FC217154F500E7FF73 /* Networking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Networking.swift; sourceTree = ""; }; - 46CFE6FE2171556D00E7FF73 /* ConcurrentOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConcurrentOperation.swift; sourceTree = ""; }; - 46CFE7002171559500E7FF73 /* FetchMediaOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchMediaOperation.swift; sourceTree = ""; }; 46CFE7022171572600E7FF73 /* ImagePostCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePostCollectionViewCell.swift; sourceTree = ""; }; - 46D571F22172D43B00E7FF73 /* Cache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Cache.swift; sourceTree = ""; }; + 46D1A48824FF0BC4008D1CA7 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; 46D571F42173CF3E00E7FF73 /* UIImage+Ratio.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Ratio.swift"; sourceTree = ""; }; 46D571F72173FC2700E7FF73 /* ImagePostDetailTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePostDetailTableViewController.swift; sourceTree = ""; }; - 5D2CCD6C68779B0A70AC37FA /* Pods-LambdaTimeline.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LambdaTimeline.release.xcconfig"; path = "Pods/Target Support Files/Pods-LambdaTimeline/Pods-LambdaTimeline.release.xcconfig"; sourceTree = ""; }; - AA39CACB677861CF84322FB5 /* Pods_LambdaTimeline.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_LambdaTimeline.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -67,29 +49,17 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - E453457955DF907FCBD117F1 /* Pods_LambdaTimeline.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 2B7D7DE8669C7C706445FCCC /* Pods */ = { - isa = PBXGroup; - children = ( - 33A2B89DE9E21538434BD640 /* Pods-LambdaTimeline.debug.xcconfig */, - 5D2CCD6C68779B0A70AC37FA /* Pods-LambdaTimeline.release.xcconfig */, - ); - name = Pods; - sourceTree = ""; - }; 4646376F216FDE4B00E7FF73 = { isa = PBXGroup; children = ( 4646377A216FDE4B00E7FF73 /* LambdaTimeline */, 46463779216FDE4B00E7FF73 /* Products */, - 2B7D7DE8669C7C706445FCCC /* Pods */, - 7C2A25429FC0F968FC1D35E7 /* Frameworks */, ); sourceTree = ""; }; @@ -119,7 +89,6 @@ isa = PBXGroup; children = ( 4646378F216FFD1B00E7FF73 /* Post.swift */, - 46CFE6FA21714E6100E7FF73 /* Author.swift */, 46463791216FFDD900E7FF73 /* Comment.swift */, ); path = Models; @@ -138,7 +107,7 @@ 46463797216FFF7400E7FF73 /* Storyboards */ = { isa = PBXGroup; children = ( - 4646377F216FDE4B00E7FF73 /* Main.storyboard */, + 46D1A48824FF0BC4008D1CA7 /* Main.storyboard */, 46463784216FDE4C00E7FF73 /* LaunchScreen.storyboard */, ); path = Storyboards; @@ -171,23 +140,10 @@ path = Views; sourceTree = ""; }; - 46CFE7052171573200E7FF73 /* Operations */ = { - isa = PBXGroup; - children = ( - 46CFE7002171559500E7FF73 /* FetchMediaOperation.swift */, - 46CFE6FE2171556D00E7FF73 /* ConcurrentOperation.swift */, - ); - path = Operations; - sourceTree = ""; - }; 46CFE7062171573F00E7FF73 /* Helpers */ = { isa = PBXGroup; children = ( - 46CFE7052171573200E7FF73 /* Operations */, 46D571F62173D6D200E7FF73 /* Extensions */, - 46D571F22172D43B00E7FF73 /* Cache.swift */, - 464637982170048900E7FF73 /* FirebaseConvertible.swift */, - 46CFE6FC217154F500E7FF73 /* Networking.swift */, 46CFE6F82170862C00E7FF73 /* ShiftableViewController.swift */, ); path = Helpers; @@ -196,21 +152,12 @@ 46D571F62173D6D200E7FF73 /* Extensions */ = { isa = PBXGroup; children = ( - 46CFE6F22170757F00E7FF73 /* User+DictionaryRepresentation.swift */, 46D571F42173CF3E00E7FF73 /* UIImage+Ratio.swift */, 46CFE6F621707FA600E7FF73 /* UIViewController+InformationalAlert.swift */, ); path = Extensions; sourceTree = ""; }; - 7C2A25429FC0F968FC1D35E7 /* Frameworks */ = { - isa = PBXGroup; - children = ( - AA39CACB677861CF84322FB5 /* Pods_LambdaTimeline.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -218,12 +165,9 @@ isa = PBXNativeTarget; buildConfigurationList = 4646378A216FDE4C00E7FF73 /* Build configuration list for PBXNativeTarget "LambdaTimeline" */; buildPhases = ( - 4E9D00EBF902E9530D554E98 /* [CP] Check Pods Manifest.lock */, 46463774216FDE4B00E7FF73 /* Sources */, 46463775216FDE4B00E7FF73 /* Frameworks */, 46463776216FDE4B00E7FF73 /* Resources */, - 931381193B5D2856F9B0CA8C /* [CP] Embed Pods Frameworks */, - 8108F7AF1127B3F2AAFC77E1 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -273,93 +217,27 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 46D1A48924FF0BC4008D1CA7 /* Main.storyboard in Resources */, 46463786216FDE4C00E7FF73 /* LaunchScreen.storyboard in Resources */, 46463783216FDE4C00E7FF73 /* Assets.xcassets in Resources */, - 46463781216FDE4B00E7FF73 /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ -/* Begin PBXShellScriptBuildPhase section */ - 4E9D00EBF902E9530D554E98 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-LambdaTimeline-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - 8108F7AF1127B3F2AAFC77E1 /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-LambdaTimeline/Pods-LambdaTimeline-resources-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Copy Pods Resources"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-LambdaTimeline/Pods-LambdaTimeline-resources-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-LambdaTimeline/Pods-LambdaTimeline-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; - 931381193B5D2856F9B0CA8C /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-LambdaTimeline/Pods-LambdaTimeline-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-LambdaTimeline/Pods-LambdaTimeline-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-LambdaTimeline/Pods-LambdaTimeline-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; -/* End PBXShellScriptBuildPhase section */ - /* Begin PBXSourcesBuildPhase section */ 46463774216FDE4B00E7FF73 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 46A0366D2170158900E7FF73 /* SignInViewController.swift in Sources */, - 46CFE6FB21714E6100E7FF73 /* Author.swift in Sources */, 46CFE6F92170862C00E7FF73 /* ShiftableViewController.swift in Sources */, 4646377C216FDE4B00E7FF73 /* AppDelegate.swift in Sources */, - 46CFE6FD217154F500E7FF73 /* Networking.swift in Sources */, 46CFE6F521707D0000E7FF73 /* ImagePostViewController.swift in Sources */, - 46CFE6FF2171556D00E7FF73 /* ConcurrentOperation.swift in Sources */, 46463790216FFD1B00E7FF73 /* Post.swift in Sources */, - 46CFE6F32170757F00E7FF73 /* User+DictionaryRepresentation.swift in Sources */, - 46D571F32172D43B00E7FF73 /* Cache.swift in Sources */, - 46CFE7012171559500E7FF73 /* FetchMediaOperation.swift in Sources */, 46D571F82173FC2700E7FF73 /* ImagePostDetailTableViewController.swift in Sources */, 46CFE7032171572600E7FF73 /* ImagePostCollectionViewCell.swift in Sources */, 4646379C2170091A00E7FF73 /* PostController.swift in Sources */, - 464637992170048900E7FF73 /* FirebaseConvertible.swift in Sources */, 46A0366A21700F5100E7FF73 /* PostsCollectionViewController.swift in Sources */, 46D571F52173CF3E00E7FF73 /* UIImage+Ratio.swift in Sources */, 46463792216FFDD900E7FF73 /* Comment.swift in Sources */, @@ -370,14 +248,6 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ - 4646377F216FDE4B00E7FF73 /* Main.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 46463780216FDE4B00E7FF73 /* Base */, - ); - name = Main.storyboard; - sourceTree = ""; - }; 46463784216FDE4C00E7FF73 /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( @@ -507,17 +377,17 @@ }; 4646378B216FDE4C00E7FF73 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 33A2B89DE9E21538434BD640 /* Pods-LambdaTimeline.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = "$(SRCROOT)/LambdaTimeline/Resources/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = ""; + PRODUCT_BUNDLE_IDENTIFIER = com.LambdaSchool.LambdaTimeline; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -526,17 +396,17 @@ }; 4646378C216FDE4C00E7FF73 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 5D2CCD6C68779B0A70AC37FA /* Pods-LambdaTimeline.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = "$(SRCROOT)/LambdaTimeline/Resources/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = ""; + PRODUCT_BUNDLE_IDENTIFIER = com.LambdaSchool.LambdaTimeline; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/LambdaTimeline/Helpers/Cache.swift b/LambdaTimeline/Helpers/Cache.swift deleted file mode 100644 index 42996051..00000000 --- a/LambdaTimeline/Helpers/Cache.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// Cache.swift -// LambdaTimeline -// -// Created by Spencer Curtis on 10/13/18. -// Copyright © 2018 Lambda School. All rights reserved. -// - -import Foundation - -class Cache { - - func cache(value: Value, for key: Key) { - queue.async { - self.cache[key] = value - } - } - - func value(for key: Key) -> Value? { - return queue.sync { cache[key] } - } - - private var cache = [Key : Value]() - private let queue = DispatchQueue(label: "com.LambdaSchool.LambdaTimeline.CacheQueue") -} diff --git a/LambdaTimeline/Helpers/Extensions/User+DictionaryRepresentation.swift b/LambdaTimeline/Helpers/Extensions/User+DictionaryRepresentation.swift deleted file mode 100644 index 7740ab89..00000000 --- a/LambdaTimeline/Helpers/Extensions/User+DictionaryRepresentation.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// User+DictionaryRepresentation.swift -// LambdaTimeline -// -// Created by Spencer Curtis on 10/12/18. -// Copyright © 2018 Lambda School. All rights reserved. -// - -import Foundation -import FirebaseAuth - -extension User { - - private static let uidKey = "uid" - private static let displayNameKey = "displayName" - - var dictionaryRepresentation: [String: String] { - return [User.uidKey: uid, - User.displayNameKey: displayName ?? "No display name"] - } -} diff --git a/LambdaTimeline/Helpers/FirebaseConvertible.swift b/LambdaTimeline/Helpers/FirebaseConvertible.swift deleted file mode 100644 index a3608644..00000000 --- a/LambdaTimeline/Helpers/FirebaseConvertible.swift +++ /dev/null @@ -1,13 +0,0 @@ -// -// FirebaseConvertible.swift -// LambdaTimeline -// -// Created by Spencer Curtis on 10/11/18. -// Copyright © 2018 Lambda School. All rights reserved. -// - -import Foundation - -protocol FirebaseConvertible { - var dictionaryRepresentation: [String: Any] { get } -} diff --git a/LambdaTimeline/Helpers/Networking.swift b/LambdaTimeline/Helpers/Networking.swift deleted file mode 100644 index 90e171bf..00000000 --- a/LambdaTimeline/Helpers/Networking.swift +++ /dev/null @@ -1,51 +0,0 @@ -// -// Networking.swift -// LambdaTimeline -// -// Created by Spencer Curtis on 10/12/18. -// Copyright © 2018 Lambda School. All rights reserved. -// - -import Foundation - -enum HTTPMethod: String { - case get = "GET" - case post = "POST" - case put = "PUT" - case patch = "PATCH" - case delete = "DELETE" -} - -enum Networking { - - static func performRequestFor(url: URL, httpMethod: HTTPMethod, parameters: [String: String]? = nil, headers: [String: String]? = nil, body: Data? = nil, completion: @escaping (Data?, URLResponse?, Error?) -> Void) { - - var formattedURL: URL? = url - - if let parameters = parameters { formattedURL = format(url: url, with: parameters) } - - guard let requestURL = formattedURL else { fatalError("requestURL is nil") } - - var request = URLRequest(url: requestURL) - - request.httpMethod = httpMethod.rawValue - request.httpBody = body - - if let headers = headers { - headers.forEach { (key, value) in - request.setValue(value, forHTTPHeaderField: key) - } - } - - URLSession.shared.dataTask(with: request, completionHandler: completion).resume() - } - - static private func format(url: URL, with queryParameters: [String: String]) -> URL? { - - var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: true) - - urlComponents?.queryItems = queryParameters.compactMap({ URLQueryItem(name: $0.key, value: $0.value) }) - - return urlComponents?.url ?? nil - } -} diff --git a/LambdaTimeline/Helpers/Operations/ConcurrentOperation.swift b/LambdaTimeline/Helpers/Operations/ConcurrentOperation.swift deleted file mode 100644 index 53121118..00000000 --- a/LambdaTimeline/Helpers/Operations/ConcurrentOperation.swift +++ /dev/null @@ -1,64 +0,0 @@ -// -// ConcurrentOperation.swift -// LambdaTimeline -// -// Created by Andrew Madsen -// Copyright © 2018 Lambda School. All rights reserved. -// - -import Foundation - -class ConcurrentOperation: Operation { - - // MARK: Types - - enum State: String { - case isReady, isExecuting, isFinished - } - - // MARK: Properties - - private var _state = State.isReady - - private let stateQueue = DispatchQueue(label: "com.LambdaSchool.Astronomy.ConcurrentOperationStateQueue") - var state: State { - get { - var result: State? - let queue = self.stateQueue - queue.sync { - result = _state - } - return result! - } - - set { - let oldValue = state - willChangeValue(forKey: newValue.rawValue) - willChangeValue(forKey: oldValue.rawValue) - - stateQueue.sync { self._state = newValue } - - didChangeValue(forKey: oldValue.rawValue) - didChangeValue(forKey: newValue.rawValue) - } - } - - // MARK: NSOperation - - override dynamic var isReady: Bool { - return super.isReady && state == .isReady - } - - override dynamic var isExecuting: Bool { - return state == .isExecuting - } - - override dynamic var isFinished: Bool { - return state == .isFinished - } - - override var isAsynchronous: Bool { - return true - } - -} diff --git a/LambdaTimeline/Helpers/Operations/FetchMediaOperation.swift b/LambdaTimeline/Helpers/Operations/FetchMediaOperation.swift deleted file mode 100644 index 7632555b..00000000 --- a/LambdaTimeline/Helpers/Operations/FetchMediaOperation.swift +++ /dev/null @@ -1,59 +0,0 @@ -// -// FetchMediaOperation.swift -// LambdaTimeline -// -// Created by Spencer Curtis on 10/12/18. -// Copyright © 2018 Lambda School. All rights reserved. -// - -import Foundation - -class FetchMediaOperation: ConcurrentOperation { - - init(post: Post, postController: PostController, session: URLSession = URLSession.shared) { - self.post = post - self.postController = postController - self.session = session - super.init() - } - - override func start() { - state = .isExecuting - - let url = post.mediaURL - - let task = session.dataTask(with: url) { (data, response, error) in - defer { self.state = .isFinished } - if self.isCancelled { return } - if let error = error { - NSLog("Error fetching data for \(self.post): \(error)") - return - } - - guard let data = data else { - NSLog("No data returned from fetch media operation data task.") - return - } - - self.mediaData = data - } - task.resume() - dataTask = task - } - - override func cancel() { - dataTask?.cancel() - super.cancel() - } - - // MARK: Properties - - let post: Post - let postController: PostController - var mediaData: Data? - - private let session: URLSession - - private var dataTask: URLSessionDataTask? - -} diff --git a/LambdaTimeline/Model Controllers/PostController.swift b/LambdaTimeline/Model Controllers/PostController.swift index 29ce3869..3df87148 100644 --- a/LambdaTimeline/Model Controllers/PostController.swift +++ b/LambdaTimeline/Model Controllers/PostController.swift @@ -6,122 +6,31 @@ // Copyright © 2018 Lambda School. All rights reserved. // -import Foundation -import FirebaseAuth -import FirebaseDatabase -import FirebaseStorage +import UIKit class PostController { - func createPost(with title: String, ofType mediaType: MediaType, mediaData: Data, ratio: CGFloat? = nil, completion: @escaping (Bool) -> Void = { _ in }) { - - guard let currentUser = Auth.auth().currentUser, - let author = Author(user: currentUser) else { return } - - store(mediaData: mediaData, mediaType: mediaType) { (mediaURL) in - - guard let mediaURL = mediaURL else { completion(false); return } - - let imagePost = Post(title: title, mediaURL: mediaURL, ratio: ratio, author: author) - - self.postsRef.childByAutoId().setValue(imagePost.dictionaryRepresentation) { (error, ref) in - if let error = error { - NSLog("Error posting image post: \(error)") - completion(false) - } - - completion(true) - } - } + var posts: [Post] = [] + + var currentUser: String? { + UserDefaults.standard.string(forKey: "username") } - func addComment(with text: String, to post: inout Post) { + func createImagePost(with title: String, image: UIImage, ratio: CGFloat?) { - guard let currentUser = Auth.auth().currentUser, - let author = Author(user: currentUser) else { return } + guard let currentUser = currentUser else { return } - let comment = Comment(text: text, author: author) - post.comments.append(comment) - - savePostToFirebase(post) - } - - func observePosts(completion: @escaping (Error?) -> Void) { + let post = Post(title: title, mediaType: .image(image), ratio: ratio, author: currentUser) - postsRef.observe(.value, with: { (snapshot) in - - guard let postDictionaries = snapshot.value as? [String: [String: Any]] else { return } - - var posts: [Post] = [] - - for (key, value) in postDictionaries { - - guard let post = Post(dictionary: value, id: key) else { continue } - - posts.append(post) - } - - self.posts = posts.sorted(by: { $0.timestamp > $1.timestamp }) - - completion(nil) - - }) { (error) in - NSLog("Error fetching posts: \(error)") - } + posts.append(post) } - func savePostToFirebase(_ post: Post, completion: (Error?) -> Void = { _ in }) { - - guard let postID = post.id else { return } + func addComment(with text: String, to post: inout Post) { - let ref = postsRef.child(postID) + guard let currentUser = currentUser else { return } - ref.setValue(post.dictionaryRepresentation) - } + let comment = Comment(text: text, author: currentUser) + post.comments.append(comment) - private func store(mediaData: Data, mediaType: MediaType, completion: @escaping (URL?) -> Void) { - - let mediaID = UUID().uuidString - - let mediaRef = storageRef.child(mediaType.rawValue).child(mediaID) - - let uploadTask = mediaRef.putData(mediaData, metadata: nil) { (metadata, error) in - if let error = error { - NSLog("Error storing media data: \(error)") - completion(nil) - return - } - - if metadata == nil { - NSLog("No metadata returned from upload task.") - completion(nil) - return - } - - mediaRef.downloadURL(completion: { (url, error) in - - if let error = error { - NSLog("Error getting download url of media: \(error)") - } - - guard let url = url else { - NSLog("Download url is nil. Unable to create a Media object") - - completion(nil) - return - } - completion(url) - }) - } - - uploadTask.resume() } - - var posts: [Post] = [] - let currentUser = Auth.auth().currentUser - let postsRef = Database.database().reference().child("posts") - - let storageRef = Storage.storage().reference() - - } diff --git a/LambdaTimeline/Models/Author.swift b/LambdaTimeline/Models/Author.swift deleted file mode 100644 index 4d847ad1..00000000 --- a/LambdaTimeline/Models/Author.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// Author.swift -// LambdaTimeline -// -// Created by Spencer Curtis on 10/12/18. -// Copyright © 2018 Lambda School. All rights reserved. -// - -import Foundation -import FirebaseAuth - -struct Author: FirebaseConvertible, Equatable { - - init?(user: User) { - self.init(dictionary: user.dictionaryRepresentation) - } - - init?(dictionary: [String: Any]) { - guard let uid = dictionary[Author.uidKey] as? String, - let displayName = dictionary[Author.displayNameKey] as? String else { return nil } - - self.uid = uid - self.displayName = displayName - } - - let uid: String - let displayName: String? - - private static let uidKey = "uid" - private static let displayNameKey = "displayName" - - var dictionaryRepresentation: [String: Any] { - return [Author.uidKey: uid, - Author.displayNameKey: displayName ?? "No display name"] - } -} diff --git a/LambdaTimeline/Models/Comment.swift b/LambdaTimeline/Models/Comment.swift index a71ffe4d..a7dad6f5 100644 --- a/LambdaTimeline/Models/Comment.swift +++ b/LambdaTimeline/Models/Comment.swift @@ -7,41 +7,27 @@ // import Foundation -import FirebaseAuth -class Comment: FirebaseConvertible, Equatable { +class Comment: Hashable { static private let textKey = "text" - static private let author = "author" + static private let authorKey = "author" static private let timestampKey = "timestamp" - let text: String - let author: Author + let text: String? + let author: String let timestamp: Date - init(text: String, author: Author, timestamp: Date = Date()) { + init(text: String? = nil, author: String, timestamp: Date = Date()) { self.text = text self.author = author self.timestamp = timestamp } - init?(dictionary: [String : Any]) { - guard let text = dictionary[Comment.textKey] as? String, - let authorDictionary = dictionary[Comment.author] as? [String: Any], - let author = Author(dictionary: authorDictionary), - let timestampTimeInterval = dictionary[Comment.timestampKey] as? TimeInterval else { return nil } - - self.text = text - self.author = author - self.timestamp = Date(timeIntervalSince1970: timestampTimeInterval) - } - - var dictionaryRepresentation: [String: Any] { - return [Comment.textKey: text, - Comment.author: author.dictionaryRepresentation, - Comment.timestampKey: timestamp.timeIntervalSince1970] + func hash(into hasher: inout Hasher) { + hasher.combine(timestamp.hashValue ^ author.hashValue) } - + static func ==(lhs: Comment, rhs: Comment) -> Bool { return lhs.author == rhs.author && lhs.timestamp == rhs.timestamp diff --git a/LambdaTimeline/Models/Post.swift b/LambdaTimeline/Models/Post.swift index ec961e9f..15e1d65c 100644 --- a/LambdaTimeline/Models/Post.swift +++ b/LambdaTimeline/Models/Post.swift @@ -6,74 +6,35 @@ // Copyright © 2018 Lambda School. All rights reserved. // -import Foundation -import FirebaseAuth +import UIKit -enum MediaType: String { - case image +enum MediaType { + case image(UIImage) } -class Post { +class Post: Equatable { - init(title: String, mediaURL: URL, ratio: CGFloat? = nil, author: Author, timestamp: Date = Date()) { - self.mediaURL = mediaURL - self.ratio = ratio - self.mediaType = .image - self.author = author - self.comments = [Comment(text: title, author: author)] - self.timestamp = timestamp - } - - init?(dictionary: [String : Any], id: String) { - guard let mediaURLString = dictionary[Post.mediaKey] as? String, - let mediaURL = URL(string: mediaURLString), - let mediaTypeString = dictionary[Post.mediaTypeKey] as? String, - let mediaType = MediaType(rawValue: mediaTypeString), - let authorDictionary = dictionary[Post.authorKey] as? [String: Any], - let author = Author(dictionary: authorDictionary), - let timestampTimeInterval = dictionary[Post.timestampKey] as? TimeInterval, - let captionDictionaries = dictionary[Post.commentsKey] as? [[String: Any]] else { return nil } - - self.mediaURL = mediaURL - self.mediaType = mediaType - self.ratio = dictionary[Post.ratioKey] as? CGFloat - self.author = author - self.timestamp = Date(timeIntervalSince1970: timestampTimeInterval) - self.comments = captionDictionaries.compactMap({ Comment(dictionary: $0) }) - self.id = id - } - - var dictionaryRepresentation: [String : Any] { - var dict: [String: Any] = [Post.mediaKey: mediaURL.absoluteString, - Post.mediaTypeKey: mediaType.rawValue, - Post.commentsKey: comments.map({ $0.dictionaryRepresentation }), - Post.authorKey: author.dictionaryRepresentation, - Post.timestampKey: timestamp.timeIntervalSince1970] - - guard let ratio = self.ratio else { return dict } - - dict[Post.ratioKey] = ratio - - return dict - } - - var mediaURL: URL let mediaType: MediaType - let author: Author + let author: String let timestamp: Date var comments: [Comment] - var id: String? var ratio: CGFloat? + var id: String? var title: String? { return comments.first?.text } - static private let mediaKey = "media" - static private let ratioKey = "ratio" - static private let mediaTypeKey = "mediaType" - static private let authorKey = "author" - static private let commentsKey = "comments" - static private let timestampKey = "timestamp" - static private let idKey = "id" + init(title: String, mediaType: MediaType, ratio: CGFloat?, author: String, timestamp: Date = Date()) { + self.mediaType = mediaType + self.ratio = ratio + self.author = author + self.comments = [Comment(text: title, author: author)] + self.timestamp = timestamp + self.id = UUID().uuidString + } + + static func ==(lhs: Post, rhs: Post) -> Bool { + return lhs.id == rhs.id + } } diff --git a/LambdaTimeline/Resources/AppDelegate.swift b/LambdaTimeline/Resources/AppDelegate.swift index d889d46c..a44cc8ca 100644 --- a/LambdaTimeline/Resources/AppDelegate.swift +++ b/LambdaTimeline/Resources/AppDelegate.swift @@ -7,40 +7,14 @@ // import UIKit -import Firebase -import GoogleSignIn @UIApplicationMain -class AppDelegate: UIResponder, UIApplicationDelegate, GIDSignInDelegate { +class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - FirebaseApp.configure() - - let signIn = GIDSignIn.sharedInstance() - signIn?.clientID = FirebaseApp.app()?.options.clientID - - if Auth.auth().currentUser != nil { - let storyboard = UIStoryboard(name: "Main", bundle: nil) - let postsNavigationController = storyboard.instantiateViewController(withIdentifier: "PostsNavigationController") - window?.rootViewController = postsNavigationController - window?.makeKeyAndVisible() - } - return true } - - func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser!, - withError error: Error!) { - if let error = error { - if (error as NSError).code == GIDSignInErrorCode.hasNoAuthInKeychain.rawValue { - print("Error: The user has not signed in before or they have since signed out.") - } else { - print("\(error.localizedDescription)") - } - return - } - } } diff --git a/LambdaTimeline/Storyboards/Base.lproj/Main.storyboard b/LambdaTimeline/Storyboards/Main.storyboard similarity index 84% rename from LambdaTimeline/Storyboards/Base.lproj/Main.storyboard rename to LambdaTimeline/Storyboards/Main.storyboard index fcec352d..9e2d25d6 100644 --- a/LambdaTimeline/Storyboards/Base.lproj/Main.storyboard +++ b/LambdaTimeline/Storyboards/Main.storyboard @@ -1,9 +1,9 @@ - - + + - + @@ -12,10 +12,10 @@ - - + + - + @@ -24,7 +24,7 @@ - + @@ -88,11 +88,6 @@ - - - - - @@ -106,30 +101,30 @@ - + - + - + - + - + - + - @@ -151,7 +145,7 @@ - + @@ -168,12 +162,12 @@ - + - + - + @@ -195,15 +189,16 @@ - + - + - + + - + @@ -213,59 +208,80 @@ - + - + - - + + + + + + + + + + - + - + - + - + - + @@ -274,16 +290,16 @@ - + - @@ -305,22 +320,22 @@ - - + + - + - + + + + diff --git a/LambdaTimeline/View Controllers/ImagePostDetailTableViewController.swift b/LambdaTimeline/View Controllers/ImagePostDetailTableViewController.swift index 31b43fa3..c108edd7 100644 --- a/LambdaTimeline/View Controllers/ImagePostDetailTableViewController.swift +++ b/LambdaTimeline/View Controllers/ImagePostDetailTableViewController.swift @@ -10,6 +10,16 @@ import UIKit class ImagePostDetailTableViewController: UITableViewController { + @IBOutlet weak var imageView: UIImageView! + @IBOutlet weak var titleLabel: UILabel! + @IBOutlet weak var authorLabel: UILabel! + @IBOutlet weak var imageViewAspectRatioConstraint: NSLayoutConstraint! + @IBOutlet weak var commentButton: UIBarButtonItem! + + var post: Post! + var postController: PostController! + var imageData: Data? + override func viewDidLoad() { super.viewDidLoad() updateViews() @@ -17,15 +27,14 @@ class ImagePostDetailTableViewController: UITableViewController { func updateViews() { - guard let imageData = imageData, - let image = UIImage(data: imageData) else { return } + guard case MediaType.image(let image) = post.mediaType else { return } title = post?.title imageView.image = image titleLabel.text = post.title - authorLabel.text = post.author.displayName + authorLabel.text = post.author } // MARK: - Table view data source @@ -70,19 +79,8 @@ class ImagePostDetailTableViewController: UITableViewController { let comment = post?.comments[indexPath.row + 1] cell.textLabel?.text = comment?.text - cell.detailTextLabel?.text = comment?.author.displayName + cell.detailTextLabel?.text = comment?.author return cell } - - var post: Post! - var postController: PostController! - var imageData: Data? - - - - @IBOutlet weak var imageView: UIImageView! - @IBOutlet weak var titleLabel: UILabel! - @IBOutlet weak var authorLabel: UILabel! - @IBOutlet weak var imageViewAspectRatioConstraint: NSLayoutConstraint! } diff --git a/LambdaTimeline/View Controllers/ImagePostViewController.swift b/LambdaTimeline/View Controllers/ImagePostViewController.swift index 93c4b93e..9cffbbbb 100644 --- a/LambdaTimeline/View Controllers/ImagePostViewController.swift +++ b/LambdaTimeline/View Controllers/ImagePostViewController.swift @@ -11,29 +11,20 @@ import Photos class ImagePostViewController: ShiftableViewController { + @IBOutlet weak var imageView: UIImageView! + @IBOutlet weak var titleTextField: UITextField! + @IBOutlet weak var chooseImageButton: UIButton! + @IBOutlet weak var imageHeightConstraint: NSLayoutConstraint! + @IBOutlet weak var postButton: UIBarButtonItem! + + var postController: PostController! + var post: Post? + var imageData: Data? + override func viewDidLoad() { super.viewDidLoad() setImageViewHeight(with: 1.0) - - updateViews() - } - - func updateViews() { - - guard let imageData = imageData, - let image = UIImage(data: imageData) else { - title = "New Post" - return - } - - title = post?.title - - setImageViewHeight(with: image.ratio) - - imageView.image = image - - chooseImageButton.setTitle("", for: []) } private func presentImagePickerController() { @@ -44,11 +35,9 @@ class ImagePostViewController: ShiftableViewController { } let imagePicker = UIImagePickerController() - imagePicker.delegate = self - imagePicker.sourceType = .photoLibrary - + present(imagePicker, animated: true, completion: nil) } @@ -56,24 +45,15 @@ class ImagePostViewController: ShiftableViewController { view.endEditing(true) - guard let imageData = imageView.image?.jpegData(compressionQuality: 0.1), + guard let image = imageView.image, let title = titleTextField.text, title != "" else { - presentInformationalAlertController(title: "Uh-oh", message: "Make sure that you add a photo and a caption before posting.") - return - } - - postController.createPost(with: title, ofType: .image, mediaData: imageData, ratio: imageView.image?.ratio) { (success) in - guard success else { - DispatchQueue.main.async { - self.presentInformationalAlertController(title: "Error", message: "Unable to create post. Try again.") - } + presentInformationalAlertController(title: "Uh-oh", message: "Make sure that you add a photo and a caption before posting.") return - } - - DispatchQueue.main.async { - self.navigationController?.popViewController(animated: true) - } } + + postController.createImagePost(with: title, image: image, ratio: image.ratio) + + navigationController?.popViewController(animated: true) } @IBAction func chooseImage(_ sender: Any) { @@ -100,29 +80,22 @@ class ImagePostViewController: ShiftableViewController { self.presentInformationalAlertController(title: "Error", message: "In order to access the photo library, you must allow this application access to it.") case .restricted: self.presentInformationalAlertController(title: "Error", message: "Unable to access the photo library. Your device's restrictions do not allow access.") - - @unknown default: - print("FatalError") + default: + break } presentImagePickerController() } + @IBAction func addFilter(_ sender: Any) { + + } + func setImageViewHeight(with aspectRatio: CGFloat) { imageHeightConstraint.constant = imageView.frame.size.width * aspectRatio view.layoutSubviews() } - - var postController: PostController! - var post: Post? - var imageData: Data? - - @IBOutlet weak var imageView: UIImageView! - @IBOutlet weak var titleTextField: UITextField! - @IBOutlet weak var chooseImageButton: UIButton! - @IBOutlet weak var imageHeightConstraint: NSLayoutConstraint! - @IBOutlet weak var postButton: UIBarButtonItem! } extension ImagePostViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate { diff --git a/LambdaTimeline/View Controllers/PostsCollectionViewController.swift b/LambdaTimeline/View Controllers/PostsCollectionViewController.swift index 7a8191b4..b9ed6b54 100644 --- a/LambdaTimeline/View Controllers/PostsCollectionViewController.swift +++ b/LambdaTimeline/View Controllers/PostsCollectionViewController.swift @@ -7,29 +7,15 @@ // import UIKit -import FirebaseAuth -import FirebaseUI class PostsCollectionViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout { - override func viewDidLoad() { - super.viewDidLoad() - - postController.observePosts { (_) in - DispatchQueue.main.async { - self.collectionView.reloadData() - } - } - } + var postController: PostController! - @IBAction func signout(_ sender: Any) { - let firebaseAuth = Auth.auth() - do { - try firebaseAuth.signOut() - } catch let signOutError as NSError { - print ("Error signing out: %@", signOutError) - } - dismiss(animated: true, completion: nil) + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + collectionView.reloadData() } @IBAction func addPost(_ sender: Any) { @@ -51,10 +37,11 @@ class PostsCollectionViewController: UICollectionViewController, UICollectionVie // MARK: UICollectionViewDataSource override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { - return postController.posts.count + return postController?.posts.count ?? 0 } override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let post = postController.posts[indexPath.row] switch post.mediaType { @@ -64,8 +51,6 @@ class PostsCollectionViewController: UICollectionViewController, UICollectionVie cell.post = post - loadImage(for: cell, forItemAt: indexPath) - return cell } } @@ -76,14 +61,9 @@ class PostsCollectionViewController: UICollectionViewController, UICollectionVie let post = postController.posts[indexPath.row] - switch post.mediaType { - - case .image: - - guard let ratio = post.ratio else { return size } - - size.height = size.width * ratio - } + guard let ratio = post.ratio else { return size } + + size.height = size.width * ratio return size } @@ -98,59 +78,7 @@ class PostsCollectionViewController: UICollectionViewController, UICollectionVie } } - override func collectionView(_ collectionView: UICollectionView, didEndDisplayingSupplementaryView view: UICollectionReusableView, forElementOfKind elementKind: String, at indexPath: IndexPath) { - - guard let postID = postController.posts[indexPath.row].id else { return } - operations[postID]?.cancel() - } - func loadImage(for imagePostCell: ImagePostCollectionViewCell, forItemAt indexPath: IndexPath) { - let post = postController.posts[indexPath.row] - - guard let postID = post.id else { return } - - if let mediaData = cache.value(for: postID), - let image = UIImage(data: mediaData) { - imagePostCell.setImage(image) - self.collectionView.reloadItems(at: [indexPath]) - return - } - - let fetchOp = FetchMediaOperation(post: post, postController: postController) - - let cacheOp = BlockOperation { - if let data = fetchOp.mediaData { - self.cache.cache(value: data, for: postID) - DispatchQueue.main.async { - self.collectionView.reloadItems(at: [indexPath]) - } - } - } - - let completionOp = BlockOperation { - defer { self.operations.removeValue(forKey: postID) } - - if let currentIndexPath = self.collectionView?.indexPath(for: imagePostCell), - currentIndexPath != indexPath { - print("Got image for now-reused cell") - return - } - - if let data = fetchOp.mediaData { - imagePostCell.setImage(UIImage(data: data)) - self.collectionView.reloadItems(at: [indexPath]) - } - } - - cacheOp.addDependency(fetchOp) - completionOp.addDependency(fetchOp) - - mediaFetchQueue.addOperation(fetchOp) - mediaFetchQueue.addOperation(cacheOp) - OperationQueue.main.addOperation(completionOp) - - operations[postID] = fetchOp - } // MARK: - Navigation // In a storyboard-based application, you will often want to do a little preparation before navigation @@ -163,17 +91,10 @@ class PostsCollectionViewController: UICollectionViewController, UICollectionVie let destinationVC = segue.destination as? ImagePostDetailTableViewController - guard let indexPath = collectionView.indexPathsForSelectedItems?.first, - let postID = postController.posts[indexPath.row].id else { return } + guard let indexPath = collectionView.indexPathsForSelectedItems?.first else { return } destinationVC?.postController = postController destinationVC?.post = postController.posts[indexPath.row] - destinationVC?.imageData = cache.value(for: postID) } } - - private let postController = PostController() - private var operations = [String : Operation]() - private let mediaFetchQueue = OperationQueue() - private let cache = Cache() } diff --git a/LambdaTimeline/View Controllers/SignInViewController.swift b/LambdaTimeline/View Controllers/SignInViewController.swift index 5b44b832..c81bb748 100644 --- a/LambdaTimeline/View Controllers/SignInViewController.swift +++ b/LambdaTimeline/View Controllers/SignInViewController.swift @@ -7,70 +7,38 @@ // import UIKit -import Firebase -import GoogleSignIn class SignInViewController: UIViewController { - + + @IBOutlet weak var nameTextField: UITextField! + + let postController = PostController() + override func viewDidLoad() { super.viewDidLoad() - GIDSignIn.sharedInstance()?.presentingViewController = self - GIDSignIn.sharedInstance()?.delegate = self - - setUpSignInButton() } - @IBAction func googleSignIn(_ sender: Any) { - GIDSignIn.sharedInstance()?.signIn() + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + segueIfUsernameExists() } -} - -extension SignInViewController: GIDSignInDelegate { - func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser!, withError error: Error!) { - - if let error = error { - print("Error signing in with Google: \(error)") - return - } - - guard let authentication = user.authentication else { return } - - let credential = GoogleAuthProvider.credential(withIDToken: authentication.idToken, accessToken: authentication.accessToken) - - Auth.auth().signIn(with: credential) { (authResult, error) in - if let error = error { - print("Error signing in with Google: \(error)") - return - } - - DispatchQueue.main.async { - let storyboard = UIStoryboard(name: "Main", bundle: nil) - let postsNavigationController = storyboard.instantiateViewController(withIdentifier: "PostsNavigationController") - postsNavigationController.modalPresentationStyle = .fullScreen - self.present(postsNavigationController, animated: true, completion: nil) - } - } + + @IBAction func getStarted(_ sender: Any) { + UserDefaults.standard.set(nameTextField.text, forKey: "username") + segueIfUsernameExists() } - func sign(_ signIn: GIDSignIn!, didDisconnectWith user: GIDGoogleUser!, withError error: Error!) { - print("User disconnected") + func segueIfUsernameExists() { + if UserDefaults.standard.string(forKey: "username") != nil { + performSegue(withIdentifier: "ModalPostsVC", sender: nil) + } } - func setUpSignInButton() { - - let button = GIDSignInButton() - - button.translatesAutoresizingMaskIntoConstraints = false - - view.addSubview(button) - - - let buttonCenterXConstraint = button.centerXAnchor.constraint(equalTo: view.centerXAnchor) - let buttonCenterYConstraint = button.centerYAnchor.constraint(equalTo: view.centerYAnchor) - let buttonWidthConstraint = button.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5) - - view.addConstraints([buttonCenterXConstraint, - buttonCenterYConstraint, - buttonWidthConstraint]) + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + if segue.identifier == "ModalPostsVC" { + guard let postsVC = (segue.destination as? UINavigationController)?.topViewController as? PostsCollectionViewController else { return } + + postsVC.postController = postController + } } } diff --git a/LambdaTimeline/Views/ImagePostCollectionViewCell.swift b/LambdaTimeline/Views/ImagePostCollectionViewCell.swift index 3841cf65..f83f73ed 100644 --- a/LambdaTimeline/Views/ImagePostCollectionViewCell.swift +++ b/LambdaTimeline/Views/ImagePostCollectionViewCell.swift @@ -10,10 +10,22 @@ import UIKit class ImagePostCollectionViewCell: UICollectionViewCell { + @IBOutlet weak var imageView: UIImageView! + @IBOutlet weak var titleLabel: UILabel! + @IBOutlet weak var authorLabel: UILabel! + @IBOutlet weak var labelBackgroundView: UIView! + + var post: Post? { + didSet { + updateViews() + } + } + override func layoutSubviews() { super.layoutSubviews() setupLabelBackgroundView() } + override func prepareForReuse() { super.prepareForReuse() @@ -23,32 +35,21 @@ class ImagePostCollectionViewCell: UICollectionViewCell { } func updateViews() { - guard let post = post else { return } - + guard let post = post, + case MediaType.image(let image) = post.mediaType else { return } + titleLabel.text = post.title - authorLabel.text = post.author.displayName + authorLabel.text = post.author + imageView.image = image } - + func setupLabelBackgroundView() { labelBackgroundView.layer.cornerRadius = 8 -// labelBackgroundView.layer.borderColor = UIColor.white.cgColor -// labelBackgroundView.layer.borderWidth = 0.5 labelBackgroundView.clipsToBounds = true } func setImage(_ image: UIImage?) { imageView.image = image } - - var post: Post? { - didSet { - updateViews() - } - } - -@IBOutlet weak var imageView: UIImageView! -@IBOutlet weak var titleLabel: UILabel! -@IBOutlet weak var authorLabel: UILabel! -@IBOutlet weak var labelBackgroundView: UIView! - } + diff --git a/Podfile b/Podfile deleted file mode 100644 index 7d5e126d..00000000 --- a/Podfile +++ /dev/null @@ -1,15 +0,0 @@ -# Uncomment the next line to define a global platform for your project -platform :ios, '13.0' - -target 'LambdaTimeline' do - # Comment the next line if you're not using Swift and don't want to use dynamic frameworks - use_frameworks! - - # Pods for LambdaTimeline - pod 'Firebase/Core' - pod 'Firebase/Database' - pod 'Firebase/Storage' - pod 'Firebase/Auth' - pod 'FirebaseUI/Google' - pod 'GoogleSignIn' -end diff --git a/Podfile.lock b/Podfile.lock deleted file mode 100644 index 9d835784..00000000 --- a/Podfile.lock +++ /dev/null @@ -1,174 +0,0 @@ -PODS: - - AppAuth (1.3.0): - - AppAuth/Core (= 1.3.0) - - AppAuth/ExternalUserAgent (= 1.3.0) - - AppAuth/Core (1.3.0) - - AppAuth/ExternalUserAgent (1.3.0) - - Firebase/Auth (6.19.0): - - Firebase/CoreOnly - - FirebaseAuth (~> 6.4.3) - - Firebase/Core (6.19.0): - - Firebase/CoreOnly - - FirebaseAnalytics (= 6.3.1) - - Firebase/CoreOnly (6.19.0): - - FirebaseCore (= 6.6.4) - - Firebase/Database (6.19.0): - - Firebase/CoreOnly - - FirebaseDatabase (~> 6.1.4) - - Firebase/Storage (6.19.0): - - Firebase/CoreOnly - - FirebaseStorage (~> 3.6.0) - - FirebaseAnalytics (6.3.1): - - FirebaseCore (~> 6.6) - - FirebaseInstallations (~> 1.1) - - GoogleAppMeasurement (= 6.3.1) - - GoogleUtilities/AppDelegateSwizzler (~> 6.0) - - GoogleUtilities/MethodSwizzler (~> 6.0) - - GoogleUtilities/Network (~> 6.0) - - "GoogleUtilities/NSData+zlib (~> 6.0)" - - nanopb (= 0.3.9011) - - FirebaseAuth (6.4.3): - - FirebaseAuthInterop (~> 1.0) - - FirebaseCore (~> 6.6) - - GoogleUtilities/AppDelegateSwizzler (~> 6.5) - - GoogleUtilities/Environment (~> 6.5) - - GTMSessionFetcher/Core (~> 1.1) - - FirebaseAuthInterop (1.1.0) - - FirebaseCore (6.6.4): - - FirebaseCoreDiagnostics (~> 1.2) - - FirebaseCoreDiagnosticsInterop (~> 1.2) - - GoogleUtilities/Environment (~> 6.5) - - GoogleUtilities/Logger (~> 6.5) - - FirebaseCoreDiagnostics (1.2.2): - - FirebaseCoreDiagnosticsInterop (~> 1.2) - - GoogleDataTransportCCTSupport (~> 2.0) - - GoogleUtilities/Environment (~> 6.5) - - GoogleUtilities/Logger (~> 6.5) - - nanopb (~> 0.3.901) - - FirebaseCoreDiagnosticsInterop (1.2.0) - - FirebaseDatabase (6.1.4): - - FirebaseAuthInterop (~> 1.0) - - FirebaseCore (~> 6.0) - - leveldb-library (~> 1.22) - - FirebaseInstallations (1.1.0): - - FirebaseCore (~> 6.6) - - GoogleUtilities/UserDefaults (~> 6.5) - - PromisesObjC (~> 1.2) - - FirebaseStorage (3.6.0): - - FirebaseAuthInterop (~> 1.1) - - FirebaseCore (~> 6.6) - - GTMSessionFetcher/Core (~> 1.1) - - FirebaseUI/Auth (8.4.2): - - Firebase/Auth (~> 6.0) - - GoogleUtilities/UserDefaults - - FirebaseUI/Google (8.4.2): - - FirebaseUI/Auth - - GoogleSignIn (~> 5.0) - - GoogleAppMeasurement (6.3.1): - - GoogleUtilities/AppDelegateSwizzler (~> 6.0) - - GoogleUtilities/MethodSwizzler (~> 6.0) - - GoogleUtilities/Network (~> 6.0) - - "GoogleUtilities/NSData+zlib (~> 6.0)" - - nanopb (= 0.3.9011) - - GoogleDataTransport (5.0.0) - - GoogleDataTransportCCTSupport (2.0.0): - - GoogleDataTransport (~> 5.0) - - nanopb (~> 0.3.901) - - GoogleSignIn (5.0.2): - - AppAuth (~> 1.2) - - GTMAppAuth (~> 1.0) - - GTMSessionFetcher/Core (~> 1.1) - - GoogleUtilities/AppDelegateSwizzler (6.5.1): - - GoogleUtilities/Environment - - GoogleUtilities/Logger - - GoogleUtilities/Network - - GoogleUtilities/Environment (6.5.1) - - GoogleUtilities/Logger (6.5.1): - - GoogleUtilities/Environment - - GoogleUtilities/MethodSwizzler (6.5.1): - - GoogleUtilities/Logger - - GoogleUtilities/Network (6.5.1): - - GoogleUtilities/Logger - - "GoogleUtilities/NSData+zlib" - - GoogleUtilities/Reachability - - "GoogleUtilities/NSData+zlib (6.5.1)" - - GoogleUtilities/Reachability (6.5.1): - - GoogleUtilities/Logger - - GoogleUtilities/UserDefaults (6.5.1): - - GoogleUtilities/Logger - - GTMAppAuth (1.0.0): - - AppAuth/Core (~> 1.0) - - GTMSessionFetcher (~> 1.1) - - GTMSessionFetcher (1.3.1): - - GTMSessionFetcher/Full (= 1.3.1) - - GTMSessionFetcher/Core (1.3.1) - - GTMSessionFetcher/Full (1.3.1): - - GTMSessionFetcher/Core (= 1.3.1) - - leveldb-library (1.22) - - nanopb (0.3.9011): - - nanopb/decode (= 0.3.9011) - - nanopb/encode (= 0.3.9011) - - nanopb/decode (0.3.9011) - - nanopb/encode (0.3.9011) - - PromisesObjC (1.2.8) - -DEPENDENCIES: - - Firebase/Auth - - Firebase/Core - - Firebase/Database - - Firebase/Storage - - FirebaseUI/Google - - GoogleSignIn - -SPEC REPOS: - trunk: - - AppAuth - - Firebase - - FirebaseAnalytics - - FirebaseAuth - - FirebaseAuthInterop - - FirebaseCore - - FirebaseCoreDiagnostics - - FirebaseCoreDiagnosticsInterop - - FirebaseDatabase - - FirebaseInstallations - - FirebaseStorage - - FirebaseUI - - GoogleAppMeasurement - - GoogleDataTransport - - GoogleDataTransportCCTSupport - - GoogleSignIn - - GoogleUtilities - - GTMAppAuth - - GTMSessionFetcher - - leveldb-library - - nanopb - - PromisesObjC - -SPEC CHECKSUMS: - AppAuth: 73574f3013a1e65b9601a3ddc8b3158cce68c09d - Firebase: d55ddb0e0bb3207166cddc028647833d8a1b5b6f - FirebaseAnalytics: 572e467f3d977825266e8ccd52674aa3e6f47eac - FirebaseAuth: 5ce2b03a3d7fe56b7a6e4c5ec7ff1522890b1d6f - FirebaseAuthInterop: a0f37ae05833af156e72028f648d313f7e7592e9 - FirebaseCore: ed0a24c758a57c2b88c5efa8e6a8195e868af589 - FirebaseCoreDiagnostics: e9b4cd8ba60dee0f2d13347332e4b7898cca5b61 - FirebaseCoreDiagnosticsInterop: 296e2c5f5314500a850ad0b83e9e7c10b011a850 - FirebaseDatabase: 0144e0706a4761f1b0e8679572eba8095ddb59be - FirebaseInstallations: 575cd32f2aec0feeb0e44f5d0110a09e5e60b47b - FirebaseStorage: b43ee271294227e1a3225544b073f3b49b427f46 - FirebaseUI: e57e9b9c4340631151fbe67a14206d23d0974f37 - GoogleAppMeasurement: c29d405ff76e18551b5d158eaba6753fda8c7542 - GoogleDataTransport: a857c6a002d201b524dd4bc2ed7e7355ed07e785 - GoogleDataTransportCCTSupport: 32f75fbe904c82772fcbb6b6bd4525bfb6f2a862 - GoogleSignIn: 7137d297ddc022a7e0aa4619c86d72c909fa7213 - GoogleUtilities: 06eb53bb579efe7099152735900dd04bf09e7275 - GTMAppAuth: 4deac854479704f348309e7b66189e604cf5e01e - GTMSessionFetcher: cea130bbfe5a7edc8d06d3f0d17288c32ffe9925 - leveldb-library: 55d93ee664b4007aac644a782d11da33fba316f7 - nanopb: 18003b5e52dab79db540fe93fe9579f399bd1ccd - PromisesObjC: c119f3cd559f50b7ae681fa59dc1acd19173b7e6 - -PODFILE CHECKSUM: 28424f1a99ace6e65529f57867177322416678fa - -COCOAPODS: 1.9.1 diff --git a/README.md b/README.md index adfee3d4..d294b91f 100644 --- a/README.md +++ b/README.md @@ -23,57 +23,15 @@ Create a new Xcode project called `ImageFilterEditor` to use as a playground for 2. Create an `ImagePostViewController` that will get a starting image and filter it. 1. Add whatever UI elements you want to control the filters with the selected image. 2. For filters that require other parameters use a slider, UITextField, segmented control, or UIPanGestureRecognizer. -3. Ensure that the controls to add your filters, adjust them, etc. are only available to the user at the apropriate time. +3. Ensure that the controls to add your filters, adjust them, etc. are only available to the user at the appropriate time. 1. For example, you shouldn't let the user add a filter if they haven't selected an image yet. And it doesn't make sense to show the adjustment UI if they selected a filter that has no adjustment. 4. You can use a Collection or Table View to select filters with additional detail screens to fine tune amounts of filters (i.e.: Instagram or Apple Photo) -### Part 2 - Firebase Setup +### Part 2: Feature Integration -Though you have a base project, you will need to modify it. - -**Cocoapods** - -To begin, run `pod install` after navigating to the repo in terminal. (If you have issues with [Cocoapods, Homebrew, Ruby, follow this guide](Cocopods-Homebrew-and-Ruby-Install-Guide.md)) Work out of the generated `.xcworkspace` - - pod install - -NOTE: If you run `pod update` there may be breaking changes from newer versions of Firebase. The current Podfile.lock versions work with Swift 5.1 and Xcode 11. - -**Firebase Setup** - -[Watch the iOS Firebase Authentication Video](https://youtu.be/vGeuZtHmcMM) to follow these steps -[![Watch the iOS Firebase Authentication Video](https://tk-assets.lambdaschool.com/a8f55011-48ef-4eb9-af2b-17a7b92fae64_FirebaseVideo.png)](https://youtu.be/vGeuZtHmcMM) - -1. Create a new Firebase project (or use an existing one). -2. Change the project's bundle identifier to your own bundle identifier (e.g. `com.JohnSmith.LambdaTimeline`) -3. In the "Project Overview" in your Firebase project, you will need to add your app as we are using the Firebase SDK in our Xcode project. You will need to add the "GoogleService-Info.plist" file that will be given to you when you add the app. -4. Please refer to this page: https://firebase.google.com/docs/auth/ios/firebaseui and follow the steps under the “Set up sign-in methods”. - 1. You will only need to do the two steps under the [Google section](https://firebase.google.com/docs/auth/ios/firebaseui#google). - 2. The starter project will have a URL type already. You just need to paste the right URL scheme in. - 3. You can find the URL Type in your project file in the `Project Settings > Info > URL Types (expand) > URL Schemes` -5. In the Firebase project's database, change the rules to: -``` JSON -{ - "rules": { - ".read": "auth != null", - ".write": "auth != null" - } -} -``` -This will allow only users of the app who are authenticated to access the database. (Authentication is already taken care of in the starter project) - -6. In the left pane of your Firebase project under "Develop", click the Storage item. Click the "Get Started" button and it will pull up a modal window about security rules. Simply click "Got it". It will set Storage's rules to allow access to any authenticated user, which works great for our uses. - -Firebase Storage is essentially a Google Drive for data in your Firebase. It makes sense to use Storage in this application as we will be storing images, audio, and video data. If you're curious as to how Database and Storage interact, feel free to read Firebase's Storage documentation and look at the code in the base project. Particularly in the `Post`, `Media` and `PostController` objects. (Don't feel like you have to, however) - -At this point, run the app on your simulator or physical device in order to make sure that you've set up your Firebase Project correctly. If set up correctly, you should be able to create posts, comment on them, and have them get sent to Firebase. You should also be able to re-run the app and have the posts and comments get fetched correctly. If this does not work, the likely scenario is that you've not set up your Firebase project correctly. If you can't figure out what's wrong, please reach out to your TL. - -*Note: If you have a free Apple Developer account you may need to reuse your Bundle ID from a previous project to run on a real device (Apple limits you to three unique identifiers).* - -### Part 3: Feature Integration - -1. Integrate your `ImagePostViewController` into the Lambda Timeline Firebase project. -2. You should be able to create a new post, select a photo, edit the photo, and post it to your Firebase server. +1. Integrate your `ImagePostViewController` into the Lambda Timeline project. +2. You should be able to create a new post, select a photo, edit the photo, and create an image post. +3. Be sure that you are able to add comments to posts. ## Go Further