|
|
@ -182,6 +182,9 @@
|
||||||
30BAB8532FCD337C00C33B5C /* GroupService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30BAB8522FCD337C00C33B5C /* GroupService.swift */; };
|
30BAB8532FCD337C00C33B5C /* GroupService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30BAB8522FCD337C00C33B5C /* GroupService.swift */; };
|
||||||
30BAB8632FCD716C00C33B5C /* JoinGroupVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30BAB8622FCD716C00C33B5C /* JoinGroupVC.swift */; };
|
30BAB8632FCD716C00C33B5C /* JoinGroupVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30BAB8622FCD716C00C33B5C /* JoinGroupVC.swift */; };
|
||||||
30BAB8652FCD718A00C33B5C /* JoinGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30BAB8642FCD718A00C33B5C /* JoinGroupView.swift */; };
|
30BAB8652FCD718A00C33B5C /* JoinGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30BAB8642FCD718A00C33B5C /* JoinGroupView.swift */; };
|
||||||
|
30BF300C2FED09BA00D9CB52 /* ScheduleDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30BF300B2FED09BA00D9CB52 /* ScheduleDetailView.swift */; };
|
||||||
|
30BF300E2FED09CC00D9CB52 /* ScheduleDetailVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30BF300D2FED09CC00D9CB52 /* ScheduleDetailVC.swift */; };
|
||||||
|
30BF30102FED0C8E00D9CB52 /* ScheduleDetailVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30BF300F2FED0C8E00D9CB52 /* ScheduleDetailVM.swift */; };
|
||||||
30C4C0162FDB91B8009215C1 /* CheckPermissionVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30C4C0152FDB91B8009215C1 /* CheckPermissionVC.swift */; };
|
30C4C0162FDB91B8009215C1 /* CheckPermissionVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30C4C0152FDB91B8009215C1 /* CheckPermissionVC.swift */; };
|
||||||
30C4C0192FDBF094009215C1 /* RemoveMemberVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30C4C0182FDBF094009215C1 /* RemoveMemberVC.swift */; };
|
30C4C0192FDBF094009215C1 /* RemoveMemberVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30C4C0182FDBF094009215C1 /* RemoveMemberVC.swift */; };
|
||||||
30C4C01B2FDBF09D009215C1 /* RemoveMemberView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30C4C01A2FDBF09D009215C1 /* RemoveMemberView.swift */; };
|
30C4C01B2FDBF09D009215C1 /* RemoveMemberView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30C4C01A2FDBF09D009215C1 /* RemoveMemberView.swift */; };
|
||||||
|
|
@ -216,6 +219,7 @@
|
||||||
30D87D052FE1336300E958FD /* NavigationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30D87D022FE1336300E958FD /* NavigationView.swift */; };
|
30D87D052FE1336300E958FD /* NavigationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30D87D022FE1336300E958FD /* NavigationView.swift */; };
|
||||||
30D891F52FE22E0600E958FD /* OrderAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30D891F42FE22E0600E958FD /* OrderAPI.swift */; };
|
30D891F52FE22E0600E958FD /* OrderAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30D891F42FE22E0600E958FD /* OrderAPI.swift */; };
|
||||||
30D891F72FE22E6E00E958FD /* OrderService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30D891F62FE22E6E00E958FD /* OrderService.swift */; };
|
30D891F72FE22E6E00E958FD /* OrderService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30D891F62FE22E6E00E958FD /* OrderService.swift */; };
|
||||||
|
30DA36BD2FECC5AB008D5A2C /* CreateScheduleVipPopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30DA36BC2FECC5AB008D5A2C /* CreateScheduleVipPopView.swift */; };
|
||||||
30DC18522FD009CD0041DCD1 /* VipExpenseModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30DC18512FD009CD0041DCD1 /* VipExpenseModel.swift */; };
|
30DC18522FD009CD0041DCD1 /* VipExpenseModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30DC18512FD009CD0041DCD1 /* VipExpenseModel.swift */; };
|
||||||
30DC18542FD00C4A0041DCD1 /* VipRechargeVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30DC18532FD00C4A0041DCD1 /* VipRechargeVM.swift */; };
|
30DC18542FD00C4A0041DCD1 /* VipRechargeVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30DC18532FD00C4A0041DCD1 /* VipRechargeVM.swift */; };
|
||||||
30DC185A2FD11E7A0041DCD1 /* WebOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30DC18562FD11E7A0041DCD1 /* WebOperations.swift */; };
|
30DC185A2FD11E7A0041DCD1 /* WebOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30DC18562FD11E7A0041DCD1 /* WebOperations.swift */; };
|
||||||
|
|
@ -441,6 +445,9 @@
|
||||||
30BAB8522FCD337C00C33B5C /* GroupService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupService.swift; sourceTree = "<group>"; };
|
30BAB8522FCD337C00C33B5C /* GroupService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupService.swift; sourceTree = "<group>"; };
|
||||||
30BAB8622FCD716C00C33B5C /* JoinGroupVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinGroupVC.swift; sourceTree = "<group>"; };
|
30BAB8622FCD716C00C33B5C /* JoinGroupVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinGroupVC.swift; sourceTree = "<group>"; };
|
||||||
30BAB8642FCD718A00C33B5C /* JoinGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinGroupView.swift; sourceTree = "<group>"; };
|
30BAB8642FCD718A00C33B5C /* JoinGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinGroupView.swift; sourceTree = "<group>"; };
|
||||||
|
30BF300B2FED09BA00D9CB52 /* ScheduleDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScheduleDetailView.swift; sourceTree = "<group>"; };
|
||||||
|
30BF300D2FED09CC00D9CB52 /* ScheduleDetailVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScheduleDetailVC.swift; sourceTree = "<group>"; };
|
||||||
|
30BF300F2FED0C8E00D9CB52 /* ScheduleDetailVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScheduleDetailVM.swift; sourceTree = "<group>"; };
|
||||||
30C4C0112FDABC8C009215C1 /* QuickLocation.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = QuickLocation.entitlements; sourceTree = "<group>"; };
|
30C4C0112FDABC8C009215C1 /* QuickLocation.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = QuickLocation.entitlements; sourceTree = "<group>"; };
|
||||||
30C4C0152FDB91B8009215C1 /* CheckPermissionVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckPermissionVC.swift; sourceTree = "<group>"; };
|
30C4C0152FDB91B8009215C1 /* CheckPermissionVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckPermissionVC.swift; sourceTree = "<group>"; };
|
||||||
30C4C0182FDBF094009215C1 /* RemoveMemberVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoveMemberVC.swift; sourceTree = "<group>"; };
|
30C4C0182FDBF094009215C1 /* RemoveMemberVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoveMemberVC.swift; sourceTree = "<group>"; };
|
||||||
|
|
@ -476,6 +483,7 @@
|
||||||
30D87D022FE1336300E958FD /* NavigationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationView.swift; sourceTree = "<group>"; };
|
30D87D022FE1336300E958FD /* NavigationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationView.swift; sourceTree = "<group>"; };
|
||||||
30D891F42FE22E0600E958FD /* OrderAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderAPI.swift; sourceTree = "<group>"; };
|
30D891F42FE22E0600E958FD /* OrderAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderAPI.swift; sourceTree = "<group>"; };
|
||||||
30D891F62FE22E6E00E958FD /* OrderService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderService.swift; sourceTree = "<group>"; };
|
30D891F62FE22E6E00E958FD /* OrderService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderService.swift; sourceTree = "<group>"; };
|
||||||
|
30DA36BC2FECC5AB008D5A2C /* CreateScheduleVipPopView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateScheduleVipPopView.swift; sourceTree = "<group>"; };
|
||||||
30DC18512FD009CD0041DCD1 /* VipExpenseModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VipExpenseModel.swift; sourceTree = "<group>"; };
|
30DC18512FD009CD0041DCD1 /* VipExpenseModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VipExpenseModel.swift; sourceTree = "<group>"; };
|
||||||
30DC18532FD00C4A0041DCD1 /* VipRechargeVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VipRechargeVM.swift; sourceTree = "<group>"; };
|
30DC18532FD00C4A0041DCD1 /* VipRechargeVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VipRechargeVM.swift; sourceTree = "<group>"; };
|
||||||
30DC18552FD11E7A0041DCD1 /* NavigationTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationTitleView.swift; sourceTree = "<group>"; };
|
30DC18552FD11E7A0041DCD1 /* NavigationTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationTitleView.swift; sourceTree = "<group>"; };
|
||||||
|
|
@ -1226,6 +1234,16 @@
|
||||||
path = Join;
|
path = Join;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
30BF300A2FED09A300D9CB52 /* ScheduleDetail */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
30BF300D2FED09CC00D9CB52 /* ScheduleDetailVC.swift */,
|
||||||
|
30BF300F2FED0C8E00D9CB52 /* ScheduleDetailVM.swift */,
|
||||||
|
30BF300B2FED09BA00D9CB52 /* ScheduleDetailView.swift */,
|
||||||
|
);
|
||||||
|
path = ScheduleDetail;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
30C4C0122FDB9178009215C1 /* CheckPermission */ = {
|
30C4C0122FDB9178009215C1 /* CheckPermission */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
|
@ -1283,6 +1301,7 @@
|
||||||
30D74AB92FEA37AD0050EB2C /* ScheduleModel.swift */,
|
30D74AB92FEA37AD0050EB2C /* ScheduleModel.swift */,
|
||||||
30D74ABB2FEA67CE0050EB2C /* CreateSchedule */,
|
30D74ABB2FEA67CE0050EB2C /* CreateSchedule */,
|
||||||
30D74BF22FEB6F5B0050EB2C /* LocationPicker */,
|
30D74BF22FEB6F5B0050EB2C /* LocationPicker */,
|
||||||
|
30BF300A2FED09A300D9CB52 /* ScheduleDetail */,
|
||||||
);
|
);
|
||||||
path = Schedule;
|
path = Schedule;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
|
@ -1294,6 +1313,7 @@
|
||||||
30D74D1E2FEBB09B0050EB2C /* CreateScheduleVM.swift */,
|
30D74D1E2FEBB09B0050EB2C /* CreateScheduleVM.swift */,
|
||||||
30D74ABE2FEA67F30050EB2C /* CreateScheduleView.swift */,
|
30D74ABE2FEA67F30050EB2C /* CreateScheduleView.swift */,
|
||||||
30D74AC02FEA6EEF0050EB2C /* CreateSchedulePopView.swift */,
|
30D74AC02FEA6EEF0050EB2C /* CreateSchedulePopView.swift */,
|
||||||
|
30DA36BC2FECC5AB008D5A2C /* CreateScheduleVipPopView.swift */,
|
||||||
);
|
);
|
||||||
path = CreateSchedule;
|
path = CreateSchedule;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
|
@ -1698,6 +1718,7 @@
|
||||||
305A76C02FCA8C7000227D26 /* BaseNavigationController.swift in Sources */,
|
305A76C02FCA8C7000227D26 /* BaseNavigationController.swift in Sources */,
|
||||||
305A76C12FCA8C7000227D26 /* BaseViewController.swift in Sources */,
|
305A76C12FCA8C7000227D26 /* BaseViewController.swift in Sources */,
|
||||||
3062E8C02FCED7BB00CEF511 /* GroupIconListView.swift in Sources */,
|
3062E8C02FCED7BB00CEF511 /* GroupIconListView.swift in Sources */,
|
||||||
|
30BF300C2FED09BA00D9CB52 /* ScheduleDetailView.swift in Sources */,
|
||||||
305A76C22FCA8C7000227D26 /* BaseViewModel.swift in Sources */,
|
305A76C22FCA8C7000227D26 /* BaseViewModel.swift in Sources */,
|
||||||
30DC185A2FD11E7A0041DCD1 /* WebOperations.swift in Sources */,
|
30DC185A2FD11E7A0041DCD1 /* WebOperations.swift in Sources */,
|
||||||
30DC185B2FD11E7A0041DCD1 /* NavigationTitleView.swift in Sources */,
|
30DC185B2FD11E7A0041DCD1 /* NavigationTitleView.swift in Sources */,
|
||||||
|
|
@ -1799,6 +1820,7 @@
|
||||||
305A76F92FCA8C7000227D26 /* DLToast.swift in Sources */,
|
305A76F92FCA8C7000227D26 /* DLToast.swift in Sources */,
|
||||||
305A76FA2FCA8C7000227D26 /* DLEmptyDataSet.swift in Sources */,
|
305A76FA2FCA8C7000227D26 /* DLEmptyDataSet.swift in Sources */,
|
||||||
305A76FB2FCA8C7000227D26 /* EmptyDataSet.swift in Sources */,
|
305A76FB2FCA8C7000227D26 /* EmptyDataSet.swift in Sources */,
|
||||||
|
30BF300E2FED09CC00D9CB52 /* ScheduleDetailVC.swift in Sources */,
|
||||||
305A76FC2FCA8C7000227D26 /* EmptyDataSetDelegate.swift in Sources */,
|
305A76FC2FCA8C7000227D26 /* EmptyDataSetDelegate.swift in Sources */,
|
||||||
305A76FD2FCA8C7000227D26 /* EmptyDataSetSource.swift in Sources */,
|
305A76FD2FCA8C7000227D26 /* EmptyDataSetSource.swift in Sources */,
|
||||||
30D87CDB2FDFA9EE00E958FD /* MQTTService.swift in Sources */,
|
30D87CDB2FDFA9EE00E958FD /* MQTTService.swift in Sources */,
|
||||||
|
|
@ -1816,6 +1838,8 @@
|
||||||
30BAB8652FCD718A00C33B5C /* JoinGroupView.swift in Sources */,
|
30BAB8652FCD718A00C33B5C /* JoinGroupView.swift in Sources */,
|
||||||
305A77062FCA8C7000227D26 /* MXScrollViewController.m in Sources */,
|
305A77062FCA8C7000227D26 /* MXScrollViewController.m in Sources */,
|
||||||
305A77072FCA8C7000227D26 /* Helper.swift in Sources */,
|
305A77072FCA8C7000227D26 /* Helper.swift in Sources */,
|
||||||
|
30BF30102FED0C8E00D9CB52 /* ScheduleDetailVM.swift in Sources */,
|
||||||
|
30DA36BD2FECC5AB008D5A2C /* CreateScheduleVipPopView.swift in Sources */,
|
||||||
305A77082FCA8C7000227D26 /* PageCollectionViewFlowLayout.swift in Sources */,
|
305A77082FCA8C7000227D26 /* PageCollectionViewFlowLayout.swift in Sources */,
|
||||||
3062E8C42FCFC90F00CEF511 /* CreateGroupVipPopView.swift in Sources */,
|
3062E8C42FCFC90F00CEF511 /* CreateGroupVipPopView.swift in Sources */,
|
||||||
30D74ABA2FEA37AD0050EB2C /* ScheduleModel.swift in Sources */,
|
30D74ABA2FEA37AD0050EB2C /* ScheduleModel.swift in Sources */,
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ internal import Alamofire
|
||||||
|
|
||||||
/// 行程API
|
/// 行程API
|
||||||
enum ItineraryAPI {
|
enum ItineraryAPI {
|
||||||
/// 查询行程
|
/// 查询行程列表
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - follow: 只查看关注的行程
|
/// - follow: 只查看关注的行程
|
||||||
/// - own: 只查看自己创建的行程
|
/// - own: 只查看自己创建的行程
|
||||||
|
|
@ -19,6 +19,23 @@ enum ItineraryAPI {
|
||||||
/// - group_key: 过滤圈子查询
|
/// - group_key: 过滤圈子查询
|
||||||
case query(follow: Bool, own: Bool, history: Bool, group_key: String, page: Int)
|
case query(follow: Bool, own: Bool, history: Bool, group_key: String, page: Int)
|
||||||
|
|
||||||
|
/// 查询行程关注人
|
||||||
|
/// - Parameters:
|
||||||
|
/// - id: 行程ID
|
||||||
|
case queryFollowList(id: String)
|
||||||
|
|
||||||
|
/// 设置行程
|
||||||
|
/// - Parameters:
|
||||||
|
/// - id: 更新时传入
|
||||||
|
/// - group_keys: 只查看关注的行程
|
||||||
|
/// - timestamp: 行程日期
|
||||||
|
/// - points: 行程点
|
||||||
|
case set(id: String, group_keys: [String], timestamp: Int64, points: [[String: Any]])
|
||||||
|
|
||||||
|
/// 删除
|
||||||
|
/// - Parameters:
|
||||||
|
/// - id: 行程ID
|
||||||
|
case delete(id: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ItineraryAPI: MultiTargetProtocol {
|
extension ItineraryAPI: MultiTargetProtocol {
|
||||||
|
|
@ -27,13 +44,21 @@ extension ItineraryAPI: MultiTargetProtocol {
|
||||||
switch self {
|
switch self {
|
||||||
case .query:
|
case .query:
|
||||||
return "mapi/itinerary/route"
|
return "mapi/itinerary/route"
|
||||||
|
case .queryFollowList:
|
||||||
|
return "mapi/itinerary/route/follower"
|
||||||
|
case .set:
|
||||||
|
return "mapi/itinerary/route/set"
|
||||||
|
case .delete:
|
||||||
|
return "mapi/itinerary/route/delete"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var method: Moya.Method {
|
var method: Moya.Method {
|
||||||
switch self {
|
switch self {
|
||||||
case .query:
|
case .query, .queryFollowList:
|
||||||
return .get
|
return .get
|
||||||
|
case .delete:
|
||||||
|
return .delete
|
||||||
default:
|
default:
|
||||||
return .post
|
return .post
|
||||||
}
|
}
|
||||||
|
|
@ -50,6 +75,29 @@ extension ItineraryAPI: MultiTargetProtocol {
|
||||||
params["page"] = page
|
params["page"] = page
|
||||||
params["limit"] = 20
|
params["limit"] = 20
|
||||||
return .requestParameters(parameters: params, encoding: URLEncoding())
|
return .requestParameters(parameters: params, encoding: URLEncoding())
|
||||||
|
|
||||||
|
case let .queryFollowList(id):
|
||||||
|
var params = Parameters()
|
||||||
|
params["route_id"] = id
|
||||||
|
return .requestParameters(parameters: params, encoding: URLEncoding())
|
||||||
|
|
||||||
|
case let .set(id, group_keys, timestamp, points):
|
||||||
|
var params = Parameters()
|
||||||
|
if id.isEmpty {
|
||||||
|
params["timestamp"] = timestamp
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
params["id"] = id
|
||||||
|
}
|
||||||
|
params["group_keys"] = group_keys
|
||||||
|
params["points"] = points
|
||||||
|
|
||||||
|
return .requestParameters(parameters: params, encoding: JSONEncoding())
|
||||||
|
|
||||||
|
case let .delete(id):
|
||||||
|
var params = Parameters()
|
||||||
|
params["id"] = id
|
||||||
|
return .requestParameters(parameters: params, encoding: URLEncoding())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Group_2302@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Group_2302@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Group_2381@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Group_2381@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Group_2385@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Group_2385@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "按钮@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "按钮@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 88 KiB |
|
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Group_2396@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Group_2396@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 429 KiB |
|
After Width: | Height: | Size: 751 KiB |
|
|
@ -55,6 +55,8 @@ enum Route: String {
|
||||||
case checkPermission = "checkPermission"
|
case checkPermission = "checkPermission"
|
||||||
/// 创建行程
|
/// 创建行程
|
||||||
case createSchedule = "createSchedule"
|
case createSchedule = "createSchedule"
|
||||||
|
/// 行程详情
|
||||||
|
case scheduleDetail = "scheduleDetail"
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Route: RouterTarget {
|
extension Route: RouterTarget {
|
||||||
|
|
@ -262,7 +264,16 @@ extension AppRouter: AppRouterProtocol {
|
||||||
|
|
||||||
// MARK: - 创建行程
|
// MARK: - 创建行程
|
||||||
AppRouter.register(Route.createSchedule) { url, parameters in
|
AppRouter.register(Route.createSchedule) { url, parameters in
|
||||||
CreateScheduleVC()
|
let routeId = parameters["routeId"].safeString
|
||||||
|
return CreateScheduleVC(routeId: routeId,
|
||||||
|
scheduleJson: parameters["scheduleJson"].safeDictionary as! [String : Any])
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - 行程详情
|
||||||
|
AppRouter.register(Route.scheduleDetail) { url, parameters in
|
||||||
|
let routeId = parameters["routeId"].safeString
|
||||||
|
return ScheduleDetailVC(routeId: routeId,
|
||||||
|
scheduleJson: parameters["scheduleJson"].safeDictionary as! [String : Any])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,9 @@ import TagListView
|
||||||
|
|
||||||
// MARK: - CreateSchedulePopView
|
// MARK: - CreateSchedulePopView
|
||||||
class CreateSchedulePopView: UIView {
|
class CreateSchedulePopView: UIView {
|
||||||
|
|
||||||
var disposeBag = DisposeBag()
|
var disposeBag = DisposeBag()
|
||||||
|
|
||||||
func setupTagData(_ list: [GroupInfoModel]) {
|
func setupTagData(_ list: [GroupInfoModel]) {
|
||||||
let nameArr = list.map { $0.name }
|
let nameArr = list.map { $0.name }
|
||||||
tagListView.removeAllTags()
|
tagListView.removeAllTags()
|
||||||
|
|
@ -35,18 +35,18 @@ class CreateSchedulePopView: UIView {
|
||||||
addSubview(dateView)
|
addSubview(dateView)
|
||||||
addSubview(detailView)
|
addSubview(detailView)
|
||||||
addSubview(scrollView)
|
addSubview(scrollView)
|
||||||
|
|
||||||
lineView.layoutChain
|
lineView.layoutChain
|
||||||
.top(15)
|
.top(15)
|
||||||
.centerX()
|
.centerX()
|
||||||
.width(36)
|
.width(36)
|
||||||
.height(4)
|
.height(4)
|
||||||
|
|
||||||
dateView.layoutChain
|
dateView.layoutChain
|
||||||
.topToBottomOfView(lineView, offset: 19)
|
.topToBottomOfView(lineView, offset: 19)
|
||||||
.edgesHorzontal()
|
.edgesHorzontal()
|
||||||
.height(22)
|
.height(22)
|
||||||
|
|
||||||
detailView.layoutChain
|
detailView.layoutChain
|
||||||
.topToBottomOfView(dateView, offset: 20)
|
.topToBottomOfView(dateView, offset: 20)
|
||||||
.edgesHorzontal()
|
.edgesHorzontal()
|
||||||
|
|
@ -56,16 +56,16 @@ class CreateSchedulePopView: UIView {
|
||||||
.topToBottomOfView(detailView, offset: 15)
|
.topToBottomOfView(detailView, offset: 15)
|
||||||
.edges(excludingEdge: .top)
|
.edges(excludingEdge: .top)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Views
|
// MARK: - Views
|
||||||
|
|
||||||
lazy var lineView: UIView = {
|
lazy var lineView: UIView = {
|
||||||
let v = UIView()
|
let v = UIView()
|
||||||
v.backgroundColor = UIColor(hexStr: "#EBEBEB")
|
v.backgroundColor = UIColor(hexStr: "#EBEBEB")
|
||||||
v.cornerRadius = 2
|
v.cornerRadius = 2
|
||||||
return v
|
return v
|
||||||
}()
|
}()
|
||||||
|
|
||||||
lazy var dateView: UIView = {
|
lazy var dateView: UIView = {
|
||||||
let view = UIView()
|
let view = UIView()
|
||||||
view.backgroundColor = .clear
|
view.backgroundColor = .clear
|
||||||
|
|
@ -79,7 +79,7 @@ class CreateSchedulePopView: UIView {
|
||||||
dateLab.layoutChain.leftToRightOfView(titleLab).centerY()
|
dateLab.layoutChain.leftToRightOfView(titleLab).centerY()
|
||||||
return view
|
return view
|
||||||
}()
|
}()
|
||||||
|
|
||||||
lazy var dateLab: UILabel = {
|
lazy var dateLab: UILabel = {
|
||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
label.font = .systemFont(ofSize: 14, weight: .medium)
|
label.font = .systemFont(ofSize: 14, weight: .medium)
|
||||||
|
|
@ -90,7 +90,7 @@ class CreateSchedulePopView: UIView {
|
||||||
label.text = fmt.string(from: Date())
|
label.text = fmt.string(from: Date())
|
||||||
return label
|
return label
|
||||||
}()
|
}()
|
||||||
|
|
||||||
lazy var detailView: UIView = {
|
lazy var detailView: UIView = {
|
||||||
let view = UIView()
|
let view = UIView()
|
||||||
view.backgroundColor = .clear
|
view.backgroundColor = .clear
|
||||||
|
|
@ -104,7 +104,7 @@ class CreateSchedulePopView: UIView {
|
||||||
addBtn.layoutChain.right(15).centerY().width(18).height(18)
|
addBtn.layoutChain.right(15).centerY().width(18).height(18)
|
||||||
return view
|
return view
|
||||||
}()
|
}()
|
||||||
|
|
||||||
lazy var addBtn: UIButton = {
|
lazy var addBtn: UIButton = {
|
||||||
let btn = UIButton(type: .custom)
|
let btn = UIButton(type: .custom)
|
||||||
btn.setImage(UIImage(named: "Schedule/add"), for: .normal)
|
btn.setImage(UIImage(named: "Schedule/add"), for: .normal)
|
||||||
|
|
@ -112,7 +112,7 @@ class CreateSchedulePopView: UIView {
|
||||||
btn.extendEdgeInsets = UIEdgeInsets(top: 30, left: 30, bottom: 10, right: 15)
|
btn.extendEdgeInsets = UIEdgeInsets(top: 30, left: 30, bottom: 10, right: 15)
|
||||||
return btn
|
return btn
|
||||||
}()
|
}()
|
||||||
|
|
||||||
lazy var scrollView: UIScrollView = {
|
lazy var scrollView: UIScrollView = {
|
||||||
let view = UIScrollView()
|
let view = UIScrollView()
|
||||||
view.backgroundColor = .white
|
view.backgroundColor = .white
|
||||||
|
|
@ -145,7 +145,7 @@ class CreateSchedulePopView: UIView {
|
||||||
|
|
||||||
return view
|
return view
|
||||||
}()
|
}()
|
||||||
|
|
||||||
lazy var tableView: UITableView = {
|
lazy var tableView: UITableView = {
|
||||||
let tv = UITableView(frame: .zero, style: .plain)
|
let tv = UITableView(frame: .zero, style: .plain)
|
||||||
tv.backgroundColor = .clear
|
tv.backgroundColor = .clear
|
||||||
|
|
@ -157,7 +157,7 @@ class CreateSchedulePopView: UIView {
|
||||||
tv.register(SchedulePointCell.self)
|
tv.register(SchedulePointCell.self)
|
||||||
return tv
|
return tv
|
||||||
}()
|
}()
|
||||||
|
|
||||||
/// 选择分享的圈子
|
/// 选择分享的圈子
|
||||||
lazy var shareGroupView: UIView = {
|
lazy var shareGroupView: UIView = {
|
||||||
let view = UIView()
|
let view = UIView()
|
||||||
|
|
@ -207,22 +207,19 @@ class CreateSchedulePopView: UIView {
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
backgroundColor = .white
|
backgroundColor = .white
|
||||||
|
// 使用原生圆角避免 CAShapeLayer mask 隐式动画导致的弹跳
|
||||||
|
layer.cornerRadius = 30
|
||||||
|
layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
|
||||||
|
layer.masksToBounds = true
|
||||||
setupUI()
|
setupUI()
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
required init?(coder: NSCoder) {
|
||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
override func layoutSubviews() {
|
|
||||||
super.layoutSubviews()
|
|
||||||
layoutIfNeeded()
|
|
||||||
setCornerRadius(corners: [.topLeft, .topRight], withCornerRadii: CGSize(width: 30, height: 30))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// MARK: - SchedulePointCell
|
// MARK: - SchedulePointCell
|
||||||
|
|
||||||
class SchedulePointCell: UITableViewCell {
|
class SchedulePointCell: UITableViewCell {
|
||||||
|
|
@ -231,11 +228,12 @@ class SchedulePointCell: UITableViewCell {
|
||||||
|
|
||||||
var onLocationTap: (() -> Void)?
|
var onLocationTap: (() -> Void)?
|
||||||
var onTimeTap: (() -> Void)?
|
var onTimeTap: (() -> Void)?
|
||||||
|
var onRemarkChanged: ((String) -> Void)?
|
||||||
|
|
||||||
func configure(item: SchedulePointItem, index: Int, total: Int, onDelete: @escaping () -> Void) {
|
func configure(item: SchedulePointItem, index: Int, total: Int, onDelete: @escaping () -> Void) {
|
||||||
disposeBag = DisposeBag()
|
|
||||||
indexLabel.text = "\(index + 1)"
|
indexLabel.text = "\(index + 1)"
|
||||||
locationLabel.text = item.locationName.isEmpty ? "点击选择地点" : item.locationName
|
locationLabel.text = item.street.isEmpty ? "点击选择地点" : item.street
|
||||||
|
remarkTF.text = item.remark
|
||||||
|
|
||||||
locationLabel.rx.tapGesture
|
locationLabel.rx.tapGesture
|
||||||
.when(.recognized)
|
.when(.recognized)
|
||||||
|
|
@ -274,6 +272,10 @@ class SchedulePointCell: UITableViewCell {
|
||||||
|
|
||||||
// MARK: - Init
|
// MARK: - Init
|
||||||
|
|
||||||
|
@objc private func remarkDidChange() {
|
||||||
|
onRemarkChanged?(remarkTF.text ?? "")
|
||||||
|
}
|
||||||
|
|
||||||
override init(style: CellStyle, reuseIdentifier: String?) {
|
override init(style: CellStyle, reuseIdentifier: String?) {
|
||||||
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||||
selectionStyle = .none
|
selectionStyle = .none
|
||||||
|
|
@ -283,6 +285,11 @@ class SchedulePointCell: UITableViewCell {
|
||||||
|
|
||||||
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
||||||
|
|
||||||
|
override func prepareForReuse() {
|
||||||
|
super.prepareForReuse()
|
||||||
|
disposeBag = DisposeBag()
|
||||||
|
}
|
||||||
|
|
||||||
private func setupViews() {
|
private func setupViews() {
|
||||||
contentView.addSubview(topDashView)
|
contentView.addSubview(topDashView)
|
||||||
contentView.addSubview(pointIcon)
|
contentView.addSubview(pointIcon)
|
||||||
|
|
@ -297,7 +304,7 @@ class SchedulePointCell: UITableViewCell {
|
||||||
cardCornerView.addSubview(deleteBtn)
|
cardCornerView.addSubview(deleteBtn)
|
||||||
|
|
||||||
topDashView.layoutChain
|
topDashView.layoutChain
|
||||||
.top().width(2)
|
.top().width(1)
|
||||||
|
|
||||||
pointIcon.layoutChain
|
pointIcon.layoutChain
|
||||||
.centerY()
|
.centerY()
|
||||||
|
|
@ -311,7 +318,7 @@ class SchedulePointCell: UITableViewCell {
|
||||||
bottomDashView.layoutChain
|
bottomDashView.layoutChain
|
||||||
.topToBottomOfView(pointIcon, offset: 10)
|
.topToBottomOfView(pointIcon, offset: 10)
|
||||||
.centerX(pointIcon)
|
.centerX(pointIcon)
|
||||||
.width(2)
|
.width(1)
|
||||||
.bottom()
|
.bottom()
|
||||||
|
|
||||||
// 右侧卡片
|
// 右侧卡片
|
||||||
|
|
@ -349,15 +356,15 @@ class SchedulePointCell: UITableViewCell {
|
||||||
.leftToView(locationLabel)
|
.leftToView(locationLabel)
|
||||||
.rightToLeftOfView(deleteBtn, offset: -10)
|
.rightToLeftOfView(deleteBtn, offset: -10)
|
||||||
.bottom(15)
|
.bottom(15)
|
||||||
|
|
||||||
deleteBtn.layoutChain.centerY(remarkTF)
|
deleteBtn.layoutChain.centerY(remarkTF)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Views
|
// MARK: - Views
|
||||||
private let topDashView: UIView = {
|
private let topDashView: DashLineView = {
|
||||||
let v = UIView()
|
let v = DashLineView()
|
||||||
v.backgroundColor = UIColor(hexStr: "#E0E0E0")
|
|
||||||
v.isHidden = true
|
v.isHidden = true
|
||||||
|
v.backgroundColor = .clear
|
||||||
return v
|
return v
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
@ -367,10 +374,10 @@ class SchedulePointCell: UITableViewCell {
|
||||||
return iv
|
return iv
|
||||||
}()
|
}()
|
||||||
|
|
||||||
private let bottomDashView: UIView = {
|
private let bottomDashView: DashLineView = {
|
||||||
let v = UIView()
|
let v = DashLineView()
|
||||||
v.backgroundColor = UIColor(hexStr: "#E0E0E0")
|
|
||||||
v.isHidden = true
|
v.isHidden = true
|
||||||
|
v.backgroundColor = .clear
|
||||||
return v
|
return v
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
@ -438,3 +445,45 @@ class SchedulePointCell: UITableViewCell {
|
||||||
indexLabel.setCornerRadius(corners: [.bottomRight], withCornerRadii: CGSize(width: 10, height: 10))
|
indexLabel.setCornerRadius(corners: [.bottomRight], withCornerRadii: CGSize(width: 10, height: 10))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 虚线绘制视图(竖线)
|
||||||
|
class DashLineView: UIView {
|
||||||
|
override func layoutSubviews() {
|
||||||
|
super.layoutSubviews()
|
||||||
|
layer.sublayers?.forEach { if $0 is CAShapeLayer { $0.removeFromSuperlayer() } }
|
||||||
|
|
||||||
|
let shapeLayer = CAShapeLayer()
|
||||||
|
shapeLayer.strokeColor = UIColor(hexStr: "#E0E0E0").cgColor
|
||||||
|
shapeLayer.lineWidth = 1
|
||||||
|
shapeLayer.lineDashPattern = [4, 4]
|
||||||
|
shapeLayer.fillColor = nil
|
||||||
|
|
||||||
|
let path = UIBezierPath()
|
||||||
|
path.move(to: CGPoint(x: bounds.midX, y: 0))
|
||||||
|
path.addLine(to: CGPoint(x: bounds.midX, y: bounds.height))
|
||||||
|
shapeLayer.path = path.cgPath
|
||||||
|
|
||||||
|
layer.addSublayer(shapeLayer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 虚线绘制视图(横线)
|
||||||
|
class HorizontalDashLineView: UIView {
|
||||||
|
override func layoutSubviews() {
|
||||||
|
super.layoutSubviews()
|
||||||
|
layer.sublayers?.forEach { if $0 is CAShapeLayer { $0.removeFromSuperlayer() } }
|
||||||
|
|
||||||
|
let shapeLayer = CAShapeLayer()
|
||||||
|
shapeLayer.strokeColor = UIColor(hexStr: "#E0E0E0").cgColor
|
||||||
|
shapeLayer.lineWidth = 1
|
||||||
|
shapeLayer.lineDashPattern = [4, 4]
|
||||||
|
shapeLayer.fillColor = nil
|
||||||
|
|
||||||
|
let path = UIBezierPath()
|
||||||
|
path.move(to: CGPoint(x: 0, y: bounds.midY))
|
||||||
|
path.addLine(to: CGPoint(x: bounds.maxX, y: bounds.midY))
|
||||||
|
shapeLayer.path = path.cgPath
|
||||||
|
|
||||||
|
layer.addSublayer(shapeLayer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ import RxGesture
|
||||||
import AMapNaviKit
|
import AMapNaviKit
|
||||||
import AMapSearchKit
|
import AMapSearchKit
|
||||||
import TagListView
|
import TagListView
|
||||||
|
import ObjectMapper
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
class CreateScheduleVC: BaseViewController, MAMapViewDelegate {
|
class CreateScheduleVC: BaseViewController, MAMapViewDelegate {
|
||||||
|
|
@ -24,7 +25,7 @@ class CreateScheduleVC: BaseViewController, MAMapViewDelegate {
|
||||||
override var isNavigationBarHidden: Bool { true }
|
override var isNavigationBarHidden: Bool { true }
|
||||||
|
|
||||||
fileprivate var rootView: CreateScheduleView!
|
fileprivate var rootView: CreateScheduleView!
|
||||||
private let viewModel = CreateScheduleVM()
|
private let viewModel: CreateScheduleVM
|
||||||
private var popView: CreateSchedulePopView { rootView.createSchedulePopView }
|
private var popView: CreateSchedulePopView { rootView.createSchedulePopView }
|
||||||
|
|
||||||
override func loadView() {
|
override func loadView() {
|
||||||
|
|
@ -39,8 +40,13 @@ class CreateScheduleVC: BaseViewController, MAMapViewDelegate {
|
||||||
popView.tagListView.delegate = self
|
popView.tagListView.delegate = self
|
||||||
setupMap()
|
setupMap()
|
||||||
bindViewModel()
|
bindViewModel()
|
||||||
|
reactiveAction()
|
||||||
requestGroupInfo()
|
requestGroupInfo()
|
||||||
|
|
||||||
|
guard let _ = viewModel.scheduModel else { return }
|
||||||
|
rootView.navTitleLabel.text = "编辑行程"
|
||||||
|
rootView.deleteBtn.isHidden = false
|
||||||
|
// requestFollowList(id: model.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidDisappear(_ animated: Bool) {
|
override func viewDidDisappear(_ animated: Bool) {
|
||||||
|
|
@ -52,12 +58,74 @@ class CreateScheduleVC: BaseViewController, MAMapViewDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Actions
|
||||||
|
private func reactiveAction() {
|
||||||
|
rootView.deleteBtn.rx.tap.subscribe(onNext: { _ in
|
||||||
|
guard let model = self.viewModel.scheduModel else { return }
|
||||||
|
self.showConfirmPop(title: "确定要删除吗?",
|
||||||
|
message: "此条行程路线将被永久删除",
|
||||||
|
confirmText: "删除",
|
||||||
|
confirmBlock: {
|
||||||
|
self.requestDelete(id: model.id)
|
||||||
|
}, cancelText: "取消")
|
||||||
|
}).disposed(by: disposeBag)
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - API
|
// MARK: - API
|
||||||
|
private func requestFollowList(id: String) {
|
||||||
|
dl.showLoading()
|
||||||
|
ItineraryService.queryFollowList(id: id).subscribe { response in
|
||||||
|
self.dl.dismiss()
|
||||||
|
}.disposed(by: disposeBag)
|
||||||
|
}
|
||||||
|
|
||||||
private func requestGroupInfo() {
|
private func requestGroupInfo() {
|
||||||
GroupService.groupInfo().subscribe { response in
|
GroupService.groupInfo().subscribe { response in
|
||||||
guard let model = response.model else { return }
|
guard let model = response.model else { return }
|
||||||
self.groupList = model.groups
|
self.groupList = model.groups
|
||||||
self.popView.setupTagData(model.groups)
|
self.popView.setupTagData(model.groups)
|
||||||
|
// 编辑模式:预选中已分享的圈子
|
||||||
|
#if !targetEnvironment(simulator)
|
||||||
|
if !self.viewModel.selectedGroupKeys.isEmpty {
|
||||||
|
for (i, group) in self.groupList.enumerated() {
|
||||||
|
guard i < self.popView.tagListView.tagViews.count,
|
||||||
|
self.viewModel.selectedGroupKeys.contains(group.group_key) else { continue }
|
||||||
|
self.popView.tagListView.tagViews[i].isSelected = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}.disposed(by: disposeBag)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func requestSet(id: String="", points: [[String: Any]]) {
|
||||||
|
let selectedDate = Int64(viewModel.selectedDate.value.timeIntervalSince1970 * 1000)
|
||||||
|
dl.showLoading()
|
||||||
|
ItineraryService.set(id: id,
|
||||||
|
group_keys: viewModel.selectedGroupKeys,
|
||||||
|
timestamp: selectedDate,
|
||||||
|
points: points).subscribe(onNext: { response in
|
||||||
|
self.dl.dismiss()
|
||||||
|
self.dl.show(text: id.isEmpty ? "创建成功" : "更新成功") {
|
||||||
|
AppRouter.shared.popOrDismiss()
|
||||||
|
}
|
||||||
|
}, onError: { error in
|
||||||
|
guard let code = error.underlyingError?.code else { return }
|
||||||
|
if code == 20010 { // "创建的行程数达到上限"
|
||||||
|
CreateScheduleVipPopView.show()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
self.dl.show(text: error.localizedDescription)
|
||||||
|
}
|
||||||
|
}).disposed(by: disposeBag)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func requestDelete(id: String) {
|
||||||
|
dl.showLoading()
|
||||||
|
ItineraryService.delete(id: id).subscribe { response in
|
||||||
|
self.dl.dismiss()
|
||||||
|
self.dl.show(text: "删除成功") {
|
||||||
|
AppRouter.shared.popOrDismiss()
|
||||||
|
}
|
||||||
}.disposed(by: disposeBag)
|
}.disposed(by: disposeBag)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -67,6 +135,14 @@ class CreateScheduleVC: BaseViewController, MAMapViewDelegate {
|
||||||
RxTableViewSectionedReloadDataSource<SchedulePointSection>(
|
RxTableViewSectionedReloadDataSource<SchedulePointSection>(
|
||||||
configureCell: { [weak self] _, tv, indexPath, item in
|
configureCell: { [weak self] _, tv, indexPath, item in
|
||||||
let cell: SchedulePointCell = tv.dequeueReusableCell(for: indexPath)
|
let cell: SchedulePointCell = tv.dequeueReusableCell(for: indexPath)
|
||||||
|
|
||||||
|
cell.configure(item: item, index: indexPath.row,
|
||||||
|
total: self?.viewModel.pointsRelay.value.count ?? 0,
|
||||||
|
onDelete: { [weak self] in
|
||||||
|
self?.viewModel.deletePointAt.onNext(indexPath.row)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 到达时间
|
||||||
cell.onTimeTap = { [weak self] in
|
cell.onTimeTap = { [weak self] in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
let picker = BRDatePickerView(pickerMode: .HM)
|
let picker = BRDatePickerView(pickerMode: .HM)
|
||||||
|
|
@ -92,7 +168,7 @@ class CreateScheduleVC: BaseViewController, MAMapViewDelegate {
|
||||||
}
|
}
|
||||||
picker.show()
|
picker.show()
|
||||||
}
|
}
|
||||||
|
// 地点
|
||||||
cell.onLocationTap = { [weak self] in
|
cell.onLocationTap = { [weak self] in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
let coord = item.latitude != 0 || item.longitude != 0
|
let coord = item.latitude != 0 || item.longitude != 0
|
||||||
|
|
@ -110,21 +186,34 @@ class CreateScheduleVC: BaseViewController, MAMapViewDelegate {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
vc.onPickedLocation = { picked in
|
vc.onPickedLocation = { picked in
|
||||||
self.viewModel.updatePointLocation(index: indexPath.row, name: picked.name, address: picked.address)
|
|
||||||
// 补充坐标
|
|
||||||
var list = self.viewModel.pointsRelay.value
|
var list = self.viewModel.pointsRelay.value
|
||||||
guard indexPath.row < list.count else { return }
|
guard indexPath.row < list.count else { return }
|
||||||
|
list[indexPath.row].locationName = picked.name
|
||||||
|
list[indexPath.row].address = picked.address
|
||||||
list[indexPath.row].latitude = picked.coordinate.latitude
|
list[indexPath.row].latitude = picked.coordinate.latitude
|
||||||
list[indexPath.row].longitude = picked.coordinate.longitude
|
list[indexPath.row].longitude = picked.coordinate.longitude
|
||||||
|
list[indexPath.row].province = picked.province
|
||||||
|
list[indexPath.row].city = picked.city
|
||||||
|
list[indexPath.row].district = picked.district
|
||||||
|
list[indexPath.row].street = picked.street
|
||||||
|
list[indexPath.row].country = picked.country
|
||||||
|
list[indexPath.row].formatted_address = picked.formatted_address
|
||||||
self.viewModel.pointsRelay.accept(list)
|
self.viewModel.pointsRelay.accept(list)
|
||||||
}
|
}
|
||||||
self.present(vc, animated: true)
|
self.present(vc, animated: true)
|
||||||
}
|
}
|
||||||
cell.configure(item: item, index: indexPath.row,
|
|
||||||
total: self?.viewModel.pointsRelay.value.count ?? 0,
|
// 备注
|
||||||
onDelete: { [weak self] in
|
cell.remarkTF.rx.controlEvent(.editingDidEnd)
|
||||||
self?.viewModel.deletePointAt.onNext(indexPath.row)
|
.subscribe(onNext: {
|
||||||
})
|
guard let self = self, let text = cell.remarkTF.text else { return }
|
||||||
|
var list = self.viewModel.pointsRelay.value
|
||||||
|
guard indexPath.row < list.count else { return }
|
||||||
|
list[indexPath.row].remark = text
|
||||||
|
self.viewModel.pointsRelay.accept(list)
|
||||||
|
})
|
||||||
|
.disposed(by: cell.disposeBag)
|
||||||
|
|
||||||
return cell
|
return cell
|
||||||
})
|
})
|
||||||
}()
|
}()
|
||||||
|
|
@ -184,9 +273,6 @@ class CreateScheduleVC: BaseViewController, MAMapViewDelegate {
|
||||||
guard hasTime.count == points.count else { DLToast.show(text: "请为每个行程点选择到达时间"); return }
|
guard hasTime.count == points.count else { DLToast.show(text: "请为每个行程点选择到达时间"); return }
|
||||||
guard !viewModel.selectedGroupKeys.isEmpty else { DLToast.show(text: "请选择分享的圈子"); return }
|
guard !viewModel.selectedGroupKeys.isEmpty else { DLToast.show(text: "请选择分享的圈子"); return }
|
||||||
|
|
||||||
// 时间戳(毫秒)
|
|
||||||
let ts = Int64(viewModel.selectedDate.value.timeIntervalSince1970 * 1000)
|
|
||||||
|
|
||||||
// 生成 points 数组
|
// 生成 points 数组
|
||||||
let pointsJSON: [[String: Any]] = points.map { p in
|
let pointsJSON: [[String: Any]] = points.map { p in
|
||||||
let expectedTs = p.expectedTime.map { Int64($0.timeIntervalSince1970 * 1000) } ?? 0
|
let expectedTs = p.expectedTime.map { Int64($0.timeIntervalSince1970 * 1000) } ?? 0
|
||||||
|
|
@ -205,13 +291,7 @@ class CreateScheduleVC: BaseViewController, MAMapViewDelegate {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
let params: [String: Any] = [
|
requestSet(id: viewModel.scheduModel?.id ?? "", points: pointsJSON)
|
||||||
"group_keys": viewModel.selectedGroupKeys,
|
|
||||||
"timestamp": ts,
|
|
||||||
"points": pointsJSON
|
|
||||||
]
|
|
||||||
print("📋 Create schedule: \(params)")
|
|
||||||
DLToast.show(text: "创建成功")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setupMap() {
|
private func setupMap() {
|
||||||
|
|
@ -362,6 +442,17 @@ class CreateScheduleVC: BaseViewController, MAMapViewDelegate {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Init
|
||||||
|
init(routeId: String, scheduleJson: [String: Any]) {
|
||||||
|
let model = ScheduleModel.init(JSON: scheduleJson)
|
||||||
|
self.viewModel = CreateScheduleVM(scheduleJson.isEmpty ? nil : model)
|
||||||
|
super.init(nibName: nil, bundle: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - TagListViewDelegate
|
// MARK: - TagListViewDelegate
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,8 @@ class CreateScheduleVM {
|
||||||
|
|
||||||
let disposeBag = DisposeBag()
|
let disposeBag = DisposeBag()
|
||||||
|
|
||||||
|
let scheduModel: ScheduleModel?
|
||||||
|
|
||||||
// MARK: - Input
|
// MARK: - Input
|
||||||
let addPointTapped = PublishSubject<Void>()
|
let addPointTapped = PublishSubject<Void>()
|
||||||
let deletePointAt = PublishSubject<Int>()
|
let deletePointAt = PublishSubject<Int>()
|
||||||
|
|
@ -57,10 +59,17 @@ class CreateScheduleVM {
|
||||||
let pointsRelay: BehaviorRelay<[SchedulePointItem]>
|
let pointsRelay: BehaviorRelay<[SchedulePointItem]>
|
||||||
let dateString: Observable<String>
|
let dateString: Observable<String>
|
||||||
|
|
||||||
init() {
|
init(_ model: ScheduleModel?) {
|
||||||
pointsRelay = BehaviorRelay<[SchedulePointItem]>(value: [
|
self.scheduModel = model
|
||||||
SchedulePointItem()
|
|
||||||
])
|
// 编辑模式:从 model 回显数据
|
||||||
|
if let m = model, !m.points.isEmpty {
|
||||||
|
pointsRelay = BehaviorRelay<[SchedulePointItem]>(value: m.points.map { $0.toSchedulePointItem() })
|
||||||
|
selectedDate.accept(Date(timeIntervalSince1970: TimeInterval(m.timestamp) / 1000))
|
||||||
|
selectedGroupKeys = m.groups.map { $0.group_key }
|
||||||
|
} else {
|
||||||
|
pointsRelay = BehaviorRelay<[SchedulePointItem]>(value: [SchedulePointItem()])
|
||||||
|
}
|
||||||
|
|
||||||
dateString = selectedDate
|
dateString = selectedDate
|
||||||
.map {
|
.map {
|
||||||
|
|
@ -103,12 +112,4 @@ class CreateScheduleVM {
|
||||||
.disposed(by: disposeBag)
|
.disposed(by: disposeBag)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 从 LocationPicker 回调更新某个点的位置
|
|
||||||
func updatePointLocation(index: Int, name: String, address: String) {
|
|
||||||
var list = pointsRelay.value
|
|
||||||
guard index < list.count else { return }
|
|
||||||
list[index].locationName = name
|
|
||||||
list[index].address = address
|
|
||||||
pointsRelay.accept(list)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,14 +31,8 @@ class CreateScheduleView: UIView {
|
||||||
AppRouter.shared.popOrDismiss()
|
AppRouter.shared.popOrDismiss()
|
||||||
}).disposed(by: disposeBag)
|
}).disposed(by: disposeBag)
|
||||||
|
|
||||||
// scrollView 到达顶部后,由 pan 手势接管 PopView 滑动
|
// 嵌套滑动协调(参考 GroupView 的 PanScrollView 模式)
|
||||||
createSchedulePopView.scrollView.rx.contentOffset
|
createSchedulePopView.scrollView.delegate = self
|
||||||
.observe(on: MainScheduler.asyncInstance)
|
|
||||||
.subscribe(onNext: { [weak self] offset in
|
|
||||||
guard let self = self, self.isSubCanScroll, offset.y <= 0 else { return }
|
|
||||||
self.createSchedulePopView.scrollView.setContentOffset(.zero, animated: false)
|
|
||||||
})
|
|
||||||
.disposed(by: disposeBag)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setupUI() {
|
private func setupUI() {
|
||||||
|
|
@ -49,6 +43,7 @@ class CreateScheduleView: UIView {
|
||||||
addSubview(navBarView)
|
addSubview(navBarView)
|
||||||
navBarView.addSubview(backBtn)
|
navBarView.addSubview(backBtn)
|
||||||
navBarView.addSubview(navTitleLabel)
|
navBarView.addSubview(navTitleLabel)
|
||||||
|
navBarView.addSubview(deleteBtn)
|
||||||
addSubview(createSchedulePopView)
|
addSubview(createSchedulePopView)
|
||||||
|
|
||||||
navBgView.layoutChain
|
navBgView.layoutChain
|
||||||
|
|
@ -68,6 +63,12 @@ class CreateScheduleView: UIView {
|
||||||
.top(kStatusBarHeight + 12)
|
.top(kStatusBarHeight + 12)
|
||||||
.centerX()
|
.centerX()
|
||||||
|
|
||||||
|
deleteBtn.layoutChain
|
||||||
|
.centerY(navTitleLabel)
|
||||||
|
.right(15)
|
||||||
|
.width(16)
|
||||||
|
.height(16)
|
||||||
|
|
||||||
#if !targetEnvironment(simulator)
|
#if !targetEnvironment(simulator)
|
||||||
mapView.layoutChain
|
mapView.layoutChain
|
||||||
.top()
|
.top()
|
||||||
|
|
@ -102,14 +103,24 @@ class CreateScheduleView: UIView {
|
||||||
|
|
||||||
case .changed:
|
case .changed:
|
||||||
let newTop = panStartTop + pan.translation(in: self).y
|
let newTop = panStartTop + pan.translation(in: self).y
|
||||||
|
let scrollOffset = createSchedulePopView.scrollView.contentOffset.y
|
||||||
|
|
||||||
if isSubCanScroll {
|
if isSubCanScroll {
|
||||||
let scrollOffset = self.createSchedulePopView.scrollView.contentOffset.y
|
// 内容正在滑动,不移动 PopView
|
||||||
// 内容未到顶部时,不干涉 scrollView 的滑动
|
|
||||||
if scrollOffset > 0 { return }
|
if scrollOffset > 0 { return }
|
||||||
// 到顶部后切为 view 拖拽
|
// 内容滑到顶部,切回 Pan 拖拽(用户下拉时 velocity > 0)
|
||||||
isSubCanScroll = false
|
if pan.velocity(in: self).y > 0 || createSchedulePopView.frame.minY > popUpLimit + 1 {
|
||||||
panStartTop = createSchedulePopView.frame.minY
|
isSubCanScroll = false
|
||||||
|
panStartTop = createSchedulePopView.frame.minY
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// PopView 在顶部且继续上滑 → 激活内容滑动
|
||||||
|
if createSchedulePopView.frame.minY <= popUpLimit && newTop <= popUpLimit {
|
||||||
|
isSubCanScroll = true
|
||||||
|
panStartTop = createSchedulePopView.frame.minY
|
||||||
|
topConstraint.constant = popUpLimit
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let clamped = max(popUpLimit, min(popDownLimit, newTop))
|
let clamped = max(popUpLimit, min(popDownLimit, newTop))
|
||||||
|
|
@ -119,23 +130,32 @@ class CreateScheduleView: UIView {
|
||||||
let velocity = pan.velocity(in: self)
|
let velocity = pan.velocity(in: self)
|
||||||
let isNearUp = abs(createSchedulePopView.frame.minY - popUpLimit) < abs(createSchedulePopView.frame.minY - popDownLimit)
|
let isNearUp = abs(createSchedulePopView.frame.minY - popUpLimit) < abs(createSchedulePopView.frame.minY - popDownLimit)
|
||||||
let target: CGFloat
|
let target: CGFloat
|
||||||
if abs(velocity.y) > 200 {
|
if createSchedulePopView.frame.minY <= popUpLimit + 5 {
|
||||||
|
// 顶部附近:由位置决定去向,速度不触发回收(避免 scrollView 松手误回收)
|
||||||
|
target = isNearUp ? popUpLimit : popDownLimit
|
||||||
|
} else if abs(velocity.y) > 200 {
|
||||||
target = velocity.y < 0 ? popUpLimit : popDownLimit
|
target = velocity.y < 0 ? popUpLimit : popDownLimit
|
||||||
} else {
|
} else {
|
||||||
target = isNearUp ? popUpLimit : popDownLimit
|
target = isNearUp ? popUpLimit : popDownLimit
|
||||||
}
|
}
|
||||||
topConstraint.constant = target
|
topConstraint.constant = target
|
||||||
|
|
||||||
// 非最大化时禁用 scollView 滚动
|
// 动画前设置 scrollView 状态,避免弹跳
|
||||||
let atTop = target == self.popUpLimit
|
let atTop = target == self.popUpLimit
|
||||||
createSchedulePopView.scrollView.isScrollEnabled = atTop
|
|
||||||
if !atTop {
|
if !atTop {
|
||||||
|
isSubCanScroll = false
|
||||||
|
createSchedulePopView.scrollView.isScrollEnabled = false
|
||||||
createSchedulePopView.scrollView.setContentOffset(.zero, animated: false)
|
createSchedulePopView.scrollView.setContentOffset(.zero, animated: false)
|
||||||
}
|
}
|
||||||
|
isSubCanScroll = atTop
|
||||||
|
|
||||||
UIView.animate(withDuration: 0.2, delay: 0,
|
UIView.animate(withDuration: 0.35, delay: 0,
|
||||||
options: [.curveEaseInOut, .allowUserInteraction]) {
|
usingSpringWithDamping: 0.85,
|
||||||
|
initialSpringVelocity: abs(velocity.y) / 1000,
|
||||||
|
options: [.allowUserInteraction]) {
|
||||||
self.layoutIfNeeded()
|
self.layoutIfNeeded()
|
||||||
|
} completion: { _ in
|
||||||
|
self.createSchedulePopView.scrollView.isScrollEnabled = atTop
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
@ -181,6 +201,14 @@ class CreateScheduleView: UIView {
|
||||||
label.text = "创建行程"
|
label.text = "创建行程"
|
||||||
return label
|
return label
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
lazy var deleteBtn: UIButton = {
|
||||||
|
let btn = UIButton()
|
||||||
|
btn.setImage(UIImage(named: "Common/delete"), for: .normal)
|
||||||
|
btn.extendEdgeInsets = UIEdgeInsets(top: 15, left: 20, bottom: 15, right: 15)
|
||||||
|
btn.isHidden = true
|
||||||
|
return btn
|
||||||
|
}()
|
||||||
|
|
||||||
#if !targetEnvironment(simulator)
|
#if !targetEnvironment(simulator)
|
||||||
lazy var mapView: MAMapView! = {
|
lazy var mapView: MAMapView! = {
|
||||||
|
|
@ -215,7 +243,7 @@ class CreateScheduleView: UIView {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - UIScrollViewDelegate
|
// MARK: - UIScrollViewDelegate(参考 GroupView 嵌套滑动协调)
|
||||||
extension CreateScheduleView: UIScrollViewDelegate {
|
extension CreateScheduleView: UIScrollViewDelegate {
|
||||||
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
||||||
// 内容已有偏移时才允许子滚动
|
// 内容已有偏移时才允许子滚动
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,120 @@
|
||||||
|
//
|
||||||
|
// CreateScheduleVipPopView.swift
|
||||||
|
// QuickLocation
|
||||||
|
//
|
||||||
|
// Created by 八条 on 2026/6/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import RxSwift
|
||||||
|
import RxCocoa
|
||||||
|
|
||||||
|
class CreateScheduleVipPopView: UIView {
|
||||||
|
|
||||||
|
private static let shared = CreateScheduleVipPopView(frame: CGRect(origin: .zero, size: kScreenSize))
|
||||||
|
|
||||||
|
var disposeBag = DisposeBag()
|
||||||
|
|
||||||
|
static func show() {
|
||||||
|
guard let superView = kKeyWindow else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if CreateScheduleVipPopView.shared.superview != nil {
|
||||||
|
CreateScheduleVipPopView.shared.removeFromSuperview()
|
||||||
|
CreateScheduleVipPopView.shared.bgView.frame = .zero
|
||||||
|
}
|
||||||
|
CreateScheduleVipPopView.shared.bgView.alpha = 1
|
||||||
|
CreateScheduleVipPopView.shared.bgView.frame = CGRect(x: 0, y: 0, width: kScreenWidth, height: kScreenHeight)
|
||||||
|
superView.addSubview(CreateScheduleVipPopView.shared)
|
||||||
|
superView.bringSubviewToFront(CreateScheduleVipPopView.shared)
|
||||||
|
|
||||||
|
UIView.animate(withDuration: 0.25) {
|
||||||
|
CreateScheduleVipPopView.shared.bgView.alpha = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 关闭
|
||||||
|
static func dismiss() {
|
||||||
|
guard CreateScheduleVipPopView.shared.superview != nil else { return }
|
||||||
|
|
||||||
|
UIView.animate(withDuration: 0.25, delay: 0, options: [.curveEaseIn]) {
|
||||||
|
CreateScheduleVipPopView.shared.bgView.alpha = 0
|
||||||
|
} completion: { _ in
|
||||||
|
CreateScheduleVipPopView.shared.removeFromSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setupRx() {
|
||||||
|
upgradedBtn.rx.tap.subscribe(onNext: { _ in
|
||||||
|
CreateScheduleVipPopView.dismiss()
|
||||||
|
AppRouter.push(Route.vipRecharge)
|
||||||
|
}).disposed(by: disposeBag)
|
||||||
|
|
||||||
|
closeBtn.rx.tap.subscribe(onNext: { _ in
|
||||||
|
CreateScheduleVipPopView.dismiss()
|
||||||
|
}).disposed(by: disposeBag)
|
||||||
|
}
|
||||||
|
|
||||||
|
private lazy var bgView: UIView = {
|
||||||
|
let view = UIView()
|
||||||
|
view.backgroundColor = .black.withAlphaComponent(0.5)
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var vipImgView: UIImageView = {
|
||||||
|
let view = UIImageView()
|
||||||
|
view.image = UIImage(named: "Schedule/vip_pop")
|
||||||
|
view.contentMode = .scaleAspectFill
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var upgradedBtn: UIButton = {
|
||||||
|
let btn = UIButton(type: .custom)
|
||||||
|
btn.backgroundColor = .clear
|
||||||
|
btn.setBackgroundImage(UIImage(named: "Schedule/upgrade_bg"), for: .normal)
|
||||||
|
return btn
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var closeBtn: UIButton = {
|
||||||
|
let btn = UIButton(type: .custom)
|
||||||
|
btn.backgroundColor = .clear
|
||||||
|
btn.setBackgroundImage(UIImage(named: "Group/close"), for: .normal)
|
||||||
|
btn.extendEdgeInsets = UIEdgeInsets(top: 10, left: 100, bottom: 100, right: 100)
|
||||||
|
return btn
|
||||||
|
}()
|
||||||
|
|
||||||
|
// MARK: - Init
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
backgroundColor = .clear
|
||||||
|
addSubview(bgView)
|
||||||
|
bgView.addSubview(vipImgView)
|
||||||
|
bgView.addSubview(upgradedBtn)
|
||||||
|
bgView.addSubview(closeBtn)
|
||||||
|
|
||||||
|
vipImgView.layoutChain
|
||||||
|
.centerY()
|
||||||
|
.edgesHorzontal(25)
|
||||||
|
.heightToWidth(814/648)
|
||||||
|
|
||||||
|
upgradedBtn.layoutChain
|
||||||
|
.topToBottomOfView(vipImgView, offset: -20)
|
||||||
|
.centerX()
|
||||||
|
.width(240)
|
||||||
|
.height(60)
|
||||||
|
|
||||||
|
closeBtn.layoutChain
|
||||||
|
.topToBottomOfView(upgradedBtn, offset: 15)
|
||||||
|
.centerX()
|
||||||
|
.width(22)
|
||||||
|
.height(22)
|
||||||
|
|
||||||
|
setupRx()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder aDecoder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -182,6 +182,7 @@ class LocationPickerVC: BaseViewController {
|
||||||
province: poi.province ?? "",
|
province: poi.province ?? "",
|
||||||
city: poi.city ?? "",
|
city: poi.city ?? "",
|
||||||
district: poi.district ?? "",
|
district: poi.district ?? "",
|
||||||
|
street: "",
|
||||||
formatted_address: poi.address ?? ""
|
formatted_address: poi.address ?? ""
|
||||||
)
|
)
|
||||||
rootView.resultTableView.isHidden = true
|
rootView.resultTableView.isHidden = true
|
||||||
|
|
@ -191,6 +192,14 @@ class LocationPickerVC: BaseViewController {
|
||||||
rootView.mapView.setCenter(coord, animated: true)
|
rootView.mapView.setCenter(coord, animated: true)
|
||||||
rootView.mapView.setZoomLevel(19, animated: true)
|
rootView.mapView.setZoomLevel(19, animated: true)
|
||||||
addLocationAnnotation(coordinate: coord)
|
addLocationAnnotation(coordinate: coord)
|
||||||
|
// 逆地理编码补充 street
|
||||||
|
#if !targetEnvironment(simulator)
|
||||||
|
let regeo = AMapReGeocodeSearchRequest()
|
||||||
|
regeo.location = AMapGeoPoint.location(withLatitude: CGFloat(coord.latitude),
|
||||||
|
longitude: CGFloat(coord.longitude))
|
||||||
|
regeo.requireExtension = true
|
||||||
|
searchAPI?.aMapReGoecodeSearch(regeo)
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateTableHeight(itemCount: Int) {
|
private func updateTableHeight(itemCount: Int) {
|
||||||
|
|
@ -252,11 +261,23 @@ extension LocationPickerVC: AMapSearchDelegate {
|
||||||
func onReGeocodeSearchDone(_ request: AMapReGeocodeSearchRequest!, response: AMapReGeocodeSearchResponse!) {
|
func onReGeocodeSearchDone(_ request: AMapReGeocodeSearchRequest!, response: AMapReGeocodeSearchResponse!) {
|
||||||
guard let regeo = response.regeocode else { return }
|
guard let regeo = response.regeocode else { return }
|
||||||
let address = regeo.formattedAddress ?? ""
|
let address = regeo.formattedAddress ?? ""
|
||||||
|
// 取第一个 POI 名称作为地点名,没有则取街道/区
|
||||||
|
let poiName = regeo.pois?.first?.name
|
||||||
|
?? regeo.pois?.first?.address
|
||||||
|
?? regeo.pois?.first?.district
|
||||||
|
?? ""
|
||||||
selectedLocation = PickedLocation(
|
selectedLocation = PickedLocation(
|
||||||
name: rootView.poiNameLab.text ?? "",
|
name: poiName,
|
||||||
address: address,
|
address: regeo.pois?.first?.address ?? "",
|
||||||
coordinate: selectedLocation?.coordinate ?? kCLLocationCoordinate2DInvalid
|
coordinate: selectedLocation?.coordinate ?? kCLLocationCoordinate2DInvalid,
|
||||||
|
province: regeo.pois?.first?.province ?? "",
|
||||||
|
city: regeo.pois?.first?.city ?? "",
|
||||||
|
district: regeo.pois?.first?.district ?? "",
|
||||||
|
street: regeo.addressComponent?.streetNumber?.street ?? "",
|
||||||
|
formatted_address: regeo.pois?.first?.address ?? ""
|
||||||
)
|
)
|
||||||
|
rootView.bottomView.isHidden = false
|
||||||
|
rootView.poiNameLab.text = poiName
|
||||||
rootView.poiAddressLab.text = address
|
rootView.poiAddressLab.text = address
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -267,13 +288,25 @@ extension LocationPickerVC: AMapSearchDelegate {
|
||||||
|
|
||||||
// MARK: - MAMapViewDelegate
|
// MARK: - MAMapViewDelegate
|
||||||
extension LocationPickerVC: MAMapViewDelegate {
|
extension LocationPickerVC: MAMapViewDelegate {
|
||||||
func mapView(_ mapView: MAMapView!, didTouchPois pois: [Any]!) {
|
func mapView(_ mapView: MAMapView!, didSingleTappedAt coordinate: CLLocationCoordinate2D) {
|
||||||
guard let touchPoi = pois?.first as? MATouchPoi, let uid = touchPoi.uid, !uid.isEmpty else { return }
|
isShowPoi = true
|
||||||
let request = AMapPOIIDSearchRequest()
|
selectedLocation = PickedLocation(
|
||||||
request.uid = uid
|
name: "",
|
||||||
searchAPI?.aMapPOIIDSearch(request)
|
address: "",
|
||||||
|
coordinate: coordinate
|
||||||
|
)
|
||||||
|
addLocationAnnotation(coordinate: coordinate)
|
||||||
|
rootView.mapView.setCenter(coordinate, animated: true)
|
||||||
|
// 逆地理编码获取地址
|
||||||
|
#if !targetEnvironment(simulator)
|
||||||
|
let regeo = AMapReGeocodeSearchRequest()
|
||||||
|
regeo.location = AMapGeoPoint.location(withLatitude: CGFloat(coordinate.latitude),
|
||||||
|
longitude: CGFloat(coordinate.longitude))
|
||||||
|
regeo.requireExtension = true
|
||||||
|
searchAPI?.aMapReGoecodeSearch(regeo)
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
func mapView(_ mapView: MAMapView!, viewFor annotation: MAAnnotation!) -> MAAnnotationView! {
|
func mapView(_ mapView: MAMapView!, viewFor annotation: MAAnnotation!) -> MAAnnotationView! {
|
||||||
if annotation is MAUserLocation { return nil }
|
if annotation is MAUserLocation { return nil }
|
||||||
guard annotation is MAPointAnnotation else { return nil }
|
guard annotation is MAPointAnnotation else { return nil }
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,99 @@
|
||||||
|
//
|
||||||
|
// ScheduleDetailVC.swift
|
||||||
|
// QuickLocation
|
||||||
|
//
|
||||||
|
// Created by 八条 on 2026/6/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import RxSwift
|
||||||
|
import RxCocoa
|
||||||
|
import RxDataSources
|
||||||
|
import ObjectMapper
|
||||||
|
|
||||||
|
class ScheduleDetailVC: BaseViewController {
|
||||||
|
|
||||||
|
fileprivate var rootView: ScheduleDetailView!
|
||||||
|
|
||||||
|
override func loadView() {
|
||||||
|
rootView = ScheduleDetailView(frame: UIScreen.main.bounds)
|
||||||
|
view = rootView
|
||||||
|
}
|
||||||
|
|
||||||
|
private var viewModel: ScheduleDetailViewModel
|
||||||
|
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
// Do any additional setup after loading the view.
|
||||||
|
setupData()
|
||||||
|
bindViewModel()
|
||||||
|
requestFollowList()
|
||||||
|
|
||||||
|
viewModel.loadPointData()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func bindViewModel() {
|
||||||
|
viewModel.output.sectionedItems
|
||||||
|
.bind(to: rootView.collectionView.rx.items(dataSource: dataSource))
|
||||||
|
.disposed(by: disposeBag)
|
||||||
|
|
||||||
|
viewModel.output.pointSectionedItems
|
||||||
|
.bind(to: rootView.tableView.rx.items(dataSource: tableViewDataSource))
|
||||||
|
.disposed(by: disposeBag)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setupData() {
|
||||||
|
guard let model = viewModel.scheduModel else { return }
|
||||||
|
rootView.dateLab.text = rootView.dateLab.getDateInterval2String(date: "\(model.timestamp / 1000)", dateFormat: "yyyy年MM月dd日")
|
||||||
|
rootView.creatorIcon.image = model.userIcon
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - API
|
||||||
|
private func requestFollowList() {
|
||||||
|
dl.showLoading()
|
||||||
|
UserService.followList().subscribe(onNext: { response in
|
||||||
|
self.dl.dismiss()
|
||||||
|
self.viewModel.loadViewedData(response.list)
|
||||||
|
self.rootView.noDataLab.isHidden = response.list.count > 0
|
||||||
|
}, onError: { _ in }).disposed(by: disposeBag)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - dataSource
|
||||||
|
private lazy var dataSource: RxCollectionViewSectionedReloadDataSource<ViewedListSectionModel> = {
|
||||||
|
RxCollectionViewSectionedReloadDataSource<ViewedListSectionModel> { datasource, collectionView, indexPath, model in
|
||||||
|
let cell: ViewedCell = collectionView.dequeueReusableCell(for: indexPath)
|
||||||
|
cell.configure(model)
|
||||||
|
return cell
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy private var tableViewDataSource: RxTableViewSectionedReloadDataSource<SchedulePointDetailSectionModel> = {
|
||||||
|
return RxTableViewSectionedReloadDataSource<SchedulePointDetailSectionModel>(
|
||||||
|
configureCell: { (dataSource, tableView, indexPath, item) in
|
||||||
|
switch item {
|
||||||
|
case let .point(model):
|
||||||
|
let cell: SchedulePointDetailCell = tableView.dequeueReusableCell(for: indexPath)
|
||||||
|
cell.configure(model: model, index: indexPath.section, total: dataSource.sectionModels.count)
|
||||||
|
return cell
|
||||||
|
case let .event(model):
|
||||||
|
let cell: SchedulePointEventCell = tableView.dequeueReusableCell(for: indexPath)
|
||||||
|
cell.configure(model: model, index: indexPath.section, total: dataSource.sectionModels.count)
|
||||||
|
return cell
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// MARK: - Init
|
||||||
|
init(routeId: String, scheduleJson: [String: Any]) {
|
||||||
|
self.viewModel = ScheduleDetailViewModel(routeId: routeId,
|
||||||
|
model: scheduleJson.isEmpty ? nil : ScheduleModel.init(JSON: scheduleJson))
|
||||||
|
super.init(nibName: nil, bundle: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
//
|
||||||
|
// ScheduleDetailVM.swift
|
||||||
|
// QuickLocation
|
||||||
|
//
|
||||||
|
// Created by 八条 on 2026/6/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
import RxSwift
|
||||||
|
import RxCocoa
|
||||||
|
import RxDataSources
|
||||||
|
|
||||||
|
enum SchedulePointDetailItem {
|
||||||
|
case point(model: SchedulePointModel)
|
||||||
|
case event(model: SchedulePointEventModel)
|
||||||
|
}
|
||||||
|
|
||||||
|
typealias SchedulePointDetailSectionModel = SectionModel<String, SchedulePointDetailItem>
|
||||||
|
|
||||||
|
struct ScheduleDetailViewModel {
|
||||||
|
|
||||||
|
struct Output {
|
||||||
|
var sectionedItems: Observable<[ViewedListSectionModel]>
|
||||||
|
var pointSectionedItems: Observable<[SchedulePointDetailSectionModel]>
|
||||||
|
}
|
||||||
|
|
||||||
|
let output: Output
|
||||||
|
|
||||||
|
private let sectionedItems = PublishSubject<[ViewedListSectionModel]>()
|
||||||
|
private let pointSectionedItems = PublishSubject<[SchedulePointDetailSectionModel]>()
|
||||||
|
|
||||||
|
let routeId: String
|
||||||
|
let scheduModel: ScheduleModel?
|
||||||
|
|
||||||
|
func loadViewedData(_ list: [ViewedModel]) {
|
||||||
|
sectionedItems.onNext(list.mapSection())
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadPointData() {
|
||||||
|
guard let model = scheduModel else { return }
|
||||||
|
|
||||||
|
var sectionModels: [SchedulePointDetailSectionModel] = []
|
||||||
|
var items: [SchedulePointDetailItem] = []
|
||||||
|
|
||||||
|
for point in model.points {
|
||||||
|
items.append(.point(model: point))
|
||||||
|
for event in point.events {
|
||||||
|
items.append(.event(model: event))
|
||||||
|
}
|
||||||
|
sectionModels.append(SchedulePointDetailSectionModel(model: point.id, items: items))
|
||||||
|
items = []
|
||||||
|
}
|
||||||
|
|
||||||
|
pointSectionedItems.onNext(sectionModels)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - init
|
||||||
|
init(routeId: String, model: ScheduleModel?) {
|
||||||
|
self.routeId = routeId
|
||||||
|
self.scheduModel = model
|
||||||
|
output = Output(
|
||||||
|
sectionedItems: sectionedItems.asObservable(),
|
||||||
|
pointSectionedItems: pointSectionedItems.asObservable()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,534 @@
|
||||||
|
//
|
||||||
|
// ScheduleDetailView.swift
|
||||||
|
// QuickLocation
|
||||||
|
//
|
||||||
|
// Created by 八条 on 2026/6/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import RxSwift
|
||||||
|
import RxCocoa
|
||||||
|
|
||||||
|
class ScheduleDetailView: UIView {
|
||||||
|
|
||||||
|
var disposeBag = DisposeBag()
|
||||||
|
|
||||||
|
private func setupRx() {
|
||||||
|
backBtn.rx.tap.subscribe(onNext: { _ in
|
||||||
|
AppRouter.shared.popOrDismiss()
|
||||||
|
}).disposed(by: disposeBag)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setupUI() {
|
||||||
|
addSubview(navBgView)
|
||||||
|
addSubview(navBarView)
|
||||||
|
navBarView.addSubview(navTitleLabel)
|
||||||
|
navBarView.addSubview(backBtn)
|
||||||
|
addSubview(headerView)
|
||||||
|
addSubview(travelRouteView)
|
||||||
|
addSubview(tableView)
|
||||||
|
addSubview(bottomView)
|
||||||
|
|
||||||
|
navBgView.layoutChain
|
||||||
|
.edges(excludingEdge: .bottom)
|
||||||
|
.heightToWidth(160/375)
|
||||||
|
|
||||||
|
navBarView.layoutChain
|
||||||
|
.edges(excludingEdge: .bottom)
|
||||||
|
.height(kNaviHeight)
|
||||||
|
|
||||||
|
navTitleLabel.layoutChain
|
||||||
|
.top(kStatusBarHeight + 12)
|
||||||
|
.centerY(backBtn)
|
||||||
|
.centerX()
|
||||||
|
|
||||||
|
backBtn.layoutChain
|
||||||
|
.centerY(navTitleLabel)
|
||||||
|
.left(15)
|
||||||
|
.width(24)
|
||||||
|
.height(24)
|
||||||
|
|
||||||
|
headerView.layoutChain
|
||||||
|
.topToBottomOfView(navBarView)
|
||||||
|
.edgesHorzontal()
|
||||||
|
|
||||||
|
travelRouteView.layoutChain
|
||||||
|
.topToBottomOfView(headerView)
|
||||||
|
.edgesHorzontal()
|
||||||
|
|
||||||
|
bottomView.layoutChain
|
||||||
|
.edgesHorzontal()
|
||||||
|
.height(kSafeBottomMargin + 80)
|
||||||
|
.bottom()
|
||||||
|
|
||||||
|
tableView.layoutChain
|
||||||
|
.topToBottomOfView(travelRouteView, offset: 15)
|
||||||
|
.edgesHorzontal()
|
||||||
|
.bottomToTopOfView(bottomView, offset: 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy var navBgView: UIImageView = {
|
||||||
|
let iv = UIImageView()
|
||||||
|
iv.image = UIImage(named: "Common/navBar_bg_2")
|
||||||
|
iv.contentMode = .scaleAspectFill
|
||||||
|
return iv
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var navBarView: UIView = {
|
||||||
|
let view = UIView()
|
||||||
|
view.backgroundColor = .clear
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var navTitleLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.text = "行程详情"
|
||||||
|
label.font = .systemFont(ofSize: 18, weight: .medium)
|
||||||
|
label.textColor = ThemeManager.shared.color.titleAuxColor
|
||||||
|
label.textAlignment = .center
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var backBtn: UIButton = {
|
||||||
|
let btn = UIButton(type: .custom)
|
||||||
|
btn.setImage(UIImage(named: "Common/back"), for: .normal)
|
||||||
|
btn.extendEdgeInsets = UIEdgeInsets(top: 54, left: 15, bottom: 100, right: 100)
|
||||||
|
return btn
|
||||||
|
}()
|
||||||
|
|
||||||
|
/// 谁在关注
|
||||||
|
lazy var headerView: UIView = {
|
||||||
|
let view = UIView()
|
||||||
|
view.backgroundColor = .clear
|
||||||
|
|
||||||
|
let titleLab = UILabel()
|
||||||
|
titleLab.text = "谁在关注"
|
||||||
|
titleLab.font = .systemFont(ofSize: 16, weight: .medium)
|
||||||
|
titleLab.textColor = ThemeManager.shared.color.titleAuxColor
|
||||||
|
view.addSubview(titleLab)
|
||||||
|
titleLab.layoutChain
|
||||||
|
.top(15)
|
||||||
|
|
||||||
|
let dotView = UIView()
|
||||||
|
dotView.backgroundColor = UIColor(hexStr: "#16B3FF")
|
||||||
|
dotView.cornerRadius = 2
|
||||||
|
view.addSubview(dotView)
|
||||||
|
dotView.layoutChain
|
||||||
|
.left(15)
|
||||||
|
.centerY(titleLab)
|
||||||
|
.width(4)
|
||||||
|
.height(11)
|
||||||
|
|
||||||
|
titleLab.layoutChain.leftToRightOfView(dotView, offset: 5)
|
||||||
|
|
||||||
|
view.addSubview(collectionView)
|
||||||
|
collectionView.layoutChain
|
||||||
|
.topToBottomOfView(titleLab, offset: 15)
|
||||||
|
.edgesHorzontal()
|
||||||
|
.height(80)
|
||||||
|
.bottom(15)
|
||||||
|
|
||||||
|
view.addSubview(noDataLab)
|
||||||
|
noDataLab.layoutChain
|
||||||
|
.centerX().centerY()
|
||||||
|
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var collectionView: UICollectionView = {
|
||||||
|
let layout = UICollectionViewFlowLayout()
|
||||||
|
layout.scrollDirection = .horizontal
|
||||||
|
layout.itemSize = CGSize(width: 80, height: 80)
|
||||||
|
layout.minimumLineSpacing = 15
|
||||||
|
layout.sectionInset = UIEdgeInsets(top: 0, left: 15, bottom: 0, right: 15)
|
||||||
|
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
|
||||||
|
cv.backgroundColor = .clear
|
||||||
|
cv.showsHorizontalScrollIndicator = false
|
||||||
|
cv.register(ViewedCell.self)
|
||||||
|
return cv
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var noDataLab: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.text = " 暂无关注"
|
||||||
|
label.textColor = UIColor(hexStr: "#999999")
|
||||||
|
label.font = .systemFont(ofSize: 14, weight: .regular)
|
||||||
|
label.isHidden = true
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
/// 行程日期
|
||||||
|
lazy var travelRouteView: UIView = {
|
||||||
|
let view = UIView()
|
||||||
|
view.backgroundColor = .clear
|
||||||
|
|
||||||
|
let titleLab = UILabel()
|
||||||
|
titleLab.text = "行程日期"
|
||||||
|
titleLab.font = .systemFont(ofSize: 16, weight: .medium)
|
||||||
|
titleLab.textColor = ThemeManager.shared.color.titleAuxColor
|
||||||
|
view.addSubview(titleLab)
|
||||||
|
titleLab.layoutChain
|
||||||
|
.top(5)
|
||||||
|
|
||||||
|
let dotView = UIView()
|
||||||
|
dotView.backgroundColor = UIColor(hexStr: "#16B3FF")
|
||||||
|
dotView.cornerRadius = 2
|
||||||
|
view.addSubview(dotView)
|
||||||
|
dotView.layoutChain
|
||||||
|
.left(15)
|
||||||
|
.centerY(titleLab)
|
||||||
|
.width(4)
|
||||||
|
.height(11)
|
||||||
|
|
||||||
|
titleLab.layoutChain.leftToRightOfView(dotView, offset: 5)
|
||||||
|
|
||||||
|
view.addSubview(dateLab)
|
||||||
|
dateLab.layoutChain
|
||||||
|
.centerY(titleLab)
|
||||||
|
.leftToRightOfView(titleLab, offset: 10)
|
||||||
|
|
||||||
|
view.addSubview(creatorIcon)
|
||||||
|
creatorIcon.layoutChain
|
||||||
|
.right(15)
|
||||||
|
.width(30)
|
||||||
|
.height(30)
|
||||||
|
.centerY(titleLab)
|
||||||
|
|
||||||
|
let creatorTitleLab = UILabel()
|
||||||
|
creatorTitleLab.text = "创建人"
|
||||||
|
creatorTitleLab.font = .systemFont(ofSize: 12, weight: .medium)
|
||||||
|
creatorTitleLab.textColor = ThemeManager.shared.color.titleAuxColor
|
||||||
|
view.addSubview(creatorTitleLab)
|
||||||
|
creatorTitleLab.layoutChain
|
||||||
|
.rightToLeftOfView(creatorIcon, offset: -5)
|
||||||
|
.centerY(titleLab)
|
||||||
|
|
||||||
|
view.addSubview(vipTipsLab)
|
||||||
|
vipTipsLab.layoutChain
|
||||||
|
.topToBottomOfView(titleLab, offset: 5)
|
||||||
|
.leftToView(titleLab)
|
||||||
|
.bottom()
|
||||||
|
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var dateLab: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.textColor = UIColor(hexStr: "#16B3FF")
|
||||||
|
label.font = .systemFont(ofSize: 14, weight: .medium)
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var creatorIcon: UIImageView = {
|
||||||
|
let view = UIImageView()
|
||||||
|
view.cornerRadius = 15
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var vipTipsLab: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.textColor = UIColor(hexStr: "#FF7D52")
|
||||||
|
label.font = .systemFont(ofSize: 10, weight: .regular)
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var tableView: UITableView = {
|
||||||
|
let tv = UITableView(frame: .zero, style: .plain)
|
||||||
|
tv.backgroundColor = .clear
|
||||||
|
tv.separatorStyle = .none
|
||||||
|
tv.estimatedRowHeight = 77
|
||||||
|
tv.showsVerticalScrollIndicator = false
|
||||||
|
tv.bounces = false
|
||||||
|
tv.register(SchedulePointDetailCell.self)
|
||||||
|
tv.register(SchedulePointEventCell.self)
|
||||||
|
tv.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 15, right: 0)
|
||||||
|
return tv
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var bottomView: UIView = {
|
||||||
|
let v = UIView()
|
||||||
|
v.backgroundColor = .white
|
||||||
|
v.layer.cornerRadius = 16
|
||||||
|
v.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
|
||||||
|
v.layer.shadowColor = UIColor.black.withAlphaComponent(0.1).cgColor
|
||||||
|
v.layer.shadowOffset = CGSize(width: 0, height: -2)
|
||||||
|
v.layer.shadowRadius = 10
|
||||||
|
v.layer.shadowOpacity = 1
|
||||||
|
return v
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var operateBtn: UIButton = {
|
||||||
|
let btn = UIButton()
|
||||||
|
btn.setTitleColor(.white, for: .normal)
|
||||||
|
btn.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
|
||||||
|
btn.setBackgroundImage(UIImage(named: "Common/button_bg_2"), for: .normal)
|
||||||
|
btn.cornerRadius = 25
|
||||||
|
return btn
|
||||||
|
}()
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: .zero)
|
||||||
|
backgroundColor = .white
|
||||||
|
setupUI()
|
||||||
|
setupRx()
|
||||||
|
|
||||||
|
vipTipsLab.text = AppContextManager.shared.vip > 1 ? "" : "升级 VIP,查看具体事件"
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder aDecoder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - SchedulePointDetailCell
|
||||||
|
class SchedulePointDetailCell: UITableViewCell {
|
||||||
|
|
||||||
|
var disposeBag = DisposeBag()
|
||||||
|
|
||||||
|
func configure(model: SchedulePointModel, index: Int, total: Int) {
|
||||||
|
indexLabel.text = "\(index + 1)"
|
||||||
|
locationLabel.text = model.street
|
||||||
|
remarkLab.text = model.remark.isEmpty ? "备注:无备注" : model.remark
|
||||||
|
timeLabel.text = getDateInterval2String(date: "\(model.expected_timestamp / 1000)", dateFormat: "HH:mm")
|
||||||
|
|
||||||
|
var indexName = ""
|
||||||
|
if index == 0 {
|
||||||
|
indexName = "起点:"
|
||||||
|
}
|
||||||
|
else if index == total - 1 {
|
||||||
|
indexName = "终点:"
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
indexName = "途经点:"
|
||||||
|
}
|
||||||
|
indexNameLab.text = indexName
|
||||||
|
bottomDashView.isHidden = index == total - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Init
|
||||||
|
override init(style: CellStyle, reuseIdentifier: String?) {
|
||||||
|
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||||
|
selectionStyle = .none
|
||||||
|
backgroundColor = .clear
|
||||||
|
setupViews()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
||||||
|
|
||||||
|
override func prepareForReuse() {
|
||||||
|
super.prepareForReuse()
|
||||||
|
disposeBag = DisposeBag()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setupViews() {
|
||||||
|
contentView.addSubview(pointIcon)
|
||||||
|
contentView.addSubview(bottomDashView)
|
||||||
|
contentView.addSubview(indexLabel)
|
||||||
|
contentView.addSubview(indexNameLab)
|
||||||
|
contentView.addSubview(locationLabel)
|
||||||
|
contentView.addSubview(timeLabel)
|
||||||
|
contentView.addSubview(remarkLab)
|
||||||
|
|
||||||
|
pointIcon.layoutChain
|
||||||
|
.top()
|
||||||
|
.left(15)
|
||||||
|
.width(18).height(18)
|
||||||
|
|
||||||
|
indexLabel.layoutChain
|
||||||
|
.top().leftToRightOfView(pointIcon, offset: 10)
|
||||||
|
.width(22).height(20)
|
||||||
|
|
||||||
|
indexNameLab.layoutChain
|
||||||
|
.leftToRightOfView(indexLabel, offset: 5)
|
||||||
|
.centerY(indexLabel)
|
||||||
|
|
||||||
|
locationLabel.layoutChain
|
||||||
|
.centerY(indexLabel)
|
||||||
|
.leftToRightOfView(indexNameLab, offset: 2)
|
||||||
|
.compressionHorizontal(.defaultLow)
|
||||||
|
|
||||||
|
timeLabel.layoutChain
|
||||||
|
.centerY(indexLabel)
|
||||||
|
.leftToRightOfView(locationLabel, offset: 20)
|
||||||
|
.right(10, relation: .greaterThanOrEqual)
|
||||||
|
.compressionHorizontal(.required)
|
||||||
|
|
||||||
|
remarkLab.layoutChain
|
||||||
|
.topToBottomOfView(indexLabel, offset: 5)
|
||||||
|
.leftToView(indexLabel)
|
||||||
|
.right(15)
|
||||||
|
.bottom(10)
|
||||||
|
.compressionVertical(.required)
|
||||||
|
|
||||||
|
bottomDashView.layoutChain
|
||||||
|
.topToBottomOfView(pointIcon, offset: 5)
|
||||||
|
.centerX(pointIcon)
|
||||||
|
.width(0.5)
|
||||||
|
.bottom(5)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Views
|
||||||
|
private let pointIcon: UIImageView = {
|
||||||
|
let iv = UIImageView(image: UIImage(named: "Schedule/point"))
|
||||||
|
iv.contentMode = .scaleAspectFit
|
||||||
|
return iv
|
||||||
|
}()
|
||||||
|
|
||||||
|
private let bottomDashView: DashLineView = {
|
||||||
|
let v = DashLineView()
|
||||||
|
v.backgroundColor = .clear
|
||||||
|
return v
|
||||||
|
}()
|
||||||
|
|
||||||
|
private let indexLabel: UILabel = {
|
||||||
|
let l = UILabel()
|
||||||
|
l.backgroundColor = UIColor(hexStr: "#DCF4FF")
|
||||||
|
l.textColor = UIColor(hexStr: "#176F9B")
|
||||||
|
l.font = .systemFont(ofSize: 12, weight: .medium)
|
||||||
|
l.textAlignment = .center
|
||||||
|
return l
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var indexNameLab: UILabel = {
|
||||||
|
let l = UILabel()
|
||||||
|
l.font = .systemFont(ofSize: 14, weight: .medium)
|
||||||
|
l.textColor = UIColor(hexStr: "#16B3FF")
|
||||||
|
return l
|
||||||
|
}()
|
||||||
|
|
||||||
|
private let locationLabel: UILabel = {
|
||||||
|
let l = UILabel()
|
||||||
|
l.font = .systemFont(ofSize: 14, weight: .medium)
|
||||||
|
l.textColor = UIColor(hexStr: "#333333")
|
||||||
|
// l.numberOfLines = 0
|
||||||
|
return l
|
||||||
|
}()
|
||||||
|
|
||||||
|
private let timeLabel: UILabel = {
|
||||||
|
let l = UILabel()
|
||||||
|
l.font = .systemFont(ofSize: 12, weight: .medium)
|
||||||
|
l.textColor = UIColor(hexStr: "#333333")
|
||||||
|
l.isUserInteractionEnabled = true
|
||||||
|
l.textAlignment = .right
|
||||||
|
return l
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var remarkLab: UILabel = {
|
||||||
|
let l = UILabel()
|
||||||
|
l.font = .systemFont(ofSize: 12, weight: .regular)
|
||||||
|
l.textColor = UIColor(hexStr: "#999999")
|
||||||
|
l.numberOfLines = 0
|
||||||
|
return l
|
||||||
|
}()
|
||||||
|
|
||||||
|
override func layoutSubviews() {
|
||||||
|
super.layoutSubviews()
|
||||||
|
contentView.layoutIfNeeded()
|
||||||
|
indexLabel.setCornerRadius(corners: [.bottomRight, .topLeft], withCornerRadii: CGSize(width: 10, height: 10))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - SchedulePointEventCell
|
||||||
|
class SchedulePointEventCell: UITableViewCell {
|
||||||
|
|
||||||
|
var disposeBag = DisposeBag()
|
||||||
|
|
||||||
|
func configure(model: SchedulePointEventModel, index: Int, total: Int) {
|
||||||
|
var suffix = ""
|
||||||
|
if index == 0 {
|
||||||
|
suffix = " 起点"
|
||||||
|
} else if index == total - 1 {
|
||||||
|
suffix = " 终点"
|
||||||
|
} else {
|
||||||
|
suffix = " 途经点"
|
||||||
|
}
|
||||||
|
infoLab.text = model.text + "到达" + suffix
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Init
|
||||||
|
override init(style: CellStyle, reuseIdentifier: String?) {
|
||||||
|
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
||||||
|
selectionStyle = .none
|
||||||
|
backgroundColor = .clear
|
||||||
|
setupViews()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
||||||
|
|
||||||
|
override func prepareForReuse() {
|
||||||
|
super.prepareForReuse()
|
||||||
|
disposeBag = DisposeBag()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setupViews() {
|
||||||
|
contentView.addSubview(verticalDashLineView)
|
||||||
|
contentView.addSubview(horizontalDashLineView)
|
||||||
|
contentView.addSubview(dotView)
|
||||||
|
contentView.addSubview(infoView)
|
||||||
|
infoView.addSubview(infoLab)
|
||||||
|
|
||||||
|
verticalDashLineView.layoutChain
|
||||||
|
.left(24)
|
||||||
|
.edgesVertical()
|
||||||
|
.width(1)
|
||||||
|
|
||||||
|
dotView.layoutChain
|
||||||
|
.centerX(verticalDashLineView)
|
||||||
|
.centerY()
|
||||||
|
.width(8)
|
||||||
|
.height(8)
|
||||||
|
|
||||||
|
horizontalDashLineView.layoutChain
|
||||||
|
.leftToRightOfView(dotView)
|
||||||
|
.centerY(dotView)
|
||||||
|
.height(1)
|
||||||
|
.width(30)
|
||||||
|
|
||||||
|
infoView.layoutChain
|
||||||
|
.leftToRightOfView(horizontalDashLineView, offset: 5)
|
||||||
|
.edgesVertical(10)
|
||||||
|
.right(15)
|
||||||
|
|
||||||
|
infoLab.layoutChain
|
||||||
|
.edgesHorzontal(15)
|
||||||
|
.edgesVertical(15)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Views
|
||||||
|
lazy var verticalDashLineView: DashLineView = {
|
||||||
|
let v = DashLineView()
|
||||||
|
v.backgroundColor = .clear
|
||||||
|
return v
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var horizontalDashLineView: HorizontalDashLineView = {
|
||||||
|
let v = HorizontalDashLineView()
|
||||||
|
v.backgroundColor = .clear
|
||||||
|
return v
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var dotView: UIView = {
|
||||||
|
let view = UIView()
|
||||||
|
view.backgroundColor = UIColor(hexStr: "#16B3FF")
|
||||||
|
view.cornerRadius = 4
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var infoView: UIView = {
|
||||||
|
let v = UIView()
|
||||||
|
v.backgroundColor = .white
|
||||||
|
v.layer.cornerRadius = 10
|
||||||
|
v.layer.shadowColor = UIColor.black.withAlphaComponent(0.1).cgColor
|
||||||
|
v.layer.shadowOffset = CGSize(width: 0, height: -2)
|
||||||
|
v.layer.shadowRadius = 10
|
||||||
|
v.layer.shadowOpacity = 1
|
||||||
|
return v
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var infoLab: UILabel = {
|
||||||
|
let l = UILabel()
|
||||||
|
l.font = .systemFont(ofSize: 12, weight: .medium)
|
||||||
|
l.textColor = UIColor(hexStr: "#333333")
|
||||||
|
return l
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
@ -64,10 +64,8 @@ struct ScheduleModel: Mappable, Equatable {
|
||||||
var is_own: Bool = false
|
var is_own: Bool = false
|
||||||
/// 行程点
|
/// 行程点
|
||||||
var points: [SchedulePointModel] = []
|
var points: [SchedulePointModel] = []
|
||||||
/// 圈子名
|
/// 圈子
|
||||||
// var group_name: String = ""
|
var groups: [ScheduleGroupModel] = []
|
||||||
/// 圈子key
|
|
||||||
// var group_key: String = ""
|
|
||||||
|
|
||||||
init?(map: Map) {
|
init?(map: Map) {
|
||||||
|
|
||||||
|
|
@ -82,8 +80,7 @@ struct ScheduleModel: Mappable, Equatable {
|
||||||
is_follow <- map["is_follow"]
|
is_follow <- map["is_follow"]
|
||||||
is_own <- map["is_own"]
|
is_own <- map["is_own"]
|
||||||
points <- map["points"]
|
points <- map["points"]
|
||||||
// group_name <- map["groups.group_name"]
|
groups <- map["groups"]
|
||||||
// group_key <- map["groups.group_key"]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -118,7 +115,7 @@ struct SchedulePointModel: Mappable, Equatable {
|
||||||
/// 备注
|
/// 备注
|
||||||
var remark: String = ""
|
var remark: String = ""
|
||||||
/// 事件
|
/// 事件
|
||||||
var eventText: String = ""
|
var events: [SchedulePointEventModel] = []
|
||||||
/// 节点顺序
|
/// 节点顺序
|
||||||
var sequence: Int = 0
|
var sequence: Int = 0
|
||||||
|
|
||||||
|
|
@ -133,7 +130,7 @@ struct SchedulePointModel: Mappable, Equatable {
|
||||||
longitude <- map["location.longitude"]
|
longitude <- map["location.longitude"]
|
||||||
latitude <- map["location.latitude"]
|
latitude <- map["location.latitude"]
|
||||||
remark <- map["remark"]
|
remark <- map["remark"]
|
||||||
eventText <- map["events.text"]
|
events <- map["events"]
|
||||||
province <- map["address.province"]
|
province <- map["address.province"]
|
||||||
district <- map["address.district"]
|
district <- map["address.district"]
|
||||||
country <- map["address.country"]
|
country <- map["address.country"]
|
||||||
|
|
@ -146,8 +143,89 @@ struct SchedulePointModel: Mappable, Equatable {
|
||||||
|
|
||||||
extension SchedulePointModel: IdentifiableType {
|
extension SchedulePointModel: IdentifiableType {
|
||||||
public typealias Identity = String
|
public typealias Identity = String
|
||||||
|
|
||||||
public var identity: String {
|
public var identity: String {
|
||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension SchedulePointModel {
|
||||||
|
/// 转为创建行程用的 SchedulePointItem
|
||||||
|
func toSchedulePointItem() -> SchedulePointItem {
|
||||||
|
var item = SchedulePointItem()
|
||||||
|
item.locationName = formatted_address
|
||||||
|
item.address = formatted_address
|
||||||
|
item.latitude = latitude ?? 0
|
||||||
|
item.longitude = longitude ?? 0
|
||||||
|
item.province = province
|
||||||
|
item.city = city
|
||||||
|
item.district = district
|
||||||
|
item.street = street
|
||||||
|
item.country = country
|
||||||
|
item.formatted_address = formatted_address
|
||||||
|
if expected_timestamp > 0 {
|
||||||
|
item.expectedTime = Date(timeIntervalSince1970: TimeInterval(expected_timestamp) / 1000)
|
||||||
|
}
|
||||||
|
item.remark = remark
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 行程点事件
|
||||||
|
struct SchedulePointEventModel: Mappable, Equatable {
|
||||||
|
var uuid: String = UUID().uuidString
|
||||||
|
/// 事件内容
|
||||||
|
var text: String = ""
|
||||||
|
/// 到达时间
|
||||||
|
var arrival_timestamp: Int64 = 0
|
||||||
|
///
|
||||||
|
var user_id: String = ""
|
||||||
|
var nick_name: String = ""
|
||||||
|
var point_id: String = ""
|
||||||
|
|
||||||
|
init?(map: Map) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating func mapping(map: Map) {
|
||||||
|
text <- map["text"]
|
||||||
|
arrival_timestamp <- map["arrival_timestamp"]
|
||||||
|
user_id <- map[user_id]
|
||||||
|
nick_name <- map["nick_name"]
|
||||||
|
point_id <- map["point_id"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SchedulePointEventModel: IdentifiableType {
|
||||||
|
public typealias Identity = String
|
||||||
|
|
||||||
|
public var identity: String {
|
||||||
|
return uuid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 行程分享的圈子
|
||||||
|
struct ScheduleGroupModel: Mappable, Equatable {
|
||||||
|
var uuid: String = UUID().uuidString
|
||||||
|
/// id
|
||||||
|
var group_key: String = ""
|
||||||
|
/// 圈子名
|
||||||
|
var group_name: String = ""
|
||||||
|
|
||||||
|
init?(map: Map) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating func mapping(map: Map) {
|
||||||
|
group_key <- map["group_key"]
|
||||||
|
group_name <- map["group_name"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ScheduleGroupModel: IdentifiableType {
|
||||||
|
public typealias Identity = String
|
||||||
|
|
||||||
|
public var identity: String {
|
||||||
|
return uuid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import RxSwift
|
||||||
import RxCocoa
|
import RxCocoa
|
||||||
import RxDataSources
|
import RxDataSources
|
||||||
import MJRefresh
|
import MJRefresh
|
||||||
|
import ObjectMapper
|
||||||
|
|
||||||
class ScheduleVC: BaseViewController {
|
class ScheduleVC: BaseViewController {
|
||||||
|
|
||||||
|
|
@ -30,10 +31,14 @@ class ScheduleVC: BaseViewController {
|
||||||
bindViewModel()
|
bindViewModel()
|
||||||
reactiveAction()
|
reactiveAction()
|
||||||
|
|
||||||
requestData()
|
|
||||||
requestFollowList()
|
requestFollowList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func viewWillAppear(_ animated: Bool) {
|
||||||
|
super.viewWillAppear(animated)
|
||||||
|
requestData()
|
||||||
|
}
|
||||||
|
|
||||||
private func bindViewModel() {
|
private func bindViewModel() {
|
||||||
viewModel.output.sectionedItems
|
viewModel.output.sectionedItems
|
||||||
.bind(to: rootView.collectionView.rx.items(dataSource: dataSource))
|
.bind(to: rootView.collectionView.rx.items(dataSource: dataSource))
|
||||||
|
|
@ -48,6 +53,10 @@ class ScheduleVC: BaseViewController {
|
||||||
self.dl.dismiss()
|
self.dl.dismiss()
|
||||||
self.rootView.tableView.refresh(status: status, isEmpty: isEmpty)
|
self.rootView.tableView.refresh(status: status, isEmpty: isEmpty)
|
||||||
}).disposed(by: disposeBag)
|
}).disposed(by: disposeBag)
|
||||||
|
|
||||||
|
rootView.tableView.rx.modelSelected(ScheduleModel.self)
|
||||||
|
.subscribe(viewModel.cellAction.inputs)
|
||||||
|
.disposed(by: disposeBag)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func reactiveAction() {
|
private func reactiveAction() {
|
||||||
|
|
@ -98,6 +107,17 @@ class ScheduleVC: BaseViewController {
|
||||||
configureCell: { (_, tableView, indexPath, model) in
|
configureCell: { (_, tableView, indexPath, model) in
|
||||||
let cell: ScheduleListPopCell = tableView.dequeueReusableCell(for: indexPath)
|
let cell: ScheduleListPopCell = tableView.dequeueReusableCell(for: indexPath)
|
||||||
cell.configure(model)
|
cell.configure(model)
|
||||||
|
|
||||||
|
// 编辑
|
||||||
|
cell.editBtn.rx.tap.subscribe(onNext: { _ in
|
||||||
|
AppRouter.push(Route.createSchedule, userInfo: ["scheduleJson": model.toJSON()])
|
||||||
|
}).disposed(by: cell.disposeBag)
|
||||||
|
|
||||||
|
// 关注
|
||||||
|
cell.followBtn.rx.tap.subscribe(onNext: { _ in
|
||||||
|
|
||||||
|
}).disposed(by: cell.disposeBag)
|
||||||
|
|
||||||
return cell
|
return cell
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -175,7 +175,9 @@ class ScheduleView: UIView {
|
||||||
tableView.backgroundColor = .clear
|
tableView.backgroundColor = .clear
|
||||||
tableView.separatorStyle = .none
|
tableView.separatorStyle = .none
|
||||||
tableView.estimatedRowHeight = 80
|
tableView.estimatedRowHeight = 80
|
||||||
|
tableView.showsVerticalScrollIndicator = false
|
||||||
tableView.register(ScheduleListPopCell.self)
|
tableView.register(ScheduleListPopCell.self)
|
||||||
|
tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 97 + kSafeBottomMargin, right: 0)
|
||||||
return tableView
|
return tableView
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
@ -222,7 +224,7 @@ final class ViewedCell: UICollectionViewCell {
|
||||||
}()
|
}()
|
||||||
|
|
||||||
lazy var blurView: UIVisualEffectView = {
|
lazy var blurView: UIVisualEffectView = {
|
||||||
let blurEffect = UIBlurEffect(style: .systemUltraThinMaterialLight)
|
let blurEffect = UIBlurEffect(style: .systemUltraThinMaterial)
|
||||||
let view = UIVisualEffectView(effect: blurEffect)
|
let view = UIVisualEffectView(effect: blurEffect)
|
||||||
return view
|
return view
|
||||||
}()
|
}()
|
||||||
|
|
@ -270,11 +272,14 @@ final class ViewedCell: UICollectionViewCell {
|
||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - ScheduleListPopCell
|
// MARK: - ScheduleListPopCell
|
||||||
class ScheduleListPopCell: UITableViewCell {
|
class ScheduleListPopCell: UITableViewCell {
|
||||||
|
|
||||||
|
var disposeBag = DisposeBag()
|
||||||
|
|
||||||
func configure(_ model: ScheduleModel) {
|
func configure(_ model: ScheduleModel) {
|
||||||
nameLab.text = model.nick_name + " 的行程路线"
|
nameLab.text = model.nick_name + " 的行程路线"
|
||||||
monthLab.text = getDateInterval2String(date: "\(model.timestamp/1000)", dateFormat: "MM月")
|
monthLab.text = getDateInterval2String(date: "\(model.timestamp/1000)", dateFormat: "MM月")
|
||||||
|
|
@ -355,6 +360,11 @@ class ScheduleListPopCell: UITableViewCell {
|
||||||
// Configure the view for the selected state
|
// Configure the view for the selected state
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func prepareForReuse() {
|
||||||
|
super.prepareForReuse()
|
||||||
|
disposeBag = DisposeBag()
|
||||||
|
}
|
||||||
|
|
||||||
lazy var bgView: UIView = {
|
lazy var bgView: UIView = {
|
||||||
let view = UIView()
|
let view = UIView()
|
||||||
view.backgroundColor = .clear
|
view.backgroundColor = .clear
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import RxSwift
|
||||||
import RxCocoa
|
import RxCocoa
|
||||||
import RxDataSources
|
import RxDataSources
|
||||||
import MJRefresh
|
import MJRefresh
|
||||||
|
import ObjectMapper
|
||||||
|
|
||||||
typealias ViewedListSectionModel = SectionModel<String, ViewedModel>
|
typealias ViewedListSectionModel = SectionModel<String, ViewedModel>
|
||||||
typealias ScheduleListSectionModel = AnimatableSectionModel<String, ScheduleModel>
|
typealias ScheduleListSectionModel = AnimatableSectionModel<String, ScheduleModel>
|
||||||
|
|
@ -47,6 +48,13 @@ class ScheduleViewModel: ViewModelType {
|
||||||
sectionedItems.onNext(list.mapSection())
|
sectionedItems.onNext(list.mapSection())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lazy var cellAction: Action<ScheduleModel, Void> = { this in
|
||||||
|
return Action { model in
|
||||||
|
AppRouter.push(Route.scheduleDetail, userInfo: ["scheduleJson": model.toJSON()])
|
||||||
|
return .empty()
|
||||||
|
}
|
||||||
|
}(self)
|
||||||
|
|
||||||
// MARK: - init
|
// MARK: - init
|
||||||
init() {
|
init() {
|
||||||
listService = ItineraryService.query()
|
listService = ItineraryService.query()
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ import Moya
|
||||||
struct ItineraryService {
|
struct ItineraryService {
|
||||||
static let disposeBag = DisposeBag()
|
static let disposeBag = DisposeBag()
|
||||||
|
|
||||||
/// 查询行程
|
/// 查询行程列表
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
/// - follow: 只查看关注的行程
|
/// - follow: 只查看关注的行程
|
||||||
/// - own: 只查看自己创建的行程
|
/// - own: 只查看自己创建的行程
|
||||||
|
|
@ -30,4 +30,36 @@ struct ItineraryService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 查询行程关注人
|
||||||
|
/// - Parameters:
|
||||||
|
/// - id: 行程ID
|
||||||
|
static func queryFollowList(id: String) -> Observable<ResponseModel> {
|
||||||
|
let api = ItineraryAPI.queryFollowList(id: id).multiTarget
|
||||||
|
return APIProvider.request(token: api)
|
||||||
|
.map(ResponseModel.self)
|
||||||
|
.asObservable()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 设置行程
|
||||||
|
/// - Parameters:
|
||||||
|
/// - id: 更新时传入
|
||||||
|
/// - group_keys: 只查看关注的行程
|
||||||
|
/// - timestamp: 行程日期
|
||||||
|
/// - points: 行程点
|
||||||
|
static func set(id: String="", group_keys: [String], timestamp: Int64, points: [[String: Any]]) -> Observable<ResponseModel> {
|
||||||
|
let api = ItineraryAPI.set(id: id, group_keys: group_keys, timestamp: timestamp, points: points).multiTarget
|
||||||
|
return APIProvider.request(token: api)
|
||||||
|
.map(ResponseModel.self)
|
||||||
|
.asObservable()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 删除
|
||||||
|
/// - Parameters:
|
||||||
|
/// - id: 行程ID
|
||||||
|
static func delete(id: String) -> Observable<ResponseModel> {
|
||||||
|
let api = ItineraryAPI.delete(id: id).multiTarget
|
||||||
|
return APIProvider.request(token: api)
|
||||||
|
.map(ResponseModel.self)
|
||||||
|
.asObservable()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||