diff --git a/Scoop.xcodeproj/project.pbxproj b/Scoop.xcodeproj/project.pbxproj index 42af115..0b8a5c2 100644 --- a/Scoop.xcodeproj/project.pbxproj +++ b/Scoop.xcodeproj/project.pbxproj @@ -45,10 +45,16 @@ 410D78C029B8166D0007E434 /* NotificationsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 410D78BF29B8166D0007E434 /* NotificationsViewController.swift */; }; 412EDB9029B950D300C1C265 /* RequestTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 412EDB8F29B950D300C1C265 /* RequestTableViewCell.swift */; }; 4176A1E9299D2F01008B2BC3 /* Path.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4176A1E8299D2F01008B2BC3 /* Path.swift */; }; + 890D75882AFB1C4800623E18 /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 890D75872AFB1C4800623E18 /* SettingsViewController.swift */; }; 8939AD8E2AE016670038B20D /* SFProDisplay-Bold.otf in Resources */ = {isa = PBXBuildFile; fileRef = 2C85C53329E5B6F000A0C387 /* SFProDisplay-Bold.otf */; }; 8939AD902AE0166C0038B20D /* SFProDisplay-Regular.otf in Resources */ = {isa = PBXBuildFile; fileRef = 2C85C53229E5B6F000A0C387 /* SFProDisplay-Regular.otf */; }; 8940F1E72AEDB83A0025D579 /* EmptyStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8940F1E62AEDB83A0025D579 /* EmptyStateView.swift */; }; 8941635C2AC4D69900B0CAE9 /* EditProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8941635B2AC4D69900B0CAE9 /* EditProfileViewController.swift */; }; + 89619B1E2AFBD42600F7EC27 /* Roboto-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 89619B1D2AFBD42600F7EC27 /* Roboto-Regular.ttf */; }; + 89634CD62B05866F00F5E645 /* PopUpViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89634CD52B05866F00F5E645 /* PopUpViewController.swift */; }; + 8978DECF2B01DCA3008C78CC /* BlockedUsersViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8978DECE2B01DCA3008C78CC /* BlockedUsersViewController.swift */; }; + 8978DED12B01E140008C78CC /* BlockedUserTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8978DED02B01E140008C78CC /* BlockedUserTableViewCell.swift */; }; + 89B47C082AFC4B8100F3F768 /* SettingsCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89B47C072AFC4B8100F3F768 /* SettingsCellView.swift */; }; 89C410F82ACE0DCC005BB8FC /* Extension+UIImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89C410F72ACE0DCC005BB8FC /* Extension+UIImage.swift */; }; 89C410FA2ACF0B6F005BB8FC /* Extension+UIFont.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89C410F92ACF0B6F005BB8FC /* Extension+UIFont.swift */; }; 8E4464A344C5B1529768FAF3 /* Pods_Scoop.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 164EA2836D84BC7164FBB6D6 /* Pods_Scoop.framework */; }; @@ -112,8 +118,6 @@ 2C5672FD29CBE5EB001E37FB /* Prompts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Prompts.swift; sourceTree = ""; }; 2C67E4FB29945E5E00195BB5 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; 2C67E4FF299460DF00195BB5 /* UserSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSession.swift; sourceTree = ""; }; - 2C6BE1AD29EF8106007EA3AA /* Keys.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Keys.plist; path = ../../../../../../Keys.plist; sourceTree = ""; }; - 2C6BE1AE29EF8106007EA3AA /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "../../../../../../GoogleService-Info.plist"; sourceTree = ""; }; 2C85C53229E5B6F000A0C387 /* SFProDisplay-Regular.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SFProDisplay-Regular.otf"; sourceTree = ""; }; 2C85C53329E5B6F000A0C387 /* SFProDisplay-Bold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SFProDisplay-Bold.otf"; sourceTree = ""; }; 2C85C53829E5B85300A0C387 /* Request.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Request.swift; sourceTree = ""; }; @@ -143,8 +147,14 @@ 410D78BF29B8166D0007E434 /* NotificationsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsViewController.swift; sourceTree = ""; }; 412EDB8F29B950D300C1C265 /* RequestTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestTableViewCell.swift; sourceTree = ""; }; 4176A1E8299D2F01008B2BC3 /* Path.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Path.swift; sourceTree = ""; }; + 890D75872AFB1C4800623E18 /* SettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = ""; }; 8940F1E62AEDB83A0025D579 /* EmptyStateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = EmptyStateView.swift; path = Scoop/View/EmptyStateView.swift; sourceTree = SOURCE_ROOT; }; 8941635B2AC4D69900B0CAE9 /* EditProfileViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditProfileViewController.swift; sourceTree = ""; }; + 89619B1D2AFBD42600F7EC27 /* Roboto-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Roboto-Regular.ttf"; sourceTree = ""; }; + 89634CD52B05866F00F5E645 /* PopUpViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopUpViewController.swift; sourceTree = ""; }; + 8978DECE2B01DCA3008C78CC /* BlockedUsersViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockedUsersViewController.swift; sourceTree = ""; }; + 8978DED02B01E140008C78CC /* BlockedUserTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockedUserTableViewCell.swift; sourceTree = ""; }; + 89B47C072AFC4B8100F3F768 /* SettingsCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsCellView.swift; sourceTree = ""; }; 89C410F72ACE0DCC005BB8FC /* Extension+UIImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Extension+UIImage.swift"; sourceTree = ""; }; 89C410F92ACF0B6F005BB8FC /* Extension+UIFont.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Extension+UIFont.swift"; sourceTree = ""; }; 9430FC737D049567581E79B9 /* Pods-Scoop.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Scoop.debug.xcconfig"; path = "Target Support Files/Pods-Scoop/Pods-Scoop.debug.xcconfig"; sourceTree = ""; }; @@ -212,6 +222,7 @@ isa = PBXGroup; children = ( 2C42B520290F767600D053A3 /* Rambla-Regular.ttf */, + 89619B1D2AFBD42600F7EC27 /* Roboto-Regular.ttf */, 2C85C53329E5B6F000A0C387 /* SFProDisplay-Bold.otf */, 2C85C53229E5B6F000A0C387 /* SFProDisplay-Regular.otf */, 2C42B51D290F12B400D053A3 /* Sen-Regular.ttf */, @@ -225,6 +236,7 @@ 2C991EB528E0B2E7006301D9 /* LocationTableViewCell.swift */, 412EDB8F29B950D300C1C265 /* RequestTableViewCell.swift */, C8CB5E8B2811CEC9007FB8A8 /* HomeTableViewCell.swift */, + 8978DED02B01E140008C78CC /* BlockedUserTableViewCell.swift */, ); path = Cells; sourceTree = ""; @@ -243,6 +255,8 @@ 8941635B2AC4D69900B0CAE9 /* EditProfileViewController.swift */, 2CCE49D82AE04F6A0099EAF0 /* BlockUserViewController.swift */, 2CCE49DA2AE04F750099EAF0 /* ReportUserViewController.swift */, + 890D75872AFB1C4800623E18 /* SettingsViewController.swift */, + 8978DECE2B01DCA3008C78CC /* BlockedUsersViewController.swift */, ); path = Profile; sourceTree = ""; @@ -280,8 +294,6 @@ C845953727ED01A400D2C7F0 /* Utilities */ = { isa = PBXGroup; children = ( - 2C6BE1AE29EF8106007EA3AA /* GoogleService-Info.plist */, - 2C6BE1AD29EF8106007EA3AA /* Keys.plist */, C845953827ED01B200D2C7F0 /* PhoneFormatter.swift */, C83CD58927F3B27200EDA255 /* Extension+UIViewController.swift */, 89C410F72ACE0DCC005BB8FC /* Extension+UIImage.swift */, @@ -400,6 +412,7 @@ C8A544C027D18D1300132ACC /* View */ = { isa = PBXGroup; children = ( + 8940F1E62AEDB83A0025D579 /* EmptyStateView.swift */, C8CB5E8D28182CC3007FB8A8 /* HomeTableViewHeader.swift */, C8D2805E27D985FA00EDD5D3 /* ImageLabelView.swift */, 2C09F7E629E085A1006003AA /* ImageTextField.swift */, @@ -408,6 +421,8 @@ 2CECCDC22AC4D705009CC4A0 /* LabeledTextField.swift */, 2CCE49DE2AE076670099EAF0 /* LabeledTextView.swift */, 2C2015DE2AB4CFAB0065C436 /* ShiftedRightTextField.swift */, + 89B47C072AFC4B8100F3F768 /* SettingsCellView.swift */, + 89634CD52B05866F00F5E645 /* PopUpViewController.swift */, ); path = View; sourceTree = ""; @@ -434,7 +449,6 @@ children = ( C8A544BC27D18D0200132ACC /* HomeViewController.swift */, 410D78BF29B8166D0007E434 /* NotificationsViewController.swift */, - 8940F1E62AEDB83A0025D579 /* EmptyStateView.swift */, ); path = Home; sourceTree = ""; @@ -553,6 +567,7 @@ 2C42B521290F767600D053A3 /* Rambla-Regular.ttf in Resources */, C8A5449927D185BF00132ACC /* LaunchScreen.storyboard in Resources */, 2CAB93CD2AB26183009554E7 /* Keys.plist in Resources */, + 89619B1E2AFBD42600F7EC27 /* Roboto-Regular.ttf in Resources */, C8A5449627D185BF00132ACC /* Assets.xcassets in Resources */, 8939AD8E2AE016670038B20D /* SFProDisplay-Bold.otf in Resources */, ); @@ -672,12 +687,14 @@ 2CCE49DB2AE04F750099EAF0 /* ReportUserViewController.swift in Sources */, 4176A1E9299D2F01008B2BC3 /* Path.swift in Sources */, 2C991EB828E0B2E7006301D9 /* ArrivalSearchViewController.swift in Sources */, + 8978DED12B01E140008C78CC /* BlockedUserTableViewCell.swift in Sources */, 2CCE49E12AE0770D0099EAF0 /* PaddedTextView.swift in Sources */, C85E2E1F27DC0D850004C506 /* AboutYouViewController.swift in Sources */, C85E2E1D27DBFFFA0004C506 /* ProfilePictureViewController.swift in Sources */, 89C410F82ACE0DCC005BB8FC /* Extension+UIImage.swift in Sources */, C83CD58A27F3B27200EDA255 /* Extension+UIViewController.swift in Sources */, 410D78C029B8166D0007E434 /* NotificationsViewController.swift in Sources */, + 89B47C082AFC4B8100F3F768 /* SettingsCellView.swift in Sources */, C8D2806227D9992600EDD5D3 /* Ride.swift in Sources */, C8CB5E892811CC4C007FB8A8 /* Extension+UIColor.swift in Sources */, 2CECCDC32AC4D705009CC4A0 /* LabeledTextField.swift in Sources */, @@ -685,6 +702,7 @@ C8EF10CC27E2CEF800642B40 /* FavoritesViewController.swift in Sources */, 89C410FA2ACF0B6F005BB8FC /* Extension+UIFont.swift in Sources */, C8D2805F27D985FA00EDD5D3 /* ImageLabelView.swift in Sources */, + 890D75882AFB1C4800623E18 /* SettingsViewController.swift in Sources */, C8A5448F27D185BD00132ACC /* SceneDelegate.swift in Sources */, 412EDB9029B950D300C1C265 /* RequestTableViewCell.swift in Sources */, C845953927ED01B200D2C7F0 /* PhoneFormatter.swift in Sources */, @@ -694,6 +712,7 @@ 2CCE49DF2AE076670099EAF0 /* LabeledTextView.swift in Sources */, 2CE749E329DBD147009EF309 /* PostRideViewController.swift in Sources */, 2CFA071629A67C710085A0B8 /* TripDetailsViewController.swift in Sources */, + 89634CD62B05866F00F5E645 /* PopUpViewController.swift in Sources */, 2C991EBE28E0B2E7006301D9 /* LocationViewController.swift in Sources */, C8A544BD27D18D0200132ACC /* HomeViewController.swift in Sources */, 2C85C53929E5B85300A0C387 /* Request.swift in Sources */, @@ -707,6 +726,7 @@ 2C8C3EB729D4E6710035880B /* PostRidePageViewController.swift in Sources */, C8D2805D27D96B0000EDD5D3 /* ProfileViewController.swift in Sources */, C8EF10C427E2BB8000642B40 /* PreferredContactViewController.swift in Sources */, + 8978DECF2B01DCA3008C78CC /* BlockedUsersViewController.swift in Sources */, C8D2805727D9670100EDD5D3 /* PostRideSummaryViewController.swift in Sources */, 2CCE49D92AE04F6A0099EAF0 /* BlockUserViewController.swift in Sources */, 2C12DEB629C64D11003D6683 /* PostRideTripDetailsViewController.swift in Sources */, diff --git a/Scoop/Cells/BlockedUserTableViewCell.swift b/Scoop/Cells/BlockedUserTableViewCell.swift new file mode 100644 index 0000000..feb7983 --- /dev/null +++ b/Scoop/Cells/BlockedUserTableViewCell.swift @@ -0,0 +1,140 @@ +// +// BlockedUserTableViewCell.swift +// Scoop +// +// Created by Caitlyn Jin on 11/12/23. +// + +import UIKit + +class BlockedUserTableViewCell: UITableViewCell { + + // MARK: - Views + + private let profileImageView = UIImageView() + private let userLabel = UILabel() + private let unblockButton = UIButton() + + static let reuse = "BlockedUserCellReuse" + weak var delegate: BlockedUsersDelegate? + + // MARK: - User Data + + private var user: BaseUser? + + // MARK: - Initializers + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + selectionStyle = .none + + setupProfileImageView() + setupUserLabel() + setupUnblockButton() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func configure(user: BaseUser, delegate: BlockedUsersDelegate) { + self.user = user + profileImageView.sd_setImage(with: URL(string: user.profilePicUrl ?? ""), placeholderImage: UIImage.scooped.emptyImage) + userLabel.text = "\(user.firstName) \(user.lastName)" + self.delegate = delegate + } + + // MARK: Setup View Functions + + private func setupProfileImageView() { + profileImageView.contentMode = .scaleAspectFill + profileImageView.layer.masksToBounds = true + profileImageView.layer.cornerRadius = 48 / 2 + contentView.addSubview(profileImageView) + + profileImageView.snp.makeConstraints { make in + make.leading.equalToSuperview() + make.size.equalTo(48) + } + } + + private func setupUserLabel() { + userLabel.font = UIFont.scooped.bodyNormal + userLabel.textColor = UIColor.black + contentView.addSubview(userLabel) + + userLabel.snp.makeConstraints { make in + make.leading.equalTo(profileImageView.snp.trailing).offset(12) + make.centerY.equalTo(profileImageView) + } + } + + private func setupUnblockButton() { + unblockButton.setTitle("Unblock", for: .normal) + unblockButton.titleLabel?.font = UIFont.scooped.buttonSemibold + unblockButton.setTitleColor(UIColor.black, for: .normal) + unblockButton.backgroundColor = UIColor.scooped.secondaryGreen + unblockButton.layer.borderColor = UIColor.scooped.secondaryGreen.cgColor + unblockButton.layer.borderWidth = 1 + unblockButton.layer.masksToBounds = true + unblockButton.layer.cornerRadius = 15 + contentView.addSubview(unblockButton) + + unblockButton.addTarget(self, action: #selector(tapButton), for: .touchUpInside) + + unblockButton.snp.makeConstraints { make in + make.trailing.equalToSuperview() + make.height.equalTo(30) + make.width.equalTo(74) + make.centerY.equalTo(profileImageView) + } + } + + // MARK: - Helper Functions + + @objc private func tapButton() { + if unblockButton.titleLabel?.text == "Unblock" { + let popUpVC = PopUpViewController() + + let attributedTitle = NSMutableAttributedString( + string: "Are you sure you want to unblock \(user?.firstName ?? "") \(user?.lastName ?? "")?", + attributes: [NSAttributedString.Key.font: UIFont.scooped.bodyNormal] + ) + let range = attributedTitle.mutableString.range(of: "\(user?.firstName ?? "") \(user?.lastName ?? "")") + attributedTitle.addAttributes([NSAttributedString.Key.font: UIFont.scooped.bodyBold], range: range) + + popUpVC.configure( + title: attributedTitle, + subtitle: "", + actionButtonText: "Unblock", + delegate: self + ) + + delegate?.presentPopUp(popUpVC: popUpVC) + } else { + unblockButton.setTitle("Unblock", for: .normal) + unblockButton.backgroundColor = UIColor.scooped.secondaryGreen + unblockButton.layer.borderColor = UIColor.scooped.secondaryGreen.cgColor + + if let user = user { + delegate?.updateBlockedUsers(user: user, isBlocked: true) + } + } + } + +} + +extension BlockedUserTableViewCell: PopUpViewDelegate { + + func acceptPopUp() { + if let user = user { + delegate?.updateBlockedUsers(user: user, isBlocked: false) + } + + unblockButton.setTitle("Block", for: .normal) + unblockButton.backgroundColor = UIColor.white + unblockButton.layer.borderColor = UIColor.black.cgColor + } + +} + diff --git a/Scoop/Controller/Onboarding/PreferencesViewController.swift b/Scoop/Controller/Onboarding/PreferencesViewController.swift index 24e938d..7a31f78 100644 --- a/Scoop/Controller/Onboarding/PreferencesViewController.swift +++ b/Scoop/Controller/Onboarding/PreferencesViewController.swift @@ -128,7 +128,9 @@ class PreferencesViewController: OnboardingViewController { talkativeSlider.minimumTrackTintColor = .black talkativeSlider.maximumTrackTintColor = .black - talkativeSlider.setThumbImage(UIImage(named: "SliderThumb"), for: .normal) + talkativeSlider.setThumbImage(UIImage.scooped.sliderThumb, for: .normal) + talkativeSlider.setMaximumTrackImage(UIImage.scooped.sliderTrack, for: .normal) + talkativeSlider.setMinimumTrackImage(UIImage.scooped.sliderTrack, for: .normal) talkativeSlider.value = talkativeSlider.maximumValue / 2 stackView.addArrangedSubview(talkativeSlider) stackView.setCustomSpacing(60, after: talkativeSlider) @@ -138,7 +140,7 @@ class PreferencesViewController: OnboardingViewController { } let talkativeTicks = UIButton() - talkativeTicks.setImage(UIImage(named: "SliderTicks"), for: .normal) + talkativeTicks.setImage(UIImage.scooped.sliderTicks, for: .normal) talkativeSlider.addSubview(talkativeTicks) talkativeSlider.sendSubviewToBack(talkativeTicks) talkativeTicks.snp.makeConstraints { make in @@ -178,7 +180,9 @@ class PreferencesViewController: OnboardingViewController { musicSlider.minimumTrackTintColor = .black musicSlider.maximumTrackTintColor = .black - musicSlider.setThumbImage(UIImage(named: "SliderThumb"), for: .normal) + musicSlider.setThumbImage(UIImage.scooped.sliderThumb, for: .normal) + musicSlider.setMaximumTrackImage(UIImage.scooped.sliderTrack, for: .normal) + musicSlider.setMinimumTrackImage(UIImage.scooped.sliderTrack, for: .normal) musicSlider.value = musicSlider.maximumValue/2 stackView.addArrangedSubview(musicSlider) @@ -187,7 +191,7 @@ class PreferencesViewController: OnboardingViewController { } let musicTicks = UIButton() - musicTicks.setImage(UIImage(named: "SliderTicks"), for: .normal) + musicTicks.setImage(UIImage.scooped.sliderTicks, for: .normal) musicSlider.addSubview(musicTicks) musicSlider.sendSubviewToBack(musicTicks) musicTicks.snp.makeConstraints { make in diff --git a/Scoop/Controller/Profile/BlockedUsersViewController.swift b/Scoop/Controller/Profile/BlockedUsersViewController.swift new file mode 100644 index 0000000..bd21f42 --- /dev/null +++ b/Scoop/Controller/Profile/BlockedUsersViewController.swift @@ -0,0 +1,169 @@ +// +// BlockedUsersViewController.swift +// Scoop +// +// Created by Caitlyn Jin on 11/12/23. +// + +import UIKit + +class BlockedUsersViewController: UIViewController { + + // MARK: - Views + + private let emptyStateView = EmptyStateView() + private let tableView = UITableView() + + // MARK: - User Data + + private var user: BaseUser? + private var blockedUsers: [BaseUser] = BaseUser.blockedUsersDummyData + + // MARK: - Lifecycle Functions + + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = UIColor.white + + // TODO: Uncomment after networking calls are implemented +// fetchBlockedUsers() + + setupHeader() + blockedUsers.isEmpty ? setupEmptyStateView() : setupTableView() + } + + // MARK: - Setup View Functions + + private func setupHeader() { + let dottedLineMultiplier = 0.52 + let solidLineVerticalInset = -12.75 + let solidLineMultiplier = 0.32 + let screenSize = UIScreen.main.bounds + let dottedline = UIImageView(image: UIImage.scooped.dottedLine) + let solidline = UIView() + + dottedline.contentMode = .scaleAspectFit + view.addSubview(dottedline) + + dottedline.snp.makeConstraints { make in + make.height.equalTo(1) + make.width.equalTo(screenSize.width * dottedLineMultiplier) + make.trailing.equalToSuperview().inset(10) + make.top.equalTo(view.safeAreaLayoutGuide) + } + + solidline.backgroundColor = UIColor.black + view.addSubview(solidline) + + solidline.snp.makeConstraints { make in + make.height.equalTo(1) + make.width.equalTo(screenSize.width * solidLineMultiplier) + make.trailing.equalToSuperview() + make.top.equalTo(view.safeAreaLayoutGuide).inset(solidLineVerticalInset) + } + + navigationController?.navigationBar.prefersLargeTitles = false + navigationItem.title = "Blocked users" + navigationController?.navigationBar.titleTextAttributes = [.font: UIFont.scooped.flowHeader] + } + + private func setupEmptyStateView() { + emptyStateView.setup( + image: UIImage(), + title: "", + subtitle: "No blocked users" + ) + view.addSubview(emptyStateView) + + emptyStateView.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + } + + private func setupTableView() { + tableView.register(BlockedUserTableViewCell.self, forCellReuseIdentifier: BlockedUserTableViewCell.reuse) + tableView.dataSource = self + tableView.delegate = self + tableView.separatorStyle = .none + view.addSubview(tableView) + + tableView.snp.makeConstraints { make in + make.top.equalToSuperview().offset(120) + make.leading.trailing.equalToSuperview().inset(25) + make.bottom.equalToSuperview().inset(50) + } + } + + // MARK: - Networking + + private func fetchBlockedUsers() { + NetworkManager.shared.getBlockedUsers { [weak self] result in + guard let self = self else { return } + + switch result { + case .success(let users): + blockedUsers = users + tableView.reloadData() + case .failure(let error): + print("Error in BlockedUsersViewController: \(error.localizedDescription)") + } + } + } + + private func blockUser() { + // TODO: Network call to block user + } + + private func unblockUser() { + // TODO: Network call to unblock user + } + +} + +extension BlockedUsersViewController: UITableViewDelegate { + + func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + return 64 + } + +} + +extension BlockedUsersViewController: UITableViewDataSource { + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return blockedUsers.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + guard let cell = tableView.dequeueReusableCell(withIdentifier: BlockedUserTableViewCell.reuse, for: indexPath) as? BlockedUserTableViewCell else { return UITableViewCell() } + let user = blockedUsers[indexPath.row] + cell.configure(user: user, delegate: self) + return cell + } + +} + +protocol BlockedUsersDelegate: AnyObject { + func updateBlockedUsers(user: BaseUser, isBlocked: Bool) + func presentPopUp(popUpVC: PopUpViewController) +} + +extension BlockedUsersViewController: BlockedUsersDelegate { + + func updateBlockedUsers(user: BaseUser, isBlocked: Bool) { + if isBlocked { + blockUser() + // TODO: Temporary. Remove below once networking call is implemented + BaseUser.blockedUsersDummyData.append(user) + } else { + unblockUser() + // TODO: Temporary. Remove below once networking call is implemented + BaseUser.blockedUsersDummyData.removeAll { $0.id == user.id } + } + } + + func presentPopUp(popUpVC: PopUpViewController) { + present(popUpVC, animated: true) + } + +} diff --git a/Scoop/Controller/Profile/EditProfileViewController.swift b/Scoop/Controller/Profile/EditProfileViewController.swift index 9488604..8f352e1 100644 --- a/Scoop/Controller/Profile/EditProfileViewController.swift +++ b/Scoop/Controller/Profile/EditProfileViewController.swift @@ -222,8 +222,9 @@ class EditProfileViewController: UIViewController { uploadPhotoButton.backgroundColor = UIColor.gray uploadPhotoButton.contentMode = .scaleAspectFill uploadPhotoButton.clipsToBounds = true - uploadPhotoButton.addTarget(self, action: #selector(updateProfileImage), for: .touchUpInside) imageView.addSubview(uploadPhotoButton) + + uploadPhotoButton.addTarget(self, action: #selector(updateProfileImage), for: .touchUpInside) uploadPhotoButton.snp.makeConstraints { make in make.top.equalTo(profileImageView).offset(96) @@ -449,6 +450,8 @@ class EditProfileViewController: UIViewController { talkativeSlider.minimumTrackTintColor = UIColor.black talkativeSlider.maximumTrackTintColor = UIColor.black talkativeSlider.setThumbImage(UIImage.scooped.sliderThumb, for: .normal) + talkativeSlider.setMaximumTrackImage(UIImage.scooped.sliderTrack, for: .normal) + talkativeSlider.setMinimumTrackImage(UIImage.scooped.sliderTrack, for: .normal) preferencesStackView.addArrangedSubview(talkativeSlider) talkativeSlider.snp.makeConstraints { make in @@ -503,6 +506,8 @@ class EditProfileViewController: UIViewController { musicSlider.minimumTrackTintColor = UIColor.black musicSlider.maximumTrackTintColor = UIColor.black musicSlider.setThumbImage(UIImage.scooped.sliderThumb, for: .normal) + musicSlider.setMaximumTrackImage(UIImage.scooped.sliderTrack, for: .normal) + musicSlider.setMinimumTrackImage(UIImage.scooped.sliderTrack, for: .normal) preferencesStackView.addArrangedSubview(musicSlider) musicSlider.snp.makeConstraints { make in @@ -556,7 +561,7 @@ class EditProfileViewController: UIViewController { } } - func setupDeleteButton() { + private func setupDeleteButton() { deleteButton.setTitle("Delete account", for: .normal) deleteButton.setTitleColor(UIColor.white, for: .normal) deleteButton.titleLabel?.font = UIFont.scooped.bodyBold @@ -573,7 +578,7 @@ class EditProfileViewController: UIViewController { } private func setupGradientView() { - gradientLayer.colors = UIColor.scooped.whiteGradientColors + gradientLayer.colors = UIColor.scooped.hoverGradientColors gradientView.isUserInteractionEnabled = false gradientView.layer.insertSublayer(gradientLayer, at: 1) view.addSubview(gradientView) @@ -639,18 +644,15 @@ class EditProfileViewController: UIViewController { // MARK: - Helper Functions @objc private func deleteAccount() { - deleteUserRequest( - firstName: user?.firstName ?? "", - lastName: user?.lastName ?? "", - grade: user?.grade, - phoneNumber: user?.phoneNumber, - pronouns: user?.pronouns, - prompts: getUserAnswers() + let popUpVC = PopUpViewController() + let attributedTitle = NSMutableAttributedString(string: "Are you sure you want to delete your account?", attributes: [NSAttributedString.Key.font: UIFont.scooped.bodyNormal]) + popUpVC.configure( + title: attributedTitle, + subtitle: "This action cannot be undone.", + actionButtonText: "Delete", + delegate: self ) - - let loginVC = LoginViewController() - loginVC.navigationItem.hidesBackButton = true - navigationController?.pushViewController(loginVC, animated: false) + present(popUpVC, animated: true) } @objc private func cancelEdit() { @@ -664,6 +666,7 @@ class EditProfileViewController: UIViewController { let grade = classTextField.getText() let pronouns = pronounsTextField.getText() let phoneNumber = phoneNumTextField.getText() + let profilePicUrl = profileImageView.image?.imageToB64(compression: 0.5) hometown = hometownTextField.getText() talkative = talkativeSlider.value @@ -672,7 +675,7 @@ class EditProfileViewController: UIViewController { song = songTextField.getText() stop = stopTextField.getText() - updateAuthenticatedUserRequest(firstName: firstName, lastName: lastName, grade: grade, phoneNumber: phoneNumber, pronouns: pronouns, prompts: getUserAnswers()) + updateAuthenticatedUserRequest(firstName: firstName, lastName: lastName, grade: grade, phoneNumber: phoneNumber, pronouns: pronouns, profilePicUrl: profilePicUrl, prompts: getUserAnswers()) self.navigationController?.popViewController(animated: true) } @@ -722,6 +725,7 @@ class EditProfileViewController: UIViewController { grade: String?, phoneNumber: String?, pronouns: String?, + profilePicUrl: String?, prompts: [UserAnswer] ) { NetworkManager.shared.updateAuthenticatedUser( @@ -731,7 +735,7 @@ class EditProfileViewController: UIViewController { grade: grade ?? "", phone_number: phoneNumber ?? "", pronouns: pronouns ?? "", - prof_pic: user?.profilePicUrl ?? "", + prof_pic: profilePicUrl ?? "", prompts: prompts ) { [weak self] result in guard let self = self else { return } @@ -752,6 +756,7 @@ class EditProfileViewController: UIViewController { grade: String?, phoneNumber: String?, pronouns: String?, + profilePic: String?, prompts: [UserAnswer] ) { NetworkManager.shared.deleteUser( @@ -761,7 +766,7 @@ class EditProfileViewController: UIViewController { grade: grade ?? "", phone_number: phoneNumber ?? "", pronouns: pronouns ?? "", - prof_pic: user?.profilePicUrl ?? "", + prof_pic: profilePic ?? "", prompts: prompts ) { result in switch result { @@ -874,3 +879,23 @@ extension EditProfileViewController: UINavigationControllerDelegate, UIImagePick } +extension EditProfileViewController: PopUpViewDelegate { + + func acceptPopUp() { + deleteUserRequest( + firstName: user?.firstName ?? "", + lastName: user?.lastName ?? "", + grade: user?.grade, + phoneNumber: user?.phoneNumber, + pronouns: user?.pronouns, + profilePic: user?.profilePicUrl, + prompts: getUserAnswers() + ) + + let loginVC = LoginViewController() + loginVC.navigationItem.hidesBackButton = true + navigationController?.pushViewController(loginVC, animated: false) + } + +} + diff --git a/Scoop/Controller/Profile/ProfileViewController.swift b/Scoop/Controller/Profile/ProfileViewController.swift index 9d8e9f1..8dec560 100644 --- a/Scoop/Controller/Profile/ProfileViewController.swift +++ b/Scoop/Controller/Profile/ProfileViewController.swift @@ -20,19 +20,23 @@ class ProfileViewController: UIViewController, ProfileViewDelegate { private let actionsButton = UIButton() private let containerView = UIView() + private let detailsStackView = UIStackView() + private let editButton = UIButton() + private let favoritesStackView = UIStackView() + private let gradientLayer = CAGradientLayer() + private let gradientView = UIView() private let headerImageView = UIImageView() private let profileImageView = UIImageView() private let profileStackView = UIStackView() - private let detailsStackView = UIStackView() + private let settingsButton = UIButton() private let travelingStackView = UIStackView() - private let favoritesStackView = UIStackView() private let musicSlider = UISlider() private let nameLabel = UILabel() private let phoneLabel = UILabel() - private let snackSection = ImageLabelView() - private let songSection = ImageLabelView() - private let stopSection = ImageLabelView() + private let snackLabel = UILabel() + private let songLabel = UILabel() + private let stopLabel = UILabel() private let subLabel = UILabel() private let talkativeSlider = UISlider() @@ -62,21 +66,42 @@ class ProfileViewController: UIViewController, ProfileViewDelegate { override func viewDidLoad() { super.viewDidLoad() - view.backgroundColor = .systemGray5 - + view.backgroundColor = UIColor.systemGray5 + getUserPreferences() - setupHeaderImage() + setupBackground() setupContainerView() setupProfileImageView() setupProfileStackView() - isBeingPresented ? updateDriverProfile() : updateUserProfile() - isBeingPresented ? setupActionsButton() : setupEditButton() + + if isBeingPresented { + updateDriverProfile() + setupActionsButton() + } else { + updateUserProfile() + setupEditButton() + setupSettingsButton() + } + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + navigationController?.setNavigationBarHidden(true, animated: animated) + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + navigationController?.setNavigationBarHidden(false, animated: animated) + } + + override func viewDidLayoutSubviews() { + gradientLayer.frame = gradientView.bounds } // MARK: - Setup View Functions private func setupContainerView() { - containerView.backgroundColor = UIColor.white + containerView.backgroundColor = UIColor.white.withAlphaComponent(0) containerView.layer.cornerRadius = 24 view.addSubview(containerView) @@ -86,14 +111,22 @@ class ProfileViewController: UIViewController, ProfileViewDelegate { } } - private func setupHeaderImage() { + private func setupBackground() { headerImageView.contentMode = .scaleAspectFill - headerImageView.image = UIImage(named: "ProfileHeader") + headerImageView.image = UIImage.scooped.profileBackground view.addSubview(headerImageView) headerImageView.snp.makeConstraints { make in make.top.leading.trailing.equalToSuperview() - make.height.equalTo(140) + } + + gradientLayer.colors = UIColor.scooped.backgroundGradientColors + gradientView.isUserInteractionEnabled = false + gradientView.layer.insertSublayer(gradientLayer, at: 0) + view.addSubview(gradientView) + + gradientView.snp.makeConstraints { make in + make.edges.equalToSuperview() } } @@ -101,26 +134,42 @@ class ProfileViewController: UIViewController, ProfileViewDelegate { actionsButton.setImage(UIImage(systemName: "ellipsis", withConfiguration: UIImage.SymbolConfiguration(pointSize: 24, weight: .semibold)), for: .normal) actionsButton.tintColor = .black actionsButton.addTarget(self, action: #selector(presentActionOptions), for: .touchUpInside) - containerView.addSubview(actionsButton) + view.addSubview(actionsButton) actionsButton.snp.makeConstraints { make in make.size.equalTo(30) - make.top.trailing.equalToSuperview().inset(20) + make.top.equalToSuperview().inset(64) + make.trailing.equalToSuperview().inset(20) } } private func setupEditButton() { - let editButton = UIButton() - editButton.setImage(UIImage(systemName: "square.and.pencil", withConfiguration: UIImage.SymbolConfiguration(pointSize: 36, weight: .semibold)), for: .normal) + editButton.setImage(UIImage.scooped.editProfileIcon, for: .normal) editButton.tintColor = UIColor.black editButton.imageView?.contentMode = .scaleAspectFit - containerView.addSubview(editButton) + view.addSubview(editButton) editButton.addTarget(self, action: #selector(pushEditProfileVC), for: .touchUpInside) editButton.snp.makeConstraints { make in make.size.equalTo(30) - make.top.trailing.equalToSuperview().inset(20) + make.top.equalToSuperview().inset(64) + make.trailing.equalToSuperview().inset(58) + } + } + + private func setupSettingsButton() { + settingsButton.setImage(UIImage.scooped.setttingsIcon, for: .normal) + settingsButton.tintColor = UIColor.black + settingsButton.imageView?.contentMode = .scaleAspectFit + view.addSubview(settingsButton) + + settingsButton.addTarget(self, action: #selector(pushSettingsVC), for: .touchUpInside) + + settingsButton.snp.makeConstraints { make in + make.size.equalTo(30) + make.top.equalToSuperview().inset(64) + make.trailing.equalToSuperview().inset(20) } } @@ -136,7 +185,7 @@ class ProfileViewController: UIViewController, ProfileViewDelegate { profileImageView.snp.makeConstraints { make in make.size.equalTo(120) make.centerX.equalToSuperview() - make.centerY.equalTo(containerView.snp.top) + make.top.equalToSuperview().inset(68) } } @@ -178,7 +227,7 @@ class ProfileViewController: UIViewController, ProfileViewDelegate { view.addSubview(detailsStackView) detailsStackView.snp.makeConstraints { make in - make.top.equalTo(profileStackView.snp.bottom).offset(40) + make.top.equalTo(profileStackView.snp.bottom).offset(44) make.leading.trailing.equalToSuperview().inset(20) make.bottom.lessThanOrEqualToSuperview().inset(40) } @@ -205,7 +254,7 @@ class ProfileViewController: UIViewController, ProfileViewDelegate { travelingStackView.axis = .vertical travelingStackView.distribution = .fill - travelingStackView.spacing = 10 + travelingStackView.spacing = 16 travelingStackView.alignment = .leading travelingContainerView.addSubview(travelingStackView) @@ -222,7 +271,7 @@ class ProfileViewController: UIViewController, ProfileViewDelegate { private func createTalkingSlider() { let talkingView = UIView() travelingStackView.addArrangedSubview(talkingView) - travelingStackView.setCustomSpacing(5, after: talkingView) + travelingStackView.setCustomSpacing(8, after: talkingView) talkingView.snp.makeConstraints { make in make.width.equalToSuperview() @@ -251,9 +300,9 @@ class ProfileViewController: UIViewController, ProfileViewDelegate { talkativeSlider.isUserInteractionEnabled = false talkativeSlider.minimumTrackTintColor = UIColor.black talkativeSlider.maximumTrackTintColor = UIColor.black - talkativeSlider.setThumbImage(UIImage(named: "SliderThumb"), for: .normal) - talkativeSlider.setMaximumTrackImage(UIImage(named: "track"), for: .normal) - talkativeSlider.setMinimumTrackImage(UIImage(named: "track"), for: .normal) + talkativeSlider.setThumbImage(UIImage.scooped.sliderThumb, for: .normal) + talkativeSlider.setMaximumTrackImage(UIImage.scooped.sliderTrack, for: .normal) + talkativeSlider.setMinimumTrackImage(UIImage.scooped.sliderTrack, for: .normal) travelingStackView.addArrangedSubview(talkativeSlider) talkativeSlider.snp.makeConstraints { make in @@ -264,7 +313,7 @@ class ProfileViewController: UIViewController, ProfileViewDelegate { private func createMusicSlider() { let musicView = UIView() travelingStackView.addArrangedSubview(musicView) - travelingStackView.setCustomSpacing(5, after: musicView) + travelingStackView.setCustomSpacing(8, after: musicView) musicView.snp.makeConstraints { make in make.width.equalToSuperview() @@ -293,9 +342,9 @@ class ProfileViewController: UIViewController, ProfileViewDelegate { musicSlider.isUserInteractionEnabled = false musicSlider.minimumTrackTintColor = UIColor.black musicSlider.maximumTrackTintColor = UIColor.black - musicSlider.setThumbImage(UIImage(named: "SliderThumb"), for: .normal) - musicSlider.setMaximumTrackImage(UIImage(named: "track"), for: .normal) - musicSlider.setMinimumTrackImage(UIImage(named: "track"), for: .normal) + musicSlider.setThumbImage(UIImage.scooped.sliderThumb, for: .normal) + musicSlider.setMaximumTrackImage(UIImage.scooped.sliderTrack, for: .normal) + musicSlider.setMinimumTrackImage(UIImage.scooped.sliderTrack, for: .normal) travelingStackView.addArrangedSubview(musicSlider) musicSlider.snp.makeConstraints { make in @@ -321,28 +370,25 @@ class ProfileViewController: UIViewController, ProfileViewDelegate { favoritesStackView.axis = .vertical favoritesStackView.distribution = .fill - favoritesStackView.spacing = 10 + favoritesStackView.spacing = 16 favoritesStackView.alignment = .leading favoritesContainerView.addSubview(favoritesStackView) favoritesStackView.snp.makeConstraints { make in make.edges.equalToSuperview().inset(20) } - - songSection.label.font = .systemFont(ofSize: 14) - songSection.label.adjustsFontSizeToFitWidth = true - songSection.imageView.image = UIImage(systemName: "music.note", withConfiguration: UIImage.SymbolConfiguration(pointSize: 24)) - favoritesStackView.addArrangedSubview(songSection) - - snackSection.label.font = .systemFont(ofSize: 14) - snackSection.label.adjustsFontSizeToFitWidth = true - snackSection.imageView.image = UIImage(systemName: "fork.knife", withConfiguration: UIImage.SymbolConfiguration(pointSize: 24)) - favoritesStackView.addArrangedSubview(snackSection) - - stopSection.label.font = .systemFont(ofSize: 14) - stopSection.label.adjustsFontSizeToFitWidth = true - stopSection.imageView.image = UIImage(systemName: "location.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 24)) - favoritesStackView.addArrangedSubview(stopSection) + + snackLabel.font = UIFont.systemFont(ofSize: 14) + snackLabel.numberOfLines = 1 + favoritesStackView.addArrangedSubview(snackLabel) + + songLabel.font = UIFont.systemFont(ofSize: 14) + songLabel.numberOfLines = 1 + favoritesStackView.addArrangedSubview(songLabel) + + stopLabel.font = UIFont.systemFont(ofSize: 14) + stopLabel.numberOfLines = 1 + favoritesStackView.addArrangedSubview(stopLabel) } // MARK: - Helper Functions @@ -373,6 +419,11 @@ class ProfileViewController: UIViewController, ProfileViewDelegate { editProfileVC.navigationItem.hidesBackButton = true navigationController?.pushViewController(editProfileVC, animated: true) } + + @objc private func pushSettingsVC() { + let settingsVC = SettingsViewController() + navigationController?.pushViewController(settingsVC, animated: true) + } private func getUserPreferences() { user.prompts.forEach { prompt in @@ -410,9 +461,9 @@ class ProfileViewController: UIViewController, ProfileViewDelegate { phoneLabel.text = user.phoneNumber talkativeSlider.value = talkative ?? 0 musicSlider.value = music ?? 0 - songSection.label.attributedText = makeBoldNormalText(bold: "Song / ", normal: song ?? "") - snackSection.label.attributedText = makeBoldNormalText(bold: "Snack / ", normal: snack ?? "") - stopSection.label.attributedText = makeBoldNormalText(bold: "Stop / ", normal: stop ?? "") + songLabel.attributedText = makeBoldNormalText(bold: "Song / ", normal: song ?? "") + snackLabel.attributedText = makeBoldNormalText(bold: "Snack / ", normal: snack ?? "") + stopLabel.attributedText = makeBoldNormalText(bold: "Stop / ", normal: stop ?? "") } func updateUserProfile() { @@ -435,9 +486,9 @@ class ProfileViewController: UIViewController, ProfileViewDelegate { strongSelf.phoneLabel.text = user.phoneNumber strongSelf.talkativeSlider.value = strongSelf.talkative ?? 0 strongSelf.musicSlider.value = strongSelf.music ?? 0 - strongSelf.songSection.label.attributedText = strongSelf.makeBoldNormalText(bold: "Song / ", normal: strongSelf.song ?? "") - strongSelf.snackSection.label.attributedText = strongSelf.makeBoldNormalText(bold: "Snack / ", normal: strongSelf.snack ?? "") - strongSelf.stopSection.label.attributedText = strongSelf.makeBoldNormalText(bold: "Stop / ", normal: strongSelf.stop ?? "") + strongSelf.songLabel.attributedText = strongSelf.makeBoldNormalText(bold: "Song / ", normal: strongSelf.song ?? "") + strongSelf.snackLabel.attributedText = strongSelf.makeBoldNormalText(bold: "Snack / ", normal: strongSelf.snack ?? "") + strongSelf.stopLabel.attributedText = strongSelf.makeBoldNormalText(bold: "Stop / ", normal: strongSelf.stop ?? "") case .failure(let error): print("Error in ProfileViewController: \(error.localizedDescription)") } @@ -457,9 +508,9 @@ class ProfileViewController: UIViewController, ProfileViewDelegate { phoneLabel.text = user.phoneNumber talkativeSlider.value = self.talkative ?? 0.5 musicSlider.value = self.music ?? 0.5 - songSection.label.attributedText = self.makeBoldNormalText(bold: "Song / ", normal: self.song ?? "") - snackSection.label.attributedText = self.makeBoldNormalText(bold: "Snack / ", normal: self.snack ?? "") - stopSection.label.attributedText = self.makeBoldNormalText(bold: "Stop / ", normal: self.stop ?? "") + songLabel.attributedText = makeBoldNormalText(bold: "Song / ", normal: song ?? "") + snackLabel.attributedText = makeBoldNormalText(bold: "Snack / ", normal: snack ?? "") + stopLabel.attributedText = makeBoldNormalText(bold: "Stop / ", normal: stop ?? "") } func makeBoldNormalText(bold: String, normal: String) -> NSAttributedString { diff --git a/Scoop/Controller/Profile/SettingsViewController.swift b/Scoop/Controller/Profile/SettingsViewController.swift new file mode 100644 index 0000000..a586ffb --- /dev/null +++ b/Scoop/Controller/Profile/SettingsViewController.swift @@ -0,0 +1,138 @@ +// +// SettingsViewController.swift +// Scoop +// +// Created by Caitlyn Jin on 11/7/23. +// + +import UIKit + +class SettingsViewController: UIViewController { + + // MARK: - Views + + private let blockedView = SettingsCellView() + private let reportView = SettingsCellView() + private let signoutView = SettingsCellView() + private let stackView = UIStackView() + + // MARK: - Lifecycle Functions + + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = UIColor.white + + setupHeader() + setupStackView() + setupSettingsCells() + } + + // MARK: - Setup View Functions + + private func setupHeader() { + let dottedLineMultiplier = 0.52 + let solidLineVerticalInset = -12.75 + let solidLineMultiplier = 0.32 + let screenSize = UIScreen.main.bounds + let dottedline = UIImageView(image: UIImage.scooped.dottedLine) + let solidline = UIView() + + dottedline.contentMode = .scaleAspectFit + view.addSubview(dottedline) + + dottedline.snp.makeConstraints { make in + make.height.equalTo(1) + make.width.equalTo(screenSize.width * dottedLineMultiplier) + make.trailing.equalToSuperview().inset(10) + make.top.equalTo(view.safeAreaLayoutGuide) + } + + solidline.backgroundColor = UIColor.black + view.addSubview(solidline) + + solidline.snp.makeConstraints { make in + make.height.equalTo(1) + make.width.equalTo(screenSize.width * solidLineMultiplier) + make.trailing.equalToSuperview() + make.top.equalTo(view.safeAreaLayoutGuide).inset(solidLineVerticalInset) + } + + navigationController?.navigationBar.prefersLargeTitles = false + navigationItem.title = "Settings" + navigationController?.navigationBar.titleTextAttributes = [.font: UIFont.scooped.flowHeader] + } + + private func setupStackView() { + stackView.axis = .vertical + stackView.spacing = 0 + stackView.alignment = .leading + stackView.distribution = .fill + view.addSubview(stackView) + + stackView.snp.makeConstraints { make in + make.top.equalToSuperview().offset(120) + make.leading.trailing.equalToSuperview().inset(24) + } + } + + private func setupSettingsCells() { + let line1 = UIImageView() + let line2 = UIImageView() + + let lineBreaks = [line1, line2] + lineBreaks.forEach { line in + line.image = UIImage.scooped.lineBreak + line.contentMode = .scaleAspectFit + + line.snp.makeConstraints { make in + make.height.equalTo(4) + } + } + + let blockedTap = UITapGestureRecognizer(target: self, action: #selector(pushBlockedUsersVC)) + blockedView.addGestureRecognizer(blockedTap) + blockedView.setup(icon: UIImage.scooped.blockIcon!, text: "Blocked users") + stackView.addArrangedSubview(blockedView) + + stackView.addArrangedSubview(line1) + + let reportTap = UITapGestureRecognizer(target: self, action: #selector(pushReportProblemVC)) + reportView.addGestureRecognizer(reportTap) + reportView.setup(icon: UIImage.scooped.reportIcon!, text: "Report a problem") + stackView.addArrangedSubview(reportView) + + stackView.addArrangedSubview(line2) + + let signoutTap = UITapGestureRecognizer(target: self, action: #selector(signout)) + signoutView.addGestureRecognizer(signoutTap) + signoutView.setup(icon: UIImage.scooped.signoutIcon!, text: "Sign out") + stackView.addArrangedSubview(signoutView) + + let cells = [blockedView, reportView, signoutView] + cells.forEach { cell in + cell.snp.makeConstraints { make in + make.leading.trailing.equalToSuperview() + make.height.equalTo(52) + } + } + } + + // MARK: - Helper Functions + + @objc private func pushBlockedUsersVC() { + let blockedUsersVC = BlockedUsersViewController() + navigationController?.pushViewController(blockedUsersVC, animated: true) + } + + @objc private func pushReportProblemVC() { + // TODO: Push report problem VC + } + + @objc private func signout() { + let loginVC = LoginViewController() + loginVC.hidesBottomBarWhenPushed = true + loginVC.navigationItem.hidesBackButton = true + navigationController?.pushViewController(loginVC, animated: false) + } + +} diff --git a/Scoop/Model/User.swift b/Scoop/Model/User.swift index 0fcf2c6..48d2392 100644 --- a/Scoop/Model/User.swift +++ b/Scoop/Model/User.swift @@ -8,24 +8,6 @@ import Foundation import UIKit -class User: Codable { - var id: String = "" - var profilePicUrl: String = "" - var netid: String = "" - var firstName: String = "" - var lastName: String = "" - var pronouns: String = "" - var hometown: String = "" - var grade: String = "" - var email: String = "" - var phoneNumber: String = "" - var talkingPref: Float = 0.0 - var musicPref: Float = 0.0 - var favoriteSnack: String = "" - var favoriteSong: String = "" - var favoriteStop: String = "" -} - class BaseUser: Codable { var id: Int var netid: String @@ -37,7 +19,7 @@ class BaseUser: Codable { var pronouns: String? var prompts: [Prompt] var rides: [HomeVCRide] - + init(id: Int, netid: String, firstName: String, lastName: String, phoneNumber: String? = nil, profilePicUrl: String? = nil, grade: String? = nil, pronouns: String? = nil, prompts: [Prompt], rides: [HomeVCRide]) { self.id = id self.netid = netid @@ -62,3 +44,12 @@ struct TruncUser: Codable { var pronouns: String? var prompts: [Prompt] } + +extension BaseUser { + static var blockedUsersDummyData = [ + BaseUser(id: 0, netid: "netid", firstName: "Michelle", lastName: "White", phoneNumber: "phone", profilePicUrl: "", grade: "grade", pronouns: "pronouns", prompts: [], rides: []), + BaseUser(id: 1, netid: "netid", firstName: "Lauren", lastName: "Davidson", phoneNumber: "phone", profilePicUrl: "", grade: "grade", pronouns: "pronouns", prompts: [], rides: []), + BaseUser(id: 2, netid: "netid", firstName: "Victor", lastName: "Hunter", phoneNumber: "phone", profilePicUrl: "", grade: "grade", pronouns: "pronouns", prompts: [], rides: []), + BaseUser(id: 3, netid: "netid", firstName: "Finley", lastName: "Carpenter", phoneNumber: "phone", profilePicUrl: "", grade: "grade", pronouns: "pronouns", prompts: [], rides: []) + ] +} diff --git a/Scoop/NetworkManager.swift b/Scoop/NetworkManager.swift index 8083fc3..b6b8a9c 100644 --- a/Scoop/NetworkManager.swift +++ b/Scoop/NetworkManager.swift @@ -75,10 +75,10 @@ class NetworkManager { "pronouns": pronouns, "profile_pic_base64": prof_pic, "prompts": prompts.map({ prompt -> [String: Any] in - return [ - "id": prompt.id, - "answer": prompt.answer - ] + return [ + "id": prompt.id, + "answer": prompt.answer + ] }) ] AF.request("\(hostEndpoint)/api/me/", method: .post, parameters: parameters, encoding: JSONEncoding.default, headers: headers).validate().responseData { response in @@ -155,7 +155,7 @@ class NetworkManager { } } - func getAllUsers(completion: @escaping (Result<[BaseUser], Error>) -> Void ) { + func getAllUsers(completion: @escaping (Result<[BaseUser], Error>) -> Void) { AF.request("\(hostEndpoint)/api/dev/", method: .get, encoding: JSONEncoding.default, headers: headers).validate().responseData { response in switch response.result { case .success(let data): @@ -173,6 +173,25 @@ class NetworkManager { } } } + + func getBlockedUsers(completion: @escaping (Result<[BaseUser], Error>) -> Void) { + AF.request("\(hostEndpoint)/api/block/", method: .get, encoding: JSONEncoding.default, headers: headers).validate().responseData { response in + switch response.result { + case .success(let data): + let jsonDecoder = JSONDecoder() + jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase + do { + let blockedUsers = try jsonDecoder.decode([BaseUser].self, from: data) + completion(.success(blockedUsers)) + } catch { + completion(.failure(error)) + } + case .failure(let error): + completion(.failure(error)) + print("Request getBlockedUsers Failed: \(error.localizedDescription)") + } + } + } // MARK: - Prompt Functions @@ -346,7 +365,7 @@ class NetworkManager { "departure_datetime": departureTime, "driver": driver ] - + AF.request(endpoint, method: .post, parameters: params, encoding: JSONEncoding.default, headers: headers).validate().responseData { response in switch response.result { case .success(let data): diff --git a/Scoop/Supporting/Assets.xcassets/SliderThumb.imageset/Contents.json b/Scoop/Supporting/Assets.xcassets/SliderThumb.imageset/Contents.json index 5855efd..8851fa8 100644 --- a/Scoop/Supporting/Assets.xcassets/SliderThumb.imageset/Contents.json +++ b/Scoop/Supporting/Assets.xcassets/SliderThumb.imageset/Contents.json @@ -5,11 +5,11 @@ "scale" : "1x" }, { - "filename" : "circle.fill@2x.png", "idiom" : "universal", "scale" : "2x" }, { + "filename" : "SliderThumb.png", "idiom" : "universal", "scale" : "3x" } diff --git a/Scoop/Supporting/Assets.xcassets/SliderThumb.imageset/SliderThumb.png b/Scoop/Supporting/Assets.xcassets/SliderThumb.imageset/SliderThumb.png new file mode 100644 index 0000000..56d06da Binary files /dev/null and b/Scoop/Supporting/Assets.xcassets/SliderThumb.imageset/SliderThumb.png differ diff --git a/Scoop/Supporting/Assets.xcassets/SliderThumb.imageset/circle.fill@2x.png b/Scoop/Supporting/Assets.xcassets/SliderThumb.imageset/circle.fill@2x.png deleted file mode 100644 index c0f4f24..0000000 Binary files a/Scoop/Supporting/Assets.xcassets/SliderThumb.imageset/circle.fill@2x.png and /dev/null differ diff --git a/Scoop/Supporting/Assets.xcassets/SliderTrack.imageset/Image.png b/Scoop/Supporting/Assets.xcassets/SliderTrack.imageset/Image.png deleted file mode 100644 index 45ef0f1..0000000 Binary files a/Scoop/Supporting/Assets.xcassets/SliderTrack.imageset/Image.png and /dev/null differ diff --git a/Scoop/Supporting/Assets.xcassets/SliderTrack.imageset/Contents.json b/Scoop/Supporting/Assets.xcassets/blockIcon.imageset/Contents.json similarity index 88% rename from Scoop/Supporting/Assets.xcassets/SliderTrack.imageset/Contents.json rename to Scoop/Supporting/Assets.xcassets/blockIcon.imageset/Contents.json index 17f6fd9..1b39906 100644 --- a/Scoop/Supporting/Assets.xcassets/SliderTrack.imageset/Contents.json +++ b/Scoop/Supporting/Assets.xcassets/blockIcon.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "Image.png", + "filename" : "blockIcon.png", "idiom" : "universal", "scale" : "1x" }, diff --git a/Scoop/Supporting/Assets.xcassets/blockIcon.imageset/blockIcon.png b/Scoop/Supporting/Assets.xcassets/blockIcon.imageset/blockIcon.png new file mode 100644 index 0000000..10b0a5b Binary files /dev/null and b/Scoop/Supporting/Assets.xcassets/blockIcon.imageset/blockIcon.png differ diff --git a/Scoop/Supporting/Assets.xcassets/editProfileIcon.imageset/Contents.json b/Scoop/Supporting/Assets.xcassets/editProfileIcon.imageset/Contents.json new file mode 100644 index 0000000..d3ba712 --- /dev/null +++ b/Scoop/Supporting/Assets.xcassets/editProfileIcon.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "editProfileIcon.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Scoop/Supporting/Assets.xcassets/editProfileIcon.imageset/editProfileIcon.png b/Scoop/Supporting/Assets.xcassets/editProfileIcon.imageset/editProfileIcon.png new file mode 100644 index 0000000..c51ad07 Binary files /dev/null and b/Scoop/Supporting/Assets.xcassets/editProfileIcon.imageset/editProfileIcon.png differ diff --git a/Scoop/Supporting/Assets.xcassets/lineBreak.imageset/Contents.json b/Scoop/Supporting/Assets.xcassets/lineBreak.imageset/Contents.json new file mode 100644 index 0000000..1f72c09 --- /dev/null +++ b/Scoop/Supporting/Assets.xcassets/lineBreak.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "lineBreak.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Scoop/Supporting/Assets.xcassets/lineBreak.imageset/lineBreak.png b/Scoop/Supporting/Assets.xcassets/lineBreak.imageset/lineBreak.png new file mode 100644 index 0000000..cbb2295 Binary files /dev/null and b/Scoop/Supporting/Assets.xcassets/lineBreak.imageset/lineBreak.png differ diff --git a/Scoop/Supporting/Assets.xcassets/profileBackground.imageset/Contents.json b/Scoop/Supporting/Assets.xcassets/profileBackground.imageset/Contents.json new file mode 100644 index 0000000..371605e --- /dev/null +++ b/Scoop/Supporting/Assets.xcassets/profileBackground.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "profileBackground.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Scoop/Supporting/Assets.xcassets/profileBackground.imageset/profileBackground.png b/Scoop/Supporting/Assets.xcassets/profileBackground.imageset/profileBackground.png new file mode 100644 index 0000000..09e8691 Binary files /dev/null and b/Scoop/Supporting/Assets.xcassets/profileBackground.imageset/profileBackground.png differ diff --git a/Scoop/Supporting/Assets.xcassets/reportIcon.imageset/Contents.json b/Scoop/Supporting/Assets.xcassets/reportIcon.imageset/Contents.json new file mode 100644 index 0000000..0449302 --- /dev/null +++ b/Scoop/Supporting/Assets.xcassets/reportIcon.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "reportIcon.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Scoop/Supporting/Assets.xcassets/reportIcon.imageset/reportIcon.png b/Scoop/Supporting/Assets.xcassets/reportIcon.imageset/reportIcon.png new file mode 100644 index 0000000..adc3e55 Binary files /dev/null and b/Scoop/Supporting/Assets.xcassets/reportIcon.imageset/reportIcon.png differ diff --git a/Scoop/Supporting/Assets.xcassets/rideHistoryIcon.imageset/Contents.json b/Scoop/Supporting/Assets.xcassets/rideHistoryIcon.imageset/Contents.json new file mode 100644 index 0000000..5e835d2 --- /dev/null +++ b/Scoop/Supporting/Assets.xcassets/rideHistoryIcon.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "rideHistoryIcon.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Scoop/Supporting/Assets.xcassets/rideHistoryIcon.imageset/rideHistoryIcon.png b/Scoop/Supporting/Assets.xcassets/rideHistoryIcon.imageset/rideHistoryIcon.png new file mode 100644 index 0000000..3221397 Binary files /dev/null and b/Scoop/Supporting/Assets.xcassets/rideHistoryIcon.imageset/rideHistoryIcon.png differ diff --git a/Scoop/Supporting/Assets.xcassets/settingsIcon.imageset/Contents.json b/Scoop/Supporting/Assets.xcassets/settingsIcon.imageset/Contents.json index df7afe6..b1c0cf2 100644 --- a/Scoop/Supporting/Assets.xcassets/settingsIcon.imageset/Contents.json +++ b/Scoop/Supporting/Assets.xcassets/settingsIcon.imageset/Contents.json @@ -1,7 +1,6 @@ { "images" : [ { - "filename" : "settingsIcon.svg", "idiom" : "universal", "scale" : "1x" }, @@ -10,6 +9,7 @@ "scale" : "2x" }, { + "filename" : "settingsIcon.png", "idiom" : "universal", "scale" : "3x" } diff --git a/Scoop/Supporting/Assets.xcassets/settingsIcon.imageset/settingsIcon.png b/Scoop/Supporting/Assets.xcassets/settingsIcon.imageset/settingsIcon.png new file mode 100644 index 0000000..b28a14e Binary files /dev/null and b/Scoop/Supporting/Assets.xcassets/settingsIcon.imageset/settingsIcon.png differ diff --git a/Scoop/Supporting/Assets.xcassets/settingsIcon.imageset/settingsIcon.svg b/Scoop/Supporting/Assets.xcassets/settingsIcon.imageset/settingsIcon.svg deleted file mode 100644 index d9fab0a..0000000 --- a/Scoop/Supporting/Assets.xcassets/settingsIcon.imageset/settingsIcon.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/Scoop/Supporting/Assets.xcassets/signoutIcon.imageset/Contents.json b/Scoop/Supporting/Assets.xcassets/signoutIcon.imageset/Contents.json new file mode 100644 index 0000000..d4a4166 --- /dev/null +++ b/Scoop/Supporting/Assets.xcassets/signoutIcon.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "signoutIcon.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Scoop/Supporting/Assets.xcassets/signoutIcon.imageset/signoutIcon.png b/Scoop/Supporting/Assets.xcassets/signoutIcon.imageset/signoutIcon.png new file mode 100644 index 0000000..5166c9c Binary files /dev/null and b/Scoop/Supporting/Assets.xcassets/signoutIcon.imageset/signoutIcon.png differ diff --git a/Scoop/Supporting/Assets.xcassets/track.imageset/Contents.json b/Scoop/Supporting/Assets.xcassets/track.imageset/Contents.json index 660b917..879fd48 100644 --- a/Scoop/Supporting/Assets.xcassets/track.imageset/Contents.json +++ b/Scoop/Supporting/Assets.xcassets/track.imageset/Contents.json @@ -1,7 +1,6 @@ { "images" : [ { - "filename" : "track.png", "idiom" : "universal", "scale" : "1x" }, @@ -10,6 +9,7 @@ "scale" : "2x" }, { + "filename" : "track.png", "idiom" : "universal", "scale" : "3x" } diff --git a/Scoop/Supporting/Assets.xcassets/track.imageset/track.png b/Scoop/Supporting/Assets.xcassets/track.imageset/track.png index 6d00c54..ba978cf 100644 Binary files a/Scoop/Supporting/Assets.xcassets/track.imageset/track.png and b/Scoop/Supporting/Assets.xcassets/track.imageset/track.png differ diff --git a/Scoop/Supporting/Info.plist b/Scoop/Supporting/Info.plist index 25e646e..ae48187 100644 --- a/Scoop/Supporting/Info.plist +++ b/Scoop/Supporting/Info.plist @@ -16,6 +16,7 @@ UIAppFonts Rambla-Regular.ttf + Roboto-Regular.ttf SFProDisplay-Bold.otf SFProDisplay-Regular.otf Sen-Regular.ttf diff --git a/Scoop/Supporting/Utilities/Extension+UIColor.swift b/Scoop/Supporting/Utilities/Extension+UIColor.swift index fdfedf2..1a85bad 100644 --- a/Scoop/Supporting/Utilities/Extension+UIColor.swift +++ b/Scoop/Supporting/Utilities/Extension+UIColor.swift @@ -29,7 +29,7 @@ extension UIColor { let skipButtonColor = UIColor(red: 151/255, green: 151/255, blue: 151/255, alpha: 1) let textFieldBorderColor = UIColor(red: 106/255, green: 115/255, blue: 125/255, alpha: 1) - let whiteGradientColors = [ + let hoverGradientColors = [ UIColor.white.withAlphaComponent(0).cgColor, UIColor.white.withAlphaComponent(0.3).cgColor, UIColor.white.withAlphaComponent(0.6).cgColor, @@ -43,6 +43,22 @@ extension UIColor { UIColor.white.cgColor, UIColor.white.cgColor ] + + let backgroundGradientColors = [ + UIColor.white.withAlphaComponent(0).cgColor, + UIColor.white.withAlphaComponent(0).cgColor, + UIColor.white.withAlphaComponent(0.1).cgColor, + UIColor.white.withAlphaComponent(0.4).cgColor, + UIColor.white.cgColor, + UIColor.white.cgColor, + UIColor.white.cgColor, + UIColor.white.cgColor, + UIColor.white.cgColor, + UIColor.white.cgColor, + UIColor.white.cgColor, + UIColor.white.cgColor, + UIColor.white.cgColor + ] } } diff --git a/Scoop/Supporting/Utilities/Extension+UIFont.swift b/Scoop/Supporting/Utilities/Extension+UIFont.swift index 7bd55c3..33acd40 100644 --- a/Scoop/Supporting/Utilities/Extension+UIFont.swift +++ b/Scoop/Supporting/Utilities/Extension+UIFont.swift @@ -14,8 +14,10 @@ extension UIFont { struct Scooped { let bodyBold = UIFont.systemFont(ofSize: 16, weight: .bold) + let bodyMedium = UIFont(name: "Roboto-Regular", size: 14) let bodyNormal = UIFont.systemFont(ofSize: 16) let bodySemibold = UIFont.systemFont(ofSize: 16, weight: .semibold) + let buttonSemibold = UIFont.systemFont(ofSize: 12, weight: .semibold) let flowHeader = UIFont(name: "Sen-Regular", size: 24) let subheader = UIFont(name: "Rambla-Regular", size: 16) } diff --git a/Scoop/Supporting/Utilities/Extension+UIImage.swift b/Scoop/Supporting/Utilities/Extension+UIImage.swift index 6546c6f..2dcbcd9 100644 --- a/Scoop/Supporting/Utilities/Extension+UIImage.swift +++ b/Scoop/Supporting/Utilities/Extension+UIImage.swift @@ -14,14 +14,22 @@ extension UIImage { struct Scooped { let blockCallIcon = UIImage(named: "blockCallIcon") + let blockIcon = UIImage(named: "blockIcon") let cancelIcon = UIImage(named: "cancelIcon") let dottedLine = UIImage(named: "dottedline") + let editProfileIcon = UIImage(named: "editProfileIcon") let emptyImage = UIImage(named: "emptyimage") + let lineBreak = UIImage(named: "lineBreak") let notifIcon = UIImage(named: "notifIcon") + let profileBackground = UIImage(named: "profileBackground") let profileButton = UIImage(named: "profilebutton") let setttingsIcon = UIImage(named: "settingsIcon") + let signoutIcon = UIImage(named: "signoutIcon") let sliderThumb = UIImage(named: "SliderThumb") let sliderTicks = UIImage(named: "SliderTicks") + let sliderTrack = UIImage(named: "track") + let reportIcon = UIImage(named: "reportIcon") + let rideHistoryIcon = UIImage(named: "rideHistoryIcon") } func imageToB64(compression: CGFloat) -> String { diff --git a/Scoop/Supporting/fonts/Roboto-Regular.ttf b/Scoop/Supporting/fonts/Roboto-Regular.ttf new file mode 100644 index 0000000..ddf4bfa Binary files /dev/null and b/Scoop/Supporting/fonts/Roboto-Regular.ttf differ diff --git a/Scoop/View/EmptyStateView.swift b/Scoop/View/EmptyStateView.swift index 158a08c..c7f3677 100644 --- a/Scoop/View/EmptyStateView.swift +++ b/Scoop/View/EmptyStateView.swift @@ -12,9 +12,9 @@ class EmptyStateView: UIView { // MARK: - Views - private let mainView = UIView() private let imageView = UIImageView() private let titleLabel = UILabel() + private let stackView = UIStackView() private let subLabel = UILabel() // MARK: - Set Up Views @@ -24,29 +24,33 @@ class EmptyStateView: UIView { titleLabel.text = title subLabel.text = subtitle - setupMainView() + setupStackView() setupImageView() - setupTitleLabel() + if !title.isEmpty { setupTitleLabel() } setupSubLabel() } - private func setupMainView() { - addSubview(mainView) + private func setupStackView() { + stackView.axis = .vertical + stackView.spacing = 16 + stackView.alignment = .center + stackView.distribution = .fill + addSubview(stackView) - mainView.snp.makeConstraints { make in + stackView.snp.makeConstraints { make in make.centerX.equalToSuperview() - make.centerY.equalToSuperview().offset(-64) + make.centerY.equalTo(safeAreaLayoutGuide.snp.centerY).offset(-28) } } private func setupImageView() { + stackView.setCustomSpacing(24, after: imageView) imageView.contentMode = .scaleAspectFit imageView.layer.masksToBounds = true - mainView.addSubview(imageView) + stackView.addArrangedSubview(imageView) + stackView.setCustomSpacing(24, after: imageView) imageView.snp.makeConstraints { make in - make.centerX.equalToSuperview() - make.top.equalToSuperview() make.width.height.equalTo(50) } } @@ -54,23 +58,13 @@ class EmptyStateView: UIView { private func setupTitleLabel() { titleLabel.font = UIFont.scooped.bodySemibold titleLabel.textColor = UIColor.black - mainView.addSubview(titleLabel) - - titleLabel.snp.makeConstraints { make in - make.centerX.equalToSuperview() - make.top.equalTo(imageView.snp.bottom).offset(24) - } + stackView.addArrangedSubview(titleLabel) } private func setupSubLabel() { subLabel.font = UIFont.scooped.bodyNormal subLabel.textColor = UIColor.secondaryLabel - mainView.addSubview(subLabel) - - subLabel.snp.makeConstraints { make in - make.centerX.equalToSuperview() - make.top.equalTo(titleLabel.snp.bottom).offset(16) - } + stackView.addArrangedSubview(subLabel) } } diff --git a/Scoop/View/PopUpViewController.swift b/Scoop/View/PopUpViewController.swift new file mode 100644 index 0000000..9a10ba6 --- /dev/null +++ b/Scoop/View/PopUpViewController.swift @@ -0,0 +1,154 @@ +// +// PopUpView.swift +// Scoop +// +// Created by Caitlyn Jin on 11/15/23. +// + +import UIKit + +class PopUpViewController: UIViewController { + + // MARK: - Views + + private let actionButton = UIButton() + private let blurEffectView = UIVisualEffectView() + private let cancelButton = UIButton() + private let containerView = UIView() + private let stackView = UIStackView() + private let subtitleLabel = UILabel() + private let titleLabel = UILabel() + + weak var delegate: PopUpViewDelegate? + + // MARK: - Lifecycle Functions + + override func viewDidLoad() { + super.viewDidLoad() + + setupBlurEffectView() + setupContainerView() + setupStackView() + } + + // MARK: - Initializers + + override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { + super.init(nibName: nil, bundle: nil) + modalPresentationStyle = .overCurrentContext + modalTransitionStyle = .crossDissolve + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func configure(title: NSMutableAttributedString, subtitle: String, actionButtonText: String, delegate: PopUpViewDelegate) { + titleLabel.attributedText = title + subtitleLabel.text = subtitle + actionButton.setAttributedTitle(NSMutableAttributedString(string: actionButtonText, attributes: [NSAttributedString.Key.font: UIFont.scooped.bodyBold]), for: .normal) + self.delegate = delegate + + setupTitle() + if !subtitle.isEmpty { + setupSubtitle() + } + setupActionButton() + setupCancelButton() + } + + // MARK: - Setup View Functions + + private func setupBlurEffectView() { + let blurEffect = UIBlurEffect(style: .dark) + blurEffectView.effect = blurEffect + blurEffectView.layer.opacity = 0.4 + blurEffectView.frame = view.bounds + view.addSubview(blurEffectView) + } + + private func setupContainerView() { + containerView.backgroundColor = UIColor.white + containerView.layer.cornerRadius = 16 + view.addSubview(containerView) + + containerView.snp.makeConstraints { make in + make.center.equalToSuperview() + make.width.equalTo(276) + } + } + + private func setupStackView() { + stackView.axis = .vertical + stackView.spacing = 18 + stackView.alignment = .center + stackView.distribution = .fill + containerView.addSubview(stackView) + + stackView.snp.makeConstraints { make in + make.top.equalToSuperview().inset(40) + make.horizontalEdges.equalToSuperview().inset(28) + make.bottom.equalToSuperview().inset(24) + } + } + + private func setupTitle() { + titleLabel.textColor = UIColor.black + titleLabel.numberOfLines = 2 + titleLabel.textAlignment = .center + titleLabel.lineBreakMode = .byWordWrapping + stackView.addArrangedSubview(titleLabel) + } + + private func setupSubtitle() { + subtitleLabel.font = UIFont.systemFont(ofSize: 14, weight: .regular) + subtitleLabel.textColor = UIColor.scooped.mutedGrey + subtitleLabel.numberOfLines = 1 + subtitleLabel.textAlignment = .center + stackView.addArrangedSubview(subtitleLabel) + } + + private func setupActionButton() { + actionButton.backgroundColor = UIColor.scooped.scoopGreen + actionButton.setTitleColor(UIColor.white, for: .normal) + actionButton.layer.cornerRadius = 36 / 2 + stackView.addArrangedSubview(actionButton) + + actionButton.addTarget(self, action: #selector(doAction), for: .touchUpInside) + + actionButton.snp.makeConstraints { make in + make.width.equalTo(162) + make.height.equalTo(36) + } + } + + private func setupCancelButton() { + stackView.setCustomSpacing(8, after: actionButton) + cancelButton.setAttributedTitle(NSMutableAttributedString(string: "Cancel", attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 14, weight: .bold)]), for: .normal) + cancelButton.setTitleColor(UIColor.black, for: .normal) + stackView.addArrangedSubview(cancelButton) + + cancelButton.addTarget(self, action: #selector(popVC), for: .touchUpInside) + + cancelButton.snp.makeConstraints { make in + make.width.equalTo(162) + make.height.equalTo(36) + } + } + + // MARK: - Helper Functions + + @objc private func doAction() { + dismiss(animated: true) + delegate?.acceptPopUp() + } + + @objc private func popVC() { + dismiss(animated: true) + } + +} + +protocol PopUpViewDelegate: AnyObject { + func acceptPopUp() +} diff --git a/Scoop/View/SettingsCellView.swift b/Scoop/View/SettingsCellView.swift new file mode 100644 index 0000000..874d600 --- /dev/null +++ b/Scoop/View/SettingsCellView.swift @@ -0,0 +1,49 @@ +// +// SettingsCellView.swift +// Scoop +// +// Created by Caitlyn Jin on 11/8/23. +// + +import UIKit + +class SettingsCellView: UIView { + + // MARK: - Views + + private let iconImageView = UIImageView() + private let textLabel = UILabel() + + // MARK: - Set Up Views + + func setup(icon: UIImage, text: String) { + iconImageView.image = icon + textLabel.text = text + + setupIconImageView() + setupTextLabel() + } + + private func setupIconImageView() { + iconImageView.contentMode = .scaleAspectFill + addSubview(iconImageView) + + iconImageView.snp.makeConstraints { make in + make.leading.equalToSuperview() + make.size.equalTo(24) + make.centerY.equalToSuperview() + } + } + + private func setupTextLabel() { + textLabel.font = UIFont.scooped.bodyMedium + textLabel.textColor = UIColor.black + addSubview(textLabel) + + textLabel.snp.makeConstraints { make in + make.leading.equalTo(iconImageView.snp.trailing).offset(20) + make.centerY.equalToSuperview() + make.trailing.equalToSuperview() + } + } +}