提交
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// ScheduleConference+Injection.swift
|
||||
// TUIRoomKit
|
||||
//
|
||||
// Created by aby on 2024/6/27.
|
||||
//
|
||||
|
||||
import Factory
|
||||
|
||||
extension Container {
|
||||
var scheduleStore: Factory<ScheduleConferenceStore> {
|
||||
self {
|
||||
ScheduleConferenceStoreProvider()
|
||||
}
|
||||
.shared
|
||||
}
|
||||
var modifyScheduleStore: Factory<ScheduleConferenceStore> {
|
||||
self {
|
||||
ScheduleConferenceStoreProvider()
|
||||
}
|
||||
.shared
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
//
|
||||
// ScheduleConferenceStore.swift
|
||||
// TUIRoomKit
|
||||
//
|
||||
// Created by aby on 2024/6/27.
|
||||
//
|
||||
|
||||
import Combine
|
||||
|
||||
protocol ScheduleConferenceStore {
|
||||
func fetchAttendees(cursor: String)
|
||||
func update(conference info: ConferenceInfo)
|
||||
func fetchRoomInfo(roomId: String)
|
||||
func select<Value:Equatable>(_ selector: Selector<ConferenceInfo, Value>) -> AnyPublisher<Value, Never>
|
||||
var conferenceInfo: ConferenceInfo { get }
|
||||
}
|
||||
|
||||
class ScheduleConferenceStoreProvider {
|
||||
static let updateConferenceInfo = ActionTemplate(id: "updateConferenceInfo", payloadType: ConferenceInfo.self)
|
||||
static let fetchAttendeeList = ActionTemplate(id: "fetchAttendeeList", payloadType: (String, String, Int).self)
|
||||
static let updateAttendeeList = ActionTemplate(id: "updateAttendeeList", payloadType: ([UserInfo], String, UInt).self)
|
||||
static let updateBasicInfo = ActionTemplate(id: "updateBasicInfo", payloadType: RoomInfo.self)
|
||||
static let fetchRoomInfo = ActionTemplate(id: ".fetchRoomInfo", payloadType: String.self)
|
||||
static let attendeesPerFetch = 20
|
||||
|
||||
// MARK: - private property.
|
||||
private lazy var store: Store<ConferenceInfo, ServiceCenter> = {
|
||||
let store = Store.init(initialState: ConferenceInfo(), environment: ServiceCenter(), reducers: [self.conferenceReducer])
|
||||
store.register(effects: scheduleConferenceEffects())
|
||||
return store
|
||||
}()
|
||||
|
||||
private let conferenceReducer = Reducer<ConferenceInfo>(
|
||||
ReduceOn(updateConferenceInfo, reduce: { state, action in
|
||||
state = action.payload
|
||||
}),
|
||||
ReduceOn(updateAttendeeList, reduce: { state, action in
|
||||
state.attendeeListResult.attendeeList.append(contentsOf: action.payload.0)
|
||||
state.attendeeListResult.fetchCursor = action.payload.1
|
||||
state.attendeeListResult.totalCount = action.payload.2
|
||||
}),
|
||||
ReduceOn(updateBasicInfo, reduce: { state, action in
|
||||
state.basicInfo = action.payload
|
||||
})
|
||||
)
|
||||
|
||||
deinit {
|
||||
store.unregister(reducer: conferenceReducer)
|
||||
store.unregisterEffects(withId: scheduleConferenceEffects.id)
|
||||
}
|
||||
}
|
||||
|
||||
extension ScheduleConferenceStoreProvider: ScheduleConferenceStore {
|
||||
func fetchAttendees(cursor: String) {
|
||||
let conferenceId = conferenceInfo.basicInfo.roomId
|
||||
store.dispatch(action: ScheduleConferenceStoreProvider.fetchAttendeeList(payload: (conferenceId, cursor, ScheduleConferenceStoreProvider.attendeesPerFetch)))
|
||||
}
|
||||
|
||||
func update(conference info: ConferenceInfo) {
|
||||
store.dispatch(action: ScheduleConferenceStoreProvider.updateConferenceInfo(payload: info))
|
||||
}
|
||||
|
||||
func fetchRoomInfo(roomId: String) {
|
||||
store.dispatch(action: ScheduleConferenceStoreProvider.fetchRoomInfo(payload: roomId))
|
||||
}
|
||||
|
||||
func select<Value>(_ selector: Selector<ConferenceInfo, Value>) -> AnyPublisher<Value, Never> where Value : Equatable {
|
||||
return store.select(selector)
|
||||
}
|
||||
|
||||
var conferenceInfo: ConferenceInfo {
|
||||
return store.state
|
||||
}
|
||||
}
|
||||
|
||||
class scheduleConferenceEffects: Effects {
|
||||
typealias Environment = ServiceCenter
|
||||
|
||||
let fetchAttendeeList = Effect<Environment>.dispatchingOne { actions, environment in
|
||||
actions.wasCreated(from: ScheduleConferenceStoreProvider.fetchAttendeeList)
|
||||
.flatMap { action in
|
||||
environment.conferenceListService.fetchAttendeeList(conferenceId: action.payload.0,
|
||||
cursor: action.payload.1,
|
||||
count: action.payload.2)
|
||||
.map { (userInfoList, cursor, totalCount) in
|
||||
ScheduleConferenceStoreProvider.updateAttendeeList(payload: (userInfoList, cursor, totalCount))
|
||||
}
|
||||
.catch { error -> Just<Action> in
|
||||
Just(ErrorActions.throwError(payload: error))
|
||||
}
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
let fetchRoomInfo = Effect<Environment>.dispatchingOne { actions, environment in
|
||||
actions.wasCreated(from: ScheduleConferenceStoreProvider.fetchRoomInfo)
|
||||
.flatMap { action in
|
||||
environment.conferenceListService.fetchConferenceInfo(roomId: action.payload)
|
||||
.map { conferenceInfo in
|
||||
ScheduleConferenceStoreProvider.updateBasicInfo(payload: conferenceInfo.basicInfo)
|
||||
}
|
||||
.catch { error -> Just<Action> in
|
||||
Just(ErrorActions.throwError(payload: error))
|
||||
}
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,357 @@
|
||||
//
|
||||
// ScheduleConferenceDataHelper.swift
|
||||
// TUIRoomKit
|
||||
//
|
||||
// Created by janejntang on 2024/6/19.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import RTCRoomEngine
|
||||
|
||||
class ScheduleConferenceDataHelper {
|
||||
open class func generateScheduleConferenceData(route: Route,
|
||||
store: ScheduleConferenceStore,
|
||||
operation: ConferenceStore,
|
||||
viewController: ContactViewSelectDelegate? = nil) -> [Int: [CellConfigItem]] {
|
||||
var menus: [Int:[CellConfigItem]] = [:]
|
||||
menus[0] = getFirstSectionMenus(route: route, store: store, viewController: viewController)
|
||||
menus[1] = getSecondSectionMenus(route: route, store: store)
|
||||
menus[2] = getThirdSectionMenus(route: route, store: store)
|
||||
menus[3] = getFourthSectionMenus(route: route, store: store, operation: operation)
|
||||
return menus
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - private function.
|
||||
extension ScheduleConferenceDataHelper {
|
||||
|
||||
class func getFirstSectionMenus(route: Route, store: ScheduleConferenceStore, viewController: ContactViewSelectDelegate?) -> [CellConfigItem] {
|
||||
var array: [CellConfigItem] = []
|
||||
array.append(getConferenceNameItem(route: route, store: store))
|
||||
array.append(getConferenceTypeItem(route: route, store: store))
|
||||
array.append(getStartTimeItem(route: route, store: store))
|
||||
array.append(getDurationTimeItem(route: route, store: store))
|
||||
array.append(getTimeZoneItem(route: route, store: store))
|
||||
array.append(getParticipatingMembersItem(route: route, store: store, viewController: viewController))
|
||||
return array
|
||||
}
|
||||
|
||||
class func getSecondSectionMenus(route: Route, store: ScheduleConferenceStore) -> [CellConfigItem] {
|
||||
var array: [CellConfigItem] = []
|
||||
array.append(getEncryptRoomItem(store: store))
|
||||
if store.conferenceInfo.basicInfo.isPasswordEnabled {
|
||||
array.append(getRoomPasswordItem(store: store))
|
||||
}
|
||||
return array
|
||||
}
|
||||
|
||||
class func getThirdSectionMenus(route: Route, store: ScheduleConferenceStore) -> [CellConfigItem] {
|
||||
var array: [CellConfigItem] = []
|
||||
array.append(getMuteAllItem(route: route, store: store))
|
||||
array.append(getFreezeVideoItem(route: route, store: store))
|
||||
return array
|
||||
}
|
||||
|
||||
class func getFourthSectionMenus(route: Route, store: ScheduleConferenceStore, operation: ConferenceStore) -> [CellConfigItem] {
|
||||
return [getbookItem(route: route, store: store, operation: operation)]
|
||||
}
|
||||
|
||||
class func getConferenceNameItem(route: Route, store: ScheduleConferenceStore) -> TextFieldItem{
|
||||
var conferenceNameItem = TextFieldItem(title: .roomNameText, content: store.conferenceInfo.basicInfo.name)
|
||||
conferenceNameItem.saveTextClosure = { text in
|
||||
var conferenceInfo = store.conferenceInfo
|
||||
conferenceInfo.basicInfo.name = text
|
||||
store.update(conference: conferenceInfo)
|
||||
}
|
||||
conferenceNameItem.bindStateClosure = { cell, cancellableSet in
|
||||
let getBasicInfo = Selector(keyPath: \ConferenceInfo.basicInfo)
|
||||
let selector = Selector.with(getBasicInfo, keyPath: \RoomInfo.name)
|
||||
store.select(selector)
|
||||
.receive(on: RunLoop.main)
|
||||
.sink { [weak cell] text in
|
||||
if let cell = cell as? TextFieldCell {
|
||||
cell.textField.text = text
|
||||
}
|
||||
}
|
||||
.store(in: &cancellableSet)
|
||||
}
|
||||
return conferenceNameItem
|
||||
}
|
||||
|
||||
class func getConferenceTypeItem(route: Route, store: ScheduleConferenceStore) -> ListItem {
|
||||
let enableSeatControl = store.conferenceInfo.basicInfo.isSeatEnabled
|
||||
var conferenceTypeItem = ListItem(title: .roomTypeText, content: enableSeatControl ? .onStageSpeechRoomText: .freeSpeechRoomText)
|
||||
conferenceTypeItem.showButton = true
|
||||
conferenceTypeItem.selectClosure = {
|
||||
let view = RoomTypeView()
|
||||
view.dismissAction = {
|
||||
route.dismiss(animated: true)
|
||||
}
|
||||
route.present(route: .popup(view: view))
|
||||
}
|
||||
conferenceTypeItem.bindStateClosure = { cell, cancellableSet in
|
||||
let getBasicInfo = Selector(keyPath: \ConferenceInfo.basicInfo)
|
||||
let selector = Selector.with(getBasicInfo, keyPath: \RoomInfo.isSeatEnabled)
|
||||
store.select(selector)
|
||||
.receive(on: RunLoop.main)
|
||||
.sink { [weak cell] enableSeatControl in
|
||||
if let cell = cell as? ScheduleTabCell {
|
||||
cell.messageLabel.text = enableSeatControl ? .onStageSpeechRoomText: .freeSpeechRoomText
|
||||
}
|
||||
}
|
||||
.store(in: &cancellableSet)
|
||||
}
|
||||
return conferenceTypeItem
|
||||
}
|
||||
|
||||
class func getStartTimeItem(route: Route, store: ScheduleConferenceStore) -> ListItem {
|
||||
let startTime = TimeInterval(store.conferenceInfo.scheduleStartTime)
|
||||
var startTimeItem = ListItem(title: .startingTimeText, content: getTimeIntervalString(startTime, timeZone: store.conferenceInfo.timeZone))
|
||||
startTimeItem.showButton = true
|
||||
startTimeItem.selectClosure = {
|
||||
let view = TimePickerView()
|
||||
view.pickerDate = Date(timeIntervalSince1970: TimeInterval(store.conferenceInfo.scheduleStartTime))
|
||||
view.dismissAction = {
|
||||
route.dismiss(animated: true)
|
||||
}
|
||||
route.present(route: .popup(view: view))
|
||||
}
|
||||
startTimeItem.bindStateClosure = { cell, cancellableSet in
|
||||
let selector = Selector(keyPath: \ConferenceInfo.scheduleStartTime)
|
||||
store.select(selector)
|
||||
.receive(on: RunLoop.main)
|
||||
.sink { [weak cell] startTime in
|
||||
if let cell = cell as? ScheduleTabCell {
|
||||
cell.messageLabel.text = getTimeIntervalString(TimeInterval(startTime), timeZone: store.conferenceInfo.timeZone)
|
||||
}
|
||||
}
|
||||
.store(in: &cancellableSet)
|
||||
}
|
||||
return startTimeItem
|
||||
}
|
||||
|
||||
class func getDurationTimeItem(route: Route, store: ScheduleConferenceStore) -> ListItem {
|
||||
var durationTimeItem = ListItem(title: .roomDurationText, content: getDurationTimeString(store.conferenceInfo.durationTime))
|
||||
durationTimeItem.showButton = true
|
||||
durationTimeItem.selectClosure = {
|
||||
let view = DurationPickerView()
|
||||
view.dismissAction = {
|
||||
route.dismiss(animated: true)
|
||||
}
|
||||
route.present(route: .popup(view: view))
|
||||
}
|
||||
durationTimeItem.bindStateClosure = { cell, cancellableSet in
|
||||
let selector = Selector(keyPath: \ConferenceInfo.durationTime)
|
||||
store.select(selector)
|
||||
.receive(on: RunLoop.main)
|
||||
.sink { durationTime in
|
||||
if let cell = cell as? ScheduleTabCell {
|
||||
cell.messageLabel.text = getDurationTimeString(durationTime)
|
||||
}
|
||||
}
|
||||
.store(in: &cancellableSet)
|
||||
}
|
||||
return durationTimeItem
|
||||
}
|
||||
|
||||
class func getTimeZoneItem(route: Route, store: ScheduleConferenceStore) -> ListItem {
|
||||
var timeZoneItem = ListItem(title: .rimeZoneText, content: store.conferenceInfo.timeZone.getTimeZoneName())
|
||||
timeZoneItem.showButton = true
|
||||
timeZoneItem.buttonIcon = "room_right_arrow1"
|
||||
timeZoneItem.selectClosure = {
|
||||
route.pushTo(route: .timeZone)
|
||||
}
|
||||
timeZoneItem.bindStateClosure = { cell, cancellableSet in
|
||||
let selector = Selector(keyPath: \ConferenceInfo.timeZone)
|
||||
store.select(selector)
|
||||
.receive(on: RunLoop.main)
|
||||
.sink { timeZone in
|
||||
if let cell = cell as? ScheduleTabCell {
|
||||
cell.messageLabel.text = timeZone.getTimeZoneName()
|
||||
}
|
||||
}
|
||||
.store(in: &cancellableSet)
|
||||
}
|
||||
return timeZoneItem
|
||||
}
|
||||
|
||||
class func getParticipatingMembersItem(route: Route, store: ScheduleConferenceStore, viewController: ContactViewSelectDelegate? = nil) -> ListItem {
|
||||
var participatingMembersItem = ListItem(title: .participatingMembersText)
|
||||
participatingMembersItem.showButton = true
|
||||
participatingMembersItem.buttonIcon = "room_right_arrow1"
|
||||
participatingMembersItem.selectClosure = { [weak viewController] in
|
||||
guard let vc = viewController else { return }
|
||||
let users = store.conferenceInfo.attendeeListResult.attendeeList.map { $0.convertToUser() }
|
||||
let participants = ConferenceParticipants(selectedList: users)
|
||||
route.showContactView(delegate: vc, participants: participants)
|
||||
}
|
||||
participatingMembersItem.bindStateClosure = { cell, cancellableSet in
|
||||
let selector = Selector(keyPath: \ConferenceInfo.attendeeListResult.attendeeList)
|
||||
store.select(selector)
|
||||
.receive(on: RunLoop.main)
|
||||
.sink { list in
|
||||
if let cell = cell as? ScheduleTabCell {
|
||||
var iconList: [String] = []
|
||||
for i in 0...2 {
|
||||
if let userInfo = list[safe: i] {
|
||||
let avatarUrl = userInfo.avatarUrl.count > 0 ? userInfo.avatarUrl : "room_default_avatar_rect"
|
||||
iconList.append(avatarUrl)
|
||||
}
|
||||
}
|
||||
cell.updateStackView(iconList: iconList)
|
||||
let totalCount = store.conferenceInfo.attendeeListResult.totalCount
|
||||
cell.messageLabel.text = totalCount == 0 ? .addToText : localizedReplace(.participantsNumber, replace: String(totalCount))
|
||||
}
|
||||
}
|
||||
.store(in: &cancellableSet)
|
||||
}
|
||||
return participatingMembersItem
|
||||
}
|
||||
|
||||
class func getEncryptRoomItem(store: ScheduleConferenceStore) -> SwitchItem {
|
||||
var encryptRoomItem = SwitchItem(title: .encryptTheRoomText)
|
||||
encryptRoomItem.isOn = store.conferenceInfo.basicInfo.isPasswordEnabled
|
||||
encryptRoomItem.selectClosure = {
|
||||
var conferenceInfo = store.conferenceInfo
|
||||
conferenceInfo.basicInfo.isPasswordEnabled = !store.conferenceInfo.basicInfo.isPasswordEnabled
|
||||
store.update(conference: conferenceInfo)
|
||||
}
|
||||
return encryptRoomItem
|
||||
}
|
||||
|
||||
class func getRoomPasswordItem(store: ScheduleConferenceStore) -> TextFieldItem {
|
||||
var roomPasswordItem = TextFieldItem(title: .roomPasswordText, content: store.conferenceInfo.basicInfo.password)
|
||||
roomPasswordItem.keyboardType = .numberPad
|
||||
roomPasswordItem.maxLengthInBytes = 6
|
||||
roomPasswordItem.placeholder = .enterJoinRoomPassword
|
||||
roomPasswordItem.saveTextClosure = { text in
|
||||
var conferenceInfo = store.conferenceInfo
|
||||
conferenceInfo.basicInfo.password = text
|
||||
store.update(conference: conferenceInfo)
|
||||
}
|
||||
return roomPasswordItem
|
||||
}
|
||||
|
||||
class func getMuteAllItem(route: Route, store: ScheduleConferenceStore) -> SwitchItem {
|
||||
var muteAllItem = SwitchItem(title: .muteAllText)
|
||||
muteAllItem.isOn = store.conferenceInfo.basicInfo.isMicrophoneDisableForAllUser
|
||||
muteAllItem.selectClosure = {
|
||||
var conferenceInfo = store.conferenceInfo
|
||||
conferenceInfo.basicInfo.isMicrophoneDisableForAllUser = !conferenceInfo.basicInfo.isMicrophoneDisableForAllUser
|
||||
store.update(conference: conferenceInfo)
|
||||
}
|
||||
muteAllItem.bindStateClosure = { cell, cancellableSet in
|
||||
let selector = Selector(keyPath: \ConferenceInfo.basicInfo.isMicrophoneDisableForAllUser)
|
||||
store.select(selector)
|
||||
.receive(on: RunLoop.main)
|
||||
.sink { isMicrophoneDisableForAllUser in
|
||||
if let cell = cell as? SwitchCell {
|
||||
cell.rightSwitch.isOn = isMicrophoneDisableForAllUser
|
||||
}
|
||||
}
|
||||
.store(in: &cancellableSet)
|
||||
}
|
||||
return muteAllItem
|
||||
}
|
||||
|
||||
class func getFreezeVideoItem(route: Route, store: ScheduleConferenceStore) -> SwitchItem {
|
||||
var freezeVideoItem = SwitchItem(title: .freezeVideoText)
|
||||
freezeVideoItem.isOn = store.conferenceInfo.basicInfo.isCameraDisableForAllUser
|
||||
freezeVideoItem.selectClosure = {
|
||||
var conferenceInfo = store.conferenceInfo
|
||||
conferenceInfo.basicInfo.isCameraDisableForAllUser = !conferenceInfo.basicInfo.isCameraDisableForAllUser
|
||||
store.update(conference: conferenceInfo)
|
||||
}
|
||||
freezeVideoItem.bindStateClosure = { cell, cancellableSet in
|
||||
let selector = Selector(keyPath: \ConferenceInfo.basicInfo.isCameraDisableForAllUser)
|
||||
store.select(selector)
|
||||
.receive(on: RunLoop.main)
|
||||
.sink { isCameraDisableForAllUser in
|
||||
if let cell = cell as? SwitchCell {
|
||||
cell.rightSwitch.isOn = isCameraDisableForAllUser
|
||||
}
|
||||
}
|
||||
.store(in: &cancellableSet)
|
||||
}
|
||||
return freezeVideoItem
|
||||
}
|
||||
|
||||
class func getbookItem(route: Route, store: ScheduleConferenceStore, operation: ConferenceStore) -> ButtonItem {
|
||||
var bookItem = ButtonItem(title: .bookRoomText)
|
||||
bookItem.titleColor = UIColor(0xFFFFFF)
|
||||
bookItem.backgroudColor = UIColor(0x1C66E5)
|
||||
bookItem.selectClosure = {
|
||||
guard TimeInterval(store.conferenceInfo.scheduleStartTime) >= Date().timeIntervalSince1970 else {
|
||||
operation.dispatch(action: ViewActions.showToast(payload: ToastInfo(message: .startTimeCannotEarlierCurrentTime)))
|
||||
return
|
||||
}
|
||||
guard store.conferenceInfo.basicInfo.name.count > 0 else {
|
||||
operation.dispatch(action: ViewActions.showToast(payload: ToastInfo(message: .nameCannotBeEmptyText)))
|
||||
return
|
||||
}
|
||||
guard checkPasswordFormat(conferenceInfo: store.conferenceInfo) else {
|
||||
operation.dispatch(action: ViewActions.showToast(payload: ToastInfo(message: .passwordFormatIsIncorrect)))
|
||||
return
|
||||
}
|
||||
let conferenceInfo = TUIConferenceInfo(conferenceInfo: store.conferenceInfo)
|
||||
operation.dispatch(action: ConferenceListActions.scheduleConference(payload: conferenceInfo))
|
||||
}
|
||||
return bookItem
|
||||
}
|
||||
|
||||
class func checkPasswordFormat(conferenceInfo: ConferenceInfo) -> Bool {
|
||||
if conferenceInfo.basicInfo.isPasswordEnabled {
|
||||
let password = conferenceInfo.basicInfo.password
|
||||
let passwordLength = 6
|
||||
return password.count == passwordLength && password.isStringOnlyDigits()
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
class func getTimeIntervalString(_ time: TimeInterval, timeZone: TimeZone) -> String {
|
||||
let date = Date(timeIntervalSince1970: time)
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateFormat = "MM-dd HH:mm"
|
||||
dateFormatter.timeZone = timeZone
|
||||
return dateFormatter.string(from: date)
|
||||
}
|
||||
|
||||
class func getDurationTimeString(_ time: UInt) -> String {
|
||||
guard time > 0 else { return "" }
|
||||
let hour = time / 3_600
|
||||
let minute = (time / 60) % 60
|
||||
var text = ""
|
||||
if hour > 0 {
|
||||
text = String(hour) + .hour
|
||||
}
|
||||
if minute > 0 {
|
||||
text = text + String(minute) + .minute
|
||||
}
|
||||
return text
|
||||
}
|
||||
}
|
||||
|
||||
private extension String {
|
||||
static let roomNameText = localized("Room name")
|
||||
static let roomTypeText = localized("Room type")
|
||||
static let startingTimeText = localized("Starting time")
|
||||
static let roomDurationText = localized("Room duration")
|
||||
static let rimeZoneText = localized("Time zone")
|
||||
static let freeSpeechRoomText = localized("Free Speech Room")
|
||||
static let onStageSpeechRoomText = localized("On-stage Speech Room")
|
||||
static let muteAllText = localized("Mute All")
|
||||
static let freezeVideoText = localized("Freeze video")
|
||||
static let hour = localized("hour")
|
||||
static let minute = localized("minute")
|
||||
static let participatingMembersText = localized("Participating members")
|
||||
static let addToText = localized("Add to")
|
||||
static let encryptTheRoomText = localized("Encrypt the room")
|
||||
static let roomPasswordText = localized("Room Password")
|
||||
static let bookRoomText = localized("Schedule Room")
|
||||
static let nameCannotBeEmptyText = localized("Conference name cannot be empty!")
|
||||
static let participantsNumber = localized("xx/300 people")
|
||||
static let passwordFormatIsIncorrect = localized("Your room password format is incorrect, please check it")
|
||||
static let enterJoinRoomPassword = localized("Enter 6-digit password")
|
||||
static let startTimeCannotEarlierCurrentTime = localized("The start time cannot be earlier than the current time")
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
//
|
||||
// ScheduleConferenceTableView.swift
|
||||
// TUIRoomKit
|
||||
//
|
||||
// Created by janejntang on 2024/6/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Factory
|
||||
|
||||
class ScheduleConferenceTableView: UIView {
|
||||
var menus: [Int: [CellConfigItem]]
|
||||
|
||||
lazy var tableView: UITableView = {
|
||||
let tableView = UITableView(frame: .zero, style: .grouped)
|
||||
tableView.separatorStyle = .none
|
||||
tableView.delegate = self
|
||||
tableView.dataSource = self
|
||||
tableView.backgroundColor = .clear
|
||||
tableView.sectionFooterHeight = 20.scale375Height()
|
||||
tableView.sectionHeaderHeight = 0
|
||||
tableView.register(ScheduleTabCell.self, forCellReuseIdentifier: ScheduleTabCell.identifier)
|
||||
tableView.register(SwitchCell.self, forCellReuseIdentifier: SwitchCell.identifier)
|
||||
tableView.register(TextFieldCell.self, forCellReuseIdentifier: TextFieldCell.identifier)
|
||||
tableView.register(ButtonCell.self, forCellReuseIdentifier: ButtonCell.identifier)
|
||||
return tableView
|
||||
}()
|
||||
|
||||
private var isViewReady: Bool = false
|
||||
override func didMoveToWindow() {
|
||||
super.didMoveToWindow()
|
||||
guard !isViewReady else { return }
|
||||
isViewReady = true
|
||||
constructViewHierarchy()
|
||||
activateConstraints()
|
||||
backgroundColor = UIColor(0xF8F9FB)
|
||||
}
|
||||
|
||||
init(menus: [Int : [CellConfigItem]]) {
|
||||
self.menus = menus
|
||||
super.init(frame: .zero)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
private func constructViewHierarchy() {
|
||||
addSubview(tableView)
|
||||
}
|
||||
|
||||
private func activateConstraints() {
|
||||
tableView.snp.makeConstraints { make in
|
||||
make.top.equalToSuperview()
|
||||
make.leading.equalToSuperview().offset(16.scale375())
|
||||
make.trailing.equalToSuperview().offset(-16.scale375())
|
||||
make.bottom.equalToSuperview().offset(-10.scale375Height())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ScheduleConferenceTableView: UITableViewDataSource {
|
||||
func numberOfSections(in tableView: UITableView) -> Int {
|
||||
return menus.count
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
guard let value = menus[section] else { return 0 }
|
||||
return value.count
|
||||
}
|
||||
}
|
||||
|
||||
extension ScheduleConferenceTableView: UITableViewDelegate {
|
||||
func tableView(_ tableView: UITableView,
|
||||
cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let items = menus[indexPath.section] ?? []
|
||||
let item = items[indexPath.row]
|
||||
let identifier = item.cellType.cellIdentifier
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier:identifier, for: indexPath)
|
||||
if let scheduleTabCell = cell as? ScheduleTabCell {
|
||||
scheduleTabCell.updateView(item: item)
|
||||
} else if let switchCell = cell as? SwitchCell {
|
||||
switchCell.updateView(item: item)
|
||||
} else if let textFieldCell = cell as? TextFieldCell {
|
||||
textFieldCell.updateView(item: item)
|
||||
} else if let buttonCell = cell as? ButtonCell {
|
||||
buttonCell.updateView(item: item)
|
||||
}
|
||||
if let baseCell = cell as? ScheduleBaseCell {
|
||||
item.bindStateClosure?(baseCell, &baseCell.cancellableSet)
|
||||
}
|
||||
cell.selectionStyle = .none
|
||||
return cell
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
let items = menus[indexPath.section] ?? []
|
||||
let item = items[indexPath.row]
|
||||
let cell = tableView.cellForRow(at: indexPath)
|
||||
if cell is ScheduleTabCell {
|
||||
item.selectClosure?()
|
||||
}
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
|
||||
let normalHeight = 45.scale375Height()
|
||||
if indexPath.section == 0 {
|
||||
return normalHeight
|
||||
}
|
||||
guard let itemArray = menus[indexPath.section], let item = itemArray[safe: indexPath.item] else { return normalHeight }
|
||||
switch item.cellType {
|
||||
case .switcher, .textField:
|
||||
return 54.scale375Height()
|
||||
case .button:
|
||||
return 44.scale375Height()
|
||||
default:
|
||||
return normalHeight
|
||||
}
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
|
||||
let rows = tableView.numberOfRows(inSection: indexPath.section)
|
||||
if indexPath.row == 0 || indexPath.row == rows - 1 {
|
||||
var corner = UIRectCorner()
|
||||
if rows == 1 {
|
||||
corner = .allCorners
|
||||
} else if indexPath.row == 0 {
|
||||
corner = [.topLeft, .topRight]
|
||||
} else if indexPath.row == rows - 1 {
|
||||
corner = [.bottomLeft, .bottomRight]
|
||||
}
|
||||
cell.roundedRect(rect: cell.bounds,
|
||||
byRoundingCorners: corner,
|
||||
cornerRadii: CGSize(width: 12, height: 12))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
//
|
||||
// ScheduleDetailsDataHelper.swift
|
||||
// TUIRoomKit
|
||||
//
|
||||
// Created by janejntang on 2024/6/26.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import RTCRoomEngine
|
||||
|
||||
class ScheduleDetailsDataHelper: ScheduleConferenceDataHelper {
|
||||
|
||||
class func generateScheduleDetailsConferenceData(route: Route, store: ScheduleConferenceStore, operation: ConferenceStore, viewStore: ConferenceMainViewStore) -> [Int : [CellConfigItem]] {
|
||||
var menus: [Int:[CellConfigItem]] = [:]
|
||||
menus[0] = getFirstSectionDetailsMenus(route: route, store: store, operation: operation)
|
||||
menus[1] = getSecondSectionDetailsMenus(store: store, operation: operation, viewStore: viewStore)
|
||||
menus[2] = getThirdSectionDetailsMenus(route: route, store: store, operation: operation)
|
||||
guard let fourthSectionDetailsMenus = getFourthSectionDetailsMenus(route: route, store: store, operation: operation) else { return menus }
|
||||
menus[3] = fourthSectionDetailsMenus
|
||||
return menus
|
||||
}
|
||||
|
||||
private class func getFirstSectionDetailsMenus(route: Route, store: ScheduleConferenceStore, operation: ConferenceStore) -> [CellConfigItem] {
|
||||
var array: [CellConfigItem] = []
|
||||
array.append(getDetailsConferenceNameItem(route: route, store: store))
|
||||
array.append(getDetailsConferenceIdItem(route: route, store: store, operation: operation))
|
||||
array.append(getDetailsStartTimeItem(route: route, store: store))
|
||||
array.append(getDetailsDurationTimeItem(route: route, store: store))
|
||||
array.append(getDetailsConferenceTypeItem(route: route, store: store))
|
||||
if let passwordItem = getConferencePasswordItem(store: store) {
|
||||
array.append(passwordItem)
|
||||
}
|
||||
array.append(getRoomHostItem(route: route, store: store))
|
||||
array.append(getDetailsParticipatingMembersItem(route: route, store: store))
|
||||
return array
|
||||
}
|
||||
|
||||
private class func getSecondSectionDetailsMenus(store: ScheduleConferenceStore, operation: ConferenceStore, viewStore: ConferenceMainViewStore) -> [CellConfigItem] {
|
||||
return [getEnterRoomItem(store: store, operation: operation, viewStore: viewStore)]
|
||||
}
|
||||
|
||||
private class func getThirdSectionDetailsMenus(route: Route, store: ScheduleConferenceStore, operation: ConferenceStore) -> [CellConfigItem] {
|
||||
return [getInviteItem(route: route, store: store)]
|
||||
}
|
||||
|
||||
private class func getFourthSectionDetailsMenus(route: Route, store: ScheduleConferenceStore, operation: ConferenceStore) -> [CellConfigItem]? {
|
||||
guard store.conferenceInfo.basicInfo.ownerId == operation.selectCurrent(UserSelectors.getSelfId) else { return nil }
|
||||
guard store.conferenceInfo.status == .notStarted else { return nil }
|
||||
return [getCancelRoomItem(route: route, store: store, operation: operation)]
|
||||
}
|
||||
|
||||
private class func getDetailsConferenceNameItem(route: Route, store: ScheduleConferenceStore) -> TextFieldItem {
|
||||
var conferenceNameItem = getConferenceNameItem(route: route, store: store)
|
||||
conferenceNameItem.isEnable = false
|
||||
return conferenceNameItem
|
||||
}
|
||||
|
||||
private class func getDetailsConferenceIdItem(route: Route, store: ScheduleConferenceStore, operation: ConferenceStore) -> ListItem {
|
||||
var conferenceIdItem = ListItem(title: .roomIDText, content: store.conferenceInfo.basicInfo.roomId)
|
||||
conferenceIdItem.showButton = true
|
||||
conferenceIdItem.buttonIcon = "room_copy_blue"
|
||||
conferenceIdItem.selectClosure = {
|
||||
UIPasteboard.general.string = store.conferenceInfo.basicInfo.roomId
|
||||
operation.dispatch(action: ViewActions.showToast(payload: ToastInfo(message: .copyRoomIdSuccess)))
|
||||
}
|
||||
return conferenceIdItem
|
||||
}
|
||||
|
||||
private class func getDetailsConferenceTypeItem(route: Route, store: ScheduleConferenceStore) -> ListItem {
|
||||
var conferenceTypeItem = getConferenceTypeItem(route: route, store: store)
|
||||
conferenceTypeItem.showButton = false
|
||||
conferenceTypeItem.selectClosure = nil
|
||||
return conferenceTypeItem
|
||||
}
|
||||
|
||||
private class func getDetailsStartTimeItem(route: Route, store: ScheduleConferenceStore) -> ListItem {
|
||||
var startTimeItem = getStartTimeItem(route: route, store: store)
|
||||
startTimeItem.selectClosure = nil
|
||||
startTimeItem.showButton = false
|
||||
return startTimeItem
|
||||
}
|
||||
|
||||
private class func getDetailsDurationTimeItem(route: Route, store: ScheduleConferenceStore) -> ListItem {
|
||||
var durationTimeItem = getDurationTimeItem(route: route, store: store)
|
||||
durationTimeItem.selectClosure = nil
|
||||
durationTimeItem.showButton = false
|
||||
return durationTimeItem
|
||||
}
|
||||
|
||||
private class func getRoomHostItem(route: Route, store: ScheduleConferenceStore) -> ListItem {
|
||||
var hostItem = ListItem(title: .creatorText, content: store.conferenceInfo.basicInfo.ownerName)
|
||||
hostItem.iconList = [store.conferenceInfo.basicInfo.ownerAvatarUrl]
|
||||
return hostItem
|
||||
}
|
||||
|
||||
private class func getDetailsParticipatingMembersItem(route: Route, store: ScheduleConferenceStore) -> ListItem {
|
||||
var item = getParticipatingMembersItem(route: route, store: store)
|
||||
item.buttonIcon = "room_down_arrow1"
|
||||
item.selectClosure = {
|
||||
if store.conferenceInfo.attendeeListResult.attendeeList.count > 0 {
|
||||
route.present(route: .selectedMember(showDeleteButton: false, selectedMembers: store.conferenceInfo.attendeeListResult.attendeeList))
|
||||
}
|
||||
}
|
||||
item.bindStateClosure = { cell, cancellableSet in
|
||||
let selector = Selector(keyPath: \ConferenceInfo.attendeeListResult.attendeeList)
|
||||
store.select(selector)
|
||||
.receive(on: RunLoop.main)
|
||||
.removeDuplicates()
|
||||
.sink { list in
|
||||
if let cell = cell as? ScheduleTabCell {
|
||||
var iconList: [String] = []
|
||||
for i in 0...2 {
|
||||
if let userInfo = list[safe: i] {
|
||||
let avatarUrl = userInfo.avatarUrl.count > 0 ? userInfo.avatarUrl : "room_default_avatar_rect"
|
||||
iconList.append(avatarUrl)
|
||||
}
|
||||
}
|
||||
cell.updateStackView(iconList: iconList)
|
||||
let totalCount = store.conferenceInfo.attendeeListResult.totalCount
|
||||
cell.messageLabel.text = totalCount == 0 ? .noParticipantsYet : localizedReplace(.participantsNumber, replace: String(totalCount))
|
||||
cell.updateButton(isShown: list.count > 0)
|
||||
}
|
||||
}
|
||||
.store(in: &cancellableSet)
|
||||
}
|
||||
return item
|
||||
}
|
||||
|
||||
private class func getConferencePasswordItem(store: ScheduleConferenceStore) -> ListItem? {
|
||||
guard store.conferenceInfo.basicInfo.password.count > 0 else { return nil }
|
||||
var passwordItem = ListItem(title: .conferencePasswordText)
|
||||
passwordItem.content = store.conferenceInfo.basicInfo.password
|
||||
passwordItem.selectClosure = nil
|
||||
passwordItem.showButton = false
|
||||
return passwordItem
|
||||
}
|
||||
|
||||
private class func getEnterRoomItem(store: ScheduleConferenceStore, operation: ConferenceStore, viewStore: ConferenceMainViewStore) -> ButtonItem {
|
||||
var item = ButtonItem(title: .enterTheRoomText)
|
||||
item.titleColor = UIColor(0x0961F7)
|
||||
item.backgroudColor = UIColor(0xF0F3FA)
|
||||
item.selectClosure = {
|
||||
let conferenceId = store.conferenceInfo.basicInfo.roomId
|
||||
operation.dispatch(action: RoomActions.joinConference(payload: conferenceId))
|
||||
operation.dispatch(action: ScheduleViewActions.popDetailView())
|
||||
viewStore.updateInternalCreation(isInternalCreation: true)
|
||||
}
|
||||
return item
|
||||
}
|
||||
|
||||
private class func getInviteItem(route: Route, store: ScheduleConferenceStore) -> ButtonItem {
|
||||
var item = ButtonItem(title: .inviteMemberText)
|
||||
item.titleColor = UIColor(0x0961F7)
|
||||
item.backgroudColor = UIColor(0xF0F3FA)
|
||||
item.selectClosure = {
|
||||
let view = InviteEnterRoomView(conferenceInfo: store.conferenceInfo)
|
||||
route.present(route: .popup(view: view))
|
||||
}
|
||||
return item
|
||||
}
|
||||
|
||||
private class func getCancelRoomItem(route: Route, store: ScheduleConferenceStore, operation: ConferenceStore) -> ButtonItem {
|
||||
var item = ButtonItem(title: .cancelRoomText)
|
||||
item.titleColor = UIColor(0xED414D)
|
||||
item.backgroudColor = UIColor(0xFAF0F0)
|
||||
item.selectClosure = {
|
||||
let declineAction = UIAlertAction(title: .notCanceledYet, style: .cancel)
|
||||
declineAction.setValue(UIColor(0x4F586B), forKey: "titleTextColor")
|
||||
let sureAction = UIAlertAction(title: .cancelRoom, style: .default) { _ in
|
||||
operation.dispatch(action: ConferenceListActions.cancelConference(payload: store.conferenceInfo.basicInfo.roomId))
|
||||
}
|
||||
sureAction.setValue(UIColor(0xED414D), forKey: "titleTextColor")
|
||||
let alertState = AlertState(title: .cancelBookedRoomTitle, message: .cancelBookedRoomMessage, sureAction: sureAction, declineAction: declineAction)
|
||||
route.present(route: .alert(state: alertState))
|
||||
}
|
||||
return item
|
||||
}
|
||||
}
|
||||
|
||||
private extension String {
|
||||
static let creatorText: String = localized("Creator")
|
||||
static let roomIDText: String = localized("Room ID")
|
||||
static let enterTheRoomText = localized("Enter the room")
|
||||
static let inviteMemberText = localized("Invite member")
|
||||
static let cancelRoomText = localized("Cancel Room")
|
||||
static let cancelBookedRoomTitle = localized("Cancel this booked room")
|
||||
static let cancelBookedRoomMessage = localized("After cancellation, other members will not be able to join")
|
||||
static let notCanceledYet = localized("Not canceled yet")
|
||||
static let cancelRoom = localized("Cancel Room")
|
||||
static let copyRoomIdSuccess = localized("Conference ID copied.")
|
||||
static let noParticipantsYet = localized("No participants yet")
|
||||
static let participantsNumber = localized("xx/300 people")
|
||||
static let conferencePasswordText = localized("Conference password")
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
//
|
||||
// ScheduleDetailsViewController .swift
|
||||
// TUIRoomKit
|
||||
//
|
||||
// Created by janejntang on 2024/6/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Factory
|
||||
import Combine
|
||||
import TUICore
|
||||
import RTCRoomEngine
|
||||
|
||||
class ScheduleDetailsViewController: UIViewController {
|
||||
var conferenceInfo: ConferenceInfo
|
||||
private var cancellableSet = Set<AnyCancellable>()
|
||||
|
||||
private lazy var rootView: ScheduleConferenceTableView = {
|
||||
return ScheduleConferenceTableView(menus: ScheduleDetailsDataHelper.generateScheduleDetailsConferenceData(route: route, store: store, operation: operation, viewStore: viewStore))
|
||||
}()
|
||||
|
||||
private lazy var conferenceListPublisher = {
|
||||
operation.select(ConferenceListSelectors.getConferenceList)
|
||||
}()
|
||||
|
||||
init(conferenceInfo: ConferenceInfo) {
|
||||
self.conferenceInfo = conferenceInfo
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
store.update(conference: self.conferenceInfo)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override var shouldAutorotate: Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
|
||||
return .portrait
|
||||
}
|
||||
|
||||
override func loadView() {
|
||||
self.view = rootView
|
||||
}
|
||||
|
||||
lazy var modifyButton: UIButton = {
|
||||
let button = UIButton()
|
||||
button.setTitle(.reviseText, for: .normal)
|
||||
button.setTitleColor(UIColor(0x1C66E5), for: .normal)
|
||||
button.titleLabel?.font = UIFont.systemFont(ofSize: 16, weight: .medium)
|
||||
button.addTarget(self, action: #selector(modifyAction(sender:)), for: .touchUpInside)
|
||||
return button
|
||||
}()
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
initState()
|
||||
subscribeToast()
|
||||
subscribeScheduleSubject()
|
||||
navigationItem.title = .roomDetailsText
|
||||
if store.conferenceInfo.basicInfo.ownerId == operation.selectCurrent(UserSelectors.getSelfId),
|
||||
store.conferenceInfo.status == .notStarted {
|
||||
navigationItem.rightBarButtonItem = UIBarButtonItem(customView: modifyButton)
|
||||
}
|
||||
store.fetchAttendees(cursor: "")
|
||||
let cursorSelector = Selector(keyPath: \ConferenceInfo.attendeeListResult.fetchCursor)
|
||||
store.select(cursorSelector)
|
||||
.removeDuplicates()
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink {[weak self] cursor in
|
||||
guard let self = self else { return }
|
||||
if !cursor.isEmpty {
|
||||
self.store.fetchAttendees(cursor: cursor)
|
||||
}
|
||||
}
|
||||
.store(in: &cancellableSet)
|
||||
|
||||
let selector = Selector(keyPath: \ConferenceInfo.basicInfo.isPasswordEnabled)
|
||||
store.select(selector)
|
||||
.removeDuplicates()
|
||||
.receive(on: RunLoop.main)
|
||||
.sink { [weak self] isPasswordEnabled in
|
||||
guard let self = self else { return }
|
||||
self.rootView.menus = ScheduleDetailsDataHelper.generateScheduleDetailsConferenceData(route: self.route, store: self.store, operation: self.operation, viewStore: self.viewStore)
|
||||
self.rootView.tableView.reloadData()
|
||||
}
|
||||
.store(in: &cancellableSet)
|
||||
|
||||
operation.select(ViewSelectors.getPopDetailFlag)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.removeDuplicates()
|
||||
.sink { [weak self] shouldPopDetail in
|
||||
guard let self = self else { return }
|
||||
if shouldPopDetail {
|
||||
self.route.pop(route: .scheduleDetails(conferenceInfo:self.conferenceInfo))
|
||||
}
|
||||
}
|
||||
.store(in: &cancellableSet)
|
||||
|
||||
conferenceListPublisher
|
||||
.receive(on: RunLoop.main)
|
||||
.sink { [weak self] list in
|
||||
guard let self = self else { return }
|
||||
guard let selectedConferenceInfo = list.first(where: { $0.basicInfo.roomId == self.conferenceInfo.basicInfo.roomId }) else { return }
|
||||
guard selectedConferenceInfo.status != self.store.conferenceInfo.status, selectedConferenceInfo.status == .running else { return }
|
||||
self.modifyButton.isHidden = true
|
||||
var conferenceInfo = self.store.conferenceInfo
|
||||
conferenceInfo.status = selectedConferenceInfo.status
|
||||
self.store.update(conference: conferenceInfo)
|
||||
let menus = ScheduleDetailsDataHelper.generateScheduleDetailsConferenceData(route: self.route, store: self.store, operation: self.operation, viewStore: self.viewStore)
|
||||
self.rootView.menus = menus
|
||||
self.rootView.tableView.reloadData()
|
||||
}
|
||||
.store(in: &cancellableSet)
|
||||
store.fetchRoomInfo(roomId: conferenceInfo.basicInfo.roomId)
|
||||
}
|
||||
|
||||
private func initState() {
|
||||
self.operation.dispatch(action: UserActions.getSelfInfo())
|
||||
self.operation.dispatch(action: ScheduleViewActions.resetPopDetailFlag())
|
||||
|
||||
}
|
||||
|
||||
@objc func modifyAction(sender: UIButton) {
|
||||
route.pushTo(route: .modifySchedule(conferenceInfo: store.conferenceInfo))
|
||||
}
|
||||
|
||||
deinit {
|
||||
debugPrint("deinit \(self)")
|
||||
}
|
||||
|
||||
// MARK: - private property.
|
||||
@Injected(\.navigation) private var route
|
||||
@Injected(\.scheduleStore) private var store
|
||||
@Injected(\.conferenceStore) private var operation
|
||||
@Injected(\.conferenceMainViewStore) private var viewStore
|
||||
}
|
||||
|
||||
extension ScheduleDetailsViewController {
|
||||
private func subscribeToast() {
|
||||
operation.toastSubject
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] toast in
|
||||
guard let self = self else { return }
|
||||
var position = TUICSToastPositionBottom
|
||||
switch toast.position {
|
||||
case .center:
|
||||
position = TUICSToastPositionCenter
|
||||
default:
|
||||
break
|
||||
}
|
||||
if self.presentedViewController == nil {
|
||||
self.view.makeToast(toast.message, duration: toast.duration, position: position)
|
||||
}
|
||||
}
|
||||
.store(in: &cancellableSet)
|
||||
}
|
||||
|
||||
private func subscribeScheduleSubject() {
|
||||
operation.scheduleActionSubject
|
||||
.receive(on: RunLoop.main)
|
||||
.filter { $0.id == ScheduleResponseActions.onCancelSuccess.id }
|
||||
.sink { [weak self] action in
|
||||
guard let self = self else { return }
|
||||
self.route.pop()
|
||||
}
|
||||
.store(in: &cancellableSet)
|
||||
}
|
||||
}
|
||||
|
||||
private extension String {
|
||||
static let roomDetailsText = localized("Room Details")
|
||||
static let reviseText = localized("Revise")
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
//
|
||||
// ButtonCell.swift
|
||||
// TUIRoomKit
|
||||
//
|
||||
// Created by janejntang on 2024/7/5.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class ButtonCell: ScheduleBaseCell {
|
||||
static let identifier = "ButtonCell"
|
||||
var item: CellConfigItem?
|
||||
|
||||
let button: UIButton = {
|
||||
let button = UIButton()
|
||||
button.titleLabel?.font = UIFont.systemFont(ofSize: 16, weight: .medium)
|
||||
button.titleLabel?.textAlignment = .center
|
||||
button.layer.cornerRadius = 12
|
||||
return button
|
||||
}()
|
||||
|
||||
private var isViewReady: Bool = false
|
||||
override func didMoveToWindow() {
|
||||
super.didMoveToWindow()
|
||||
guard !isViewReady else { return }
|
||||
isViewReady = true
|
||||
constructViewHierarchy()
|
||||
activateConstraints()
|
||||
bindInteraction()
|
||||
}
|
||||
|
||||
private func constructViewHierarchy() {
|
||||
contentView.addSubview(button)
|
||||
}
|
||||
|
||||
private func activateConstraints() {
|
||||
button.snp.makeConstraints { make in
|
||||
make.edges.equalToSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
private func bindInteraction() {
|
||||
button.addTarget(self, action: #selector(buttonAction(sender:)), for: .touchUpInside)
|
||||
}
|
||||
|
||||
@objc func buttonAction(sender: UIButton) {
|
||||
item?.selectClosure?()
|
||||
}
|
||||
|
||||
func updateView(item: CellConfigItem) {
|
||||
self.item = item
|
||||
guard let buttonItem = item as? ButtonItem else { return }
|
||||
button.setTitle(buttonItem.title, for: .normal)
|
||||
button.setTitleColor(buttonItem.titleColor, for: .normal)
|
||||
button.backgroundColor = buttonItem.backgroudColor
|
||||
}
|
||||
|
||||
deinit {
|
||||
debugPrint("deinit:\(self)")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
//
|
||||
// DurationPickerView.swift
|
||||
// TUIRoomKit
|
||||
//
|
||||
// Created by janejntang on 2024/6/14.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Factory
|
||||
|
||||
class DurationPickerView: UIView {
|
||||
var dismissAction: (() -> Void)?
|
||||
var timeDuration: TimeInterval = 1800
|
||||
|
||||
let topView: UIView = {
|
||||
let view = UIView()
|
||||
return view
|
||||
}()
|
||||
|
||||
let topLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.text = .roomDurationText
|
||||
label.font = UIFont.systemFont(ofSize: 16, weight: .heavy)
|
||||
label.textColor = UIColor(0x22262E)
|
||||
return label
|
||||
}()
|
||||
|
||||
let cancelButton: UIButton = {
|
||||
let button = UIButton()
|
||||
button.setImage(UIImage(named: "schedule_wrong", in: tuiRoomKitBundle(), compatibleWith: nil), for: .normal)
|
||||
return button
|
||||
}()
|
||||
|
||||
let sureButton: UIButton = {
|
||||
let button = UIButton()
|
||||
button.setImage(UIImage(named: "schedule_right", in: tuiRoomKitBundle(), compatibleWith: nil), for: .normal)
|
||||
return button
|
||||
}()
|
||||
|
||||
let timePickerView: UIDatePicker = {
|
||||
let pickerView = UIDatePicker()
|
||||
pickerView.datePickerMode = .countDownTimer
|
||||
if #available(iOS 13.4, *) {
|
||||
pickerView.preferredDatePickerStyle = .wheels
|
||||
}
|
||||
pickerView.minuteInterval = 5
|
||||
return pickerView
|
||||
}()
|
||||
|
||||
private var isViewReady: Bool = false
|
||||
override func didMoveToWindow() {
|
||||
super.didMoveToWindow()
|
||||
backgroundColor = UIColor(0xFFFFFF)
|
||||
guard !isViewReady else { return }
|
||||
constructViewHierarchy()
|
||||
activateConstraints()
|
||||
bindInteraction()
|
||||
isViewReady = true
|
||||
}
|
||||
|
||||
func constructViewHierarchy() {
|
||||
addSubview(topView)
|
||||
topView.addSubview(topLabel)
|
||||
topView.addSubview(cancelButton)
|
||||
topView.addSubview(sureButton)
|
||||
addSubview(timePickerView)
|
||||
}
|
||||
|
||||
func activateConstraints() {
|
||||
topView.snp.makeConstraints { make in
|
||||
make.top.equalToSuperview()
|
||||
make.bottom.equalTo(timePickerView.snp.top)
|
||||
make.leading.trailing.equalToSuperview()
|
||||
make.height.equalTo(54.scale375Height())
|
||||
}
|
||||
topLabel.snp.makeConstraints { make in
|
||||
make.center.equalToSuperview()
|
||||
}
|
||||
cancelButton.snp.makeConstraints { make in
|
||||
make.width.height.equalTo(24.scale375())
|
||||
make.leading.equalToSuperview().offset(20.scale375())
|
||||
make.centerY.equalToSuperview()
|
||||
}
|
||||
sureButton.snp.makeConstraints { make in
|
||||
make.trailing.equalToSuperview().offset(-20.scale375())
|
||||
make.width.height.equalTo(cancelButton)
|
||||
make.centerY.equalTo(cancelButton)
|
||||
}
|
||||
timePickerView.snp.makeConstraints { make in
|
||||
make.leading.trailing.equalToSuperview()
|
||||
make.height.equalTo(270.scale375Height())
|
||||
make.bottom.equalToSuperview().offset(-5.scale375Height())
|
||||
}
|
||||
}
|
||||
|
||||
func bindInteraction() {
|
||||
self.layer.cornerRadius = 12
|
||||
timePickerView.countDownDuration = timeDuration
|
||||
timePickerView.addTarget(self, action: #selector(datePickerValueChanged(_:)), for: .valueChanged)
|
||||
sureButton.addTarget(self, action: #selector(sureAction(sender:)), for: .touchUpInside)
|
||||
cancelButton.addTarget(self, action: #selector(cancelAction(sender:)), for: .touchUpInside)
|
||||
}
|
||||
|
||||
@objc func sureAction(sender: UIButton) {
|
||||
var conferenceInfo = store.conferenceInfo
|
||||
conferenceInfo.durationTime = UInt(timePickerView.countDownDuration)
|
||||
store.update(conference: conferenceInfo)
|
||||
dismissAction?()
|
||||
}
|
||||
|
||||
@objc func cancelAction(sender: UIButton) {
|
||||
dismissAction?()
|
||||
}
|
||||
|
||||
@objc func datePickerValueChanged(_ sender: UIDatePicker) {
|
||||
let selectedTimeInterval = sender.countDownDuration
|
||||
let minTimeInterval = TimeInterval(900)
|
||||
if selectedTimeInterval < minTimeInterval {
|
||||
sender.countDownDuration = minTimeInterval
|
||||
}
|
||||
}
|
||||
|
||||
deinit{
|
||||
debugPrint("deinit:\(self)")
|
||||
}
|
||||
|
||||
@Injected(\.modifyScheduleStore) var store: ScheduleConferenceStore
|
||||
}
|
||||
|
||||
private extension String {
|
||||
static var roomDurationText: String {
|
||||
localized("Room duration")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
//
|
||||
// InviteEnterRoomDataHelper.swift
|
||||
// TUIRoomKit
|
||||
//
|
||||
// Created by janejntang on 2024/7/5.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Factory
|
||||
|
||||
class InviteEnterRoomDataHelper {
|
||||
class func generateInviteEnterRoomHelperData(conferenceInfo: ConferenceInfo, operation: ConferenceStore) -> [ListCellItemData] {
|
||||
var array: [ListCellItemData] = []
|
||||
array.append(getRoomNameItem(conferenceInfo: conferenceInfo))
|
||||
array.append(getRoomTypeItem(conferenceInfo: conferenceInfo))
|
||||
array.append(getRoomDurationItem(conferenceInfo: conferenceInfo))
|
||||
array.append(getRoomIdItem(conferenceInfo: conferenceInfo, operation: operation))
|
||||
if let passwordItem = getRoomPasswordItem(conferenceInfo: conferenceInfo, operation: operation) {
|
||||
array.append(passwordItem)
|
||||
}
|
||||
return array
|
||||
}
|
||||
|
||||
private class func getRoomNameItem(conferenceInfo: ConferenceInfo) -> ListCellItemData {
|
||||
let roomNameItem = getListCellItem(title: .roomName, message: conferenceInfo.basicInfo.name, hasRightButton: false)
|
||||
return roomNameItem
|
||||
}
|
||||
|
||||
private class func getRoomTypeItem(conferenceInfo: ConferenceInfo) -> ListCellItemData {
|
||||
let message: String = conferenceInfo.basicInfo.isSeatEnabled ? .onStageSpeechRoom : .freeSpeechRoom
|
||||
let roomNameItem = getListCellItem(title: .roomType, message: message, hasRightButton: false)
|
||||
return roomNameItem
|
||||
}
|
||||
|
||||
private class func getRoomDurationItem(conferenceInfo: ConferenceInfo) -> ListCellItemData {
|
||||
let scheduleStartTime = getTimeIntervalString(TimeInterval(conferenceInfo.scheduleStartTime), dateFormat: "MM-dd HH:mm")
|
||||
let scheduleEndTime = getTimeIntervalString(TimeInterval(conferenceInfo.scheduleEndTime), dateFormat: "HH:mm")
|
||||
let nextDayText = isTimeInNextDay(conferenceInfo: conferenceInfo) ? .nextDay : ""
|
||||
let message = scheduleStartTime + "-" + nextDayText + scheduleEndTime
|
||||
let roomNameItem = getListCellItem(title: .roomDuration, message: message, hasRightButton: false)
|
||||
return roomNameItem
|
||||
}
|
||||
|
||||
private class func isTimeInNextDay(conferenceInfo: ConferenceInfo) -> Bool {
|
||||
let startDate = Date(timeIntervalSince1970: TimeInterval(conferenceInfo.scheduleStartTime))
|
||||
let endDate = Date(timeIntervalSince1970: TimeInterval(conferenceInfo.scheduleEndTime))
|
||||
let calendar = Calendar.current
|
||||
let startDay = calendar.dateComponents([.year, .month, .day], from: startDate).day ?? 0
|
||||
let endDay = calendar.dateComponents([.year, .month, .day], from: endDate).day ?? 0
|
||||
return endDay - startDay == 1
|
||||
}
|
||||
|
||||
private class func getTimeIntervalString(_ time: TimeInterval, dateFormat: String) -> String {
|
||||
let date = Date(timeIntervalSince1970: time)
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateFormat = dateFormat
|
||||
return dateFormatter.string(from: date)
|
||||
}
|
||||
|
||||
private class func getRoomIdItem(conferenceInfo: ConferenceInfo, operation: ConferenceStore) -> ListCellItemData {
|
||||
let roomIdItem = getListCellItem(title: .roomIdText, message: conferenceInfo.basicInfo.roomId, hasRightButton: true)
|
||||
roomIdItem.buttonData?.action = { _ in
|
||||
UIPasteboard.general.string = conferenceInfo.basicInfo.roomId
|
||||
operation.dispatch(action: ViewActions.showToast(payload: ToastInfo(message: .copyRoomIdSuccess)))
|
||||
}
|
||||
return roomIdItem
|
||||
}
|
||||
|
||||
private class func getRoomPasswordItem(conferenceInfo: ConferenceInfo, operation: ConferenceStore) -> ListCellItemData? {
|
||||
let password = conferenceInfo.basicInfo.password
|
||||
guard password.count > 0 else { return nil }
|
||||
let passwordItem = getListCellItem(title: .conferencePasswordText, message: password, hasRightButton: true)
|
||||
passwordItem.buttonData?.action = { _ in
|
||||
UIPasteboard.general.string = password
|
||||
operation.dispatch(action: ViewActions.showToast(payload: ToastInfo(message: .conferencePasswordSuccess)))
|
||||
}
|
||||
return passwordItem
|
||||
}
|
||||
|
||||
private class func getRoomLinkItem(roomId: String, operation: ConferenceStore) -> ListCellItemData? {
|
||||
guard let roomLink = getRoomLink(roomId: roomId) else { return nil }
|
||||
let roomLinkItem = getListCellItem(title: .roomLinkText, message: roomLink, hasRightButton: true)
|
||||
roomLinkItem.buttonData?.action = { _ in
|
||||
UIPasteboard.general.string = roomLink
|
||||
operation.dispatch(action: ViewActions.showToast(payload: ToastInfo(message: .copyRoomLinkSuccess)))
|
||||
}
|
||||
return roomLinkItem
|
||||
}
|
||||
|
||||
private class func getListCellItem(title: String, message: String, hasRightButton: Bool) -> ListCellItemData {
|
||||
let item = ListCellItemData()
|
||||
item.titleText = title
|
||||
item.messageText = message
|
||||
item.hasRightButton = hasRightButton
|
||||
if hasRightButton {
|
||||
item.buttonData = getCopyButtonItem()
|
||||
}
|
||||
item.titleColor = UIColor(0x8F9AB2)
|
||||
item.messageColor = UIColor(0x4F586B)
|
||||
item.backgroundColor = .clear
|
||||
return item
|
||||
}
|
||||
|
||||
private class func getCopyButtonItem() -> ButtonItemData {
|
||||
let buttonData = ButtonItemData()
|
||||
buttonData.normalIcon = "room_copy"
|
||||
buttonData.normalTitle = .copyText
|
||||
buttonData.cornerRadius = 4
|
||||
buttonData.titleFont = UIFont(name: "PingFangSC-Regular", size: 12)
|
||||
buttonData.titleColor = UIColor(0x4F586B)
|
||||
buttonData.backgroundColor = UIColor(0xD5E0F2).withAlphaComponent(0.7)
|
||||
buttonData.resourceBundle = tuiRoomKitBundle()
|
||||
return buttonData
|
||||
}
|
||||
|
||||
private class func getRoomLink(roomId: String) -> String? {
|
||||
guard let bundleId = Bundle.main.bundleIdentifier else { return nil }
|
||||
if bundleId == "com.tencent.tuiroom.apiexample" || bundleId == "com.tencent.fx.rtmpdemo" {
|
||||
return "https://web.sdk.qcloud.com/trtc/webrtc/test/tuiroom-inner/index.html#/" + "room?roomId=" + roomId
|
||||
} else if bundleId == "com.tencent.mrtc" {
|
||||
return "https://web.sdk.qcloud.com/component/tuiroom/index.html#/" + "room?roomId=" + roomId
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@Injected(\.conferenceStore) private var operation
|
||||
}
|
||||
|
||||
private extension String {
|
||||
static var roomIdText: String {
|
||||
localized("Room ID")
|
||||
}
|
||||
static var roomLinkText: String {
|
||||
localized("Room link")
|
||||
}
|
||||
static var copyText: String {
|
||||
localized("Copy")
|
||||
}
|
||||
static var copyRoomIdSuccess: String {
|
||||
localized("Conference ID copied.")
|
||||
}
|
||||
static var copyRoomLinkSuccess: String {
|
||||
localized("Conference Link copied.")
|
||||
}
|
||||
static let conferencePasswordText = localized("Conference password")
|
||||
static let conferencePasswordSuccess = localized("Conference password copied successfully.")
|
||||
static let roomName = localized("Room name")
|
||||
static let roomType = localized("Room type")
|
||||
static let roomDuration = localized("Room duration")
|
||||
static let freeSpeechRoom = localized("Free Speech Room")
|
||||
static let onStageSpeechRoom = localized("On-stage Speech Room")
|
||||
static let nextDay = localized("Next Day")
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
//
|
||||
// InviteEnterRoomView.swift
|
||||
// TUIRoomKit
|
||||
//
|
||||
// Created by janejntang on 2024/7/5.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Factory
|
||||
import TUICore
|
||||
import Combine
|
||||
|
||||
enum InviteEnterRoomViewStyle {
|
||||
case normal
|
||||
case inviteWhenSuccess
|
||||
}
|
||||
|
||||
class InviteEnterRoomView: UIView {
|
||||
private var cancellableSet = Set<AnyCancellable>()
|
||||
var style: InviteEnterRoomViewStyle = .normal
|
||||
var title: String {
|
||||
if self.style == .normal {
|
||||
return .inviteMember
|
||||
} else {
|
||||
return .inviteWhenSuccess
|
||||
}
|
||||
}
|
||||
let conferenceInfo: ConferenceInfo
|
||||
lazy var menus = {
|
||||
InviteEnterRoomDataHelper.generateInviteEnterRoomHelperData(conferenceInfo: conferenceInfo, operation: operation)
|
||||
}()
|
||||
|
||||
private let dropArrowButton: UIButton = {
|
||||
let button = UIButton()
|
||||
button.setImage(UIImage(named: "room_drop_arrow", in: tuiRoomKitBundle(), compatibleWith: nil), for: .normal)
|
||||
button.contentEdgeInsets = UIEdgeInsets(top: 10.scale375Height(), left: 20.scale375(), bottom: 20.scale375Height(), right: 20.scale375())
|
||||
return button
|
||||
}()
|
||||
|
||||
private lazy var titleLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.text = title
|
||||
label.textColor = UIColor(0x4F586B)
|
||||
label.font = UIFont.systemFont(ofSize: 18, weight: .medium)
|
||||
label.textAlignment = isRTL ? .right : .left
|
||||
return label
|
||||
}()
|
||||
|
||||
private let stackView: UIStackView = {
|
||||
let view = UIStackView()
|
||||
view.axis = .vertical
|
||||
view.alignment = .center
|
||||
view.distribution = .equalSpacing
|
||||
view.spacing = 10
|
||||
view.backgroundColor = .clear
|
||||
return view
|
||||
}()
|
||||
|
||||
private let copyButton: UIButton = {
|
||||
let button = UIButton()
|
||||
button.setTitle(.copyRoomInformation, for: .normal)
|
||||
button.titleLabel?.font = UIFont.systemFont(ofSize: 16, weight: .medium)
|
||||
button.setTitleColor(UIColor(0x1C66E5), for: .normal)
|
||||
button.titleLabel?.textAlignment = .center
|
||||
button.layer.cornerRadius = 6
|
||||
button.layer.borderWidth = 1
|
||||
button.layer.borderColor = UIColor(0x1C66E5).cgColor
|
||||
return button
|
||||
}()
|
||||
|
||||
init(conferenceInfo: ConferenceInfo, style: InviteEnterRoomViewStyle = .normal) {
|
||||
self.conferenceInfo = conferenceInfo
|
||||
self.style = style
|
||||
super.init(frame: .zero)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
private var isViewReady: Bool = false
|
||||
override func didMoveToWindow() {
|
||||
super.didMoveToWindow()
|
||||
guard !isViewReady else { return }
|
||||
isViewReady = true
|
||||
constructViewHierarchy()
|
||||
activateConstraints()
|
||||
bindInteraction()
|
||||
backgroundColor = UIColor(0xFFFFFF)
|
||||
layer.cornerRadius = 14
|
||||
}
|
||||
|
||||
private func constructViewHierarchy() {
|
||||
addSubview(dropArrowButton)
|
||||
addSubview(titleLabel)
|
||||
addSubview(stackView)
|
||||
addSubview(copyButton)
|
||||
}
|
||||
|
||||
private func activateConstraints() {
|
||||
dropArrowButton.snp.makeConstraints { make in
|
||||
make.top.equalToSuperview()
|
||||
make.centerX.equalToSuperview()
|
||||
}
|
||||
titleLabel.snp.makeConstraints { make in
|
||||
make.top.equalTo(dropArrowButton.snp.bottom)
|
||||
make.leading.equalToSuperview().offset(16.scale375())
|
||||
}
|
||||
stackView.snp.makeConstraints { make in
|
||||
make.top.equalTo(titleLabel.snp.bottom).offset(20.scale375Height())
|
||||
make.leading.equalToSuperview().offset(16.scale375())
|
||||
make.trailing.equalToSuperview().offset(-16.scale375())
|
||||
}
|
||||
copyButton.snp.makeConstraints { make in
|
||||
make.leading.equalToSuperview().offset(16.scale375())
|
||||
make.trailing.equalToSuperview().offset(-16.scale375())
|
||||
make.height.equalTo(44.scale375Height())
|
||||
make.top.equalTo(stackView.snp.bottom).offset(20.scale375Height())
|
||||
make.bottom.equalToSuperview().offset(-34.scale375Height())
|
||||
}
|
||||
|
||||
for item in menus {
|
||||
let view = ListCellItemView(itemData: item)
|
||||
stackView.addArrangedSubview(view)
|
||||
view.snp.makeConstraints { make in
|
||||
make.height.equalTo(20.scale375Height())
|
||||
make.width.equalToSuperview()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func bindInteraction() {
|
||||
subscribeToast()
|
||||
dropArrowButton.addTarget(self, action: #selector(dropAction(sender: )), for: .touchUpInside)
|
||||
copyButton.addTarget(self, action: #selector(copyAction(sender: )), for: .touchUpInside)
|
||||
}
|
||||
|
||||
@objc func dropAction(sender: UIButton) {
|
||||
route.dismiss(animated: true)
|
||||
}
|
||||
|
||||
@objc func copyAction(sender: UIButton) {
|
||||
var conferenceDetails = title
|
||||
menus.forEach { item in
|
||||
conferenceDetails = conferenceDetails + "\n\(item.titleText) : \(item.messageText)"
|
||||
}
|
||||
UIPasteboard.general.string = conferenceDetails
|
||||
operation.dispatch(action: ViewActions.showToast(payload: ToastInfo(message: .roomInformationCopiedSuccessfully)))
|
||||
}
|
||||
|
||||
deinit {
|
||||
debugPrint("deinit \(self)")
|
||||
}
|
||||
|
||||
@Injected(\.navigation) private var route
|
||||
@Injected(\.conferenceStore) private var operation
|
||||
}
|
||||
|
||||
|
||||
extension InviteEnterRoomView {
|
||||
private func subscribeToast() {
|
||||
operation.toastSubject
|
||||
.removeDuplicates()
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] toast in
|
||||
guard let self = self else { return }
|
||||
var position = TUICSToastPositionBottom
|
||||
switch toast.position {
|
||||
case .center:
|
||||
position = TUICSToastPositionCenter
|
||||
default:
|
||||
break
|
||||
}
|
||||
self.makeToast(toast.message, duration: toast.duration, position: position)
|
||||
}
|
||||
.store(in: &cancellableSet)
|
||||
}}
|
||||
|
||||
private extension String {
|
||||
static let inviteMember = localized("Invite members to join")
|
||||
static let inviteWhenSuccess = localized("Booking successful, invite members to join")
|
||||
static let copyRoomInformation = localized("Copy room information")
|
||||
static let roomInformationCopiedSuccessfully = localized("Room information copied successfully")
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
//
|
||||
// RoomTypeView.swift
|
||||
// TUIRoomKit
|
||||
//
|
||||
// Created by janejntang on 2024/6/14.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Factory
|
||||
|
||||
class RoomTypeView: UIView {
|
||||
var dismissAction: (() -> Void)?
|
||||
|
||||
let freedomButton: UIButton = {
|
||||
let button = UIButton()
|
||||
button.setTitle(.freedomSpeakText, for: .normal)
|
||||
button.titleLabel?.font = UIFont.systemFont(ofSize: 16, weight: .medium)
|
||||
button.setTitleColor(UIColor(0x22262E), for: .normal)
|
||||
button.backgroundColor = UIColor(0xFFFFFF)
|
||||
return button
|
||||
}()
|
||||
|
||||
let raiseHandButton: UIButton = {
|
||||
let button = UIButton()
|
||||
button.setTitle(.raiseHandSpeakText, for: .normal)
|
||||
button.titleLabel?.font = UIFont.systemFont(ofSize: 16, weight: .medium)
|
||||
button.setTitleColor(UIColor(0x22262E), for: .normal)
|
||||
button.backgroundColor = UIColor(0xFFFFFF)
|
||||
return button
|
||||
}()
|
||||
|
||||
let cancelButton: UIButton = {
|
||||
let button = UIButton()
|
||||
button.setTitle(.cancelText, for: .normal)
|
||||
button.titleLabel?.font = UIFont.systemFont(ofSize: 16, weight: .regular)
|
||||
button.setTitleColor(UIColor(0x22262E), for: .normal)
|
||||
button.backgroundColor = UIColor(0xFFFFFF)
|
||||
return button
|
||||
}()
|
||||
|
||||
private var isViewReady: Bool = false
|
||||
override func didMoveToWindow() {
|
||||
super.didMoveToWindow()
|
||||
guard !isViewReady else { return }
|
||||
constructViewHierarchy()
|
||||
activateConstraints()
|
||||
bindInteraction()
|
||||
isViewReady = true
|
||||
}
|
||||
|
||||
func constructViewHierarchy() {
|
||||
addSubview(freedomButton)
|
||||
addSubview(raiseHandButton)
|
||||
addSubview(cancelButton)
|
||||
}
|
||||
|
||||
func activateConstraints() {
|
||||
freedomButton.snp.makeConstraints { make in
|
||||
make.top.equalToSuperview()
|
||||
make.bottom.equalTo(raiseHandButton.snp.top)
|
||||
make.leading.trailing.equalToSuperview()
|
||||
make.height.equalTo(58.scale375Height())
|
||||
}
|
||||
raiseHandButton.snp.makeConstraints { make in
|
||||
make.bottom.equalTo(cancelButton.snp.top)
|
||||
make.leading.trailing.equalToSuperview()
|
||||
make.height.equalTo(58.scale375Height())
|
||||
}
|
||||
cancelButton.snp.makeConstraints { make in
|
||||
make.bottom.equalToSuperview()
|
||||
make.leading.trailing.equalToSuperview()
|
||||
make.height.equalTo(97.scale375Height())
|
||||
}
|
||||
}
|
||||
|
||||
func bindInteraction() {
|
||||
layer.cornerRadius = 12
|
||||
freedomButton.addTarget(self, action: #selector(freedomAction(sender:)), for: .touchUpInside)
|
||||
raiseHandButton.addTarget(self, action: #selector(raiseHandAction(sender:)), for: .touchUpInside)
|
||||
cancelButton.addTarget(self, action: #selector(cancelAction(sender:)), for: .touchUpInside)
|
||||
}
|
||||
|
||||
@objc func freedomAction(sender: UIButton) {
|
||||
var conferenceInfo = store.conferenceInfo
|
||||
conferenceInfo.basicInfo.isSeatEnabled = false
|
||||
store.update(conference: conferenceInfo)
|
||||
dismissAction?()
|
||||
}
|
||||
|
||||
@objc func raiseHandAction(sender: UIButton) {
|
||||
var conferenceInfo = store.conferenceInfo
|
||||
conferenceInfo.basicInfo.isSeatEnabled = true
|
||||
store.update(conference: conferenceInfo)
|
||||
dismissAction?()
|
||||
}
|
||||
|
||||
@objc func cancelAction(sender: UIButton) {
|
||||
dismissAction?()
|
||||
}
|
||||
|
||||
deinit {
|
||||
debugPrint("deinit \(self)")
|
||||
}
|
||||
|
||||
@Injected(\.modifyScheduleStore) var store: ScheduleConferenceStore
|
||||
}
|
||||
|
||||
private extension String {
|
||||
static var raiseHandSpeakText: String {
|
||||
localized("On-stage Speech Room")
|
||||
}
|
||||
static var freedomSpeakText: String {
|
||||
localized("Free Speech Room")
|
||||
}
|
||||
static var cancelText: String {
|
||||
localized("Cancel")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
//
|
||||
// ScheduleBaseCell.swift
|
||||
// TUIRoomKit
|
||||
//
|
||||
// Created by aby on 2024/6/28.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Combine
|
||||
|
||||
class ScheduleBaseCell: UITableViewCell {
|
||||
var cancellableSet = Set<AnyCancellable>()
|
||||
|
||||
override func prepareForReuse() {
|
||||
super.prepareForReuse()
|
||||
cancellableSet.forEach { cancelable in
|
||||
cancelable.cancel()
|
||||
}
|
||||
cancellableSet.removeAll()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
//
|
||||
// CellConfigItem.swift
|
||||
// TUIRoomKit
|
||||
//
|
||||
// Created by janejntang on 2024/6/19.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Combine
|
||||
|
||||
enum ScheduleConfigCellType {
|
||||
case list
|
||||
case switcher
|
||||
case textField
|
||||
case button
|
||||
|
||||
var cellIdentifier: String {
|
||||
switch self {
|
||||
case .list:
|
||||
return ScheduleTabCell.identifier
|
||||
case .switcher:
|
||||
return SwitchCell.identifier
|
||||
case .textField:
|
||||
return TextFieldCell.identifier
|
||||
case .button:
|
||||
return ButtonCell.identifier
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typealias CellSelectClosure = ()->Void
|
||||
typealias CellStateBinderClosure = (UITableViewCell, inout Set<AnyCancellable>)->Void
|
||||
|
||||
protocol CellConfigItem {
|
||||
var cellType: ScheduleConfigCellType { get }
|
||||
var title: String { get }
|
||||
var selectClosure: CellSelectClosure? { get set }
|
||||
var bindStateClosure: CellStateBinderClosure? { get set }
|
||||
var isEnable: Bool { get set }
|
||||
}
|
||||
|
||||
struct ListItem: CellConfigItem {
|
||||
var cellType: ScheduleConfigCellType = .list
|
||||
var title: String
|
||||
var content: String = ""
|
||||
var isEnable: Bool = true
|
||||
|
||||
var showButton: Bool = false
|
||||
var buttonIcon: String = "room_down_arrow1"
|
||||
|
||||
var selectClosure: CellSelectClosure?
|
||||
var bindStateClosure: CellStateBinderClosure?
|
||||
|
||||
var iconList: [String] = []
|
||||
}
|
||||
|
||||
struct SwitchItem: CellConfigItem {
|
||||
var cellType: ScheduleConfigCellType = .switcher
|
||||
var title: String
|
||||
var isOn: Bool = true
|
||||
var isEnable: Bool = true
|
||||
|
||||
var selectClosure: CellSelectClosure?
|
||||
var bindStateClosure: CellStateBinderClosure?
|
||||
}
|
||||
|
||||
struct TextFieldItem: CellConfigItem {
|
||||
var cellType: ScheduleConfigCellType = .textField
|
||||
var title: String
|
||||
var selectClosure: CellSelectClosure?
|
||||
var bindStateClosure: CellStateBinderClosure?
|
||||
var saveTextClosure: ((String) -> Void)?
|
||||
var isEnable: Bool = true
|
||||
var content: String = ""
|
||||
var keyboardType: UIKeyboardType = .default
|
||||
var maxLengthInBytes: Int = 100
|
||||
var placeholder: String = ""
|
||||
}
|
||||
|
||||
struct ButtonItem: CellConfigItem {
|
||||
var cellType: ScheduleConfigCellType = .button
|
||||
var title: String
|
||||
var selectClosure: CellSelectClosure?
|
||||
var bindStateClosure: CellStateBinderClosure?
|
||||
var isEnable: Bool = true
|
||||
var titleColor: UIColor?
|
||||
var backgroudColor: UIColor?
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
//
|
||||
// ScheduleTabCell.swift
|
||||
// TUIRoomKit
|
||||
//
|
||||
// Created by janejntang on 2024/6/5.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Combine
|
||||
|
||||
class ScheduleTabCell: ScheduleBaseCell {
|
||||
static let identifier = "ScheduleTabCell"
|
||||
|
||||
let titleLabel: UILabel = {
|
||||
let view = UILabel()
|
||||
view.backgroundColor = .clear
|
||||
view.textColor = UIColor(0x2B2E38)
|
||||
view.font = UIFont.systemFont(ofSize: 16, weight: .regular)
|
||||
view.textAlignment = isRTL ? .right : .left
|
||||
return view
|
||||
}()
|
||||
|
||||
let messageLabel: UILabel = {
|
||||
let view = UILabel()
|
||||
view.backgroundColor = .clear
|
||||
view.textColor = UIColor(0x2B2E38).withAlphaComponent(0.7)
|
||||
view.font = UIFont.systemFont(ofSize: 16, weight: .regular)
|
||||
view.textAlignment = isRTL ? .left : .right
|
||||
return view
|
||||
}()
|
||||
|
||||
let button: UIButton = {
|
||||
let button = UIButton()
|
||||
let image = UIImage(named: "room_down_arrow1", in: tuiRoomKitBundle(), compatibleWith: nil)
|
||||
button.setImage(image, for: .normal)
|
||||
return button
|
||||
}()
|
||||
|
||||
let avatarsView: UIStackView = {
|
||||
let view = UIStackView()
|
||||
view.axis = .horizontal
|
||||
view.alignment = isRTL ? .leading : .trailing
|
||||
view.spacing = 5
|
||||
return view
|
||||
}()
|
||||
|
||||
private var isViewReady: Bool = false
|
||||
override func didMoveToWindow() {
|
||||
super.didMoveToWindow()
|
||||
guard !isViewReady else { return }
|
||||
isViewReady = true
|
||||
constructViewHierarchy()
|
||||
activateConstraints()
|
||||
setupViewStyle()
|
||||
}
|
||||
|
||||
private func constructViewHierarchy() {
|
||||
contentView.addSubview(titleLabel)
|
||||
contentView.addSubview(button)
|
||||
contentView.addSubview(messageLabel)
|
||||
contentView.addSubview(avatarsView)
|
||||
}
|
||||
|
||||
private func activateConstraints() {
|
||||
titleLabel.snp.makeConstraints { make in
|
||||
make.leading.equalToSuperview().offset(20.scale375())
|
||||
make.width.lessThanOrEqualTo(100.scale375())
|
||||
make.centerY.equalToSuperview()
|
||||
}
|
||||
button.snp.makeConstraints { make in
|
||||
make.trailing.equalToSuperview().offset(-20.scale375())
|
||||
make.width.height.equalTo(16.scale375())
|
||||
make.centerY.equalToSuperview()
|
||||
}
|
||||
messageLabel.snp.makeConstraints() { make in
|
||||
make.trailing.equalTo(button.snp.leading).offset(-5.scale375())
|
||||
make.centerY.equalToSuperview()
|
||||
make.width.lessThanOrEqualTo(170.scale375())
|
||||
}
|
||||
avatarsView.snp.makeConstraints { make in
|
||||
make.trailing.equalTo(messageLabel.snp.leading).offset(-5.scale375())
|
||||
make.centerY.equalToSuperview()
|
||||
make.width.lessThanOrEqualTo(120.scale375())
|
||||
}
|
||||
}
|
||||
|
||||
private func setupViewStyle() {
|
||||
// TODO: - @janejntang use color theme define from design graph.
|
||||
backgroundColor = UIColor(0xFFFFFF)
|
||||
}
|
||||
|
||||
func updateView(item: CellConfigItem) {
|
||||
guard let listItem = item as? ListItem else { return }
|
||||
titleLabel.text = listItem.title
|
||||
messageLabel.text = listItem.content
|
||||
button.setImage(UIImage(named: listItem.buttonIcon, in: tuiRoomKitBundle(), compatibleWith: nil), for: .normal)
|
||||
updateButton(isShown: listItem.showButton)
|
||||
updateStackView(iconList: listItem.iconList)
|
||||
}
|
||||
|
||||
func updateStackView(iconList: [String]) {
|
||||
avatarsView.arrangedSubviews.forEach { view in
|
||||
view.removeFromSuperview()
|
||||
}
|
||||
let placeHolderImage = UIImage(named: "room_default_avatar_rect", in: tuiRoomKitBundle(), compatibleWith: nil)
|
||||
for iconString in iconList {
|
||||
let imageView = UIImageView(image: placeHolderImage)
|
||||
if let url = URL(string: iconString ) {
|
||||
imageView.sd_setImage(with: url, placeholderImage: placeHolderImage)
|
||||
}
|
||||
imageView.snp.makeConstraints { make in
|
||||
make.width.height.equalTo(32.scale375())
|
||||
}
|
||||
avatarsView.addArrangedSubview(imageView)
|
||||
}
|
||||
titleLabel.snp.updateConstraints { make in
|
||||
let maxWidth = iconList.count > 0 ? 80.scale375() : 100.scale375()
|
||||
make.width.lessThanOrEqualTo(maxWidth)
|
||||
}
|
||||
}
|
||||
|
||||
func updateButton(isShown: Bool) {
|
||||
let buttonWidth = isShown ? 16.scale375() : 0
|
||||
button.snp.updateConstraints { make in
|
||||
make.width.equalTo(buttonWidth)
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
debugPrint("deinit \(self)")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
//
|
||||
// SwitchCell.swift
|
||||
// TUIRoomKit
|
||||
//
|
||||
// Created by janejntang on 2024/6/13.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class SwitchCell: ScheduleBaseCell {
|
||||
static let identifier = "SwitchCell"
|
||||
var item: CellConfigItem?
|
||||
|
||||
let titleLabel: UILabel = {
|
||||
let view = UILabel()
|
||||
view.backgroundColor = .clear
|
||||
view.textColor = UIColor(0x2B2E38)
|
||||
view.font = UIFont.systemFont(ofSize: 16, weight: .regular)
|
||||
view.textAlignment = isRTL ? .right : .left
|
||||
return view
|
||||
}()
|
||||
|
||||
let rightSwitch: UISwitch = {
|
||||
let view = UISwitch()
|
||||
view.isOn = true
|
||||
view.onTintColor = UIColor(0x0062E3)
|
||||
return view
|
||||
}()
|
||||
|
||||
private var isViewReady: Bool = false
|
||||
override func didMoveToWindow() {
|
||||
super.didMoveToWindow()
|
||||
guard !isViewReady else { return }
|
||||
isViewReady = true
|
||||
constructViewHierarchy()
|
||||
activateConstraints()
|
||||
bindInteraction()
|
||||
}
|
||||
|
||||
private func constructViewHierarchy() {
|
||||
contentView.addSubview(titleLabel)
|
||||
contentView.addSubview(rightSwitch)
|
||||
}
|
||||
|
||||
private func activateConstraints() {
|
||||
titleLabel.snp.makeConstraints { make in
|
||||
make.leading.equalToSuperview().offset(20.scale375())
|
||||
make.centerY.equalToSuperview()
|
||||
}
|
||||
rightSwitch.snp.makeConstraints { make in
|
||||
make.trailing.equalToSuperview().offset(-20.scale375())
|
||||
make.centerY.equalToSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
private func bindInteraction() {
|
||||
rightSwitch.addTarget(self, action: #selector(switchAction(sender:)), for: .touchUpInside)
|
||||
}
|
||||
|
||||
@objc func switchAction(sender: UISwitch) {
|
||||
item?.selectClosure?()
|
||||
}
|
||||
|
||||
func updateView(item: CellConfigItem) {
|
||||
guard let switchItem = item as? SwitchItem else { return }
|
||||
self.item = item
|
||||
titleLabel.text = switchItem.title
|
||||
rightSwitch.isOn = switchItem.isOn
|
||||
rightSwitch.isEnabled = switchItem.isEnable
|
||||
}
|
||||
|
||||
deinit {
|
||||
debugPrint("deinit:\(self)")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
//
|
||||
// TextFieldCell.swift
|
||||
// TUIRoomKit
|
||||
//
|
||||
// Created by janejntang on 2024/7/2.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Factory
|
||||
|
||||
class TextFieldCell: ScheduleBaseCell {
|
||||
static let identifier = "TextFieldCell"
|
||||
private var item: CellConfigItem?
|
||||
|
||||
let titleLabel: UILabel = {
|
||||
let view = UILabel()
|
||||
view.backgroundColor = .clear
|
||||
view.textColor = UIColor(0x2B2E38)
|
||||
view.font = UIFont.systemFont(ofSize: 16, weight: .regular)
|
||||
view.textAlignment = isRTL ? .right : .left
|
||||
return view
|
||||
}()
|
||||
|
||||
lazy var textField: UITextField = {
|
||||
let view = UITextField(frame: .zero)
|
||||
view.backgroundColor = .clear
|
||||
view.textColor = UIColor(0x2B2E38).withAlphaComponent(0.7)
|
||||
view.font = UIFont.systemFont(ofSize: 16, weight: .regular)
|
||||
view.keyboardType = .default
|
||||
view.textAlignment = isRTL ? .left : .right
|
||||
let toolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: 100.scale375(), height: 50.scale375Height()))
|
||||
toolbar.barStyle = .default
|
||||
toolbar.items = [UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: self, action: nil), UIBarButtonItem(title: .ok, style: .done, target: self, action: #selector(saveTextField))]
|
||||
view.inputAccessoryView = toolbar
|
||||
view.delegate = self
|
||||
return view
|
||||
}()
|
||||
|
||||
private var isViewReady: Bool = false
|
||||
override func didMoveToWindow() {
|
||||
super.didMoveToWindow()
|
||||
guard !isViewReady else { return }
|
||||
isViewReady = true
|
||||
constructViewHierarchy()
|
||||
activateConstraints()
|
||||
}
|
||||
|
||||
private func constructViewHierarchy() {
|
||||
contentView.addSubview(titleLabel)
|
||||
contentView.addSubview(textField)
|
||||
}
|
||||
|
||||
private func activateConstraints() {
|
||||
titleLabel.snp.makeConstraints { make in
|
||||
make.leading.equalToSuperview().offset(20.scale375())
|
||||
make.centerY.equalToSuperview()
|
||||
make.width.lessThanOrEqualTo(100.scale375())
|
||||
}
|
||||
textField.snp.makeConstraints { make in
|
||||
make.trailing.equalToSuperview().offset(-23.scale375())
|
||||
make.leading.equalTo(titleLabel.snp.trailing).offset(20.scale375())
|
||||
make.centerY.equalToSuperview()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func updateView(item: CellConfigItem) {
|
||||
guard let textFieldItem = item as? TextFieldItem else { return }
|
||||
self.item = item
|
||||
titleLabel.text = item.title
|
||||
textField.text = textFieldItem.content
|
||||
textField.isEnabled = textFieldItem.isEnable
|
||||
textField.keyboardType = textFieldItem.keyboardType
|
||||
textField.placeholder = textFieldItem.placeholder
|
||||
}
|
||||
|
||||
@objc func saveTextField() {
|
||||
if let textFieldItem = item as? TextFieldItem, let text = textField.text {
|
||||
textFieldItem.saveTextClosure?(text)
|
||||
}
|
||||
textField.resignFirstResponder()
|
||||
}
|
||||
|
||||
deinit {
|
||||
debugPrint("deinit:\(self)")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension TextFieldCell: UITextFieldDelegate {
|
||||
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
|
||||
guard let textFieldItem = item as? TextFieldItem else { return false }
|
||||
guard let text = textField.text else { return true }
|
||||
if string.count > 0, string.trimmingCharacters(in: .whitespaces).isEmpty, range.location == 0 {
|
||||
return false
|
||||
}
|
||||
let currentLengthInBytes = Array(text.utf8).count
|
||||
let replacementLengthInBytes = Array(string.utf8).count
|
||||
if currentLengthInBytes + replacementLengthInBytes > textFieldItem.maxLengthInBytes {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
private extension String {
|
||||
static let ok = localized("OK")
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
//
|
||||
// TimePickerView.swift
|
||||
// TUIRoomKit
|
||||
//
|
||||
// Created by janejntang on 2024/6/14.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Factory
|
||||
|
||||
class TimePickerView: UIView {
|
||||
var dismissAction: (() -> Void)?
|
||||
var pickerDate: Date?
|
||||
|
||||
let topView: UIView = {
|
||||
let view = UIView()
|
||||
return view
|
||||
}()
|
||||
|
||||
let topLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.text = .startingTimeText
|
||||
label.font = UIFont.systemFont(ofSize: 16, weight: .heavy)
|
||||
label.textColor = UIColor(0x22262E)
|
||||
return label
|
||||
}()
|
||||
|
||||
let cancelButton: UIButton = {
|
||||
let button = UIButton()
|
||||
button.setImage(UIImage(named: "schedule_wrong", in: tuiRoomKitBundle(), compatibleWith: nil), for: .normal)
|
||||
return button
|
||||
}()
|
||||
|
||||
let sureButton: UIButton = {
|
||||
let button = UIButton()
|
||||
button.setImage(UIImage(named: "schedule_right", in: tuiRoomKitBundle(), compatibleWith: nil), for: .normal)
|
||||
return button
|
||||
}()
|
||||
|
||||
lazy var timePickerView: UIDatePicker = {
|
||||
let pickerView = UIDatePicker()
|
||||
pickerView.datePickerMode = .dateAndTime
|
||||
if #available(iOS 14.0, *) {
|
||||
pickerView.preferredDatePickerStyle = .wheels
|
||||
}
|
||||
pickerView.minuteInterval = 5
|
||||
pickerView.timeZone = store.conferenceInfo.timeZone
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateFormat = "MM-dd HH:mm"
|
||||
var minimumTime = Date().timeIntervalSince1970
|
||||
let remainder = minimumTime.remainder(dividingBy: 300)
|
||||
if remainder > 60 || remainder < 0 {
|
||||
minimumTime = Date().addingTimeInterval(150).timeIntervalSince1970
|
||||
minimumTime = minimumTime - minimumTime.remainder(dividingBy: 300)
|
||||
}
|
||||
if let pickerDate = pickerDate {
|
||||
pickerView.date = pickerDate
|
||||
} else {
|
||||
pickerView.date = Date(timeIntervalSince1970: minimumTime)
|
||||
}
|
||||
pickerView.minimumDate = Date(timeIntervalSince1970: minimumTime)
|
||||
return pickerView
|
||||
}()
|
||||
|
||||
private var isViewReady: Bool = false
|
||||
override func didMoveToWindow() {
|
||||
super.didMoveToWindow()
|
||||
backgroundColor = UIColor(0xFFFFFF)
|
||||
guard !isViewReady else { return }
|
||||
constructViewHierarchy()
|
||||
activateConstraints()
|
||||
bindInteraction()
|
||||
isViewReady = true
|
||||
}
|
||||
|
||||
func constructViewHierarchy() {
|
||||
addSubview(topView)
|
||||
topView.addSubview(topLabel)
|
||||
topView.addSubview(cancelButton)
|
||||
topView.addSubview(sureButton)
|
||||
addSubview(timePickerView)
|
||||
}
|
||||
|
||||
func activateConstraints() {
|
||||
topView.snp.makeConstraints { make in
|
||||
make.top.equalToSuperview()
|
||||
make.bottom.equalTo(timePickerView.snp.top)
|
||||
make.leading.trailing.equalToSuperview()
|
||||
make.height.equalTo(54.scale375Height())
|
||||
}
|
||||
topLabel.snp.makeConstraints { make in
|
||||
make.center.equalToSuperview()
|
||||
}
|
||||
cancelButton.snp.makeConstraints { make in
|
||||
make.width.height.equalTo(24.scale375())
|
||||
make.leading.equalToSuperview().offset(20.scale375())
|
||||
make.centerY.equalToSuperview()
|
||||
}
|
||||
sureButton.snp.makeConstraints { make in
|
||||
make.trailing.equalToSuperview().offset(-20.scale375())
|
||||
make.width.height.equalTo(cancelButton)
|
||||
make.centerY.equalTo(cancelButton)
|
||||
}
|
||||
timePickerView.snp.makeConstraints { make in
|
||||
make.leading.trailing.equalToSuperview()
|
||||
make.height.equalTo(300.scale375Height())
|
||||
make.bottom.equalToSuperview().offset(-5.scale375Height())
|
||||
}
|
||||
}
|
||||
|
||||
func bindInteraction() {
|
||||
self.layer.cornerRadius = 12
|
||||
sureButton.addTarget(self, action: #selector(sureAction(sender:)), for: .touchUpInside)
|
||||
cancelButton.addTarget(self, action: #selector(cancelAction(sender:)), for: .touchUpInside)
|
||||
}
|
||||
|
||||
@objc func sureAction(sender: UIButton) {
|
||||
var conferenceInfo = store.conferenceInfo
|
||||
conferenceInfo.scheduleStartTime = UInt(timePickerView.date.timeIntervalSince1970)
|
||||
store.update(conference: conferenceInfo)
|
||||
dismissAction?()
|
||||
}
|
||||
|
||||
@objc func cancelAction(sender: UIButton) {
|
||||
dismissAction?()
|
||||
}
|
||||
|
||||
deinit{
|
||||
debugPrint("deinit:\(self)")
|
||||
}
|
||||
|
||||
@Injected(\.modifyScheduleStore) var store: ScheduleConferenceStore
|
||||
}
|
||||
|
||||
private extension String {
|
||||
static var startingTimeText: String {
|
||||
localized("Starting time")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
//
|
||||
// TimeZoneView.swift
|
||||
// TUIRoomKit
|
||||
//
|
||||
// Created by janejntang on 2024/6/27.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Factory
|
||||
|
||||
class TimeZoneView: UIView {
|
||||
lazy var menus = TimeZone.knownTimeZoneIdentifiers.sorted { id1, id2 in
|
||||
let timeZone1 = TimeZone(identifier: id1)?.secondsFromGMT() ?? 0
|
||||
let timeZone2 = TimeZone(identifier: id2)?.secondsFromGMT() ?? 0
|
||||
return timeZone1 < timeZone2
|
||||
}
|
||||
|
||||
var selectedTimeZone: String? {
|
||||
didSet {
|
||||
self.updateSelected()
|
||||
}
|
||||
}
|
||||
|
||||
let backButton: UIButton = {
|
||||
let button = UIButton()
|
||||
button.setImage(UIImage(named: "room_back_black", in: tuiRoomKitBundle(), compatibleWith: nil), for: .normal)
|
||||
button.contentEdgeInsets = UIEdgeInsets(top: 10.scale375Height(), left: 20.scale375(), bottom: 10.scale375Height(), right: 20.scale375())
|
||||
return button
|
||||
}()
|
||||
|
||||
let topLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.text = .selectTimeZoneText
|
||||
label.font = UIFont.systemFont(ofSize: 16, weight: .medium)
|
||||
label.textColor = UIColor(0x2B2E38)
|
||||
label.textAlignment = .center
|
||||
return label
|
||||
}()
|
||||
|
||||
|
||||
lazy var tableView: UITableView = {
|
||||
let tableView = UITableView(frame: .zero, style: .plain)
|
||||
tableView.separatorStyle = .none
|
||||
tableView.delegate = self
|
||||
tableView.dataSource = self
|
||||
tableView.backgroundColor = UIColor(0x17181F)
|
||||
tableView.register(TimeZoneCell.self, forCellReuseIdentifier: TimeZoneCell.identifier)
|
||||
tableView.backgroundColor = UIColor(0xFFFFFF)
|
||||
return tableView
|
||||
}()
|
||||
|
||||
private var isViewReady: Bool = false
|
||||
override func didMoveToWindow() {
|
||||
super.didMoveToWindow()
|
||||
backgroundColor = UIColor(0xFFFFFF)
|
||||
guard !isViewReady else { return }
|
||||
constructViewHierarchy()
|
||||
activateConstraints()
|
||||
bindInteraction()
|
||||
isViewReady = true
|
||||
selectedTimeZone = self.store.conferenceInfo.timeZone.identifier
|
||||
}
|
||||
|
||||
func constructViewHierarchy() {
|
||||
addSubview(backButton)
|
||||
addSubview(topLabel)
|
||||
addSubview(tableView)
|
||||
}
|
||||
|
||||
func activateConstraints() {
|
||||
backButton.snp.makeConstraints { make in
|
||||
make.top.equalTo(safeAreaLayoutGuide.snp.top)
|
||||
make.leading.equalToSuperview()
|
||||
}
|
||||
topLabel.snp.makeConstraints { make in
|
||||
make.centerY.equalTo(backButton)
|
||||
make.centerX.equalToSuperview()
|
||||
}
|
||||
tableView.snp.makeConstraints { make in
|
||||
make.top.equalTo(backButton.snp.bottom).offset(10.scale375Height())
|
||||
make.leading.equalToSuperview().offset(20.scale375())
|
||||
make.trailing.equalToSuperview().offset(-20.scale375())
|
||||
make.bottom.equalToSuperview().offset(-20.scale375Height())
|
||||
}
|
||||
}
|
||||
|
||||
func bindInteraction() {
|
||||
backButton.addTarget(self, action: #selector(backAction(sender: )), for: .touchUpInside)
|
||||
}
|
||||
|
||||
@objc func backAction(sender: UIButton) {
|
||||
route.pop()
|
||||
}
|
||||
|
||||
private func updateSelected() {
|
||||
if let selectedTimeZone = selectedTimeZone,
|
||||
let index = menus.firstIndex(of: selectedTimeZone) {
|
||||
let indexPath = IndexPath(row: index, section: 0)
|
||||
tableView.selectRow(at: indexPath, animated: false, scrollPosition: .top)
|
||||
}
|
||||
}
|
||||
|
||||
deinit{
|
||||
debugPrint("deinit:\(self)")
|
||||
}
|
||||
|
||||
@Injected(\.navigation) private var route
|
||||
@Injected(\.modifyScheduleStore) var store: ScheduleConferenceStore
|
||||
}
|
||||
|
||||
extension TimeZoneView: UITableViewDataSource {
|
||||
internal func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return menus.count
|
||||
}
|
||||
}
|
||||
|
||||
extension TimeZoneView: UITableViewDelegate {
|
||||
internal func tableView(_ tableView: UITableView,
|
||||
cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: TimeZoneCell.identifier, for: indexPath)
|
||||
if let timeZoneCell = cell as? TimeZoneCell, let timeZone = TimeZone(identifier: menus[indexPath.row]) {
|
||||
timeZoneCell.title = timeZone.getTimeZoneName()
|
||||
|
||||
if selectedTimeZone == menus[indexPath.row] {
|
||||
cell.accessoryType = .checkmark
|
||||
} else {
|
||||
cell.accessoryType = .none
|
||||
}
|
||||
}
|
||||
return cell
|
||||
}
|
||||
|
||||
internal func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
if let timeZone = TimeZone(identifier: menus[indexPath.row]) {
|
||||
var conferenceInfo = store.conferenceInfo
|
||||
conferenceInfo.timeZone = timeZone
|
||||
conferenceInfo.scheduleStartTime = UInt(conferenceInfo.scheduleStartTime)
|
||||
store.update(conference: conferenceInfo)
|
||||
}
|
||||
route.pop()
|
||||
}
|
||||
|
||||
internal func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
|
||||
return 50.scale375Height()
|
||||
}
|
||||
}
|
||||
|
||||
private extension String {
|
||||
static var roomDurationText: String {
|
||||
localized("Room duration")
|
||||
}
|
||||
static let selectTimeZoneText: String = localized("Select time zone")
|
||||
}
|
||||
|
||||
class TimeZoneCell: UITableViewCell {
|
||||
static let identifier = "TimeZoneCell"
|
||||
var title: String = "" {
|
||||
didSet {
|
||||
label.text = title
|
||||
}
|
||||
}
|
||||
let label: UILabel = {
|
||||
let label = UILabel()
|
||||
label.font = UIFont.systemFont(ofSize: 16, weight: .regular)
|
||||
label.textColor = UIColor(0x22262E)
|
||||
return label
|
||||
}()
|
||||
|
||||
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
||||
super.init(style: .default, reuseIdentifier: TimeZoneCell.identifier)
|
||||
self.selectionStyle = .none
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
private var isViewReady: Bool = false
|
||||
override func didMoveToWindow() {
|
||||
super.didMoveToWindow()
|
||||
backgroundColor = UIColor(0xFFFFFF)
|
||||
guard !isViewReady else { return }
|
||||
constructViewHierarchy()
|
||||
activateConstraints()
|
||||
isViewReady = true
|
||||
}
|
||||
|
||||
func constructViewHierarchy() {
|
||||
addSubview(label)
|
||||
}
|
||||
|
||||
func activateConstraints() {
|
||||
label.snp.makeConstraints { make in
|
||||
make.edges.equalToSuperview()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// TimeZoneViewController.swift
|
||||
// TUIRoomKit
|
||||
//
|
||||
// Created by janejntang on 2024/6/27.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class TimeZoneViewController: UIViewController {
|
||||
|
||||
override var shouldAutorotate: Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
|
||||
return .portrait
|
||||
}
|
||||
|
||||
override func loadView() {
|
||||
self.view = TimeZoneView()
|
||||
}
|
||||
|
||||
deinit {
|
||||
debugPrint("deinit \(self)")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user