diff --git a/DoIt.xcodeproj/project.pbxproj b/DoIt.xcodeproj/project.pbxproj index 5bc0398..e7e5c7f 100644 --- a/DoIt.xcodeproj/project.pbxproj +++ b/DoIt.xcodeproj/project.pbxproj @@ -12,12 +12,18 @@ 7C6382E62728807900FED2F4 /* UIColorsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C6382E52728807900FED2F4 /* UIColorsExtension.swift */; }; 7C6382E8272883AC00FED2F4 /* Printer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C6382E7272883AC00FED2F4 /* Printer.swift */; }; 7C6382F02729CCC300FED2F4 /* StringExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C6382EF2729CCC300FED2F4 /* StringExtension.swift */; }; + 7C6C3BC4275C05E000B6101F /* UserService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C6C3BC3275C05E000B6101F /* UserService.swift */; }; + 7C6C3BC8275C06B400B6101F /* AuthResultsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C6C3BC7275C06B400B6101F /* AuthResultsModel.swift */; }; + 7C6C3BCA275C072E00B6101F /* FirebaseConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C6C3BC9275C072E00B6101F /* FirebaseConstants.swift */; }; + 7C6C3BCC275C176C00B6101F /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 7C6C3BCB275C176C00B6101F /* GoogleService-Info.plist */; }; 7CAC5DCE27232C6000E2D70B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CAC5DCD27232C6000E2D70B /* AppDelegate.swift */; }; 7CAC5DD027232C6000E2D70B /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CAC5DCF27232C6000E2D70B /* SceneDelegate.swift */; }; + 83349BE227682D72008D4786 /* TasksViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83349BE127682D72008D4786 /* TasksViewModel.swift */; }; + 83349BE6276959F4008D4786 /* AuthViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83349BE5276959F4008D4786 /* AuthViewModel.swift */; }; 83369A40273855BD0025F3D9 /* TasksController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83369A3F273855BD0025F3D9 /* TasksController.swift */; }; 83369A44273856240025F3D9 /* TaskTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83369A43273856240025F3D9 /* TaskTableViewCell.swift */; }; 83369A4A273856950025F3D9 /* ChapterCollectionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83369A49273856950025F3D9 /* ChapterCollectionCell.swift */; }; - 83369A4E273857020025F3D9 /* Structures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83369A4D273857020025F3D9 /* Structures.swift */; }; + 83369A4E273857020025F3D9 /* TaskModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83369A4D273857020025F3D9 /* TaskModel.swift */; }; 8383503527583E58008847E1 /* Auth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8383502227583E57008847E1 /* Auth.swift */; }; 8383503627583E58008847E1 /* CustomNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8383502327583E57008847E1 /* CustomNavigationController.swift */; }; 8383503727583E58008847E1 /* SignInController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8383502527583E57008847E1 /* SignInController.swift */; }; @@ -38,14 +44,19 @@ 83AF41A827431F9600CA8AAA /* FeedCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83AF41A727431F9600CA8AAA /* FeedCollectionViewCell.swift */; }; D893BC3F82A1BFD5850B5F99 /* Pods_DoIt.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A133F16700AA4CCB04ED3F5D /* Pods_DoIt.framework */; }; E40B7953275FA1DA002DE826 /* KeyboardManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E40B7952275FA1DA002DE826 /* KeyboardManager.swift */; }; + E417CFD727689B2300C6E6B5 /* ProfileViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E417CFD627689B2300C6E6B5 /* ProfileViewModel.swift */; }; + E417CFD927689D8A00C6E6B5 /* TaskService.swift in Sources */ = {isa = PBXBuildFile; fileRef = E417CFD827689D8A00C6E6B5 /* TaskService.swift */; }; + E417CFDB2768BE1800C6E6B5 /* ProfileEditViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E417CFDA2768BE1800C6E6B5 /* ProfileEditViewModel.swift */; }; + E455B5D727681AD20059130D /* AuthService.swift in Sources */ = {isa = PBXBuildFile; fileRef = E455B5D527681AD10059130D /* AuthService.swift */; }; + E455B5DA276820E60059130D /* SearchUsersViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E455B5D9276820E60059130D /* SearchUsersViewModel.swift */; }; E49D6B20275BEF0C007C9A97 /* UserTasksModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E49D6B1F275BEF0C007C9A97 /* UserTasksModel.swift */; }; E4A95D4B275BE99A00EE3F59 /* UserModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A95D48275BE99900EE3F59 /* UserModel.swift */; }; - E4A95D4C275BE99A00EE3F59 /* UserStatisticsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A95D49275BE99A00EE3F59 /* UserStatisticsModel.swift */; }; E4A95D4D275BE99A00EE3F59 /* UserFollowingModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A95D4A275BE99A00EE3F59 /* UserFollowingModel.swift */; }; E4A95D59275BEA8500EE3F59 /* ProfileEditViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A95D56275BEA8500EE3F59 /* ProfileEditViewController.swift */; }; E4A95D5A275BEA8600EE3F59 /* ProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A95D57275BEA8500EE3F59 /* ProfileViewController.swift */; }; E4A95D5B275BEA8600EE3F59 /* ProfileFollowingUserCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A95D58275BEA8500EE3F59 /* ProfileFollowingUserCell.swift */; }; E4A95D5D275BEAAA00EE3F59 /* Profile.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A95D5C275BEAAA00EE3F59 /* Profile.swift */; }; + E4AB3EF7276A89490038D8AD /* ImageChacher.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4AB3EF6276A89490038D8AD /* ImageChacher.swift */; }; E4BC75FE276537E40016B6E2 /* NotificationNameExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4BC75FD276537E40016B6E2 /* NotificationNameExtension.swift */; }; /* End PBXBuildFile section */ @@ -58,14 +69,20 @@ 7C6382E52728807900FED2F4 /* UIColorsExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIColorsExtension.swift; sourceTree = ""; }; 7C6382E7272883AC00FED2F4 /* Printer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Printer.swift; sourceTree = ""; }; 7C6382EF2729CCC300FED2F4 /* StringExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringExtension.swift; sourceTree = ""; }; + 7C6C3BC3275C05E000B6101F /* UserService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserService.swift; sourceTree = ""; }; + 7C6C3BC7275C06B400B6101F /* AuthResultsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthResultsModel.swift; sourceTree = ""; }; + 7C6C3BC9275C072E00B6101F /* FirebaseConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirebaseConstants.swift; sourceTree = ""; }; + 7C6C3BCB275C176C00B6101F /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; 7CAC5DCA27232C6000E2D70B /* DoIt.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DoIt.app; sourceTree = BUILT_PRODUCTS_DIR; }; 7CAC5DCD27232C6000E2D70B /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7CAC5DCF27232C6000E2D70B /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 7CAC5DDB27232C6300E2D70B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 83349BE127682D72008D4786 /* TasksViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TasksViewModel.swift; sourceTree = ""; }; + 83349BE5276959F4008D4786 /* AuthViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthViewModel.swift; sourceTree = ""; }; 83369A3F273855BD0025F3D9 /* TasksController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TasksController.swift; sourceTree = ""; }; 83369A43273856240025F3D9 /* TaskTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskTableViewCell.swift; sourceTree = ""; }; 83369A49273856950025F3D9 /* ChapterCollectionCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChapterCollectionCell.swift; sourceTree = ""; }; - 83369A4D273857020025F3D9 /* Structures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Structures.swift; sourceTree = ""; }; + 83369A4D273857020025F3D9 /* TaskModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskModel.swift; sourceTree = ""; }; 8383502227583E57008847E1 /* Auth.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Auth.swift; sourceTree = ""; }; 8383502327583E57008847E1 /* CustomNavigationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomNavigationController.swift; sourceTree = ""; }; 8383502527583E57008847E1 /* SignInController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignInController.swift; sourceTree = ""; }; @@ -86,14 +103,19 @@ 83AF41A727431F9600CA8AAA /* FeedCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedCollectionViewCell.swift; sourceTree = ""; }; A133F16700AA4CCB04ED3F5D /* Pods_DoIt.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_DoIt.framework; sourceTree = BUILT_PRODUCTS_DIR; }; E40B7952275FA1DA002DE826 /* KeyboardManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardManager.swift; sourceTree = ""; }; + E417CFD627689B2300C6E6B5 /* ProfileViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileViewModel.swift; sourceTree = ""; }; + E417CFD827689D8A00C6E6B5 /* TaskService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskService.swift; sourceTree = ""; }; + E417CFDA2768BE1800C6E6B5 /* ProfileEditViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileEditViewModel.swift; sourceTree = ""; }; + E455B5D527681AD10059130D /* AuthService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthService.swift; sourceTree = ""; }; + E455B5D9276820E60059130D /* SearchUsersViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchUsersViewModel.swift; sourceTree = ""; }; E49D6B1F275BEF0C007C9A97 /* UserTasksModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserTasksModel.swift; sourceTree = ""; }; E4A95D48275BE99900EE3F59 /* UserModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserModel.swift; sourceTree = ""; }; - E4A95D49275BE99A00EE3F59 /* UserStatisticsModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserStatisticsModel.swift; sourceTree = ""; }; E4A95D4A275BE99A00EE3F59 /* UserFollowingModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserFollowingModel.swift; sourceTree = ""; }; E4A95D56275BEA8500EE3F59 /* ProfileEditViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProfileEditViewController.swift; sourceTree = ""; }; E4A95D57275BEA8500EE3F59 /* ProfileViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProfileViewController.swift; sourceTree = ""; }; E4A95D58275BEA8500EE3F59 /* ProfileFollowingUserCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProfileFollowingUserCell.swift; sourceTree = ""; }; E4A95D5C275BEAAA00EE3F59 /* Profile.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Profile.swift; sourceTree = ""; }; + E4AB3EF6276A89490038D8AD /* ImageChacher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageChacher.swift; sourceTree = ""; }; E4BC75FD276537E40016B6E2 /* NotificationNameExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationNameExtension.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -137,6 +159,11 @@ 7C6382CF2725C4F700FED2F4 /* ViewModels */ = { isa = PBXGroup; children = ( + 83349BE5276959F4008D4786 /* AuthViewModel.swift */, + 83349BE127682D72008D4786 /* TasksViewModel.swift */, + E455B5D9276820E60059130D /* SearchUsersViewModel.swift */, + E417CFD627689B2300C6E6B5 /* ProfileViewModel.swift */, + E417CFDA2768BE1800C6E6B5 /* ProfileEditViewModel.swift */, ); path = ViewModels; sourceTree = ""; @@ -170,11 +197,16 @@ 7C6382D52725C5E800FED2F4 /* Models */ = { isa = PBXGroup; children = ( - E4A95D4A275BE99A00EE3F59 /* UserFollowingModel.swift */, + 7C6C3BC7275C06B400B6101F /* AuthResultsModel.swift */, E4A95D48275BE99900EE3F59 /* UserModel.swift */, - E4A95D49275BE99A00EE3F59 /* UserStatisticsModel.swift */, E49D6B1F275BEF0C007C9A97 /* UserTasksModel.swift */, - 83369A4D273857020025F3D9 /* Structures.swift */, + E4A95D4A275BE99A00EE3F59 /* UserFollowingModel.swift */, + 83369A4D273857020025F3D9 /* TaskModel.swift */, + E455B5D527681AD10059130D /* AuthService.swift */, + 7C6C3BC3275C05E000B6101F /* UserService.swift */, + E417CFD827689D8A00C6E6B5 /* TaskService.swift */, + 7C6C3BC9275C072E00B6101F /* FirebaseConstants.swift */, + E4AB3EF6276A89490038D8AD /* ImageChacher.swift */, ); path = Models; sourceTree = ""; @@ -198,6 +230,8 @@ 7CAC5DCB27232C6000E2D70B /* Products */, 44250C609B2DD82274CFF714 /* Pods */, 7D03E0CC68346392C8995C87 /* Frameworks */, + 83349BD42768118E008D4786 /* Recovered References */, + E455B5D42767EA5C0059130D /* Recovered References */, ); sourceTree = ""; }; @@ -234,6 +268,13 @@ name = Frameworks; sourceTree = ""; }; + 83349BD42768118E008D4786 /* Recovered References */ = { + isa = PBXGroup; + children = ( + ); + name = "Recovered References"; + sourceTree = ""; + }; 83369A452738564B0025F3D9 /* TasksTableView */ = { isa = PBXGroup; children = ( @@ -349,6 +390,14 @@ path = KeyboardManager; sourceTree = ""; }; + E455B5D42767EA5C0059130D /* Recovered References */ = { + isa = PBXGroup; + children = ( + 7C6C3BCB275C176C00B6101F /* GoogleService-Info.plist */, + ); + name = "Recovered References"; + sourceTree = ""; + }; E4A95D4F275BEA0E00EE3F59 /* Profile */ = { isa = PBXGroup; children = ( @@ -421,6 +470,7 @@ buildActionMask = 2147483647; files = ( 7C6382DD272777F600FED2F4 /* Localizable.strings in Resources */, + 7C6C3BCC275C176C00B6101F /* GoogleService-Info.plist in Resources */, 8383504B27583F93008847E1 /* Assets.xcassets in Resources */, 8383504727583F0E008847E1 /* LaunchScreen.storyboard in Resources */, ); @@ -481,35 +531,45 @@ 8383503B27583E58008847E1 /* OnboardingCell.swift in Sources */, 83AF41A827431F9600CA8AAA /* FeedCollectionViewCell.swift in Sources */, 83369A40273855BD0025F3D9 /* TasksController.swift in Sources */, + E417CFD727689B2300C6E6B5 /* ProfileViewModel.swift in Sources */, 7CAC5DCE27232C6000E2D70B /* AppDelegate.swift in Sources */, + E455B5D727681AD20059130D /* AuthService.swift in Sources */, + E417CFD927689D8A00C6E6B5 /* TaskService.swift in Sources */, 83369A4A273856950025F3D9 /* ChapterCollectionCell.swift in Sources */, 8383504027583E58008847E1 /* SearchUsersCell.swift in Sources */, E49D6B20275BEF0C007C9A97 /* UserTasksModel.swift in Sources */, E4A95D59275BEA8500EE3F59 /* ProfileEditViewController.swift in Sources */, 83AF41A227431EB500CA8AAA /* FeedController.swift in Sources */, 8383503927583E58008847E1 /* OnboardingViewController.swift in Sources */, + E417CFDB2768BE1800C6E6B5 /* ProfileEditViewModel.swift in Sources */, + 83349BE6276959F4008D4786 /* AuthViewModel.swift in Sources */, 8383503D27583E58008847E1 /* TaskViewController.swift in Sources */, + 7C6C3BC8275C06B400B6101F /* AuthResultsModel.swift in Sources */, 8383504227583E58008847E1 /* SignUpController.swift in Sources */, 8383504127583E58008847E1 /* SearchUsersController.swift in Sources */, - E4A95D4C275BE99A00EE3F59 /* UserStatisticsModel.swift in Sources */, E4A95D4B275BE99A00EE3F59 /* UserModel.swift in Sources */, + 83349BE227682D72008D4786 /* TasksViewModel.swift in Sources */, 7C6382E32727884000FED2F4 /* UIImageExtension.swift in Sources */, E40B7953275FA1DA002DE826 /* KeyboardManager.swift in Sources */, E4A95D5B275BEA8600EE3F59 /* ProfileFollowingUserCell.swift in Sources */, 7C6382F02729CCC300FED2F4 /* StringExtension.swift in Sources */, 7CAC5DD027232C6000E2D70B /* SceneDelegate.swift in Sources */, + E455B5DA276820E60059130D /* SearchUsersViewModel.swift in Sources */, + 7C6C3BCA275C072E00B6101F /* FirebaseConstants.swift in Sources */, 8383503727583E58008847E1 /* SignInController.swift in Sources */, 7C6382E62728807900FED2F4 /* UIColorsExtension.swift in Sources */, E4A95D5D275BEAAA00EE3F59 /* Profile.swift in Sources */, E4A95D5A275BEA8600EE3F59 /* ProfileViewController.swift in Sources */, 7C6382E8272883AC00FED2F4 /* Printer.swift in Sources */, 8383504A27583F93008847E1 /* Strings.swift in Sources */, - 83369A4E273857020025F3D9 /* Structures.swift in Sources */, + 83369A4E273857020025F3D9 /* TaskModel.swift in Sources */, 8383503A27583E58008847E1 /* OnboardingStruct.swift in Sources */, E4BC75FE276537E40016B6E2 /* NotificationNameExtension.swift in Sources */, 83AF41A027429B5500CA8AAA /* CustomTabBarController.swift in Sources */, 8383503627583E58008847E1 /* CustomNavigationController.swift in Sources */, 8383503527583E58008847E1 /* Auth.swift in Sources */, + 7C6C3BC4275C05E000B6101F /* UserService.swift in Sources */, + E4AB3EF7276A89490038D8AD /* ImageChacher.swift in Sources */, 8383503C27583E58008847E1 /* TaskEditViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -676,7 +736,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.vkproj.doIt; + PRODUCT_BUNDLE_IDENTIFIER = com.vkproj.doIt1; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; @@ -705,7 +765,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.vkproj.doIt; + PRODUCT_BUNDLE_IDENTIFIER = com.vkproj.doIt1; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; diff --git a/DoIt/Application/AppDelegate.swift b/DoIt/Application/AppDelegate.swift index 34b1057..add6f21 100644 --- a/DoIt/Application/AppDelegate.swift +++ b/DoIt/Application/AppDelegate.swift @@ -6,11 +6,13 @@ // import UIKit +import Firebase @main class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + FirebaseApp.configure() // Override point for customization after application launch. return true } diff --git a/DoIt/Extensions/UIColorsExtension.swift b/DoIt/Extensions/UIColorsExtension.swift index 39216eb..c7a2109 100644 --- a/DoIt/Extensions/UIColorsExtension.swift +++ b/DoIt/Extensions/UIColorsExtension.swift @@ -61,4 +61,23 @@ extension UIColor { static let brown: UIColor = .brown } } - +extension UIColor { + + func ColorFromHex(rgbValue: Int) -> UIColor { + let red = CGFloat((rgbValue & 0xFF0000) >> 16) / 0xFF + let green = CGFloat((rgbValue & 0x00FF00) >> 8) / 0xFF + let blue = CGFloat(rgbValue & 0x0000FF) / 0xFF + let alpha = CGFloat(1.0) + return UIColor(red: red, green: green, blue: blue, alpha: alpha) + } + + func HexFromColor(color: UIColor) -> Int { + var r: CGFloat = 0 + var g: CGFloat = 0 + var b: CGFloat = 0 + var a: CGFloat = 0 + color.getRed(&r, green: &g, blue: &b, alpha: &a) + let hex = (Int(r * 255) << 16) | (Int(g * 255) << 8) | (Int(g * 255)) + return hex + } +} diff --git a/DoIt/Extensions/UIImageExtension.swift b/DoIt/Extensions/UIImageExtension.swift index a74fb88..ebf9619 100644 --- a/DoIt/Extensions/UIImageExtension.swift +++ b/DoIt/Extensions/UIImageExtension.swift @@ -6,6 +6,7 @@ // import UIKit +import Firebase extension UIImage { struct AuthIcons { diff --git a/DoIt/GoogleService-Info.plist b/DoIt/GoogleService-Info.plist new file mode 100644 index 0000000..d9885d2 --- /dev/null +++ b/DoIt/GoogleService-Info.plist @@ -0,0 +1,36 @@ + + + + + CLIENT_ID + 562011549386-m8uaj5enlra65cq7qlrg3jk91mv66fk5.apps.googleusercontent.com + REVERSED_CLIENT_ID + com.googleusercontent.apps.562011549386-m8uaj5enlra65cq7qlrg3jk91mv66fk5 + API_KEY + AIzaSyC6JYyF7IDY-kw3AH9Hl6v9E4b7L7Gj-Is + GCM_SENDER_ID + 562011549386 + PLIST_VERSION + 1 + BUNDLE_ID + com.vkproj.doIt + PROJECT_ID + doit-b9c45 + STORAGE_BUCKET + doit-b9c45.appspot.com + IS_ADS_ENABLED + + IS_ANALYTICS_ENABLED + + IS_APPINVITE_ENABLED + + IS_GCM_ENABLED + + IS_SIGNIN_ENABLED + + DATABASE_URL + https://doit-b9c45-default-rtdb.firebaseio.com/ + GOOGLE_APP_ID + 1:562011549386:ios:6fcfaa37eaad2ceab88e33 + + diff --git a/DoIt/Info.plist b/DoIt/Info.plist index 0385f2b..d90e717 100644 --- a/DoIt/Info.plist +++ b/DoIt/Info.plist @@ -2,6 +2,8 @@ + FirebaseAutomaticScreenReportingEnabled + UIApplicationSceneManifest UIApplicationSupportsMultipleScenes diff --git a/DoIt/Models/AuthResultsModel.swift b/DoIt/Models/AuthResultsModel.swift new file mode 100644 index 0000000..9da2a6c --- /dev/null +++ b/DoIt/Models/AuthResultsModel.swift @@ -0,0 +1,40 @@ +// +// AuthModel.swift +// DoIt +// +// Created by Y u l i a on 04.12.2021. +// + +import Foundation +import Firebase + +struct AuthCredentials { + let email: String + let password: String + let username: String +} + +enum AuthError { + case invalidEmail + case unknownError + case serverError +} + +extension AuthError: LocalizedError { + var errorDescription: String? { + switch self { + case .invalidEmail: + return NSLocalizedString("email_is_not_valid", comment: "") + case .unknownError: + return NSLocalizedString("server_error", comment: "") + case .serverError: + return NSLocalizedString("server_error", comment: "") + + } + } +} + +enum AuthResult { + case success + case failure(Error) +} diff --git a/DoIt/Models/AuthService.swift b/DoIt/Models/AuthService.swift new file mode 100644 index 0000000..047d382 --- /dev/null +++ b/DoIt/Models/AuthService.swift @@ -0,0 +1,59 @@ +// +// AuthService.swift +// DoIt +// +// Created by Y u l i a on 06.12.2021. +// + +import Firebase + +final class AuthService { + + static let shared = AuthService() + + func signIn(email: String?, password: String?, completion: @escaping (AuthResult) -> Void) { + guard let email = email, let password = password else { + completion(.failure(AuthError.unknownError)) + return + } + + Auth.auth().signIn(withEmail: email, password: password) { (result, error) in + guard let result = result else { + completion(.failure(error!)) + return + } + + let uid = result.user.uid + + UserDefaults.standard.set(uid, forKey: "current_user") + + completion(.success) + } + } + + func signUp(email: String?, username: String?, password: String?, completion: @escaping (AuthResult) -> Void) { + + guard let email = email, let username = username, let password = password else { + completion(.failure(AuthError.unknownError)) + return + } + + Auth.auth().createUser(withEmail: email, password: password) { (result, error) in + guard let result = result else { + completion(.failure(error!)) + return + } + + let uid = result.user.uid + + UserDefaults.standard.set(uid, forKey: "current_user") + + let values = ["email": email, + "username": username] + + let usersReferense = Database.database().reference().child("users") + usersReferense.child(uid).updateChildValues(values) + completion(.success) + } + } +} diff --git a/DoIt/Models/FirebaseConstants.swift b/DoIt/Models/FirebaseConstants.swift new file mode 100644 index 0000000..0dae4f1 --- /dev/null +++ b/DoIt/Models/FirebaseConstants.swift @@ -0,0 +1,22 @@ +// +// FirebaseConstants.swift +// DoIt +// +// Created by Yulia on 04.12.2021. +// + +import Firebase + +let STORAGE_REF = Storage.storage().reference() + +let STORAGE_PROFILE_IMAGES = STORAGE_REF.child("profile_images") +let STORAGE_TASK_IMAGES = STORAGE_REF.child("task_images") + +let DB_REF = Database.database().reference() + +let REF_USERS = DB_REF.child("users") +let REF_TASKS = DB_REF.child("tasks") +let REF_USER_TASKS = DB_REF.child("user-tasks") +let REF_USER_FOLLOWERS = DB_REF.child("user-followers") +let REF_USER_FOLLOWING = DB_REF.child("user-following") + diff --git a/DoIt/Models/ImageChacher.swift b/DoIt/Models/ImageChacher.swift new file mode 100644 index 0000000..6c5930c --- /dev/null +++ b/DoIt/Models/ImageChacher.swift @@ -0,0 +1,40 @@ +// +// ImageChacher.swift +// DoIt +// +// Created by Шестаков Никита on 15.12.2021. +// + +import Foundation +import UIKit + +class ImageCache { + private let cache = NSCache() + private var observer: NSObjectProtocol? + + static let shared = ImageCache() + + private init() { + observer = NotificationCenter.default.addObserver( + forName: UIApplication.didReceiveMemoryWarningNotification, + object: nil, queue: nil) { [weak self] notification in + self?.cache.removeAllObjects() + } + } + + deinit { + guard let observer = observer else { + return + } + + NotificationCenter.default.removeObserver(observer) + } + + func getImage(forKey key: String) -> UIImage? { + return cache.object(forKey: key as NSString) + } + + func save(image: UIImage, forKey key: String) { + cache.setObject(image, forKey: key as NSString) + } +} diff --git a/DoIt/Models/Structures.swift b/DoIt/Models/Structures.swift deleted file mode 100644 index 0ca01b9..0000000 --- a/DoIt/Models/Structures.swift +++ /dev/null @@ -1,26 +0,0 @@ -// -// Structures.swift -// DoIt -// -// Created by Данил Иванов on 07.11.2021. -// - -import UIKit - -struct Task { - let image: UIImage? - let title: String - let description: String? - let deadline: Date? - let isDone: Bool - let creatorId: String - let color: UIColor - let chapterId: Int - let creationTime: Date - let isMyTask: Bool -} - -struct Chapter { - let title: String - let color: UIColor -} diff --git a/DoIt/Models/TaskModel.swift b/DoIt/Models/TaskModel.swift new file mode 100644 index 0000000..66b8db3 --- /dev/null +++ b/DoIt/Models/TaskModel.swift @@ -0,0 +1,74 @@ +// +// Structures.swift +// DoIt +// +// Created by Данил Иванов on 07.11.2021. +// + +import UIKit +import Firebase + +final class Task { + let taskId: String + let image: URL? + let title: String + let description: String? + let deadline: Date? + let isDone: Bool + let uid: String + let color: UIColor + let chapterId: Int + let creationTime: Date + + var isMyTask: Bool { + return Auth.auth().currentUser?.uid == uid + } + + init(id: String, dictionary: [String: AnyObject]) { + taskId = id + if let profileImageUrlString = dictionary["userPhotoUrl"] as? String { + image = URL(string: profileImageUrlString) + } else { + image = nil + } + title = dictionary["title"] as? String ?? "" + description = dictionary["description"] as? String ?? "" + if let deadlineTimestamp = dictionary["deadline"] as? Double { + deadline = Date(timeIntervalSince1970: deadlineTimestamp) + } else { + deadline = nil + } + isDone = dictionary["is_done"] as? Bool ?? false + uid = dictionary["uid"] as? String ?? "" + if let colorHex = dictionary["color"] as? Int { + color = UIColor().ColorFromHex(rgbValue: colorHex) + } else { + color = .clear + } + if let creationTimestamp = dictionary["timestamp"] as? Double { + creationTime = Date(timeIntervalSince1970: creationTimestamp) + } else { + creationTime = Date(timeIntervalSince1970: 0) + } + chapterId = dictionary["chapter_id"] as? Int ?? 0 + } + + // TODO: - FIX IT + +// init(image: UIImage?, title: String, description: String?, deadline: Date?, isDone: Bool, color: UIColor, uid: String = "") { +// self.image = image +// self.title = title +// self.description = description +// self.deadline = deadline +// self.isDone = isDone +// self.uid = "" +// self.color = color +// self.creationTime = Date() +// self.chapterId = 0 +// } +} + +struct Chapter { + let title: String + let color: UIColor +} diff --git a/DoIt/Models/TaskService.swift b/DoIt/Models/TaskService.swift new file mode 100644 index 0000000..98085b7 --- /dev/null +++ b/DoIt/Models/TaskService.swift @@ -0,0 +1,67 @@ +// +// PostService.swift +// DoIt +// +// Created by Yulia on 05.12.2021. +// +// + +import Firebase + +class TaskService { + + static let shared = TaskService() + + func uploadTask(task: Task, completion: @escaping(Error?, DatabaseReference)->Void){ + guard let uid = Auth.auth().currentUser?.uid else {return} + let values = [ + "title": task.title, + "description": task.description ?? "", + "deadline": Int(task.deadline?.timeIntervalSince1970 ?? 0), + "is_done": task.isDone, + "uid": uid, + "timestamp": Int(NSDate().timeIntervalSince1970), + "color": UIColor().HexFromColor(color: task.color) + ] as [String : Any] + + REF_TASKS.childByAutoId().updateChildValues(values) { (error, ref) in + guard let taskID = ref.key else { return } + REF_USER_TASKS.child(uid).updateChildValues([taskID : 1], withCompletionBlock: completion) + } + } + + func updateTaskImage(taskId: String, image: UIImage, completion: @escaping(URL?)-> Void){ + guard let imageData = image.jpegData(compressionQuality: 1.0) else {return} + guard let uid = Auth.auth().currentUser?.uid else {return} + let ref = STORAGE_TASK_IMAGES.child(NSUUID().uuidString) + + ref.putData(imageData, metadata: nil) { (meta, err) in + ref.downloadURL { (url, err) in + guard let taskImageUrl = url?.absoluteString else {return} + let values = ["taskImageUrl": taskImageUrl] + REF_TASKS.child(taskId).updateChildValues(values) { (err, ref) in + completion(url) + } + } + } + } + + func fetchTask(taskId: String, completion: @escaping(Task) -> Void){ + REF_TASKS.child(taskId).observeSingleEvent(of: .value) { snapshot in + guard let dictionary = snapshot.value as? [String: AnyObject] else {return} + completion(Task(id: taskId, dictionary: dictionary)) + } + } + + func fetchTasks(forUser user: UserModel, completion: @escaping([Task]) -> Void) { + var tasks = [Task]() + REF_USER_TASKS.child(user.uid).observe(.childAdded) { snapshot in + let taskId = snapshot.key + + self.fetchTask(taskId: taskId) { task in + tasks.append(task) + } + } + completion(tasks) + } +} diff --git a/DoIt/Models/UserModel.swift b/DoIt/Models/UserModel.swift index bfbc120..97346ea 100644 --- a/DoIt/Models/UserModel.swift +++ b/DoIt/Models/UserModel.swift @@ -2,18 +2,45 @@ // UserModel.swift // DoIt // -// Created by Шестаков Никита on 20.11.2021. +// Created by Yulia on 05.12.2021. +// // -import Foundation -import UIKit +import Firebase -struct UserModel { - let image: UIImage? - let name: String? - let login: String - let summary: String? - let statistics: UserStatisticsModel - let isMyScreen: Bool - let isFollowed: Bool +class UserModel { + + let uid: String + let email: String + var username: String + var summary: String? = nil + var image: URL? + var name: String? = nil + + var isCurrentUser: Bool { + return Auth.auth().currentUser?.uid == uid + } + + var isFollowed: Bool? + + init(uid: String, dictionary: [String: AnyObject]) { + self.uid = uid + self.email = dictionary["email"] as? String ?? "" + self.username = dictionary["username"] as? String ?? "" + if let summary = dictionary["summary"] as? String { + if !summary.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { + self.summary = summary + } + } + if let name = dictionary["name"] as? String { + if !name.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { + self.name = name + } + } + if let profileImageUrlString = dictionary["userPhotoUrl"] as? String { + if !profileImageUrlString.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { + self.image = URL(string: profileImageUrlString) + } + } + } } diff --git a/DoIt/Models/UserService.swift b/DoIt/Models/UserService.swift new file mode 100644 index 0000000..4c356d4 --- /dev/null +++ b/DoIt/Models/UserService.swift @@ -0,0 +1,103 @@ +// +// UserModel.swift +// DoIt +// +// Created by Yulia on 04.12.2021. +// + +import Firebase + +typealias DatabaseCompletion = ((Error?, DatabaseReference)-> Void) + +class UserService { + + static let shared = UserService() + + func fetchUser(uid: String, completion: @escaping(UserModel)->Void) { + REF_USERS.child(uid).observeSingleEvent(of: .value) { snapshot in + guard let dictionary = snapshot.value as? [String: AnyObject] else {return} + + let user = UserModel(uid: uid, dictionary: dictionary) + completion(user) + } + } + + func updateUserPhoto(image: UIImage, completion: @escaping(URL?)-> Void){ + guard let imageData = image.jpegData(compressionQuality: 1.0) else {return} + guard let uid = Auth.auth().currentUser?.uid else {return} + let ref = STORAGE_PROFILE_IMAGES.child(NSUUID().uuidString) + + ref.putData(imageData, metadata: nil) { (meta, err) in + ref.downloadURL { (url, err) in + guard let userPhotoUrl = url?.absoluteString else {return} + let values = ["userPhotoUrl": userPhotoUrl] + REF_USERS.child(uid).updateChildValues(values) { (err, ref) in + completion(url) + } + } + } + } + + func updateUserData(user: UserModel, completion: @escaping(DatabaseCompletion)){ + guard let uid = Auth.auth().currentUser?.uid else {return} + let values: [String: Any] = ["username": user.username, "summary": user.summary ?? "", "name": user.name ?? "", "userPhotoUrl": user.image?.absoluteString ?? ""] + + REF_USERS.child(uid).updateChildValues(values, withCompletionBlock: completion) + } + + func fetchUsers(completion: @escaping([UserModel])->Void) { + var users = [UserModel]() + REF_USERS.observe(.childAdded) { (snapshot) in + let uid = snapshot.key + guard let dictionary = snapshot.value as? [String: AnyObject] else {return} + let user = UserModel(uid: uid, dictionary: dictionary) + users.append(user) + completion(users) + } + } + + func followUser(uid: String, completion: @escaping(DatabaseCompletion)){ + guard let currentUid = Auth.auth().currentUser?.uid else {return} + + REF_USER_FOLLOWING.child(currentUid).updateChildValues([uid : 1]) { (err, ref) in + REF_USER_FOLLOWERS.child(uid).updateChildValues([currentUid: 1], withCompletionBlock: completion) + } + } + + func unfollowUser(uid: String, completion: @escaping(DatabaseCompletion)) { + guard let currentUid = Auth.auth().currentUser?.uid else {return} + + REF_USER_FOLLOWING.child(currentUid).child(uid).removeValue { (err, ref) in + REF_USER_FOLLOWERS.child(uid).child(currentUid).removeValue(completionBlock: completion) + } + } + + func isUserFollowed(uid: String, completion: @escaping(Bool)->Void){ + guard let currentUid = Auth.auth().currentUser?.uid else {return} + + REF_USER_FOLLOWING.child(currentUid).child(uid).observeSingleEvent(of: .value) { snapshot in + completion(snapshot.exists()) + } + } + + func fetchUserFollowers(uid: String, completion: @escaping([String])->Void) { + var users_uid = [String]() + REF_USER_FOLLOWERS.child(uid).observe(.childAdded) { (snapshot) in + + let follower_uid = snapshot.key + users_uid.append(follower_uid) + completion(users_uid) + } + } + + func fetchUserFollowing(uid: String, completion: @escaping([String])->Void) { + var users_uid = [String]() + REF_USER_FOLLOWING.child(uid).observe(.childAdded) { (snapshot) in + + let following_uid = snapshot.key + users_uid.append(following_uid) + completion(users_uid) + } + } + +} diff --git a/DoIt/Models/UserStatisticsModel.swift b/DoIt/Models/UserStatisticsModel.swift deleted file mode 100644 index 0bda8f7..0000000 --- a/DoIt/Models/UserStatisticsModel.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// UserStatisticsModel.swift -// DoIt -// -// Created by Шестаков Никита on 23.11.2021. -// - -import Foundation - -struct UserStatisticsModel { - let inProgress: String - let outdated: String - let done: String - let total: String -} diff --git a/DoIt/ViewModels/AuthViewModel.swift b/DoIt/ViewModels/AuthViewModel.swift new file mode 100644 index 0000000..e8bfae7 --- /dev/null +++ b/DoIt/ViewModels/AuthViewModel.swift @@ -0,0 +1,26 @@ +// +// AuthViewModel.swift +// DoIt +// +// Created by Данил Иванов on 15.12.2021. +// + +import Foundation + +final class AuthViewModel { + private let authService = AuthService.shared + + var authResultModel: Observable = Observable() + + func signUp(email: String?, username: String?, password: String?) { + self.authService.signUp(email: email, username: username, password: password) { [weak self] authResult in + self?.authResultModel.value = authResult + } + } + + func signIn(email: String?, password: String?) { + self.authService.signIn(email: email, password: password) { [weak self] authResult in + self?.authResultModel.value = authResult + } + } +} diff --git a/DoIt/ViewModels/ProfileEditViewModel.swift b/DoIt/ViewModels/ProfileEditViewModel.swift new file mode 100644 index 0000000..41c0cb8 --- /dev/null +++ b/DoIt/ViewModels/ProfileEditViewModel.swift @@ -0,0 +1,95 @@ +// +// ProfileEditViewModel.swift +// DoIt +// +// Created by Шестаков Никита on 14.12.2021. +// + +import Foundation +import UIKit + +final class ProfileEditViewModel { + private let userService = UserService.shared + var userModel: Observable = Observable() + + func updateUserProfile(image: UIImage?, name: String?, username: String?, summary: String?, complition: @escaping () -> ()) { + DispatchQueue.global().sync { [weak self] in + guard let userModel = userModel.value else { + Logger.log("Пользователь не найден") + complition() + return + } + + var wasChanged = false + + wasChanged = image != nil + + var newName = userModel.name + if let name = name?.trimmingCharacters(in: .whitespacesAndNewlines), name != newName { + newName = name + wasChanged = true + } + + var newSummary = userModel.summary + if let summary = summary?.trimmingCharacters(in: .whitespacesAndNewlines), summary != newSummary { + newSummary = summary + wasChanged = true + } + + var newUsername = userModel.username + if let username = username?.trimmingCharacters(in: .whitespacesAndNewlines), username != newUsername, !username.isEmpty { + newUsername = username + wasChanged = true + } + + guard wasChanged else { + Logger.log("Ничего не поменялось") + complition() + return + } + + var values: [String: String] = ["email": userModel.email, "username": newUsername, "summary": newSummary ?? "", "name": newName ?? ""] + + if let image = image { + self?.userService.updateUserPhoto(image: image) { [weak self] url in + guard url != nil else { + Logger.log("Ошибка обновления изображения") + complition() + return + } + values["userPhotoUrl"] = url?.absoluteString ?? "" + self?.userModel.value = UserModel(uid: userModel.uid, dictionary: values as [String: AnyObject]) + self?.updateUser(complition: complition) + } + } else { + self?.userModel.value = UserModel(uid: userModel.uid, dictionary: values as [String: AnyObject]) + self?.updateUser(complition: complition) + } + } + } + + func downloadImage(_ url: URL?, completion: @escaping (UIImage?) -> ()) { + DispatchQueue.global().async { + var cellImage: UIImage? = nil + guard let url = url else { + completion(cellImage) + return + } + if let data = try? Data(contentsOf: url) { + cellImage = UIImage(data: data) + } + completion(cellImage) + } + } + + func updateUser(complition: @escaping () -> ()) { + guard let userModel = userModel.value else { return } + userService.updateUserData(user: userModel, completion: { error, _ in + if let error = error { + Logger.log("Ошибка обращения к бд \(error)") + } + Logger.log("Профиль обновлен") + complition() + }) + } +} diff --git a/DoIt/ViewModels/ProfileViewModel.swift b/DoIt/ViewModels/ProfileViewModel.swift new file mode 100644 index 0000000..cc481ff --- /dev/null +++ b/DoIt/ViewModels/ProfileViewModel.swift @@ -0,0 +1,102 @@ +// +// ProfileViewModel.swift +// DoIt +// +// Created by Шестаков Никита on 14.12.2021. +// + +import Foundation +import Firebase +import UIKit + +final class ProfileViewModel { + private let userService = UserService.shared + private let taskService = TaskService.shared + + var userModel: Observable = Observable() + + var userFollowingModel: Observable = Observable() + + var userTasksModel: Observable = Observable() + + func getCurrentUser() { + DispatchQueue.global(qos: .userInitiated).async { [weak self] in + guard let uid = Auth.auth().currentUser?.uid else { + return + } + self?.userService.fetchUser(uid: uid) { [weak self] user in + self?.userModel.value = user + } + } + } + + func getUserTasks() { + DispatchQueue.global().async { [weak self] in + guard let user = self?.userModel.value else { + + return + } + self?.taskService.fetchTasks(forUser: user, completion: { [weak self] tasks in + self?.userTasksModel.value = UserTasksModel(login: user.username, tasks: tasks) + }) + } + } + + func getUserFollowings() { + DispatchQueue.global().async { [weak self] in + guard let user = self?.userModel.value else { + + return + } + self?.userService.fetchUserFollowing(uid: user.uid, completion: { uids in + var followings: [UserModel] = [] + uids.forEach { uid in + self?.userService.fetchUser(uid: uid, completion: { followings.append($0) }) + } + self?.userFollowingModel.value = UserFollowingModel(login: user.username, followings: followings) + }) + } + } + + func followUser(_ user: UserModel, completion: @escaping (Bool) -> ()) { + DispatchQueue.global().async { [weak self] in + self?.userService.followUser(uid: user.uid) { error, _ in + if let error = error { + print(error.localizedDescription) + completion(false) + return + } + user.isFollowed = true + completion(true) + } + } + } + + func unfollowUser(_ user: UserModel, completion: @escaping (Bool) -> ()) { + DispatchQueue.global().async { [weak self] in + self?.userService.unfollowUser(uid: user.uid) { error, _ in + if let error = error { + print(error.localizedDescription) + completion(false) + return + } + user.isFollowed = false + completion(true) + } + } + } + + func downloadImage(_ url: URL?, completion: @escaping (UIImage?) -> ()) { + DispatchQueue.global().async { + var cellImage: UIImage? = nil + guard let url = url else { + completion(cellImage) + return + } + if let data = try? Data(contentsOf: url) { + cellImage = UIImage(data: data) + } + completion(cellImage) + } + } +} diff --git a/DoIt/ViewModels/SearchUsersViewModel.swift b/DoIt/ViewModels/SearchUsersViewModel.swift new file mode 100644 index 0000000..f35530c --- /dev/null +++ b/DoIt/ViewModels/SearchUsersViewModel.swift @@ -0,0 +1,115 @@ +// +// SearchUsersViewModel.swift +// DoIt +// +// Created by Шестаков Никита on 14.12.2021. +// + +import Foundation +import Firebase + +final class Observable { + var value: T? { + didSet { + listener?(value) + } + } + + init(_ value: T? = nil) { + self.value = value + } + + private var listener: ((T?) -> ())? + + func bind(_ listener: @escaping (T?) -> ()) { + listener(value) + self.listener = listener + } +} + +final class SearchUsersViewModel { + private let userService = UserService.shared + + var userModel: Observable = Observable() + + var userModels: Observable<[UserModel]> = Observable([]) + + var filteredUsersModel: Observable<[UserModel]> = Observable() + + func getCurrentUser() { + DispatchQueue.global(qos: .userInitiated).async { [weak self] in + guard let uid = Auth.auth().currentUser?.uid else { + return + } + self?.userService.fetchUser(uid: uid) { [weak self] user in + self?.userModel.value = user + } + } + } + + func getAllUsers() { + DispatchQueue.global(qos: .userInitiated).async { [weak self] in + self?.userService.fetchUsers(completion: { [weak self] users in + guard let user = self?.userModel.value else { return } + let users = users.filter({ $0.uid != user.uid }) + users.forEach { user in + self?.userService.isUserFollowed(uid: user.uid) { user.isFollowed = $0 } + } + self?.userModels.value = users + }) + } + } + + func followUser(_ user: UserModel, completion: @escaping (Bool) -> ()) { + DispatchQueue.global().async { [weak self] in + self?.userService.followUser(uid: user.uid) { error, _ in + if let error = error { + print(error.localizedDescription) + completion(false) + return + } + user.isFollowed = true + completion(true) + } + } + } + + func unfollowUser(_ user: UserModel, completion: @escaping (Bool) -> ()) { + DispatchQueue.global().async { [weak self] in + self?.userService.unfollowUser(uid: user.uid) { error, _ in + if let error = error { + print(error.localizedDescription) + completion(false) + return + } + user.isFollowed = false + completion(true) + } + } + } + + func downloadImage(_ url: URL?, completion: @escaping (UIImage?) -> ()) { + DispatchQueue.global().async { + var cellImage: UIImage? = nil + guard let url = url else { + completion(cellImage) + return + } + if let data = try? Data(contentsOf: url) { + cellImage = UIImage(data: data) + } + completion(cellImage) + } + } + + func filtering(username: String) { + DispatchQueue.global().async { [weak self] in + self?.filteredUsersModel.value = self?.userModels.value?.filter { $0.username.lowercased().contains(username.lowercased()) } + } + } + + func stopFiltering() { + filteredUsersModel.value = nil + } +} + diff --git a/DoIt/ViewModels/TasksScreenViewModel.swift b/DoIt/ViewModels/TasksScreenViewModel.swift new file mode 100644 index 0000000..439c9c6 --- /dev/null +++ b/DoIt/ViewModels/TasksScreenViewModel.swift @@ -0,0 +1,18 @@ +// +// TasksScreenViewModel.swift +// DoIt +// +// Created by Данил Иванов on 14.12.2021. +// + +import Foundation + +//class TasksScreenViewModel: NSObject, TasksViewModel { +// var preparedTasks: Dynamic<[Task]> +// var allTasks: [Task] +// let taskService = TaskS +// +// override init() { +// allTasks = taskService. +// } +//} diff --git a/DoIt/ViewModels/TasksViewModel.swift b/DoIt/ViewModels/TasksViewModel.swift new file mode 100644 index 0000000..f6a6286 --- /dev/null +++ b/DoIt/ViewModels/TasksViewModel.swift @@ -0,0 +1,54 @@ +// +// TasksViewModel.swift +// DoIt +// +// Created by Данил Иванов on 14.12.2021. +// + +import Foundation +import UIKit + +final class TasksViewModel { + private let taskService = TaskService.shared + + var taskModels: Observable<[Task]> = Observable([]) + + var taskModel: Observable = Observable() + //var onTasksChanged: (([Task]) -> Void)? + + func getTasks(forUser: UserModel) { + self.taskService.fetchTasks(forUser: forUser) { [weak self] tasks in + self?.taskModels.value = tasks + //self?.onTasksChanged?(tasks) + } + } + + func getTask(withId: String) { + self.taskService.fetchTask(taskId: withId) { [weak self] task in + self?.taskModel.value = task + } + } + + func uploadTask(task: Task) { + self.taskService.uploadTask(task: task) { error, _ in + if error != nil { + //костыль + print("Task Upload Failed") + } + } + } + + func downloadImage(_ url: URL?, completion: @escaping (UIImage?) -> ()) { + DispatchQueue.global().async { + var cellImage: UIImage? = nil + guard let url = url else { + completion(cellImage) + return + } + if let data = try? Data(contentsOf: url) { + cellImage = UIImage(data: data) + } + completion(cellImage) + } + } +} diff --git a/DoIt/Views/Custom Views/Auth.swift b/DoIt/Views/Custom Views/Auth.swift index 0770975..afffa82 100644 --- a/DoIt/Views/Custom Views/Auth.swift +++ b/DoIt/Views/Custom Views/Auth.swift @@ -36,7 +36,7 @@ final class InputField: UIView { // MARK: - Initializers - init(labelImage: UIImage? = nil, keyboardType: UIKeyboardType = .default, placeholderText: String) { + init(labelImage: UIImage? = nil, keyboardType: UIKeyboardType = .default, placeholderText: String?) { super.init(frame: .zero) heightAnchor.constraint(equalToConstant: UIConstants.height).isActive = true @@ -71,7 +71,7 @@ final class InputField: UIView { icon.bottomAnchor.constraint(equalTo: bottomAnchor, constant: UIConstants.paddingBottom).isActive = true } - private func addTextfield(placeholderText: String) { + private func addTextfield(placeholderText: String?) { addSubview(textField) textField.autocorrectionType = .no textField.placeholder = placeholderText diff --git a/DoIt/Views/Custom Views/CustomTabBarController.swift b/DoIt/Views/Custom Views/CustomTabBarController.swift index c5b1bd5..b573007 100644 --- a/DoIt/Views/Custom Views/CustomTabBarController.swift +++ b/DoIt/Views/Custom Views/CustomTabBarController.swift @@ -36,14 +36,10 @@ class CustomTabBarController: SwipeableTabBarController { //MARK: - Helpers private func configureViewControllers() { - let userModel = UserModel(image: nil, name: nil, login: "", summary: nil, statistics: UserStatisticsModel(inProgress: "0", outdated: "0", done: "0", total: "0"), isMyScreen: true, isFollowed: false) - let tasks = TasksController() - tasks.userModel = userModel tasks.tabBarItem = UITabBarItem(title: TabBarStrings.tasks.rawValue.localized, image: .TabBarIcons.tasksIcon, selectedImage: nil) let feed = FeedController() - feed.userModel = userModel feed.tabBarItem = UITabBarItem(title: TabBarStrings.feed.rawValue.localized, image: .TabBarIcons.feedIcon, selectedImage: nil) let controllers = [ CustomNavigationController(rootViewController: tasks), CustomNavigationController(rootViewController: feed) ] diff --git a/DoIt/Views/Feed/FeedCollectionView/FeedCollectionViewCell.swift b/DoIt/Views/Feed/FeedCollectionView/FeedCollectionViewCell.swift index 6082a21..881f124 100644 --- a/DoIt/Views/Feed/FeedCollectionView/FeedCollectionViewCell.swift +++ b/DoIt/Views/Feed/FeedCollectionView/FeedCollectionViewCell.swift @@ -77,15 +77,26 @@ class FeedCollectionViewCell: UICollectionViewCell { //MARK: - Public Methods func configureCell(taskInfo: Task, userInfo: UserModel) { - taskImage.image = taskInfo.image ?? UIImage.TaskIcons.defaultImage + + var cellImage: UIImage? = nil + if let data = try? Data(contentsOf: taskInfo.image!) { + cellImage = UIImage(data: data) + } + taskImage.image = cellImage ?? UIImage.TaskIcons.defaultImage + taskLabel.text = taskInfo.title - creatorLabel.text = "@" + userInfo.login - guard let image = userInfo.image else { + creatorLabel.text = "@" + userInfo.username + + var userImage: UIImage? = nil + if let data = try? Data(contentsOf: userInfo.image!) { + userImage = UIImage(data: data) + } + guard let image = userImage else { creatorImage.layoutIfNeeded() - creatorImage.setImageForName(userInfo.name ?? userInfo.login, circular: false, textAttributes: nil) + creatorImage.setImageForName(userInfo.name ?? userInfo.username, circular: false, textAttributes: nil) return } - creatorImage.image = image +// creatorImage.image = image } private func configureUI() { @@ -93,6 +104,7 @@ class FeedCollectionViewCell: UICollectionViewCell { layer.cornerRadius = UIConstants.cornerRadius backgroundColor = .systemBackground contentView.isUserInteractionEnabled = false + backgroundColor = .AppColors.feedBackgroundColor configureTaskImage() configureTaskLabel() diff --git a/DoIt/Views/Feed/FeedController.swift b/DoIt/Views/Feed/FeedController.swift index f8fa84c..caf5fe3 100644 --- a/DoIt/Views/Feed/FeedController.swift +++ b/DoIt/Views/Feed/FeedController.swift @@ -36,34 +36,34 @@ class FeedController: UIViewController { collectionView.delegate = self collectionView.dataSource = self collectionView.register(FeedCollectionViewCell.self, forCellWithReuseIdentifier: FeedCollectionViewCell.self.description()) - collectionView.backgroundColor = UIColor.AppColors.feedBackgroundColor + collectionView.backgroundColor = .systemBackground collectionView.translatesAutoresizingMaskIntoConstraints = false return collectionView }() var userModel: UserModel? - var following = [ - UserModel(image: nil, name: "wfqjoa fda", login: "fqFJqow", summary: "My summery is", statistics: UserStatisticsModel(inProgress: "0", outdated: "1", done: "1", total: "2"), isMyScreen: false, isFollowed: true), - UserModel(image: nil, name: "gsgdsgger", login: "GIOWJEOG", summary: nil, statistics: UserStatisticsModel(inProgress: "0", outdated: "0", done: "0", total: "0"), isMyScreen: false, isFollowed: true), - UserModel(image: nil, name: "greaiojgeo", login: "fwaojfoq", summary: nil, statistics: UserStatisticsModel(inProgress: "0", outdated: "0", done: "0", total: "0"), isMyScreen: true, isFollowed: true), - UserModel(image: nil, name: "greaiojgeo", login: "gasgs", summary: nil, statistics: UserStatisticsModel(inProgress: "0", outdated: "0", done: "0", total: "0"), isMyScreen: false, isFollowed: true), - UserModel(image: nil, name: "greaiojgeo", login: "gasg", summary: nil, statistics: UserStatisticsModel(inProgress: "0", outdated: "0", done: "0", total: "0"), isMyScreen: false, isFollowed: true), - UserModel(image: nil, name: "greaiojgeo", login: "hdgh", summary: nil, statistics: UserStatisticsModel(inProgress: "0", outdated: "0", done: "0", total: "0"), isMyScreen: false, isFollowed: true), - UserModel(image: nil, name: "greaiojgeo", login: "hgdhr", summary: nil, statistics: UserStatisticsModel(inProgress: "0", outdated: "0", done: "0", total: "0"), isMyScreen: false, isFollowed: true), - UserModel(image: nil, name: "greaiojgeo", login: "gwaegfa", summary: nil, statistics: UserStatisticsModel(inProgress: "0", outdated: "0", done: "0", total: "0"), isMyScreen: false, isFollowed: true) + var following: [UserModel] = [ +// UserModel(image: nil, name: "wfqjoa fda", login: "fqFJqow", summary: "My summery is", statistics: UserStatisticsModel(inProgress: "0", outdated: "1", done: "1", total: "2"), isMyScreen: false, isFollowed: true), +// UserModel(image: nil, name: "gsgdsgger", login: "GIOWJEOG", summary: nil, statistics: UserStatisticsModel(inProgress: "0", outdated: "0", done: "0", total: "0"), isMyScreen: false, isFollowed: true), +// UserModel(image: nil, name: "greaiojgeo", login: "fwaojfoq", summary: nil, statistics: UserStatisticsModel(inProgress: "0", outdated: "0", done: "0", total: "0"), isMyScreen: true, isFollowed: true), +// UserModel(image: nil, name: "greaiojgeo", login: "gasgs", summary: nil, statistics: UserStatisticsModel(inProgress: "0", outdated: "0", done: "0", total: "0"), isMyScreen: false, isFollowed: true), +// UserModel(image: nil, name: "greaiojgeo", login: "gasg", summary: nil, statistics: UserStatisticsModel(inProgress: "0", outdated: "0", done: "0", total: "0"), isMyScreen: false, isFollowed: true), +// UserModel(image: nil, name: "greaiojgeo", login: "hdgh", summary: nil, statistics: UserStatisticsModel(inProgress: "0", outdated: "0", done: "0", total: "0"), isMyScreen: false, isFollowed: true), +// UserModel(image: nil, name: "greaiojgeo", login: "hgdhr", summary: nil, statistics: UserStatisticsModel(inProgress: "0", outdated: "0", done: "0", total: "0"), isMyScreen: false, isFollowed: true), +// UserModel(image: nil, name: "greaiojgeo", login: "gwaegfa", summary: nil, statistics: UserStatisticsModel(inProgress: "0", outdated: "0", done: "0", total: "0"), isMyScreen: false, isFollowed: true) ] - var followingUsersTasks = [ - Task(image: UIImage(named: "bob"), title: "Поменять резину", description: nil, deadline: nil, isDone: true, creatorId: "GIOWJEOG", color: .black, chapterId: 0, creationTime: Date(), isMyTask: false), - Task(image: UIImage(named: "duck"), title: "Купиить шапку", description: nil, deadline: nil, isDone: false, creatorId: "fqFJqow", color: .yellow, chapterId: 1, creationTime: Date(), isMyTask: false), - Task(image: UIImage(named: "bob"), title: "Отдохнуть", description: "Math exam. jad;lfajslf;jasl;dfjlskfja;sldf", deadline: nil, isDone: false, creatorId: "fwaojfoq", color: .red, chapterId: 2, creationTime: Date(), isMyTask: false), - Task(image: UIImage(named: "duck"), title: "Почистить зубы", description: "Math exam. jad;lfajslf;jasl;dfjlskfja;sldf", deadline: nil, isDone: true, creatorId: "fwaojfoq", color: .orange, chapterId: 3, creationTime: Date(), isMyTask: false), - Task(image: UIImage(named: "duck"), title: "Занятие по танцам", description: nil, deadline: nil, isDone: false, creatorId: "fwaojfoq", color: .black, chapterId: 4, creationTime: Date(), isMyTask: false), - Task(image: UIImage(named: "bob"), title: "Отдохнуть", description: "Math exam. jad;lfajslf;jasl;dfjlskfja;sldf", deadline: nil, isDone: false, creatorId: "GIOWJEOG", color: .red, chapterId: 5, creationTime: Date(), isMyTask: false), - Task(image: UIImage(named: "duck"), title: "Почистить зубы", description: "Math exam. jad;lfajslf;jasl;dfjlskfja;sldf", deadline: nil, isDone: true, creatorId: "GIOWJEOG", color: .orange, chapterId: 6, creationTime: Date(), isMyTask: false), - Task(image: UIImage(named: "duck"), title: "Оплатить счета", description: nil, deadline: nil, isDone: false, creatorId: "GIOWJEOG", color: .orange, chapterId: 7, creationTime: Date(), isMyTask: false), - Task(image: UIImage(named: "bob"), title: "Отдохнуть", description: "Math exam. jad;lfajslf;jasl;dfjlskfja;sldf", deadline: nil, isDone: false, creatorId: "GIOWJEOG", color: .red, chapterId: 8, creationTime: Date(), isMyTask: false) + var followingUsersTasks: [Task] = [ +// Task(image: UIImage(named: "bob"), title: "Поменять резину", description: nil, deadline: nil, isDone: true, creatorId: "GIOWJEOG", color: .black, chapterId: 0, creationTime: Date(), isMyTask: false), +// Task(image: UIImage(named: "duck"), title: "Купиить шапку", description: nil, deadline: nil, isDone: false, creatorId: "fqFJqow", color: .yellow, chapterId: 1, creationTime: Date(), isMyTask: false), +// Task(image: UIImage(named: "bob"), title: "Отдохнуть", description: "Math exam. jad;lfajslf;jasl;dfjlskfja;sldf", deadline: nil, isDone: false, creatorId: "fwaojfoq", color: .red, chapterId: 2, creationTime: Date(), isMyTask: false), +// Task(image: UIImage(named: "duck"), title: "Почистить зубы", description: "Math exam. jad;lfajslf;jasl;dfjlskfja;sldf", deadline: nil, isDone: true, creatorId: "fwaojfoq", color: .orange, chapterId: 3, creationTime: Date(), isMyTask: false), +// Task(image: UIImage(named: "duck"), title: "Занятие по танцам", description: nil, deadline: nil, isDone: false, creatorId: "fwaojfoq", color: .black, chapterId: 4, creationTime: Date(), isMyTask: false), +// Task(image: UIImage(named: "bob"), title: "Отдохнуть", description: "Math exam. jad;lfajslf;jasl;dfjlskfja;sldf", deadline: nil, isDone: false, creatorId: "GIOWJEOG", color: .red, chapterId: 5, creationTime: Date(), isMyTask: false), +// Task(image: UIImage(named: "duck"), title: "Почистить зубы", description: "Math exam. jad;lfajslf;jasl;dfjlskfja;sldf", deadline: nil, isDone: true, creatorId: "GIOWJEOG", color: .orange, chapterId: 6, creationTime: Date(), isMyTask: false), +// Task(image: UIImage(named: "duck"), title: "Оплатить счета", description: nil, deadline: nil, isDone: false, creatorId: "GIOWJEOG", color: .orange, chapterId: 7, creationTime: Date(), isMyTask: false), +// Task(image: UIImage(named: "bob"), title: "Отдохнуть", description: "Math exam. jad;lfajslf;jasl;dfjlskfja;sldf", deadline: nil, isDone: false, creatorId: "GIOWJEOG", color: .red, chapterId: 8, creationTime: Date(), isMyTask: false) ] private var swipeToMyTasks: Bool = false @@ -88,8 +88,10 @@ class FeedController: UIViewController { } private func configureNavigationController() { - navigationItem.title = (userModel?.isMyScreen ?? false) ? FeedStrings.header.rawValue.localized : "Feed" - navigationItem.rightBarButtonItem = (userModel?.isMyScreen ?? false) ? searchButton : nil + navigationItem.title = (userModel?.isCurrentUser ?? false) ? FeedStrings.header.rawValue.localized : FeedStrings.allTasksHeader.rawValue.localized +// navigationItem.rightBarButtonItem = (userModel?.isCurrentUser ?? false) ? searchButton : nil + navigationItem.rightBarButtonItem = searchButton + } private func layoutCollection() { @@ -108,11 +110,10 @@ extension FeedController: UICollectionViewDataSource { func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: FeedCollectionViewCell.self.description(), for: indexPath) as? FeedCollectionViewCell else { return .init(frame: .zero) } - guard let userInfo = following.first(where: { $0.login == followingUsersTasks[indexPath.row].creatorId }) else { return .init(frame: .zero) } + guard let userInfo = following.first(where: { $0.username == followingUsersTasks[indexPath.row].uid }) else { return .init(frame: .zero) } cell.tapOnUser = { [weak self] in let profileViewController = ProfileViewController() - profileViewController.userModel = userInfo - profileViewController.userTasksModel = UserTasksModel(login: userInfo.login, tasks: self?.followingUsersTasks.filter({ $0.creatorId == userInfo.login }) ?? []) + profileViewController.viewModel.userModel.value = userInfo self?.navigationController?.pushViewController(profileViewController, animated: true) } cell.configureCell(taskInfo: followingUsersTasks[indexPath.row], userInfo: userInfo) @@ -135,7 +136,6 @@ extension FeedController { @objc private func openSearch() { let searchUsersController = SearchUsersController() - searchUsersController.userModel = userModel navigationController?.pushViewController(searchUsersController, animated: true) } diff --git a/DoIt/Views/Profile/ProfileEditViewController.swift b/DoIt/Views/Profile/ProfileEditViewController.swift index 4f65f6a..149cc25 100644 --- a/DoIt/Views/Profile/ProfileEditViewController.swift +++ b/DoIt/Views/Profile/ProfileEditViewController.swift @@ -124,7 +124,8 @@ final class ProfileEditViewController: UIViewController { }() private let keyboardManager = KeyboardManager.shared - var userModel: UserModel? + private var imageWasChanged: Bool = false + var viewModel: ProfileEditViewModel = ProfileEditViewModel() // MARK: - Lifecycle @@ -137,19 +138,30 @@ final class ProfileEditViewController: UIViewController { view.snapshotView(afterScreenUpdates: true) configureUI() + + configure() } // MARK: - Helpers private func configure() { - guard let userModel = userModel else { return } + guard let userModel = viewModel.userModel.value else { return } summaryTextView.text = userModel.summary - guard let image = userModel.image else { - profileImageView.layoutIfNeeded() - profileImageView.setImageForName(userModel.name ?? userModel.login, circular: false, textAttributes: nil) + profileImageView.layoutIfNeeded() + profileImageView.setImageForName(userModel.name ?? userModel.username, circular: false, textAttributes: nil) + loginInputField.textField.text = userModel.username + nameInputField.textField.text = userModel.name + guard let imageURL = userModel.image else { return } - profileImageView.image = image + viewModel.downloadImage(imageURL) { image in + guard let image = image else { + return + } + DispatchQueue.main.async { [weak self] in + self?.profileImageView.image = image + } + } } private func configureUI() { @@ -158,7 +170,6 @@ final class ProfileEditViewController: UIViewController { layoutScrollView() layoutStackView() layoutWidthInputFields() - configure() } private func configureNavigationController() { @@ -191,7 +202,17 @@ final class ProfileEditViewController: UIViewController { extension ProfileEditViewController { @objc private func doneEditing() { - + guard viewModel.userModel.value != nil else { return } + let summary = summaryTextView.textView.text == ProfileEditString.summeryPlaceholder.rawValue.localized ? nil : summaryTextView.textView.text + viewModel.updateUserProfile(image: imageWasChanged ? profileImageView.image : nil, + name: nameInputField.textField.text, + username: loginInputField.textField.text, + summary: summary, + complition: { + DispatchQueue.main.async { [weak self] in + self?.navigationController?.popViewController(animated: true) + } + }) } } @@ -208,6 +229,7 @@ extension ProfileEditViewController: UIImagePickerControllerDelegate, UINavigati func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { guard let image = info[.editedImage] as? UIImage else { return } profileImageView.image = image + imageWasChanged = true dismiss(animated: true, completion: nil) } } diff --git a/DoIt/Views/Profile/ProfileFollowingUserCell.swift b/DoIt/Views/Profile/ProfileFollowingUserCell.swift index ca0b52b..158e45d 100644 --- a/DoIt/Views/Profile/ProfileFollowingUserCell.swift +++ b/DoIt/Views/Profile/ProfileFollowingUserCell.swift @@ -51,12 +51,13 @@ class ProfileFollowingUserCell: UICollectionViewCell { // MARK: - Helpers func configureCell(with: UserModel) { - loginLabel.text = "@" + with.login - guard let image = with.image else { - profileImageView.layoutIfNeeded() - profileImageView.setImageForName(with.name ?? with.login, circular: false, textAttributes: nil) - return - } + loginLabel.text = "@" + with.username + profileImageView.layoutIfNeeded() + profileImageView.setImageForName(with.name ?? with.username, circular: false, textAttributes: nil) + } + + func configureImage(_ image: UIImage?) { + guard let image = image else { return } profileImageView.image = image } diff --git a/DoIt/Views/Profile/ProfileViewController.swift b/DoIt/Views/Profile/ProfileViewController.swift index c4eb3bc..83c3cda 100644 --- a/DoIt/Views/Profile/ProfileViewController.swift +++ b/DoIt/Views/Profile/ProfileViewController.swift @@ -277,11 +277,7 @@ class ProfileViewController: UIViewController { // MARK: - Configuration - var userModel: UserModel? - - var userFollowingModel: UserFollowingModel? - - var userTasksModel: UserTasksModel? + var viewModel: ProfileViewModel = ProfileViewModel() // MARK: - Lifecycle @@ -289,19 +285,38 @@ class ProfileViewController: UIViewController { super.viewDidLoad() configureUI() + + viewModel.userModel.bind { _ in + DispatchQueue.main.async { [weak self] in + self?.configureCells() + } + } + + viewModel.userTasksModel.bind { _ in + DispatchQueue.main.async { [weak self] in + self?.configureTasks() + self?.configureStatistics() + } + } + + viewModel.userFollowingModel.bind { _ in + DispatchQueue.main.async { [weak self] in + self?.configureFollowing() + } + } + + viewModel.getUserTasks() + viewModel.getUserFollowings() } // MARK: - Helpers private func configureCells() { - guard let userModel = userModel else { return } - configureHeader(image: userModel.image, name: userModel.name, login: userModel.login, isFollowed: userModel.isFollowed, isMyScreen: userModel.isMyScreen) + guard let userModel = viewModel.userModel.value else { return } + configureNavigationController(title: viewModel.userModel.value?.username ?? "", isMyScreen: viewModel.userModel.value?.isCurrentUser ?? false) + configureHeader(imageURL: userModel.image, name: userModel.name, login: userModel.username, isFollowed: userModel.isFollowed ?? false, isMyScreen: userModel.isCurrentUser) configureInformation(summary: userModel.summary) - configureStatistics(with: userModel.statistics) - - configureTasks(with: userTasksModel?.tasks ?? []) - - configureFollowing(isFollowingEmpty: (userFollowingModel?.followings ?? []).count == 0 ? true : false) + configureStatistics() } private func configureUI() { @@ -309,7 +324,7 @@ class ProfileViewController: UIViewController { layoutScrollView() layoutCellsStackView() - configureNavigationController(title: userModel?.login ?? "", isMyScreen: userModel?.isMyScreen ?? false) + configureNavigationController(title: viewModel.userModel.value?.username ?? "", isMyScreen: viewModel.userModel.value?.isCurrentUser ?? false) configureCells() } @@ -559,6 +574,17 @@ extension ProfileViewController { innerView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true return view } + + private func switchFollowButtonAnimated() { + UIView.animate(withDuration: 0.2, delay: 0, options: .curveEaseInOut) { + self.configureFollowButton(isFollowed: !self.followButton.isSelected) + self.followButton.transform = CGAffineTransform(scaleX: 0.9, y: 0.9) + } completion: { _ in + UIView.animate(withDuration: 0.2) { + self.followButton.transform = CGAffineTransform.identity + } + } + } } // MARK: - Configuration @@ -566,9 +592,9 @@ extension ProfileViewController { extension ProfileViewController { // MARK: - Header - private func configureHeader(image: UIImage?, name: String?, login: String, isFollowed: Bool, isMyScreen: Bool) { + private func configureHeader(imageURL: URL?, name: String?, login: String, isFollowed: Bool, isMyScreen: Bool) { configureHeaderHeight(withName: name != nil, isMyScreen: isMyScreen) - configureProfileImage(image: image, name: name, login: login) + configureProfileImage(imageURL: imageURL, name: name, login: login) nameLabel.text = name loginLabel.text = "@" + login @@ -590,13 +616,20 @@ extension ProfileViewController { } } - private func configureProfileImage(image: UIImage?, name: String?, login: String) { - guard let image = image else { + private func configureProfileImage(imageURL: URL?, name: String?, login: String) { + guard let imageURL = imageURL else { profileImageView.layoutIfNeeded() profileImageView.setImageForName(name ?? login, circular: false, textAttributes: nil) return } - profileImageView.image = image + viewModel.downloadImage(imageURL) { image in + guard let image = image else { + return + } + DispatchQueue.main.async { [weak self] in + self?.profileImageView.image = image + } + } } // MARK: - Information View @@ -607,16 +640,34 @@ extension ProfileViewController { // MARK: - Statistics View - private func configureStatistics(with: UserStatisticsModel) { - inProgressNumberLabel.text = with.inProgress - outdatedNumberLabel.text = with.outdated - doneNumberLabel.text = with.done - totalNumberLabel.text = with.total + private func configureStatistics() { + let tasks = viewModel.userTasksModel.value?.tasks ?? [] + DispatchQueue.global().async { + var done = 0 + var outdated = 0 + var inProgress = 0 + let total = tasks.count + let currentDate = Date() + tasks.forEach { task in + guard !task.isDone else { done += 1; return } + guard let deadline = task.deadline else { inProgress += 1; return } + guard deadline > currentDate else { outdated += 1; return } + inProgress += 1 + } + + DispatchQueue.main.async { [weak self] in + self?.inProgressNumberLabel.text = "\(inProgress)" + self?.outdatedNumberLabel.text = "\(outdated)" + self?.doneNumberLabel.text = "\(done)" + self?.totalNumberLabel.text = "\(total)" + } + } } // MARK: - Tasks View - private func configureTasks(with: [Task]) { + private func configureTasks() { + let with = viewModel.userTasksModel.value?.tasks ?? [] let taskToConfigure = min(with.count, taskViews.count) for i in 0.. Int { - return userFollowingModel?.followings.count ?? 0 + return viewModel.userFollowingModel.value?.followings.count ?? 0 } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: ProfileFollowingUserCell.self), for: indexPath) as? ProfileFollowingUserCell else { return .init(frame: .zero) } - guard let userFollowingModel = userFollowingModel else { return .init() } + guard let userFollowingModel = viewModel.userFollowingModel.value else { return .init() } cell.configureCell(with: userFollowingModel.followings[indexPath.row]) + viewModel.downloadImage(userFollowingModel.followings[indexPath.row].image) { image in + guard let image = image else { + return + } + DispatchQueue.main.async { + cell.configureImage(image) + } + } return cell } } @@ -694,8 +765,8 @@ extension ProfileViewController: UICollectionViewDelegateFlowLayout { } extension ProfileViewController { @objc private func showAllTasks() { - guard let userModel = userModel else { return } - guard !userModel.isMyScreen else { + guard let userModel = viewModel.userModel.value else { return } + guard !userModel.isCurrentUser else { NotificationCenter.default.post(name: .openTasksFromProfile, object: nil) navigationController?.popToRootViewController(animated: true) return @@ -703,7 +774,7 @@ extension ProfileViewController { let viewController = FeedController() viewController.userModel = userModel viewController.following = [userModel] - viewController.followingUsersTasks = userTasksModel?.tasks ?? [] + viewController.followingUsersTasks = viewModel.userTasksModel.value?.tasks ?? [] navigationController?.pushViewController(viewController, animated: true) } @@ -713,19 +784,34 @@ extension ProfileViewController { guard let i = taskViews.firstIndex(where: { $0.taskView == view } ) else { return } let taskViewController = TaskViewController() - taskViewController.taskModel = userTasksModel?.tasks[i] + taskViewController.taskModel = viewModel.userTasksModel.value?.tasks[i] taskViewController.hidesBottomBarWhenPushed = true navigationController?.pushViewController(taskViewController, animated: true) } @objc private func didTapFollowButton() { - UIView.animate(withDuration: 0.2, delay: 0, options: .curveEaseInOut) { - self.configureFollowButton(isFollowed: !self.followButton.isSelected) - self.followButton.transform = CGAffineTransform(scaleX: 0.9, y: 0.9) - } completion: { _ in - UIView.animate(withDuration: 0.2) { - self.followButton.transform = CGAffineTransform.identity + guard let userModel = viewModel.userModel.value else { return } + guard let isFollowed = userModel.isFollowed else { return } + if isFollowed { + viewModel.unfollowUser(userModel) { [weak self] done in + guard done else { + + return + } + DispatchQueue.main.async { + self?.switchFollowButtonAnimated() + } + } + } else { + viewModel.followUser(userModel) { [weak self] done in + guard done else { + + return + } + DispatchQueue.main.async { + self?.switchFollowButtonAnimated() + } } } } @@ -733,7 +819,7 @@ extension ProfileViewController { @objc private func openSettings() { let profileEditViewController = ProfileEditViewController() - profileEditViewController.userModel = userModel + profileEditViewController.viewModel.userModel = viewModel.userModel profileEditViewController.hidesBottomBarWhenPushed = true navigationController?.pushViewController(profileEditViewController, animated: true) } diff --git a/DoIt/Views/Search Users/SearchUsersCell.swift b/DoIt/Views/Search Users/SearchUsersCell.swift index fdaab7c..c59e9a1 100644 --- a/DoIt/Views/Search Users/SearchUsersCell.swift +++ b/DoIt/Views/Search Users/SearchUsersCell.swift @@ -7,6 +7,10 @@ import UIKit +protocol SearchUsersCellDelegate: AnyObject { + func didTapFollowButton(_ indexPath: Int?, completion: @escaping () -> ()) +} + final class SearchUsersCell: UITableViewCell { // MARK: - Private Properties @@ -75,6 +79,10 @@ final class SearchUsersCell: UITableViewCell { button.addTarget(self, action: #selector(didTapFollowButton), for: .touchUpInside) return button }() + + var indexPathRow: Int? + + weak var delegate: SearchUsersCellDelegate? // MARK: - Lifecycle @@ -91,16 +99,15 @@ final class SearchUsersCell: UITableViewCell { // MARK: - Helpers func configureCell(with model: UserModel) { - loginLabel.text = model.login + loginLabel.text = model.username configureSummeryLabel(text: model.summary) - configureFollowButton(isFollowed: model.isFollowed) - configureImageView(image: model.image, name: model.name, login: model.login) + configureFollowButton(isFollowed: model.isFollowed ?? false) + profileImageView.layoutIfNeeded() + profileImageView.setImageForName(model.name ?? model.username, circular: false, textAttributes: nil) } - private func configureImageView(image: UIImage?, name: String?, login: String) { - profileImageView.layoutIfNeeded() + func configureImage(image: UIImage?) { guard let image = image else { - profileImageView.setImageForName(name ?? login, circular: false, textAttributes: nil) return } profileImageView.image = image @@ -149,10 +156,7 @@ final class SearchUsersCell: UITableViewCell { followButton.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true } - // MARK: - Actions - - @objc - private func didTapFollowButton() { + private func switchFollowButtonAnimated() { UIView.animate(withDuration: 0.2, delay: 0, options: .curveEaseInOut) { self.configureFollowButton(isFollowed: !self.followButton.isSelected) self.followButton.transform = CGAffineTransform(scaleX: 0.9, y: 0.9) @@ -162,4 +166,11 @@ final class SearchUsersCell: UITableViewCell { } } } + + // MARK: - Actions + + @objc + private func didTapFollowButton() { + delegate?.didTapFollowButton(indexPathRow, completion: switchFollowButtonAnimated) + } } diff --git a/DoIt/Views/Search Users/SearchUsersController.swift b/DoIt/Views/Search Users/SearchUsersController.swift index 2ec2812..b0d7859 100644 --- a/DoIt/Views/Search Users/SearchUsersController.swift +++ b/DoIt/Views/Search Users/SearchUsersController.swift @@ -61,15 +61,31 @@ final class SearchUsersController: UIViewController { return label }() - var userModel: UserModel? - - private var userModels: [UserModel] = [] + var viewModel: SearchUsersViewModel = SearchUsersViewModel() // MARK: - Lifecycle override func viewDidLoad() { super.viewDidLoad() - + + viewModel.userModel.bind { [weak self] _ in + self?.viewModel.getAllUsers() + } + + viewModel.userModels.bind { [weak self] _ in + DispatchQueue.main.async { + self?.tableView.reloadSections(IndexSet(integer: 0), with: .fade) + } + } + + viewModel.filteredUsersModel.bind { [weak self] _ in + DispatchQueue.main.async { + self?.tableView.reloadSections(IndexSet(integer: 0), with: .fade) + } + } + + viewModel.getCurrentUser() + configureUI() } @@ -128,21 +144,42 @@ extension SearchUsersController: UITableViewDelegate { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let profileViewController = ProfileViewController() - profileViewController.userModel = userModels[indexPath.row] + profileViewController.viewModel.userModel.value = viewModel.filteredUsersModel.value?[indexPath.row] ?? viewModel.userModels.value?[indexPath.row] navigationController?.pushViewController(profileViewController, animated: true) } } extension SearchUsersController: UITableViewDataSource { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return userModels.count + if let filteredUsersModel = viewModel.filteredUsersModel.value { + return filteredUsersModel.count + } + return viewModel.userModels.value?.count ?? 0 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { guard let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: SearchUsersCell.self), for: indexPath) as? SearchUsersCell else { return .init() } - cell.configureCell(with: userModels[indexPath.row]) + cell.delegate = self + cell.indexPathRow = indexPath.row + guard viewModel.filteredUsersModel.value == nil else { + cell.configureCell(with: viewModel.filteredUsersModel.value![indexPath.row]) + viewModel.downloadImage(viewModel.filteredUsersModel.value![indexPath.row].image) { image in + DispatchQueue.main.async { + cell.configureImage(image: image) + } + } + return cell + } + if let userModels = viewModel.userModels.value { + cell.configureCell(with: userModels[indexPath.row]) + viewModel.downloadImage(userModels[indexPath.row].image) { image in + DispatchQueue.main.async { + cell.configureImage(image: image) + } + } + } return cell } } @@ -150,6 +187,11 @@ extension SearchUsersController: UITableViewDataSource { // MARK: - Actions extension SearchUsersController: UISearchBarDelegate { + func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { + guard !searchText.isEmpty else { viewModel.stopFiltering(); return } + viewModel.filtering(username: searchText) + } + func searchBarCancelButtonClicked(_ searchBar: UISearchBar) { searchBar.resignFirstResponder() searchBar.text = nil @@ -159,6 +201,42 @@ extension SearchUsersController: UISearchBarDelegate { } } +extension SearchUsersController: SearchUsersCellDelegate { + func didTapFollowButton(_ indexPath: Int?, completion: @escaping () -> ()) { + guard let indexPath = indexPath else { return } + var userModel: UserModel? + if let user = viewModel.filteredUsersModel.value?[indexPath] { userModel = user } + if let user = viewModel.userModels.value?[indexPath] { userModel = user } + guard let userModel = userModel else { return } + guard let isFollowed = userModel.isFollowed else { return } + isFollowed ? unfollowUser(userModel, completion: completion) : followUser(userModel, completion: completion) + } + + private func followUser(_ user: UserModel, completion: @escaping () -> ()) { + viewModel.followUser(user) { done in + guard done else { + + return + } + DispatchQueue.main.async { + completion() + } + } + } + + private func unfollowUser(_ user: UserModel, completion: @escaping () -> ()) { + viewModel.unfollowUser(user) { done in + guard done else { + + return + } + DispatchQueue.main.async { + completion() + } + } + } +} + extension SearchUsersController { @objc private func addSearchBarView() { diff --git a/DoIt/Views/SignIn/SignInController.swift b/DoIt/Views/SignIn/SignInController.swift index bcece3c..2c6684e 100644 --- a/DoIt/Views/SignIn/SignInController.swift +++ b/DoIt/Views/SignIn/SignInController.swift @@ -26,8 +26,22 @@ class SignInController: UIViewController { return button }() + private let viewModel = AuthViewModel() + override func viewDidLoad() { super.viewDidLoad() + + viewModel.authResultModel.bind { [weak self] authResult in + switch authResult { + case .success: + print("SignIn successed") + self?.presentTabBar() + case .failure(let error): + print("SignIn was failured: ", error.localizedDescription) + case .none: + return + } + } configureView() } @@ -51,11 +65,14 @@ class SignInController: UIViewController { stack.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true } - @objc private func signInButtonPressed(_ sender: UIButton) { + private func presentTabBar() { let tabbarController = CustomTabBarController() tabbarController.modalPresentationStyle = .fullScreen present(tabbarController, animated: true) - + } + + @objc private func signInButtonPressed(_ sender: UIButton) { + self.viewModel.signIn(email: usernameInputView.textField.text, password: passwordInputView.textField.text) } @objc private func signUpButtonPressed(_ sender: UIButton) { diff --git a/DoIt/Views/SignUp/SignUpController.swift b/DoIt/Views/SignUp/SignUpController.swift index 0b6df3a..f605a58 100644 --- a/DoIt/Views/SignUp/SignUpController.swift +++ b/DoIt/Views/SignUp/SignUpController.swift @@ -32,8 +32,59 @@ class SignUpController: UIViewController { return button }() + private let viewModel = AuthViewModel() + //private var authResult = AuthResult + override func viewDidLoad() { +// viewModel.authResultModel.bind { [weak self] _ in +// self?.authResult = viewModel.authResultModel.value +// } + super.viewDidLoad() + + +//из старой версии, с попапом на будущее + +// let title = AuthStrings.signInSuccessful.rawValue.localized +// let message = AuthStrings.welcome.rawValue.localized +// +// lazy var popup : PopupDialog = { +// let pop = PopupDialog(title: title, message: message) +// let button = CancelButton(title: AuthStrings.invitation.rawValue.localized) { +// let profileView = ProfileController(email: email) +// self.present(profileView, animated: true, completion: nil) +// } +// pop.addButton(button) +// return pop +// }() +// +// self.present(popup, animated: true, completion: nil) +// case .failure(let error): +// +// let title = AuthStrings.signInUnsuccessful.rawValue.localized +// let message = error.localizedDescription +// +// lazy var popup : PopupDialog = { +// let pop = PopupDialog(title: title, message: message) +// let button = CancelButton(title: AuthStrings.accept.rawValue.localized) {} +// pop.addButton(button) +// return pop +// }() + //self.present(popup, animated: true, completion: nil) + + + + viewModel.authResultModel.bind { [weak self] authResult in + switch authResult { + case .success: + print("SignUp successed") + self?.presentOnboarding() + case .failure(let error): + print("SignUp was failured: ", error.localizedDescription) + case .none: + return + } + } configureView() } @@ -57,11 +108,16 @@ class SignUpController: UIViewController { stack.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true } - @objc private func registerButtonPressed(_ sender: UIButton) { + private func presentOnboarding() { let onboardingViewController = OnboardingViewController() onboardingViewController.modalPresentationStyle = .fullScreen present(onboardingViewController, animated: true) - + } + + @objc private func registerButtonPressed(_ sender: UIButton) { + self.viewModel.signUp(email: envelopeInputView.textField.text, + username: usernameInputView.textField.text, + password: passwordInputView.textField.text) } @objc private func signInButtonPressed(_ sender: UIButton) { diff --git a/DoIt/Views/Task Screen/TaskEditViewController.swift b/DoIt/Views/Task Screen/TaskEditViewController.swift index 537dbf3..9a94f46 100644 --- a/DoIt/Views/Task Screen/TaskEditViewController.swift +++ b/DoIt/Views/Task Screen/TaskEditViewController.swift @@ -154,7 +154,7 @@ class TaskEditViewController: UIViewController { taskLabel.textField.text = taskModel.title taskChapter.text = TaskCategory(index: taskModel.chapterId).chapter.title taskDescription.text = taskModel.description - taskImage.image = taskModel.image +// taskImage.image = taskModel.image guard let deadline = taskModel.deadline else { return } timerLabel.text = dateFormatter.string(from: deadline) } diff --git a/DoIt/Views/Task Screen/TaskViewController.swift b/DoIt/Views/Task Screen/TaskViewController.swift index 9a7b13b..2e5ec28 100644 --- a/DoIt/Views/Task Screen/TaskViewController.swift +++ b/DoIt/Views/Task Screen/TaskViewController.swift @@ -107,7 +107,7 @@ class TaskViewController: UIViewController { private func configure() { guard let taskModel = taskModel else { return } - taskImage.image = taskModel.image ?? .TaskIcons.defaultImage +// taskImage.image = taskModel.image ?? .TaskIcons.defaultImage taskDescription.text = taskModel.description taskChapter.text = TaskCategory(index: taskModel.chapterId).chapter.title configureNavigationBar(title: taskModel.title, isDone: taskModel.isDone) diff --git a/DoIt/Views/Tasks/TasksController.swift b/DoIt/Views/Tasks/TasksController.swift index 9148bee..9f8959d 100644 --- a/DoIt/Views/Tasks/TasksController.swift +++ b/DoIt/Views/Tasks/TasksController.swift @@ -53,12 +53,12 @@ class TasksController: UIViewController { return tableView }() - var tasks = [ - Task(image: UIImage(named: "bob"), title: "Task 1: Get ready for an exam", description: nil, deadline: nil, isDone: true, creatorId: "1", color: .black, chapterId: 0, creationTime: Date(), isMyTask: true), - Task(image: UIImage(named: "bob"), title: "Task 2: Get ready for an exam", description: nil, deadline: nil, isDone: false, creatorId: "2", color: .yellow, chapterId: 1, creationTime: Date(), isMyTask: true), - Task(image: UIImage(named: "bob"), title: "Task 3: Get ready for an exam", description: "Math exam. jad;lfajslf;jasl;dfjlskfja;sldf", deadline: nil, isDone: false, creatorId: "2", color: .red, chapterId: 2, creationTime: Date(), isMyTask: true), - Task(image: UIImage(named: "bob"), title: "Task 4: Get ready for an exam", description: "Math exam. jad;lfajslf;jasl;dfjlskfja;sldf", deadline: Date(timeIntervalSinceNow: 50), isDone: true, creatorId: "1", color: .orange, chapterId: 3, creationTime: Date(), isMyTask: true) - ] + private let viewModel: TasksViewModel = TasksViewModel() + + //костыль + private lazy var tasks: [Task] = { + return [Task(id: "", dictionary: [:])] + }() private var selectedTasks: [Task]? { didSet { @@ -73,7 +73,16 @@ class TasksController: UIViewController { //MARK: - Override Methods override func viewDidLoad() { + super.viewDidLoad() + viewModel.taskModels.bind { [weak self] _ in + DispatchQueue.main.async { + self?.reload() + } + } + + //let currentUser = UserModel(uid: UserDefaults.standard.string(forKey: "current_user")!, email: "", username: "", summary: nil, image: nil, name: nil) + //viewModel.getTasks(forUser: currentUser) view.backgroundColor = .systemBackground configureNavigationController() view.addSubview(table) @@ -82,9 +91,15 @@ class TasksController: UIViewController { } //MARK: - Private Methods + private func reload() { + table.reloadData() + //guard let userTasks = viewModel.taskModels.value else { return } + //tasks = userTasks + } + private func configureNavigationController() { navigationItem.title = TasksStrings.header.rawValue.localized - navigationItem.rightBarButtonItem = (userModel?.isMyScreen ?? false) ? profileButton : nil + navigationItem.rightBarButtonItem = (userModel?.isCurrentUser ?? false) ? profileButton : nil } private func layoutCollection() { @@ -111,6 +126,7 @@ extension TasksController: UITableViewDataSource { func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { guard let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: TaskTableViewCell.self), for: indexPath) as? TaskTableViewCell else { return .init(frame: .zero) } + //тут отрисовываем ячейки с данными из modelView.sortedtasks cell.configureCell(taskInfo: selectedTasks?[indexPath.row] ?? tasks[indexPath.row]) return cell } @@ -157,12 +173,12 @@ extension TasksController { @objc private func openProfile() { let profileViewController = ProfileViewController() - profileViewController.userModel = userModel + profileViewController.viewModel.userModel.value = userModel guard let userModel = userModel else { navigationController?.pushViewController(profileViewController, animated: true) return } - profileViewController.userTasksModel = UserTasksModel(login: userModel.login, tasks: tasks) + profileViewController.viewModel.userTasksModel.value = UserTasksModel(login: userModel.username, tasks: tasks) navigationController?.pushViewController(profileViewController, animated: true) } } diff --git a/DoIt/Views/Tasks/TasksTableView/TaskTableViewCell.swift b/DoIt/Views/Tasks/TasksTableView/TaskTableViewCell.swift index 6ebf4b2..9767880 100644 --- a/DoIt/Views/Tasks/TasksTableView/TaskTableViewCell.swift +++ b/DoIt/Views/Tasks/TasksTableView/TaskTableViewCell.swift @@ -66,7 +66,14 @@ class TaskTableViewCell: UITableViewCell { func configureCell(taskInfo: Task) { chapterIndicator.backgroundColor = taskInfo.color taskInfo.isDone ? checkBox.setImage(.TaskIcons.done, for: .normal) : checkBox.setImage(.TaskIcons.notDone, for: .normal) - image.image = taskInfo.image ?? .TaskIcons.defaultImage + + var cellImage: UIImage? = nil +// if let data = try? Data(contentsOf: taskInfo.image!) { +// cellImage = UIImage(data: data) +// } + + image.image = cellImage ?? .TaskIcons.defaultImage + title.text = taskInfo.title taskDescription.text = taskInfo.description ?? TaskString.description.rawValue.localized divider.backgroundColor = taskInfo.color diff --git a/GoogleService-Info.plist b/GoogleService-Info.plist new file mode 100644 index 0000000..ab71a05 --- /dev/null +++ b/GoogleService-Info.plist @@ -0,0 +1,36 @@ + + + + + CLIENT_ID + 602688246846-qrh1cc9j0t3jp82esmfohto1npg4fajn.apps.googleusercontent.com + REVERSED_CLIENT_ID + com.googleusercontent.apps.602688246846-qrh1cc9j0t3jp82esmfohto1npg4fajn + API_KEY + AIzaSyDSEhOuurCP73LiF9FHZPjBPP2-dl3c2Tc + GCM_SENDER_ID + 602688246846 + PLIST_VERSION + 1 + BUNDLE_ID + com.vkproj.doIt1 + PROJECT_ID + doit-d4f58 + STORAGE_BUCKET + doit-d4f58.appspot.com + IS_ADS_ENABLED + + IS_ANALYTICS_ENABLED + + IS_APPINVITE_ENABLED + + IS_GCM_ENABLED + + IS_SIGNIN_ENABLED + + GOOGLE_APP_ID + 1:602688246846:ios:ac3f4f04a7c992f0e22d6f + DATABASE_URL + https://doit-d4f58-default-rtdb.europe-west1.firebasedatabase.app + + \ No newline at end of file diff --git a/Podfile b/Podfile index 7ea6084..a29d436 100644 --- a/Podfile +++ b/Podfile @@ -6,7 +6,10 @@ target 'DoIt' do use_frameworks! # Pods for DoIt - pod "EasyNotificationBadge" pod "InitialsImageView" pod 'SwipeableTabBarController' + pod 'Firebase/Core' + pod 'Firebase/Database' + pod 'Firebase/Storage' + pod 'Firebase/Auth' end diff --git a/Podfile.lock b/Podfile.lock index 8954177..e69f803 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -1,24 +1,162 @@ PODS: - - EasyNotificationBadge (1.2.5) + - Firebase/Auth (8.10.0): + - Firebase/CoreOnly + - FirebaseAuth (~> 8.10.0) + - Firebase/Core (8.10.0): + - Firebase/CoreOnly + - FirebaseAnalytics (~> 8.10.0) + - Firebase/CoreOnly (8.10.0): + - FirebaseCore (= 8.10.0) + - Firebase/Database (8.10.0): + - Firebase/CoreOnly + - FirebaseDatabase (~> 8.10.0) + - Firebase/Storage (8.10.0): + - Firebase/CoreOnly + - FirebaseStorage (~> 8.10.0) + - FirebaseAnalytics (8.10.0): + - FirebaseAnalytics/AdIdSupport (= 8.10.0) + - FirebaseCore (~> 8.0) + - FirebaseInstallations (~> 8.0) + - GoogleUtilities/AppDelegateSwizzler (~> 7.6) + - GoogleUtilities/MethodSwizzler (~> 7.6) + - GoogleUtilities/Network (~> 7.6) + - "GoogleUtilities/NSData+zlib (~> 7.6)" + - nanopb (~> 2.30908.0) + - FirebaseAnalytics/AdIdSupport (8.10.0): + - FirebaseCore (~> 8.0) + - FirebaseInstallations (~> 8.0) + - GoogleAppMeasurement (= 8.10.0) + - GoogleUtilities/AppDelegateSwizzler (~> 7.6) + - GoogleUtilities/MethodSwizzler (~> 7.6) + - GoogleUtilities/Network (~> 7.6) + - "GoogleUtilities/NSData+zlib (~> 7.6)" + - nanopb (~> 2.30908.0) + - FirebaseAuth (8.10.0): + - FirebaseCore (~> 8.0) + - GoogleUtilities/AppDelegateSwizzler (~> 7.6) + - GoogleUtilities/Environment (~> 7.6) + - GTMSessionFetcher/Core (~> 1.5) + - FirebaseCore (8.10.0): + - FirebaseCoreDiagnostics (~> 8.0) + - GoogleUtilities/Environment (~> 7.6) + - GoogleUtilities/Logger (~> 7.6) + - FirebaseCoreDiagnostics (8.10.0): + - GoogleDataTransport (~> 9.1) + - GoogleUtilities/Environment (~> 7.6) + - GoogleUtilities/Logger (~> 7.6) + - nanopb (~> 2.30908.0) + - FirebaseDatabase (8.10.0): + - FirebaseCore (~> 8.0) + - leveldb-library (~> 1.22) + - FirebaseInstallations (8.10.0): + - FirebaseCore (~> 8.0) + - GoogleUtilities/Environment (~> 7.6) + - GoogleUtilities/UserDefaults (~> 7.6) + - PromisesObjC (< 3.0, >= 1.2) + - FirebaseStorage (8.10.0): + - FirebaseCore (~> 8.0) + - GTMSessionFetcher/Core (~> 1.5) + - GoogleAppMeasurement (8.10.0): + - GoogleAppMeasurement/AdIdSupport (= 8.10.0) + - GoogleUtilities/AppDelegateSwizzler (~> 7.6) + - GoogleUtilities/MethodSwizzler (~> 7.6) + - GoogleUtilities/Network (~> 7.6) + - "GoogleUtilities/NSData+zlib (~> 7.6)" + - nanopb (~> 2.30908.0) + - GoogleAppMeasurement/AdIdSupport (8.10.0): + - GoogleAppMeasurement/WithoutAdIdSupport (= 8.10.0) + - GoogleUtilities/AppDelegateSwizzler (~> 7.6) + - GoogleUtilities/MethodSwizzler (~> 7.6) + - GoogleUtilities/Network (~> 7.6) + - "GoogleUtilities/NSData+zlib (~> 7.6)" + - nanopb (~> 2.30908.0) + - GoogleAppMeasurement/WithoutAdIdSupport (8.10.0): + - GoogleUtilities/AppDelegateSwizzler (~> 7.6) + - GoogleUtilities/MethodSwizzler (~> 7.6) + - GoogleUtilities/Network (~> 7.6) + - "GoogleUtilities/NSData+zlib (~> 7.6)" + - nanopb (~> 2.30908.0) + - GoogleDataTransport (9.1.2): + - GoogleUtilities/Environment (~> 7.2) + - nanopb (~> 2.30908.0) + - PromisesObjC (< 3.0, >= 1.2) + - GoogleUtilities/AppDelegateSwizzler (7.6.0): + - GoogleUtilities/Environment + - GoogleUtilities/Logger + - GoogleUtilities/Network + - GoogleUtilities/Environment (7.6.0): + - PromisesObjC (< 3.0, >= 1.2) + - GoogleUtilities/Logger (7.6.0): + - GoogleUtilities/Environment + - GoogleUtilities/MethodSwizzler (7.6.0): + - GoogleUtilities/Logger + - GoogleUtilities/Network (7.6.0): + - GoogleUtilities/Logger + - "GoogleUtilities/NSData+zlib" + - GoogleUtilities/Reachability + - "GoogleUtilities/NSData+zlib (7.6.0)" + - GoogleUtilities/Reachability (7.6.0): + - GoogleUtilities/Logger + - GoogleUtilities/UserDefaults (7.6.0): + - GoogleUtilities/Logger + - GTMSessionFetcher/Core (1.7.0) - InitialsImageView (0.7.0) + - leveldb-library (1.22.1) + - nanopb (2.30908.0): + - nanopb/decode (= 2.30908.0) + - nanopb/encode (= 2.30908.0) + - nanopb/decode (2.30908.0) + - nanopb/encode (2.30908.0) + - PromisesObjC (2.0.0) - SwipeableTabBarController (3.4.2) DEPENDENCIES: - - EasyNotificationBadge + - Firebase/Auth + - Firebase/Core + - Firebase/Database + - Firebase/Storage - InitialsImageView - SwipeableTabBarController SPEC REPOS: trunk: - - EasyNotificationBadge + - Firebase + - FirebaseAnalytics + - FirebaseAuth + - FirebaseCore + - FirebaseCoreDiagnostics + - FirebaseDatabase + - FirebaseInstallations + - FirebaseStorage + - GoogleAppMeasurement + - GoogleDataTransport + - GoogleUtilities + - GTMSessionFetcher - InitialsImageView + - leveldb-library + - nanopb + - PromisesObjC - SwipeableTabBarController SPEC CHECKSUMS: - EasyNotificationBadge: a3ebbcb1de0c0558e102e954380c75801f352702 + Firebase: 44213362f1dcc52555b935dc925ed35ac55f1b20 + FirebaseAnalytics: 319c9b3b1bdd400d60e2f415dff0c5f6959e6760 + FirebaseAuth: 59a2d2b933b5b79e18fb1e6ad230c6abdaa73d26 + FirebaseCore: 04186597c095da37d90ff9fd3e53bc61a1ff2440 + FirebaseCoreDiagnostics: 56fb7216d87e0e6ec2feddefa9d8a392fe8b2c18 + FirebaseDatabase: 5f3e83b0a0d8759378fbd5d05661244d14dfbbb0 + FirebaseInstallations: 830327b45345ffc859eaa9c17bcd5ae893fd5425 + FirebaseStorage: 815410224b548172c578f02554a86bbc8f817f50 + GoogleAppMeasurement: a3311dbcf3ea651e5a070fe8559b57c174ada081 + GoogleDataTransport: 629c20a4d363167143f30ea78320d5a7eb8bd940 + GoogleUtilities: 684ee790a24f73ebb2d1d966e9711c203f2a4237 + GTMSessionFetcher: 43748f93435c2aa068b1cbe39655aaf600652e91 InitialsImageView: 7a416d36fc6df5434198664db28a3ffb0228426d + leveldb-library: 50c7b45cbd7bf543c81a468fe557a16ae3db8729 + nanopb: a0ba3315591a9ae0a16a309ee504766e90db0c96 + PromisesObjC: 68159ce6952d93e17b2dfe273b8c40907db5ba58 SwipeableTabBarController: 9a218bdee9426d1bfe36d7962f852024bb08d61b -PODFILE CHECKSUM: b12fb4a56fe0bc7746ec465cf8cbf696c76ee99b +PODFILE CHECKSUM: fb210c43b30722ec9ba1eaf713b5c1e4fda3d02d COCOAPODS: 1.11.2