@ -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)
|
||||
}
|
||||
}
|