Docking two-dimensional code login network interface

dev
wenlei 1 year ago
parent 8338060fa7
commit b5a61b0988

@ -9,6 +9,11 @@
/* Begin PBXBuildFile section */
3C955224FAC6473657A027C3 /* Pods_IndieMusicTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 42CE866330C1741ADEC950B2 /* Pods_IndieMusicTests.framework */; };
49330FC1492387B5155757F6 /* Pods_IndieMusic_IndieMusicUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA03C869C12FEE5FB3D835C2 /* Pods_IndieMusic_IndieMusicUITests.framework */; };
770228E52B54FA3600E07F7A /* ActivityIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 770228E42B54FA3600E07F7A /* ActivityIndicator.swift */; };
770228E72B5514F600E07F7A /* InternationalNumberViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 770228E62B5514F600E07F7A /* InternationalNumberViewController.swift */; };
770228E92B55169C00E07F7A /* InternationalNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 770228E82B55169C00E07F7A /* InternationalNumber.swift */; };
770228EB2B55174D00E07F7A /* InternationalNumberViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 770228EA2B55174D00E07F7A /* InternationalNumberViewModel.swift */; };
770228ED2B55284F00E07F7A /* Login.swift in Sources */ = {isa = PBXBuildFile; fileRef = 770228EC2B55284F00E07F7A /* Login.swift */; };
77165D742B464493002AE0A5 /* BarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77165D732B464493002AE0A5 /* BarButtonItem.swift */; };
7736FF442B4CECF2008D5DAD /* CommentViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7736FF432B4CECF2008D5DAD /* CommentViewModel.swift */; };
7736FF462B4CF0E6008D5DAD /* CommentDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7736FF452B4CF0E6008D5DAD /* CommentDetailViewController.swift */; };
@ -204,6 +209,11 @@
3BA421D41748C113CCE36A32 /* Pods_IndieMusic.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_IndieMusic.framework; sourceTree = BUILT_PRODUCTS_DIR; };
42CE866330C1741ADEC950B2 /* Pods_IndieMusicTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_IndieMusicTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
69F724137B8F9C1F7E51080D /* Pods-IndieMusicTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-IndieMusicTests.release.xcconfig"; path = "Target Support Files/Pods-IndieMusicTests/Pods-IndieMusicTests.release.xcconfig"; sourceTree = "<group>"; };
770228E42B54FA3600E07F7A /* ActivityIndicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActivityIndicator.swift; sourceTree = "<group>"; };
770228E62B5514F600E07F7A /* InternationalNumberViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InternationalNumberViewController.swift; sourceTree = "<group>"; };
770228E82B55169C00E07F7A /* InternationalNumber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InternationalNumber.swift; sourceTree = "<group>"; };
770228EA2B55174D00E07F7A /* InternationalNumberViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InternationalNumberViewModel.swift; sourceTree = "<group>"; };
770228EC2B55284F00E07F7A /* Login.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Login.swift; sourceTree = "<group>"; };
77165D732B464493002AE0A5 /* BarButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BarButtonItem.swift; sourceTree = "<group>"; };
7736FF432B4CECF2008D5DAD /* CommentViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommentViewModel.swift; sourceTree = "<group>"; };
7736FF452B4CF0E6008D5DAD /* CommentDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommentDetailViewController.swift; sourceTree = "<group>"; };
@ -412,6 +422,14 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
770228E32B54FA3600E07F7A /* RxActivityIndicator */ = {
isa = PBXGroup;
children = (
770228E42B54FA3600E07F7A /* ActivityIndicator.swift */,
);
path = RxActivityIndicator;
sourceTree = "<group>";
};
7743999C2AFA18B0006F8EEA /* Player */ = {
isa = PBXGroup;
children = (
@ -613,6 +631,8 @@
77C9B9DE2B4BCE300006C83F /* Message.swift */,
77C9B9E42B4BDB4C0006C83F /* Like.swift */,
77C9B9EA2B4BE7E50006C83F /* Comment.swift */,
770228E82B55169C00E07F7A /* InternationalNumber.swift */,
770228EC2B55284F00E07F7A /* Login.swift */,
);
path = Models;
sourceTree = "<group>";
@ -646,6 +666,7 @@
778B8A5A2AF8EAFD0034AFD4 /* Third Party */ = {
isa = PBXGroup;
children = (
770228E32B54FA3600E07F7A /* RxActivityIndicator */,
778B8A602AF8ECC20034AFD4 /* RxErrorTracker */,
7751D3812B45324300F1F2BD /* IndieMusic-Bridging-Header.h */,
);
@ -695,6 +716,8 @@
77FA0B472B0DFB0E00404C5E /* EditBindPhoneViewController.swift */,
77FA0B432B0DFABD00404C5E /* LoginView.swift */,
7751D3792B450BB100F1F2BD /* AgreementViewController.swift */,
770228E62B5514F600E07F7A /* InternationalNumberViewController.swift */,
770228EA2B55174D00E07F7A /* InternationalNumberViewModel.swift */,
);
path = Login;
sourceTree = "<group>";
@ -1086,9 +1109,11 @@
778B8A5F2AF8ECAB0034AFD4 /* Navigator.swift in Sources */,
77FA0B302B0C4D5A00404C5E /* ShareActionController.swift in Sources */,
778B8AA82AF8ED0E0034AFD4 /* Color.swift in Sources */,
770228EB2B55174D00E07F7A /* InternationalNumberViewModel.swift in Sources */,
778B8AAE2AF8ED0E0034AFD4 /* TextView.swift in Sources */,
778B8A9B2AF8ECFC0034AFD4 /* AuthManager.swift in Sources */,
778B8AAF2AF8ED0E0034AFD4 /* UIColor+IndieMusic.swift in Sources */,
770228E52B54FA3600E07F7A /* ActivityIndicator.swift in Sources */,
7751D3722B43B9ED00F1F2BD /* Search.swift in Sources */,
774A18072B06045200F56DF1 /* HomeView.swift in Sources */,
77FAF7642B075FEB00FC2CA1 /* JournalDetailViewModel.swift in Sources */,
@ -1119,6 +1144,7 @@
7751D35E2B42B61100F1F2BD /* PersonInfo.swift in Sources */,
774399A82AFE28BA006F8EEA /* BlurEffectView.swift in Sources */,
778B8A852AF8ECE50034AFD4 /* SearchViewController.swift in Sources */,
770228E92B55169C00E07F7A /* InternationalNumber.swift in Sources */,
7751D3802B45271600F1F2BD /* BindPhoneViewModel.swift in Sources */,
774A17F52B045F1C00F56DF1 /* Player.swift in Sources */,
7751D3882B45584000F1F2BD /* LaunchADManager.swift in Sources */,
@ -1133,6 +1159,7 @@
778B8ABF2AF8ED280034AFD4 /* NavigationController.swift in Sources */,
774399AA2AFE3170006F8EEA /* PaddingLabel.swift in Sources */,
77FB7A702B48074600B64030 /* MusicStyleViewModel.swift in Sources */,
770228ED2B55284F00E07F7A /* Login.swift in Sources */,
7751D3502B42ABBF00F1F2BD /* SettingViewController.swift in Sources */,
77A659DF2B51023200B408C3 /* TestViewController.swift in Sources */,
77FA0B4E2B0EF8C700404C5E /* PhoneCodeViewModel.swift in Sources */,
@ -1196,6 +1223,7 @@
77FA0B362B0C50D800404C5E /* Share.swift in Sources */,
778B8A622AF8ECC20034AFD4 /* ErrorTracker.swift in Sources */,
774A18102B070A6900F56DF1 /* SongViewCell.swift in Sources */,
770228E72B5514F600E07F7A /* InternationalNumberViewController.swift in Sources */,
7751D3762B43E8D200F1F2BD /* MusicStyle.swift in Sources */,
77C9B9D72B4BBD780006C83F /* FollowingViewModel.swift in Sources */,
7736FF442B4CECF2008D5DAD /* CommentViewModel.swift in Sources */,

@ -56,7 +56,7 @@ class Navigator {
case phoneCode(viewModel: PhoneCodeViewModel)
case login(viewModel: LoginViewModel)
case bindPhone(viewModel: BindPhoneViewModel)
case internationalNumber(viewModel: InternationalNumberViewModel)
case player(viewModel: PlayerViewModel)
case alert
@ -162,7 +162,8 @@ class Navigator {
return LoginViewController(viewModel: viewModel, navigator: self)
case .bindPhone(viewModel: let viewModel):
return BindPhoneViewController(viewModel: viewModel, navigator: self)
case .internationalNumber(viewModel: let viewModel):
return InternationalNumberViewController.init(viewModel: viewModel, navigator: self)
case .player(viewModel: let viewModel):
let player = PlayerViewController(viewModel: viewModel, navigator: self)
return player

@ -72,6 +72,23 @@ class AlertViewController: UIViewController {
}()
var confirmClosures: (()->())?
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
self.modalPresentationStyle = .custom
self.transitioningDelegate = self
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
@ -141,10 +158,13 @@ class AlertViewController: UIViewController {
}
@objc func cancelButtonClicked() {
self.dismiss(animated: true)
}
@objc func confirmButtonClick() {
if let confirmClosures = self.confirmClosures {
confirmClosures()
}
}
}

@ -58,7 +58,7 @@ class ViewController: UIViewController, Navigatable {
}()
lazy var closeBarButton: BarButtonItem = {
let view = BarButtonItem(image: UIImage.init(named: ""),
let view = BarButtonItem(image: UIImage.init(named: "nav_close_btn")?.withRenderingMode(.alwaysOriginal),
style: .plain,
target: self,
action: nil)
@ -160,6 +160,7 @@ class ViewController: UIViewController, Navigatable {
}
func updateUI() {
view.backgroundColor = .white
}

@ -22,10 +22,10 @@ class ViewModel: NSObject {
var page = 1
// let loading = ActivityIndicator()
// let headerLoading = ActivityIndicator()
// let footerLoading = ActivityIndicator()
//
let loading = ActivityIndicator()
let headerLoading = ActivityIndicator()
let footerLoading = ActivityIndicator()
let error = ErrorTracker()
let serverError = PublishSubject<Error>()
let parsedError = PublishSubject<ApiError>()

@ -19,14 +19,14 @@ extension EnvironmentType {
case .production:
return "https://"
case .development:
return "http://"
return "http://testapi.indie.cn:9012"
}
}
}
struct Configs {
struct App {
static let environmentType: EnvironmentType = .production
static let environmentType: EnvironmentType = .development
static let aggrementUrl = environmentType.baseUrl + ""
static let universalLink = ""
static let bundleIdentifier = "cn.indie.queyue"

@ -135,7 +135,7 @@ extension UserDefaults {
enum defaultKeys: String {
case userToken
case userID
case userName
}
}

@ -20,8 +20,6 @@ class LibsManager: NSObject {
private override init() {
super.init()
setupSwiftDate()
setupWeChatSDK()
}
func setupSwiftDate() {
@ -32,7 +30,12 @@ class LibsManager: NSObject {
func setupLibs(with window: UIWindow? = nil) {
let libsManager = LibsManager.shared
libsManager.setupSwiftDate()
libsManager.setupSVProgressHUD()
// libsManager.setupDorameonKit()
// libsManager.setupBugly()
libsManager.setupWeChatSDK()
}
func setupXHLaunchAd() {
@ -40,6 +43,11 @@ class LibsManager: NSObject {
}
func setupSVProgressHUD() {
SVProgressHUD.setDefaultStyle(.dark)
SVProgressHUD.setMaximumDismissTimeInterval(1)
}
func setupWeChatSDK() {
WXApi.startLog(by: .detail) { (log) in
print("微信log\(log)")

@ -0,0 +1,28 @@
//
// InternationalNumber.swift
// IndieMusic
//
// Created by WenLei on 2024/1/15.
//
import Foundation
import RxDataSources
struct InternationalNumber {
let country: String
let number: String
}
struct InternationalNumberSection {
var items: [InternationalNumber]
}
extension InternationalNumberSection: SectionModelType {
typealias Item = InternationalNumber
init(original: InternationalNumberSection, items: [Item]) {
self = original
self.items = items
}
}

@ -0,0 +1,17 @@
//
// Login.swift
// IndieMusic
//
// Created by WenLei on 2024/1/15.
//
import Foundation
struct Login: Codable {
let id: String?
let nickname: String?
let sex: Bool?
let avatar: String?
let personality: String?
let token: String?
}

@ -88,7 +88,7 @@ class HomeViewController: TableViewController {
guard let viewModel = viewModel as? HomeViewModel else { return }
let input = HomeViewModel.Input.init(viewWillAppear: rx.viewWillAppear,
selection: tableView.rx.itemSelected.asDriver())
selection: tableView.rx.itemSelected.asDriver(), loginButtonTrigger: self.noLoginBottomView.loginButton.rx.tap.asDriver())
let output = viewModel.transform(input: input)
let dataSource = HomeViewController.dataSource()
@ -113,6 +113,14 @@ class HomeViewController: TableViewController {
self.noLoginBottomView.loginButton.rx.tap.subscribe { _ in
let loginViewModel = LoginViewModel.init(provider: viewModel.provider)
self.navigator.show(segue: .login(viewModel: loginViewModel), sender: self)
}.disposed(by: rx.disposeBag)

@ -14,6 +14,7 @@ class HomeViewModel: ViewModel, ViewModelType {
struct Input {
let viewWillAppear: ControlEvent<Bool>
let selection: Driver<IndexPath>
let loginButtonTrigger: Driver<Void>
}

@ -0,0 +1,153 @@
//
// InternationalNumberViewController.swift
// IndieMusic
//
// Created by WenLei on 2024/1/15.
//
import UIKit
import RxSwift
import RxCocoa
import RxDataSources
class InternationalNumberViewController: TableViewController {
// lazy var closeBarButton: BarButtonItem = {
// let view = BarButtonItem(image: UIImage.init(named: ""),
// style: .plain,
// target: self,
// action: nil)
// return view
// }()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.setNavigationBarHidden(false, animated: true)
self.navigationItem.leftBarButtonItem = closeBarButton
}
override func makeUI() {
super.makeUI()
view.backgroundColor = .init(hex: 0xf5f5f5)
navigationItem.title = "选择国家/地区"
tableView.mj_header = nil
tableView.mj_footer = nil
tableView.register(InternationalNumberViewCell.self, forCellReuseIdentifier: "InternationalNumberViewCell")
}
override func bindViewModel() {
super.bindViewModel()
guard let viewModel = viewModel as? InternationalNumberViewModel else { return }
let input = InternationalNumberViewModel.Input.init(viewWillAppear: rx.viewWillAppear,
modelSelected: tableView.rx.modelSelected(InternationalNumber.self).asDriver())
let output = viewModel.transform(input: input)
let dataSource = InternationalNumberViewController.dataSource()
output.items.bind(to: tableView.rx.items(dataSource: dataSource)).disposed(by: rx.disposeBag)
output.modelSelected.drive { [weak self] sectionItem in
self?.navigator.dismiss(sender: self)
}.disposed(by: rx.disposeBag)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
}
}
extension InternationalNumberViewController {
static func dataSource() -> RxTableViewSectionedReloadDataSource<InternationalNumberSection> {
return RxTableViewSectionedReloadDataSource<InternationalNumberSection>(
configureCell: { dataSource, tableView, indexPath, item in
let cell: InternationalNumberViewCell = tableView.dequeueReusableCell(withIdentifier: "InternationalNumberViewCell", for: indexPath) as! InternationalNumberViewCell
cell.internationalNumber = item
return cell
}
)
}
}
class InternationalNumberViewCell: UITableViewCell {
let titleLabel: UILabel = {
let titleLabel = UILabel.init()
titleLabel.font = UIFont.systemFont(ofSize: 17)
titleLabel.textColor = .secondaryText()
return titleLabel
}()
let detailLabel: UILabel = {
let detailLabel = UILabel.init()
detailLabel.font = UIFont.systemFont(ofSize: 17)
detailLabel.textColor = .init(hex: 0x000000)
return detailLabel
}()
var internationalNumber: InternationalNumber? {
didSet {
guard let internationalNumber = internationalNumber else { return }
titleLabel.text = internationalNumber.country
detailLabel.text = internationalNumber.number
}
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
makeUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func makeUI() {
contentView.addSubview(titleLabel)
contentView.addSubview(detailLabel)
detailLabel.snp.makeConstraints { make in
make.right.equalTo(contentView).offset(-18)
make.top.equalTo(contentView)
make.bottom.equalTo(contentView)
make.height.equalTo(60)
}
titleLabel.snp.makeConstraints { make in
make.left.equalTo(contentView).offset(18)
make.top.equalTo(contentView)
make.bottom.equalTo(contentView)
make.right.equalTo(detailLabel.snp.left).offset(-10)
}
}
}

@ -0,0 +1,63 @@
//
// InternationalNumberViewModel.swift
// IndieMusic
//
// Created by WenLei on 2024/1/15.
//
import Foundation
import RxSwift
import RxCocoa
class InternationalNumberViewModel: ViewModel, ViewModelType {
struct Input {
let viewWillAppear: ControlEvent<Bool>
let modelSelected: Driver<InternationalNumber>
}
struct Output {
let items: BehaviorRelay<[InternationalNumberSection]>
let modelSelected: Driver<InternationalNumber>
}
let items = BehaviorRelay<[InternationalNumberSection]>.init(value: [])
var number = BehaviorRelay<String>.init(value: "+86")
init(number: BehaviorRelay<String>, provider: IndieMusicAPI) {
self.number = number
super.init(provider: provider)
}
func transform(input: Input) -> Output {
input.viewWillAppear.subscribe { (_) in
}.disposed(by: rx.disposeBag)
let internationalNumber0 = InternationalNumber.init(country: "中国", number: "+86")
let internationalNumber1 = InternationalNumber.init(country: "美国", number: "+1")
let internationalNumber2 = InternationalNumber.init(country: "日本", number: "+81")
let internationalNumberSection = InternationalNumberSection.init(items: [internationalNumber0, internationalNumber1, internationalNumber2])
items.accept([internationalNumberSection])
input.modelSelected.drive { numberCode in
self.number.accept(numberCode.number)
}.disposed(by: rx.disposeBag)
return Output.init(items: items,
modelSelected: input.modelSelected)
}
}

@ -26,18 +26,19 @@ class PhoneView: UIView {
}()
lazy var phoneTextFiele: UITextField = {
let phoneTextFiele = UITextField.init()
phoneTextFiele.font = UIFont.systemFont(ofSize: 17)
phoneTextFiele.placeholder = "输入手机号"
phoneTextFiele.clearButtonMode = .whileEditing
lazy var phoneTextField: UITextField = {
let phoneTextField = UITextField.init()
phoneTextField.font = UIFont.systemFont(ofSize: 17)
phoneTextField.placeholder = "输入手机号"
phoneTextField.clearButtonMode = .whileEditing
phoneTextField.keyboardType = .numberPad
if let clearButton = phoneTextFiele.value(forKeyPath: "_clearButton") as? UIButton {
if let clearButton = phoneTextField.value(forKeyPath: "_clearButton") as? UIButton {
clearButton.setImage(UIImage(named:"login_clear_btn"), for: .normal)
}
return phoneTextFiele
return phoneTextField
}()
var bottomLineView: UIView = {
@ -63,9 +64,12 @@ class PhoneView: UIView {
func makeUI() {
layer.cornerRadius = 25
phoneTextField.delegate = self
addSubview(codeButton)
addSubview(lineView)
addSubview(phoneTextFiele)
addSubview(phoneTextField)
addSubview(bottomLineView)
}
@ -87,7 +91,7 @@ class PhoneView: UIView {
make.centerY.equalTo(codeButton)
}
phoneTextFiele.snp.makeConstraints { make in
phoneTextField.snp.makeConstraints { make in
make.left.equalTo(lineView.snp.right).offset(15)
make.right.equalTo(self).offset(-31)
make.top.equalTo(self)
@ -104,6 +108,26 @@ class PhoneView: UIView {
}
extension PhoneView: UITextFieldDelegate {
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let currentText = textField.text ?? ""
let prospectiveText = (currentText as NSString).replacingCharacters(in: range, with: string)
//
let maxLength = 11
//
let allowedCharacters = CharacterSet.decimalDigits
let characterSet = CharacterSet(charactersIn: string)
let isNumeric = allowedCharacters.isSuperset(of: characterSet)
//
return isNumeric && prospectiveText.count <= maxLength
}
}
class PhoneCodeView: UIView {
let textFieldView: UITextField = {
let textFieldView = UITextField.init()
@ -216,7 +240,9 @@ class LoginAgreementView: UIView {
lazy var announceCheckBoxView: UIButton = {
let announceCheckBoxView = UIButton.init()
announceCheckBoxView.setImage(UIImage.init(named: "login_radio_off"), for: .normal)
announceCheckBoxView.setImage(UIImage.init(named: "login_agreement_on")?.withRenderingMode(.alwaysTemplate), for: .selected)
announceCheckBoxView.setImage(UIImage.init(named: "login_radio_on"), for: .selected)
// announceCheckBoxView.setImage(UIImage.init(named: "login_agreement_on")?.withRenderingMode(.alwaysTemplate), for: .selected)
announceCheckBoxView.setContentHuggingPriority(.required, for: .horizontal)
announceCheckBoxView.setContentCompressionResistancePriority(.required, for: .horizontal)
announceCheckBoxView.addTarget(self, action: #selector(announceCheckBoxViewClick), for: .touchUpInside)
@ -264,7 +290,7 @@ class LoginAgreementView: UIView {
super.layoutSubviews()
announceCheckBoxView.snp.makeConstraints { make in
make.left.equalTo(self).offset(21)
make.left.equalTo(self)
make.top.equalTo(self)
}
@ -272,13 +298,13 @@ class LoginAgreementView: UIView {
make.left.equalTo(announceCheckBoxView.snp.right).offset(9)
make.top.equalTo(self).offset(2)
make.bottom.equalTo(self)
make.right.equalTo(-21)
make.right.equalTo(self)
}
}
@objc func announceCheckBoxViewClick() {
self.announceCheckBoxView.isSelected.toggle()
// self.announceCheckBoxView.isSelected.toggle()
}
}

@ -6,12 +6,13 @@
//
import UIKit
import SVProgressHUD
class LoginViewController: ViewController {
var backgroundImageView: UIImageView = {
let backgroundImageView = UIImageView.init(image: UIImage.init(named: "launch_bakcground"))
// backgroundImageView.contentMode = .bottom
backgroundImageView.contentMode = .scaleAspectFill
return backgroundImageView
}()
@ -79,6 +80,7 @@ class LoginViewController: ViewController {
override func makeUI() {
super.makeUI()
view.backgroundColor = .white
view.addSubview(backgroundImageView)
@ -100,31 +102,76 @@ class LoginViewController: ViewController {
let agreementBoxSelected = loginAgreementView.announceCheckBoxView.rx.tap.map{!self.loginAgreementView.announceCheckBoxView.isSelected}.asDriver(onErrorJustReturn: false)
let input = LoginViewModel.Input.init(codeButtonTrigger: loginButton.rx.tap.asDriver(),
let input = LoginViewModel.Input.init(numberButtonTrigger: phoneView.codeButton.rx.tap.asDriver(),
codeButtonTrigger: loginButton.rx.tap.asDriver(),
wechatButtonTrigger: otherLoginView.weChatButton.rx.tap.asDriver(),
appleButtonTrigger: otherLoginView.appleButton.rx.tap.asDriver(),
agreementBoxChecked: agreementBoxSelected,
notiWecahtResp: NotificationCenter.default.rx.notification(.notiWecahtResp))
let output = viewModel.transform(input: input)
_ = phoneView.phoneTextFiele.rx.textInput <-> viewModel.phone
_ = phoneView.phoneTextField.rx.textInput <-> viewModel.phone
loginButton.rx.tap.subscribe { [weak self] _ in
viewModel.number.subscribe { [weak self] number in
self?.phoneView.codeButton.setTitle(number, for: .normal)
}.disposed(by: rx.disposeBag)
self.phoneView.codeButton.rx.tap.subscribe { _ in
let internationalNumberViewModel = InternationalNumberViewModel.init(number: viewModel.number, provider: viewModel.provider)
self.navigator.show(segue: .internationalNumber(viewModel: internationalNumberViewModel), sender: self, transition: .modal)
}.disposed(by: rx.disposeBag)
loginAgreementView.announceCheckBoxView.rx.tap
.map { [weak self] in
!(self?.loginAgreementView.announceCheckBoxView.isSelected ?? false)
}
.bind(to: viewModel.isAgree)
.disposed(by: rx.disposeBag)
viewModel.isAgree
.bind(to: loginAgreementView.announceCheckBoxView.rx.isSelected)
.disposed(by: rx.disposeBag)
viewModel.isAgree.subscribe { isAgree in
print("isAgree : \(isAgree)")
}.disposed(by: rx.disposeBag)
// let phoneCodeViewModel = PhoneCodeViewModel.init(phone: "+86 18551843868", provider: viewModel.provider)
// let code = PhoneCodeController.init(viewModel: phoneCodeViewModel, navigator: self.navigator)
//
// self.navigationController?.pushViewController(code, animated: true)
let bindPhoneViewModel = BindPhoneViewModel.init(provider: viewModel.provider)
self?.navigator.show(segue: .bindPhone(viewModel: bindPhoneViewModel), sender: self)
output.isPhoneValid.drive { isPhoneValid in
print("isPhoneValid : \(isPhoneValid)")
}.disposed(by: rx.disposeBag)
output.toLogin.drive { [weak self] canLogin in
guard canLogin else { return }
let phoneCodeViewModel = PhoneCodeViewModel.init(phone: viewModel.phone.value, provider: viewModel.provider)
self?.navigator.show(segue: .phoneCode(viewModel: phoneCodeViewModel), sender: self)
}.disposed(by: rx.disposeBag)
output.errorMessage.drive { errorMessage in
guard errorMessage.count > 0 else { return }
SVProgressHUD.showText(withStatus: errorMessage)
}.disposed(by: rx.disposeBag)
}
@ -132,11 +179,12 @@ class LoginViewController: ViewController {
super.viewDidLayoutSubviews()
backgroundImageView.snp.makeConstraints { make in
make.bottom.equalTo(view).offset(-BaseDimensions.bottomHeight)
make.bottom.equalTo(view)
// .offset(-BaseDimensions.bottomHeight)
make.left.equalTo(view)
make.right.equalTo(view)
make.height.equalTo(463)
make.width.equalTo(463)
// make.height.equalTo(463)
// make.width.equalTo(463)
}

@ -13,26 +13,70 @@ import RxViewController
class LoginViewModel: ViewModel, ViewModelType {
struct Input {
let numberButtonTrigger: Driver<Void>
let codeButtonTrigger: Driver<Void>
let wechatButtonTrigger: Driver<Void>
let appleButtonTrigger: Driver<Void>
let agreementBoxChecked: Driver<Bool>
let notiWecahtResp: Observable<Notification>
}
struct Output {
let number: BehaviorRelay<String>
let isPhoneValid: Driver<Bool>
let toLogin: Driver<Bool>
let errorMessage: Driver<String>
}
let number = BehaviorRelay(value: "+86")
let phone = BehaviorRelay(value: "")
let isAgree = BehaviorRelay(value: false)
func transform(input: Input) -> Output {
number.subscribe { number in
print("number1 : \(number)")
}.disposed(by: rx.disposeBag)
let phoneValid = phone
.asObservable()
.map { [weak self] in
self?.isValidPattern($0, pattern: "^(1[0-9])\\d{9}$") ?? false
}
.asDriver(onErrorJustReturn: false)
let canLogin = Driver.combineLatest(phoneValid, isAgree.asDriver()) { $0 && $1 }
let loginTrigger = input.codeButtonTrigger.withLatestFrom(canLogin)
let toLogin = loginTrigger.flatMapLatest { [weak self] canLogin -> Driver<Bool> in
guard let self = self, canLogin else { return Driver.just(false) }
return self.requestSMSData().asDriver(onErrorJustReturn: false)
}
let errorMessage = input.codeButtonTrigger
.withLatestFrom(Driver.combineLatest(phoneValid, isAgree.asDriver()))
.flatMapLatest { isPhoneValid, isAgreed -> Driver<String> in
if !isPhoneValid {
return Driver.just("电话号码无效")
}
if !isAgreed {
return Driver.just("请同意用户协议")
}
return Driver.just("")
}
input.codeButtonTrigger.drive { _ in
}.disposed(by: rx.disposeBag)
input.wechatButtonTrigger.drive { _ in
guard WXApi.isWXAppInstalled() else {
@ -55,21 +99,42 @@ class LoginViewModel: ViewModel, ViewModelType {
}.disposed(by: rx.disposeBag)
input.agreementBoxChecked.drive { isSelected in
print("agreementBoxChecked: \(isSelected)")
// input.agreementBoxChecked.drive { isSelected in
// print("agreementBoxChecked: \(isSelected)")
//
// }.disposed(by: rx.disposeBag)
}.disposed(by: rx.disposeBag)
// phone.subscribe { phone in
// print("phone: \(phone)")
//
// }.disposed(by: rx.disposeBag)
phone.subscribe { phone in
print("phone: \(phone)")
}.disposed(by: rx.disposeBag)
return Output.init(number: number,
isPhoneValid: phoneValid, toLogin: toLogin, errorMessage: errorMessage)
}
func requestSMSData() -> Observable<Bool> {
let mobile = phone.value
return provider.sendsms(mobile: mobile)
.trackActivity(loading)
.trackError(error)
.map({ emptyModel in
guard emptyModel != nil else { return false}
return true
})
}
return Output.init()
func isValidPattern(_ string: String, pattern: String) -> Bool {
guard let regex = try? NSRegularExpression(pattern: pattern) else { return false }
let range = NSRange(location: 0, length: string.utf16.count)
return regex.firstMatch(in: string, options: [], range: range) != nil
}
}

@ -29,7 +29,8 @@ class PhoneCodeController: ViewController {
var boxInputView: CodeInputView = {
let boxInputView = CodeInputView.init()
var boxInputView = CodeInputView.init()
boxInputView.resetCodeLength(6, beginEdit: true)
let cellProperty = CRBoxInputCellProperty.init()
cellProperty.cellCursorColor = UIColor.black
cellProperty.cellCursorWidth = 2
@ -39,7 +40,7 @@ class PhoneCodeController: ViewController {
cellProperty.cellFont = UIFont.boldSystemFont(ofSize: 24)
cellProperty.cellTextColor = UIColor.black
boxInputView.boxFlowLayout?.itemSize = CGSize.init(width: 70, height: 70)
boxInputView.boxFlowLayout?.itemSize = CGSize.init(width: 40, height: 40)
boxInputView.customCellProperty = cellProperty
boxInputView.loadAndPrepare(withBeginEdit: true)
@ -66,21 +67,7 @@ class PhoneCodeController: ViewController {
boxInputView.textDidChangeblock = { (text, isFinish) in
if isFinish {
let agress = AlertViewController.init()
agress.titleLabel.text = "阅读并同意以下协议"
agress.cancelButton.setTitle("取消", for: .normal)
agress.confirmButton.setTitle("已阅读并同意", for: .normal)
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .center
paragraphStyle.lineSpacing = 5
agress.detailTextView.addAttributed(attributedes: [("已阅读并同意 ", [NSAttributedString.Key.foregroundColor: UIColor.secondaryText(), NSAttributedString.Key.paragraphStyle: paragraphStyle]),
("《用户协议隐私政策》", [NSAttributedString.Key.foregroundColor: UIColor.primaryText(), NSAttributedString.Key.link: URL.init(string: Configs.App.aggrementUrl) ?? "", NSAttributedString.Key.paragraphStyle: paragraphStyle, NSAttributedString.Key.font: UIFont.systemFont(ofSize: 12, weight: .medium)]),
(" 并授权获取本机号码", [NSAttributedString.Key.foregroundColor: UIColor.secondaryText(), NSAttributedString.Key.paragraphStyle: paragraphStyle])
])
self.present(agress, animated: true)
}
@ -124,6 +111,36 @@ class PhoneCodeController: ViewController {
output.tipsText.drive(self.tipsLabel.rx.text).disposed(by: rx.disposeBag)
output.toAlertView.subscribe { _ in
let agree = AlertViewController.init()
agree.titleLabel.text = "阅读并同意以下协议"
agree.cancelButton.setTitle("取消", for: .normal)
agree.confirmButton.setTitle("已阅读并同意", for: .normal)
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .center
paragraphStyle.lineSpacing = 5
agree.detailTextView.addAttributed(attributedes: [("已阅读并同意 ", [NSAttributedString.Key.foregroundColor: UIColor.secondaryText(), NSAttributedString.Key.paragraphStyle: paragraphStyle]),
("《用户协议隐私政策》", [NSAttributedString.Key.foregroundColor: UIColor.primaryText(), NSAttributedString.Key.link: URL.init(string: Configs.App.aggrementUrl) ?? "", NSAttributedString.Key.paragraphStyle: paragraphStyle, NSAttributedString.Key.font: UIFont.systemFont(ofSize: 12, weight: .medium)]),
(" 并授权获取本机号码", [NSAttributedString.Key.foregroundColor: UIColor.secondaryText(), NSAttributedString.Key.paragraphStyle: paragraphStyle])
])
agree.confirmClosures = {[weak self] in
self?.dismiss(animated: true)
self?.navigator.pop(sender: self, toRoot: true)
}
self.present(agree, animated: true)
// self.navigator.pop(sender: self, toRoot: true)
}.disposed(by: rx.disposeBag)
}

@ -8,6 +8,7 @@
import Foundation
import RxSwift
import RxCocoa
import SVProgressHUD
class PhoneCodeViewModel: ViewModel, ViewModelType {
@ -20,6 +21,7 @@ class PhoneCodeViewModel: ViewModel, ViewModelType {
let countdownText: Observable<String>
let isButtonEnabled: Driver<Bool>
let tipsText: Driver<String>
let toAlertView: PublishRelay<Void>
}
private var remainingSeconds = 59
@ -28,9 +30,15 @@ class PhoneCodeViewModel: ViewModel, ViewModelType {
// let mobileCheckCode = BehaviorRelay<String>.init(value: "")
let toAlertView = PublishRelay<Void>.init()
var phone: String
init(phone: String, provider: IndieMusicAPI) {
super.init(provider: provider)
self.tipsText = "验证码已发送至 \(phone)"
self.phone = phone
super.init(provider: provider)
}
@ -38,10 +46,37 @@ class PhoneCodeViewModel: ViewModel, ViewModelType {
let startImmediately = Observable.just(Void())
let startCountdown = Observable.merge(startImmediately, input.codeButtonTrigger)
.do(onNext: { [weak self] _ in
self?.remainingSeconds = 59
})
let startCountdownAfterRequest = input.codeButtonTrigger
.flatMapLatest { [weak self] _ -> Observable<Bool> in
guard let self = self else { return Observable.just(false) }
return self.requestSMSData()
}
.filter { $0 } //
.map { _ in Void() }
let startCountdown = Observable.merge(startImmediately, startCountdownAfterRequest)
.do(onNext: { [weak self] _ in
self?.remainingSeconds = 59
})
// //
// let timer = startCountdown
// .flatMapLatest { _ in
// Observable<Int>.interval(.seconds(1), scheduler
//
//
// let startCountdown = Observable.merge(startImmediately, input.codeButtonTrigger)
// .do(onNext: { [weak self] _ in
// self?.remainingSeconds = 59
// }).flatMapLatest { [weak self] _ -> Observable<Bool> in
// guard let self = self else { return Observable.just(false) }
// return self.requestSMSData()
// }
//
//
let timer = startCountdown
.flatMapLatest { _ in
@ -71,13 +106,52 @@ class PhoneCodeViewModel: ViewModel, ViewModelType {
input.boxInputText.subscribe(onNext: { (text, isFinish) in
guard let text = text, isFinish == true else { return }
print("text: \(text) isFinish: \(isFinish) ")
self.requestLoginData(mobileCheckCode: text).subscribe(onNext: { login in
UserDefaults.AccountInfo.set(login.token, forKey: .userToken)
UserDefaults.AccountInfo.set(login.id, forKey: .userID)
UserDefaults.AccountInfo.set(login.nickname, forKey: .userName)
self.toAlertView.accept(())
}, onError: { error in
if case HTTPServiceError.errorJudge(let err) = error {
SVProgressHUD.showText(withStatus: err.message)
}
}).disposed(by: self.rx.disposeBag)
}).disposed(by: rx.disposeBag)
return Output(countdownText: countdownText,
isButtonEnabled: isButtonEnabled,
tipsText: tipsText)
tipsText: tipsText,
toAlertView: toAlertView)
}
func requestLoginData(mobileCheckCode: String) -> Observable<Login> {
let mobileCheckCode = mobileCheckCode
let phone = phone
return provider.loginOrRegister(mobile: phone, mobileCheckCode: mobileCheckCode)
.trackActivity(loading)
.trackError(error)
}
func requestSMSData() -> Observable<Bool> {
let mobile = phone
return provider.sendsms(mobile: mobile)
.trackActivity(loading)
.trackError(error)
.map({ emptyModel in
guard emptyModel != nil else { return false}
return true
})
}
}

@ -13,7 +13,11 @@ protocol IndieMusicAPI {
func wechatAccessToken(appid: String, secret: String, code: String, grantType: String) -> Single<EmptyModel>
func login(dic: [String: Any]) -> Single<EmptyModel>
///
func sendsms(mobile: String) -> Single<EmptyModel>
///
func loginOrRegister(mobile: String, mobileCheckCode: String) -> Single<Login>
}

@ -17,7 +17,9 @@ protocol ProductAPIType {
enum APIConfig {
case wechatAccessToken([String: Any])
case login([String: Any])
case login(String, String, [String: Any])
case sendsms(mobil: String, [String: Any])
}
extension APIConfig: TargetType {
@ -26,8 +28,11 @@ extension APIConfig: TargetType {
case .wechatAccessToken:
return "sns/oauth2/access_token"
case .login:
return ""
case .login(let mobile, let mobileCheckCode, _):
return "user/user/appLogin/\(mobile)/\(mobileCheckCode)"
case .sendsms(let mobil, _):
return "user/user/sendsms/\(mobil)"
}
}
@ -35,7 +40,7 @@ extension APIConfig: TargetType {
switch self {
case .wechatAccessToken:
return .get
case .login:
case .sendsms, .login:
return .post
}
}
@ -46,6 +51,9 @@ extension APIConfig: TargetType {
return URLEncoding.default
case .login:
return JSONEncoding.default
case .sendsms:
return JSONEncoding.default
}
}
@ -56,7 +64,7 @@ extension APIConfig: TargetType {
case .wechatAccessToken:
return .requestPlain
case .login(let dic):
case .login(_, _, let dic), .sendsms(_, let dic):
parameters = dic
return .requestParameters(parameters: parameters, encoding: parameterEncoding)
@ -70,7 +78,7 @@ extension APIConfig: TargetType {
switch self {
case .wechatAccessToken:
return URL(string: Configs.App.wechatURL)!
case .login:
case .login, .sendsms:
return URL(string: Configs.App.environmentType.baseUrl)!
}
@ -78,6 +86,9 @@ extension APIConfig: TargetType {
var headers : [String : String]? {
switch self {
case .wechatAccessToken:
return nil
default:
return nil
}

@ -11,16 +11,16 @@ struct ErrorResponse: Codable {
var message: String?
var code: Int?
var errors: [ErrorModel] = []
// var errors: [ErrorModel] = []
var documentationUrl: String?
init() {}
func detail() -> String {
return errors.map { $0.message ?? "" }
.joined(separator: "\n")
}
// func detail() -> String {
// return errors.map { $0.message ?? "" }
// .joined(separator: "\n")
// }
}
struct ErrorModel: Codable {

@ -36,27 +36,47 @@ extension ObservableType where Element == Response {
}
//code0data西
if let errodModel = try? JSONDecoder().decode(ErrorResponse.self, from: response.data) {
print("ssss")
do {
let errodModel = try JSONDecoder().decode(ErrorResponse.self, from: response.data)
print("ssss")
if let msg = errodModel.message, msg == "处理失败,未知异常" {
print("接口错误:\(String(describing: response.request?.url))")
}
print("接口错误:\(String(describing: response.request?.url))")
}
switch errodModel.code {
case 0:
case 200:
return Observable.just(response)
case 410:
return Observable.error(HTTPServiceError.tokenInvalid(err: errodModel))
default:
return Observable.error(HTTPServiceError.errorJudge(err: errodModel))
}
} else if response.statusCode == 410 {
} catch {
print("解码错误: \(error)")
} else if response.statusCode == 200 {
return Observable.just(response)
}
//code0data西
// if let errodModel = try? JSONDecoder().decode(ErrorResponse.self, from: response.data) {
// print("ssss")
// if let msg = errodModel.message, msg == "" {
// print("\(String(describing: response.request?.url))")
// }
//
// switch errodModel.code {
// case 0:
// return Observable.just(response)
// case 410:
// return Observable.error(HTTPServiceError.tokenInvalid(err: errodModel))
// default:
// return Observable.error(HTTPServiceError.errorJudge(err: errodModel))
// }
// } else if response.statusCode == 410 {
//
// } else if response.statusCode == 200 {
// return Observable.just(response)
// }
return Observable.error(MoyaError.jsonMapping(response))
}

@ -21,11 +21,11 @@ enum ApiError: Error {
}
}
var description: String {
switch self {
case .serverError(let response): return response.detail()
}
}
// var description: String {
// switch self {
// case .serverError(let response): return response.detail()
// }
// }
}
class RestApi: IndieMusicAPI {
@ -73,12 +73,18 @@ extension RestApi {
}
func login(dic: [String: Any]) -> Single<EmptyModel> {
return requestObject(.login(dic), with: "data", type: EmptyModel.self)
func loginOrRegister(mobile: String, mobileCheckCode: String) -> Single<Login> {
return requestObject(.login(mobile, mobileCheckCode, ["": ""]), with: "data", type: Login.self)
}
func sendsms(mobile: String) -> Single<EmptyModel> {
let dic = ["": ""]
return requestObject(.sendsms(mobil: mobile, dic), with: "data", type: EmptyModel.self)
}
}

@ -5,7 +5,6 @@
"scale" : "1x"
},
{
"filename" : "launch_bakcground@2x.png",
"idiom" : "universal",
"scale" : "2x"
},

Binary file not shown.

Before

Width:  |  Height:  |  Size: 231 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 491 KiB

After

Width:  |  Height:  |  Size: 289 KiB

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "login_radio_on.svg",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="16" height="16" rx="8" fill="black" fill-opacity="0.95" style="fill:black;fill-opacity:0.95;"/>
<path d="M4.80005 8L7.20005 10.4L11.2 6" stroke="white" style="stroke:white;stroke-opacity:1;" stroke-width="1.5" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 351 B

@ -1,11 +1,4 @@
<svg width="47" height="46" viewBox="0 0 47 46" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_486_2116)">
<path d="M16.1207 15.8585C15.3801 15.8585 14.7831 16.46 14.7831 17.1961C14.7831 17.9323 15.3801 18.5383 16.1207 18.5383C16.8613 18.5383 17.4583 17.9368 17.4583 17.2006C17.4583 16.4645 16.8613 15.8585 16.1207 15.8585ZM26.4537 22.991C25.8387 22.991 25.336 23.4893 25.336 24.1087C25.336 24.7237 25.8342 25.2264 26.4537 25.2264C27.0686 25.2264 27.5713 24.7237 27.5713 24.1087C27.5713 23.4938 27.0686 22.991 26.4537 22.991ZM23.0019 18.5383C23.7425 18.5383 24.3395 17.9368 24.3395 17.2006C24.3395 16.4645 23.738 15.863 23.0019 15.863C22.2612 15.863 21.6642 16.4645 21.6642 17.2006C21.6642 17.9368 22.2612 18.5383 23.0019 18.5383ZM31.876 22.9821C31.261 22.9821 30.7583 23.4803 30.7583 24.0997C30.7583 24.7147 31.2565 25.2174 31.876 25.2174C32.4909 25.2174 32.9937 24.7192 32.9937 24.0997C32.9892 23.4848 32.4909 22.9821 31.876 22.9821Z" fill="#00C800" style="fill:#00C800;fill:color(display-p3 0.0000 0.7843 0.0000);fill-opacity:1;"/>
<path d="M23.482 0.0358887C10.7925 0.0358887 0.5 10.3239 0.5 23.0179C0.5 35.7119 10.7881 46 23.482 46C36.176 46 46.4641 35.7119 46.4641 23.0179C46.4641 10.3239 36.176 0.0358887 23.482 0.0358887ZM19.4288 28.539C18.1989 28.539 17.2114 28.2876 15.977 28.0453L12.5342 29.7734L13.5172 26.8109C11.0484 25.0872 9.57611 22.8653 9.57611 20.1586C9.57611 15.4725 14.0109 11.7783 19.4288 11.7783C24.2721 11.7783 28.5183 14.7273 29.3712 18.6953C29.057 18.6594 28.7428 18.637 28.4241 18.637C23.7469 18.637 20.0482 22.1292 20.0482 26.4338C20.0482 27.152 20.1604 27.8388 20.3534 28.4986C20.0482 28.5255 19.7385 28.539 19.4288 28.539ZM33.9631 31.9908L34.7037 34.4506L32.0016 32.9738C31.0185 33.2207 30.0265 33.4676 29.0435 33.4676C24.3573 33.4676 20.6632 30.2627 20.6632 26.3171C20.6632 22.3805 24.3528 19.1666 29.0435 19.1666C33.4694 19.1666 37.4104 22.3761 37.4104 26.3171C37.4149 28.539 35.9381 30.5051 33.9631 31.9908Z" fill="#45AA73" style="fill:#45AA73;fill:color(display-p3 0.2706 0.6667 0.4510);fill-opacity:1;"/>
</g>
<defs>
<clipPath id="clip0_486_2116">
<rect width="46" height="46" fill="white" style="fill:white;fill:white;fill-opacity:1;" transform="translate(0.5)"/>
</clipPath>
</defs>
<svg width="46" height="46" viewBox="0 0 46 46" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="23" cy="23" r="23" fill="#45AA73" style="fill:#45AA73;fill:color(display-p3 0.2706 0.6667 0.4510);fill-opacity:1;"/>
<path d="M27.9545 18.8286C28.2766 18.8286 28.5944 18.8522 28.909 18.8864C28.0518 14.9359 23.7828 12 18.9104 12C13.4627 12 9 15.6746 9 20.3403C9 23.033 10.485 25.2446 12.9657 26.9599L11.9746 29.9106L15.4387 28.1911C16.6791 28.4335 17.6731 28.6835 18.9104 28.6835C19.2213 28.6835 19.529 28.6684 19.8353 28.6441C19.6415 27.989 19.529 27.302 19.529 26.5898C19.5291 22.3057 23.247 18.8286 27.9545 18.8286ZM22.6268 16.1694C23.3731 16.1694 23.8671 16.6558 23.8671 17.3938C23.8671 18.1287 23.3731 18.6211 22.6268 18.6211C21.8835 18.6211 21.1387 18.1287 21.1387 17.3938C21.1387 16.6558 21.8836 16.1694 22.6268 16.1694ZM15.6911 18.6211C14.9478 18.6211 14.1984 18.1286 14.1984 17.3937C14.1984 16.6557 14.9478 16.1694 15.6911 16.1694C16.4335 16.1694 16.9283 16.6557 16.9283 17.3937C16.9283 18.1287 16.4335 18.6211 15.6911 18.6211ZM37 26.472C37 22.5511 33.0351 19.3553 28.5822 19.3553C23.8671 19.3553 20.1538 22.5511 20.1538 26.472C20.1538 30.3985 23.8671 33.5875 28.5822 33.5875C29.5687 33.5875 30.5643 33.3427 31.5553 33.0966L34.2739 34.5694L33.5283 32.1192C35.5181 30.6417 37 28.6835 37 26.472ZM25.8492 25.2446C25.356 25.2446 24.8582 24.7589 24.8582 24.2634C24.8582 23.7747 25.356 23.283 25.8492 23.283C26.5986 23.283 27.0895 23.7747 27.0895 24.2634C27.0895 24.7589 26.5986 25.2446 25.8492 25.2446ZM31.3 25.2446C30.8106 25.2446 30.3158 24.7589 30.3158 24.2634C30.3158 23.7747 30.8106 23.283 31.3 23.283C32.0433 23.283 32.5403 23.7747 32.5403 24.2634C32.5403 24.7589 32.0433 25.2446 31.3 25.2446Z" fill="white" style="fill:white;fill-opacity:1;"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "nav_close_btn.svg",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

@ -0,0 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 5L19 19" stroke="#17171A" style="stroke:#17171A;stroke:color(display-p3 0.0902 0.0902 0.1020);stroke-opacity:1;" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5 19L19 5" stroke="#17171A" style="stroke:#17171A;stroke:color(display-p3 0.0902 0.0902 0.1020);stroke-opacity:1;" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 489 B

@ -0,0 +1,81 @@
//
// ActivityIndicator.swift
// RxExample
//
// Created by Krunoslav Zaher on 10/18/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
#if !RX_NO_MODULE
import RxSwift
import RxCocoa
#endif
private struct ActivityToken<E>: ObservableConvertibleType, Disposable {
private let _source: Observable<E>
private let _dispose: Cancelable
init(source: Observable<E>, disposeAction: @escaping () -> Void) {
_source = source
_dispose = Disposables.create(with: disposeAction)
}
func dispose() {
_dispose.dispose()
}
func asObservable() -> Observable<E> {
return _source
}
}
/**
Enables monitoring of sequence computation.
If there is at least one sequence computation in progress, `true` will be sent.
When all activities complete `false` will be sent.
*/
public class ActivityIndicator: SharedSequenceConvertibleType {
public typealias Element = Bool
public typealias SharingStrategy = DriverSharingStrategy
private let _lock = NSRecursiveLock()
private let _relay = BehaviorRelay(value: 0)
private let _loading: SharedSequence<SharingStrategy, Bool>
public init() {
_loading = _relay.asDriver()
.map { $0 > 0 }
.distinctUntilChanged()
}
fileprivate func trackActivityOfObservable<Source: ObservableConvertibleType>(_ source: Source) -> Observable<Source.Element> {
return Observable.using({ () -> ActivityToken<Source.Element> in
self.increment()
return ActivityToken(source: source.asObservable(), disposeAction: self.decrement)
}, observableFactory: { value in
return value.asObservable()
})
}
private func increment() {
_lock.lock()
_relay.accept(_relay.value + 1)
_lock.unlock()
}
private func decrement() {
_lock.lock()
_relay.accept(_relay.value - 1)
_lock.unlock()
}
public func asSharedSequence() -> SharedSequence<SharingStrategy, Element> {
return _loading
}
}
extension ObservableConvertibleType {
public func trackActivity(_ activityIndicator: ActivityIndicator) -> Observable<Element> {
return activityIndicator.trackActivityOfObservable(self)
}
}

@ -20,9 +20,10 @@ target 'IndieMusic' do
pod 'SwiftDate'
pod 'XHLaunchAd'
pod 'DZNEmptyDataSet'
pod 'SVProgressHUD', :git => 'https://github.com/SVProgressHUD/SVProgressHUD.git'
pod 'SVProgressHUD',:git => 'https://github.com/Fidetro/SVProgressHUD.git'
pod 'AttributedString'
pod 'ESTMusicIndicator'
pod 'IQKeyboardManagerSwift'
pod 'NSObject+Rx'

Loading…
Cancel
Save