Skip to content
Snippets Groups Projects
Commit c4965691 authored by Bruno Muniz's avatar Bruno Muniz :apple:
Browse files

Finished input search logic, missing phone ui component and qr

parent 4ca61015
No related branches found
No related tags found
2 merge requests!54Releasing 1.1.4,!50Search UI 2.0
Showing
with 232 additions and 206 deletions
......@@ -6,27 +6,6 @@ import XXModels
import DrawerFeature
import DependencyInjection
enum SearchSection {
case stranger
case connections
}
enum SearchItem: Equatable, Hashable {
case stranger(Contact)
case connection(Contact)
}
class SearchTableViewDiffableDataSource: UITableViewDiffableDataSource<SearchSection, SearchItem> {
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
switch snapshot().sectionIdentifiers[section] {
case .stranger:
return ""
case .connections:
return "CONNECTIONS"
}
}
}
public final class SearchContainerController: UIViewController {
@Dependency var coordinator: SearchCoordinating
@Dependency var statusBarController: StatusBarStyleControlling
......@@ -89,6 +68,7 @@ public final class SearchContainerController: UIViewController {
screenView.scrollView.setContentOffset(point, animated: true)
} else {
screenView.scrollView.setContentOffset(.zero, animated: true)
leftController.viewModel.didSelectItem($0)
}
}.store(in: &cancellables)
......
......@@ -19,11 +19,11 @@ final class SearchLeftController: UIViewController {
lazy private var screenView = SearchLeftView()
private let viewModel = SearchLeftViewModel()
private var cancellables = Set<AnyCancellable>()
private var dataSource: SearchDiffableDataSource!
private(set) var viewModel = SearchLeftViewModel()
private var drawerCancellables = Set<AnyCancellable>()
private let adrpURLString = "https://links.xx.network/adrp"
private var dataSource: SearchTableViewDiffableDataSource!
override func loadView() {
view = screenView
......@@ -42,7 +42,7 @@ final class SearchLeftController: UIViewController {
screenView.tableView.dataSource = dataSource
screenView.tableView.delegate = self
dataSource = SearchTableViewDiffableDataSource(
dataSource = SearchDiffableDataSource(
tableView: screenView.tableView
) { tableView, indexPath, item in
let contact: Contact
......@@ -75,6 +75,13 @@ final class SearchLeftController: UIViewController {
.sink { [hud] in hud.update(with: $0) }
.store(in: &cancellables)
viewModel.statePublisher
.map(\.item)
.removeDuplicates()
.receive(on: DispatchQueue.main)
.sink { [unowned self] in screenView.updateUIForItem(item: $0) }
.store(in: &cancellables)
viewModel.statePublisher
.compactMap(\.snapshot)
.receive(on: DispatchQueue.main)
......
import UIKit
import Permissions
import DependencyInjection
enum SearchQRStatus: Equatable {
case reading
case processing
case success
case failed(SearchQRError)
}
enum SearchQRError: Equatable {
case requestOpened
case unknown(String)
case cameraPermission
case alreadyFriends(String)
}
final class SearchRightController: UIViewController {
@Dependency private var permissions: PermissionHandling
lazy private var screenView = SearchRightView()
private let camera = Camera()
// private let camera = Camera()
private var status: SearchQRStatus?
override func loadView() {
......@@ -54,111 +37,20 @@ final class SearchRightController: UIViewController {
}
private func startCamera() {
permissions.requestCamera { [weak self] granted in
guard let self = self else { return }
if granted {
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
self.camera.start()
}
} else {
DispatchQueue.main.async {
self.status = .failed(.cameraPermission)
// self.screenView.update(with: .failed(.cameraPermission))
}
}
}
}
}
import Combine
import AVFoundation
protocol CameraType {
func start()
func stop()
var previewLayer: CALayer { get }
var dataPublisher: AnyPublisher<Data, Never> { get }
}
final class Camera: NSObject, CameraType {
var dataPublisher: AnyPublisher<Data, Never> {
dataSubject
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
}
lazy var previewLayer: CALayer = {
let layer = AVCaptureVideoPreviewLayer(session: session)
layer.videoGravity = .resizeAspectFill
return layer
}()
private let session = AVCaptureSession()
private let metadataOutput = AVCaptureMetadataOutput()
private let dataSubject = PassthroughSubject<Data, Never>()
override init() {
super.init()
setupCameraDevice()
}
func start() {
guard session.isRunning == false else { return }
session.startRunning()
}
func stop() {
guard session.isRunning == true else { return }
session.stopRunning()
}
private func setupCameraDevice() {
if let captureDevice = AVCaptureDevice.default(for: .video),
let input = try? AVCaptureDeviceInput(device: captureDevice) {
if session.canAddInput(input) && session.canAddOutput(metadataOutput) {
session.addInput(input)
session.addOutput(metadataOutput)
}
metadataOutput.setMetadataObjectsDelegate(self, queue: .main)
metadataOutput.metadataObjectTypes = [.qr]
}
}
}
extension Camera: AVCaptureMetadataOutputObjectsDelegate {
func metadataOutput(
_ output: AVCaptureMetadataOutput,
didOutput metadataObjects: [AVMetadataObject],
from connection: AVCaptureConnection
) {
guard let object = metadataObjects.first as? AVMetadataMachineReadableCodeObject,
let data = object.stringValue?.data(using: .nonLossyASCII), object.type == .qr else { return }
dataSubject.send(data)
}
}
final class MockCamera: NSObject, CameraType {
private let dataSubject = PassthroughSubject<Data, Never>()
func start() {
DispatchQueue.global().asyncAfter(deadline: .now() + 2) { [weak self] in
self?.dataSubject.send("###".data(using: .utf8)!)
}
}
func stop() {}
var previewLayer: CALayer { CALayer() }
var dataPublisher: AnyPublisher<Data, Never> {
dataSubject
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
// permissions.requestCamera { [weak self] granted in
// guard let self = self else { return }
//
// if granted {
// DispatchQueue.main.async { [weak self] in
// guard let self = self else { return }
// self.camera.start()
// }
// } else {
// DispatchQueue.main.async {
// self.status = .failed(.cameraPermission)
//// self.screenView.update(with: .failed(.cameraPermission))
// }
// }
// }
}
}
import UIKit
import XXModels
enum SearchSection {
case stranger
case connections
}
enum SearchItem: Equatable, Hashable {
case stranger(Contact)
case connection(Contact)
}
class SearchDiffableDataSource: UITableViewDiffableDataSource<SearchSection, SearchItem> {
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
switch snapshot().sectionIdentifiers[section] {
case .stranger:
return ""
case .connections:
return "CONNECTIONS"
}
}
}
......@@ -10,6 +10,7 @@ typealias SearchSnapshot = NSDiffableDataSourceSnapshot<SearchSection, SearchIte
struct SearchLeftViewState {
var input = ""
var snapshot: SearchSnapshot?
var item: SearchSegmentedControl.Item = .username
}
final class SearchLeftViewModel {
......@@ -35,11 +36,17 @@ final class SearchLeftViewModel {
stateSubject.value.input = string
}
func didSelectItem(_ item: SearchSegmentedControl.Item) {
stateSubject.value.item = item
}
func didStartSearching() {
hudSubject.send(.on(nil))
let prefix = stateSubject.value.item.written.first!.uppercased()
do {
try session.search(fact: "U\(stateSubject.value.input)") { [weak self] in
try session.search(fact: "\(prefix)\(stateSubject.value.input)") { [weak self] in
guard let self = self else { return }
switch $0 {
......
import Permissions
import DependencyInjection
enum SearchQRStatus: Equatable {
case reading
case processing
case success
case failed(SearchQRError)
}
enum SearchQRError: Equatable {
case requestOpened
case unknown(String)
case cameraPermission
case alreadyFriends(String)
}
final class SearchRightViewModel {
@Dependency private var permissions: PermissionHandling
}
//
//
//import Combine
//import AVFoundation
//
//protocol CameraType {
// func start()
// func stop()
//
// var previewLayer: CALayer { get }
// var dataPublisher: AnyPublisher<Data, Never> { get }
//}
//
//final class Camera: NSObject, CameraType {
// var dataPublisher: AnyPublisher<Data, Never> {
// dataSubject
// .receive(on: DispatchQueue.main)
// .eraseToAnyPublisher()
// }
//
// lazy var previewLayer: CALayer = {
// let layer = AVCaptureVideoPreviewLayer(session: session)
// layer.videoGravity = .resizeAspectFill
// return layer
// }()
//
// private let session = AVCaptureSession()
// private let metadataOutput = AVCaptureMetadataOutput()
// private let dataSubject = PassthroughSubject<Data, Never>()
//
// override init() {
// super.init()
// setupCameraDevice()
// }
//
// func start() {
// guard session.isRunning == false else { return }
// session.startRunning()
// }
//
// func stop() {
// guard session.isRunning == true else { return }
// session.stopRunning()
// }
//
// private func setupCameraDevice() {
// if let captureDevice = AVCaptureDevice.default(for: .video),
// let input = try? AVCaptureDeviceInput(device: captureDevice) {
//
// if session.canAddInput(input) && session.canAddOutput(metadataOutput) {
// session.addInput(input)
// session.addOutput(metadataOutput)
// }
//
// metadataOutput.setMetadataObjectsDelegate(self, queue: .main)
// metadataOutput.metadataObjectTypes = [.qr]
// }
// }
//}
//
//extension Camera: AVCaptureMetadataOutputObjectsDelegate {
// func metadataOutput(
// _ output: AVCaptureMetadataOutput,
// didOutput metadataObjects: [AVMetadataObject],
// from connection: AVCaptureConnection
// ) {
// guard let object = metadataObjects.first as? AVMetadataMachineReadableCodeObject,
// let data = object.stringValue?.data(using: .nonLossyASCII), object.type == .qr else { return }
// dataSubject.send(data)
// }
//}
//
//final class MockCamera: NSObject, CameraType {
// private let dataSubject = PassthroughSubject<Data, Never>()
//
// func start() {
// DispatchQueue.global().asyncAfter(deadline: .now() + 2) { [weak self] in
// self?.dataSubject.send("###".data(using: .utf8)!)
// }
// }
//
// func stop() {}
//
// var previewLayer: CALayer { CALayer() }
//
// var dataPublisher: AnyPublisher<Data, Never> {
// dataSubject
// .receive(on: DispatchQueue.main)
// .eraseToAnyPublisher()
// }
//}
import UIKit
import Shared
final class SearchLeftEmptyView: UIView {
let titleLabel = UILabel()
init() {
super.init(frame: .zero)
titleLabel.numberOfLines = 0
titleLabel.textAlignment = .center
titleLabel.font = Fonts.Mulish.regular.font(size: 15.0)
titleLabel.textColor = Asset.neutralSecondaryAlternative.color
addSubview(titleLabel)
titleLabel.snp.makeConstraints {
$0.center.equalToSuperview()
$0.left.equalToSuperview().offset(20)
$0.right.equalToSuperview().offset(-20)
}
}
required init?(coder: NSCoder) { nil }
}
......@@ -16,7 +16,7 @@ final class SearchLeftPlaceholderView: UIView {
super.init(frame: .zero)
let attrString = NSMutableAttributedString(
string: Localized.Ud.Search.Username.Placeholder.title,
string: Localized.Ud.Search.Placeholder.title,
attributes: [
.foregroundColor: Asset.neutralDark.color,
.font: Fonts.Mulish.bold.font(size: 32.0)
......@@ -36,7 +36,7 @@ final class SearchLeftPlaceholderView: UIView {
paragraph.lineHeightMultiple = 1.3
subtitleWithInfo.setup(
text: Localized.Ud.Search.Username.Placeholder.subtitle,
text: Localized.Ud.Search.Placeholder.subtitle,
attributes: [
.paragraphStyle: paragraph,
.foregroundColor: Asset.neutralBody.color,
......
......@@ -4,36 +4,11 @@ import Shared
final class SearchLeftView: UIView {
let tableView = UITableView()
let inputField = SearchComponent()
let emptyView: UIView = {
let view = UIView()
let label = UILabel()
label.numberOfLines = 0
label.textAlignment = .center
label.font = Fonts.Mulish.regular.font(size: 15.0)
label.text = Localized.Ud.Search.Username.Empty.title
label.textColor = Asset.neutralSecondaryAlternative.color
view.addSubview(label)
label.snp.makeConstraints {
$0.center.equalToSuperview()
$0.left.equalToSuperview().offset(20)
$0.right.equalToSuperview().offset(-20)
}
return view
}()
let emptyView = SearchLeftEmptyView()
let placeholderView = SearchLeftPlaceholderView()
init() {
super.init(frame: .zero)
inputField.set(
placeholder: Localized.Ud.Search.Username.input,
imageAtRight: nil
)
emptyView.isHidden = true
addSubview(tableView)
......@@ -46,6 +21,14 @@ final class SearchLeftView: UIView {
required init?(coder: NSCoder) { nil }
func updateUIForItem(item: SearchSegmentedControl.Item) {
let emptyTitle = Localized.Ud.Search.empty(item.written)
emptyView.titleLabel.text = emptyTitle
let inputFieldTitle = Localized.Ud.Search.input(item.written)
inputField.set(placeholder: inputFieldTitle, imageAtRight: nil)
}
private func setupConstraints() {
inputField.snp.makeConstraints {
$0.top.equalToSuperview().offset(20)
......
......@@ -9,6 +9,15 @@ final class SearchSegmentedControl: UIView {
case email
case phone
case qr
var written: String {
switch self {
case .qr: return "qr"
case .email: return "email"
case .phone: return "phone number"
case .username: return "username"
}
}
}
private let trackView = UIView()
......
......@@ -1238,27 +1238,19 @@ public enum Localized {
public static let title = Localized.tr("Localizable", "ud.requestDrawer.title")
}
public enum Search {
public enum Email {
/// Search by email
public static let input = Localized.tr("Localizable", "ud.search.email.input")
/// There are no users with that %@.
public static func empty(_ p1: Any) -> String {
return Localized.tr("Localizable", "ud.search.empty", String(describing: p1))
}
public enum Phone {
/// Search by phone number
public static let input = Localized.tr("Localizable", "ud.search.phone.input")
/// Search by %@
public static func input(_ p1: Any) -> String {
return Localized.tr("Localizable", "ud.search.input", String(describing: p1))
}
public enum Username {
/// Search by username
public static let input = Localized.tr("Localizable", "ud.search.username.input")
public enum Empty {
/// There are no users with that username
public static let title = Localized.tr("Localizable", "ud.search.username.empty.title")
}
public enum Placeholder {
/// Your searches are anonymous. Search information is never linked to your account or personally identifiable.
public static let subtitle = Localized.tr("Localizable", "ud.search.username.placeholder.subtitle")
/// Search for #friends# anonymously, add them to your #connections# to start a completely private messaging channel.
public static let title = Localized.tr("Localizable", "ud.search.username.placeholder.title")
}
public enum Placeholder {
/// Your searches are anonymous. Search information is never linked to your account or personally identifiable.
public static let subtitle = Localized.tr("Localizable", "ud.search.placeholder.subtitle")
/// Search for #friends# anonymously, add them to your #connections# to start a completely private messaging channel.
public static let title = Localized.tr("Localizable", "ud.search.placeholder.title")
}
}
public enum Tab {
......
......@@ -982,19 +982,15 @@
= "Edit your new contact’s nickname so you know who they are.";
"ud.nicknameDrawer.save"
= "Save";
"ud.search.username.input"
= "Search by username";
"ud.search.email.input"
= "Search by email";
"ud.search.phone.input"
= "Search by phone number";
"ud.search.username.empty.title"
= "There are no users with that username";
"ud.search.input"
= "Search by %@";
"ud.search.empty"
= "There are no users with that %@.";
"ud.search.username.placeholder.title"
"ud.search.placeholder.title"
= "Search for #friends# anonymously, add them to your #connections# to start a completely private messaging channel.";
"ud.search.username.placeholder.subtitle"
"ud.search.placeholder.subtitle"
= "Your searches are anonymous. Search information is never linked to your account or personally identifiable.";
// LaunchFeature
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment