@ -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?
|
||||||
|
}
|
@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Before Width: | Height: | Size: 231 KiB |
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
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 351 B |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 1.7 KiB |
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
Before Width: | Height: | Size: 477 B After Width: | Height: | Size: 477 B |
@ -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
|
||||||
|
}
|
||||||
|
}
|
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)
|
||||||
|
}
|
||||||
|
}
|