Explorar o código

签到、首页

Steven %!s(int64=5) %!d(string=hai) anos
pai
achega
844fed3fd0
Modificáronse 59 ficheiros con 4882 adicións e 127 borrados
  1. 158 14
      MusicGradeExam/MusicGradeExam.xcodeproj/project.pbxproj
  2. 22 0
      MusicGradeExam/MusicGradeExam/Assets.xcassets/Home/count_bg.imageset/Contents.json
  3. 22 0
      MusicGradeExam/MusicGradeExam/Assets.xcassets/Home/home_top.imageset/Contents.json
  4. BIN=BIN
      MusicGradeExam/MusicGradeExam/Assets.xcassets/Home/home_top.imageset/home_top@2x.png
  5. BIN=BIN
      MusicGradeExam/MusicGradeExam/Assets.xcassets/Home/home_top.imageset/home_top@3x.png
  6. 22 0
      MusicGradeExam/MusicGradeExam/Assets.xcassets/wd_img_zwsj.imageset/Contents.json
  7. BIN=BIN
      MusicGradeExam/MusicGradeExam/Assets.xcassets/wd_img_zwsj.imageset/wd_img_zwsj@2x.png
  8. BIN=BIN
      MusicGradeExam/MusicGradeExam/Assets.xcassets/wd_img_zwsj.imageset/wd_img_zwsj@3x.png
  9. 1 0
      MusicGradeExam/MusicGradeExam/Base/KSBaseViewController.m
  10. 30 3
      MusicGradeExam/MusicGradeExam/KSRequestManager.h
  11. 49 7
      MusicGradeExam/MusicGradeExam/KSRequestManager.m
  12. 1 3
      MusicGradeExam/MusicGradeExam/Manager/OnlineRoomManager.h
  13. 28 60
      MusicGradeExam/MusicGradeExam/Manager/OnlineRoomManager.m
  14. 2 0
      MusicGradeExam/MusicGradeExam/UI/Classroom/Controller/ClassroomViewController.h
  15. 789 1
      MusicGradeExam/MusicGradeExam/UI/Classroom/Controller/ClassroomViewController.m
  16. 21 0
      MusicGradeExam/MusicGradeExam/UI/Classroom/Model/LocalRenderManager.h
  17. 22 0
      MusicGradeExam/MusicGradeExam/UI/Classroom/Model/LocalRenderManager.m
  18. 78 0
      MusicGradeExam/MusicGradeExam/UI/Classroom/Model/RoomLoginHelper.m
  19. 51 0
      MusicGradeExam/MusicGradeExam/UI/Classroom/View/MainContainer/ClassroomMainContainer.h
  20. 200 0
      MusicGradeExam/MusicGradeExam/UI/Classroom/View/MainContainer/ClassroomMainContainer.m
  21. 26 0
      MusicGradeExam/MusicGradeExam/UI/Classroom/View/MainContainer/EmptyView.h
  22. 80 0
      MusicGradeExam/MusicGradeExam/UI/Classroom/View/MainContainer/EmptyView.m
  23. 38 0
      MusicGradeExam/MusicGradeExam/UI/Classroom/View/MainToolbar/MainToolView.h
  24. 70 0
      MusicGradeExam/MusicGradeExam/UI/Classroom/View/MainToolbar/MainToolView.m
  25. 0 0
      MusicGradeExam/MusicGradeExam/UI/Classroom/View/NormalAlertView/KSNormalAlertView.h
  26. 0 0
      MusicGradeExam/MusicGradeExam/UI/Classroom/View/NormalAlertView/KSNormalAlertView.m
  27. 19 0
      MusicGradeExam/MusicGradeExam/UI/Classroom/View/TipsView/KSTipsView.h
  28. 108 0
      MusicGradeExam/MusicGradeExam/UI/Classroom/View/TipsView/KSTipsView.m
  29. 72 0
      MusicGradeExam/MusicGradeExam/UI/Classroom/View/TitleView/ClassTitleView.h
  30. 549 0
      MusicGradeExam/MusicGradeExam/UI/Classroom/View/TitleView/ClassTitleView.m
  31. 29 0
      MusicGradeExam/MusicGradeExam/UI/Classroom/View/VideoList/ClassVideoListCell.h
  32. 255 0
      MusicGradeExam/MusicGradeExam/UI/Classroom/View/VideoList/ClassVideoListCell.m
  33. 34 0
      MusicGradeExam/MusicGradeExam/UI/Classroom/View/VideoList/ClassVideoListView.h
  34. 205 0
      MusicGradeExam/MusicGradeExam/UI/Classroom/View/VideoList/ClassVideoListView.m
  35. 64 0
      MusicGradeExam/MusicGradeExam/UI/Exam/Controller/ExamTicketViewController.m
  36. 19 0
      MusicGradeExam/MusicGradeExam/UI/Exam/Controller/WaitExamViewController.h
  37. 133 0
      MusicGradeExam/MusicGradeExam/UI/Exam/Controller/WaitExamViewController.m
  38. 31 0
      MusicGradeExam/MusicGradeExam/UI/Exam/Model/TicketDetailModel.h
  39. 172 0
      MusicGradeExam/MusicGradeExam/UI/Exam/Model/TicketDetailModel.m
  40. 29 0
      MusicGradeExam/MusicGradeExam/UI/Exam/Model/TicketListModel.h
  41. 158 0
      MusicGradeExam/MusicGradeExam/UI/Exam/Model/TicketListModel.m
  42. 25 0
      MusicGradeExam/MusicGradeExam/UI/Exam/View/TicketBodyView.h
  43. 133 0
      MusicGradeExam/MusicGradeExam/UI/Exam/View/TicketBodyView.m
  44. 232 0
      MusicGradeExam/MusicGradeExam/UI/Exam/View/TicketBodyView.xib
  45. 32 0
      MusicGradeExam/MusicGradeExam/UI/Exam/View/WaitExamBodyView.h
  46. 150 0
      MusicGradeExam/MusicGradeExam/UI/Exam/View/WaitExamBodyView.m
  47. 260 0
      MusicGradeExam/MusicGradeExam/UI/Exam/View/WaitExamBodyView.xib
  48. 134 2
      MusicGradeExam/MusicGradeExam/UI/Home/Controller/HomeViewController.m
  49. 4 0
      MusicGradeExam/MusicGradeExam/UI/Home/View/HomeBodyView.h
  50. 26 0
      MusicGradeExam/MusicGradeExam/UI/Home/View/HomeBodyView.m
  51. 35 31
      MusicGradeExam/MusicGradeExam/UI/Home/View/HomeBodyView.xib
  52. 20 0
      MusicGradeExam/MusicGradeExam/UI/Home/View/HomeExamTicketCell.h
  53. 92 0
      MusicGradeExam/MusicGradeExam/UI/Home/View/HomeExamTicketCell.m
  54. 120 0
      MusicGradeExam/MusicGradeExam/UI/Home/View/HomeExamTicketCell.xib
  55. 1 1
      MusicGradeExam/MusicGradeExam/UI/Login/Model/UserInfoManager.m
  56. 19 0
      MusicGradeExam/MusicGradeExam/UI/NotiferMessage/Controller/NotifyMessageViewController.m
  57. 8 1
      MusicGradeExam/MusicGradeExam/UI/UserCenter/Controller/Mine/Controller/UserViewController.m
  58. 2 2
      MusicGradeExam/MusicGradeExam/UI/UserCenter/Controller/Mine/View/UserBodyView.m
  59. 2 2
      MusicGradeExam/MusicGradeExam/UI/UserCenter/View/UserCenterBodyView.m

+ 158 - 14
MusicGradeExam/MusicGradeExam.xcodeproj/project.pbxproj

@@ -87,7 +87,6 @@
 		274771CD24BC0C0500181362 /* RolePortraitView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2747702224BC0C0100181362 /* RolePortraitView.m */; };
 		274771CE24BC0C0500181362 /* HTTPUtility.m in Sources */ = {isa = PBXBuildFile; fileRef = 2747702624BC0C0100181362 /* HTTPUtility.m */; };
 		274771CF24BC0C0500181362 /* HTTPResult.m in Sources */ = {isa = PBXBuildFile; fileRef = 2747702724BC0C0100181362 /* HTTPResult.m */; };
-		274771D024BC0C0500181362 /* KSNormalAlertView.m in Sources */ = {isa = PBXBuildFile; fileRef = 2747702C24BC0C0100181362 /* KSNormalAlertView.m */; };
 		274771D124BC0C0500181362 /* ApplySpeechMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = 2747703224BC0C0100181362 /* ApplySpeechMessage.m */; };
 		274771D224BC0C0500181362 /* DisplayCommandMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = 2747703424BC0C0100181362 /* DisplayCommandMessage.m */; };
 		274771D324BC0C0500181362 /* ControlDeviceNotifyMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = 2747703724BC0C0100181362 /* ControlDeviceNotifyMessage.m */; };
@@ -319,6 +318,24 @@
 		27EF3EEE24BEE35E002068A2 /* MessageListModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 27EF3EED24BEE35E002068A2 /* MessageListModel.m */; };
 		27EF3EF224BEE885002068A2 /* NotifyMessageCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 27EF3EF024BEE885002068A2 /* NotifyMessageCell.m */; };
 		27EF3EF324BEE885002068A2 /* NotifyMessageCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 27EF3EF124BEE885002068A2 /* NotifyMessageCell.xib */; };
+		27EF3EF624BEF1DA002068A2 /* TicketBodyView.m in Sources */ = {isa = PBXBuildFile; fileRef = 27EF3EF524BEF1DA002068A2 /* TicketBodyView.m */; };
+		27EF3EF824BEF1E8002068A2 /* TicketBodyView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 27EF3EF724BEF1E8002068A2 /* TicketBodyView.xib */; };
+		27EF3EFB24BEFC79002068A2 /* WaitExamViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 27EF3EFA24BEFC79002068A2 /* WaitExamViewController.m */; };
+		27EF3EFE24BF015A002068A2 /* WaitExamBodyView.m in Sources */ = {isa = PBXBuildFile; fileRef = 27EF3EFD24BF015A002068A2 /* WaitExamBodyView.m */; };
+		27EF3F0024BF016B002068A2 /* WaitExamBodyView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 27EF3EFF24BF016B002068A2 /* WaitExamBodyView.xib */; };
+		27EF3F0324BF0F12002068A2 /* TicketListModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 27EF3F0224BF0F12002068A2 /* TicketListModel.m */; };
+		27EF3F0624BFF8A1002068A2 /* TicketDetailModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 27EF3F0424BFF8A0002068A2 /* TicketDetailModel.m */; };
+		27EF3F0924C02B3F002068A2 /* LocalRenderManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 27EF3F0724C02B3E002068A2 /* LocalRenderManager.m */; };
+		27EF3F2024C02B68002068A2 /* KSTipsView.m in Sources */ = {isa = PBXBuildFile; fileRef = 27EF3F0B24C02B67002068A2 /* KSTipsView.m */; };
+		27EF3F2124C02B68002068A2 /* KSNormalAlertView.m in Sources */ = {isa = PBXBuildFile; fileRef = 27EF3F0F24C02B67002068A2 /* KSNormalAlertView.m */; };
+		27EF3F2224C02B68002068A2 /* ClassTitleView.m in Sources */ = {isa = PBXBuildFile; fileRef = 27EF3F1224C02B67002068A2 /* ClassTitleView.m */; };
+		27EF3F2324C02B68002068A2 /* ClassVideoListView.m in Sources */ = {isa = PBXBuildFile; fileRef = 27EF3F1724C02B68002068A2 /* ClassVideoListView.m */; };
+		27EF3F2524C02B68002068A2 /* ClassVideoListCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 27EF3F1924C02B68002068A2 /* ClassVideoListCell.m */; };
+		27EF3F2624C02B68002068A2 /* MainToolView.m in Sources */ = {isa = PBXBuildFile; fileRef = 27EF3F1B24C02B68002068A2 /* MainToolView.m */; };
+		27EF3F2724C02B68002068A2 /* ClassroomMainContainer.m in Sources */ = {isa = PBXBuildFile; fileRef = 27EF3F1F24C02B68002068A2 /* ClassroomMainContainer.m */; };
+		27EF3F2A24C02DE9002068A2 /* EmptyView.m in Sources */ = {isa = PBXBuildFile; fileRef = 27EF3F2824C02DE8002068A2 /* EmptyView.m */; };
+		27EF3F2E24C0384E002068A2 /* HomeExamTicketCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 27EF3F2C24C0384E002068A2 /* HomeExamTicketCell.m */; };
+		27EF3F2F24C0384E002068A2 /* HomeExamTicketCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 27EF3F2D24C0384E002068A2 /* HomeExamTicketCell.xib */; };
 		52771C0027351695CEDB4C8E /* libPods-MusicGradeExam.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1C12DE728E343C25BB5998D7 /* libPods-MusicGradeExam.a */; };
 		5577ECF41C84E1BDFEC99DBC /* libPods-MusicGradeExamTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 8BAA64634BC925E7C2CD7008 /* libPods-MusicGradeExamTests.a */; };
 		A7C23B2E920E232C71C39B0E /* libPods-MusicGradeExam-MusicGradeExamUITests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F8D13E0A14104E75F7922812 /* libPods-MusicGradeExam-MusicGradeExamUITests.a */; };
@@ -503,8 +520,6 @@
 		2747702724BC0C0100181362 /* HTTPResult.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTTPResult.m; sourceTree = "<group>"; };
 		2747702824BC0C0100181362 /* HTTPUtility.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTTPUtility.h; sourceTree = "<group>"; };
 		2747702924BC0C0100181362 /* HTTPResult.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTTPResult.h; sourceTree = "<group>"; };
-		2747702B24BC0C0100181362 /* KSNormalAlertView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KSNormalAlertView.h; sourceTree = "<group>"; };
-		2747702C24BC0C0100181362 /* KSNormalAlertView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KSNormalAlertView.m; sourceTree = "<group>"; };
 		2747702D24BC0C0100181362 /* ClassroomService.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ClassroomService.h; sourceTree = "<group>"; };
 		2747702F24BC0C0100181362 /* RoleChangedMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RoleChangedMessage.h; sourceTree = "<group>"; };
 		2747703024BC0C0100181362 /* AssistantTransferMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AssistantTransferMessage.h; sourceTree = "<group>"; };
@@ -925,6 +940,39 @@
 		27EF3EEF24BEE885002068A2 /* NotifyMessageCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NotifyMessageCell.h; sourceTree = "<group>"; };
 		27EF3EF024BEE885002068A2 /* NotifyMessageCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NotifyMessageCell.m; sourceTree = "<group>"; };
 		27EF3EF124BEE885002068A2 /* NotifyMessageCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = NotifyMessageCell.xib; sourceTree = "<group>"; };
+		27EF3EF424BEF1DA002068A2 /* TicketBodyView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TicketBodyView.h; sourceTree = "<group>"; };
+		27EF3EF524BEF1DA002068A2 /* TicketBodyView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TicketBodyView.m; sourceTree = "<group>"; };
+		27EF3EF724BEF1E8002068A2 /* TicketBodyView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TicketBodyView.xib; sourceTree = "<group>"; };
+		27EF3EF924BEFC79002068A2 /* WaitExamViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WaitExamViewController.h; sourceTree = "<group>"; };
+		27EF3EFA24BEFC79002068A2 /* WaitExamViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = WaitExamViewController.m; sourceTree = "<group>"; };
+		27EF3EFC24BF015A002068A2 /* WaitExamBodyView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WaitExamBodyView.h; sourceTree = "<group>"; };
+		27EF3EFD24BF015A002068A2 /* WaitExamBodyView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = WaitExamBodyView.m; sourceTree = "<group>"; };
+		27EF3EFF24BF016B002068A2 /* WaitExamBodyView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = WaitExamBodyView.xib; sourceTree = "<group>"; };
+		27EF3F0124BF0F11002068A2 /* TicketListModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TicketListModel.h; sourceTree = "<group>"; };
+		27EF3F0224BF0F12002068A2 /* TicketListModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TicketListModel.m; sourceTree = "<group>"; };
+		27EF3F0424BFF8A0002068A2 /* TicketDetailModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TicketDetailModel.m; sourceTree = "<group>"; };
+		27EF3F0524BFF8A1002068A2 /* TicketDetailModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TicketDetailModel.h; sourceTree = "<group>"; };
+		27EF3F0724C02B3E002068A2 /* LocalRenderManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LocalRenderManager.m; sourceTree = "<group>"; };
+		27EF3F0824C02B3F002068A2 /* LocalRenderManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LocalRenderManager.h; sourceTree = "<group>"; };
+		27EF3F0B24C02B67002068A2 /* KSTipsView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KSTipsView.m; sourceTree = "<group>"; };
+		27EF3F0C24C02B67002068A2 /* KSTipsView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KSTipsView.h; sourceTree = "<group>"; };
+		27EF3F0E24C02B67002068A2 /* KSNormalAlertView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KSNormalAlertView.h; sourceTree = "<group>"; };
+		27EF3F0F24C02B67002068A2 /* KSNormalAlertView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KSNormalAlertView.m; sourceTree = "<group>"; };
+		27EF3F1124C02B67002068A2 /* ClassTitleView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ClassTitleView.h; sourceTree = "<group>"; };
+		27EF3F1224C02B67002068A2 /* ClassTitleView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ClassTitleView.m; sourceTree = "<group>"; };
+		27EF3F1424C02B67002068A2 /* ClassVideoListView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ClassVideoListView.h; sourceTree = "<group>"; };
+		27EF3F1524C02B68002068A2 /* ClassVideoListCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ClassVideoListCell.h; sourceTree = "<group>"; };
+		27EF3F1724C02B68002068A2 /* ClassVideoListView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ClassVideoListView.m; sourceTree = "<group>"; };
+		27EF3F1924C02B68002068A2 /* ClassVideoListCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ClassVideoListCell.m; sourceTree = "<group>"; };
+		27EF3F1B24C02B68002068A2 /* MainToolView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MainToolView.m; sourceTree = "<group>"; };
+		27EF3F1C24C02B68002068A2 /* MainToolView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MainToolView.h; sourceTree = "<group>"; };
+		27EF3F1E24C02B68002068A2 /* ClassroomMainContainer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ClassroomMainContainer.h; sourceTree = "<group>"; };
+		27EF3F1F24C02B68002068A2 /* ClassroomMainContainer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ClassroomMainContainer.m; sourceTree = "<group>"; };
+		27EF3F2824C02DE8002068A2 /* EmptyView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EmptyView.m; sourceTree = "<group>"; };
+		27EF3F2924C02DE9002068A2 /* EmptyView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EmptyView.h; sourceTree = "<group>"; };
+		27EF3F2B24C0384E002068A2 /* HomeExamTicketCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HomeExamTicketCell.h; sourceTree = "<group>"; };
+		27EF3F2C24C0384E002068A2 /* HomeExamTicketCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HomeExamTicketCell.m; sourceTree = "<group>"; };
+		27EF3F2D24C0384E002068A2 /* HomeExamTicketCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = HomeExamTicketCell.xib; sourceTree = "<group>"; };
 		2BB32BBE582672362BB6E017 /* Pods-MusicGradeExam-MusicGradeExamUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MusicGradeExam-MusicGradeExamUITests.release.xcconfig"; path = "Target Support Files/Pods-MusicGradeExam-MusicGradeExamUITests/Pods-MusicGradeExam-MusicGradeExamUITests.release.xcconfig"; sourceTree = "<group>"; };
 		2F7D3758362ED28D51286A60 /* Pods-MusicGradeExam.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MusicGradeExam.debug.xcconfig"; path = "Target Support Files/Pods-MusicGradeExam/Pods-MusicGradeExam.debug.xcconfig"; sourceTree = "<group>"; };
 		5402063E714DA9D9107F8070 /* Pods-MusicGradeExamTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MusicGradeExamTests.debug.xcconfig"; path = "Target Support Files/Pods-MusicGradeExamTests/Pods-MusicGradeExamTests.debug.xcconfig"; sourceTree = "<group>"; };
@@ -1305,6 +1353,9 @@
 				27A008FA24BECDC40002452B /* HomeBodyView.h */,
 				27A008FB24BECDC40002452B /* HomeBodyView.m */,
 				27A008FD24BECDFC0002452B /* HomeBodyView.xib */,
+				27EF3F2B24C0384E002068A2 /* HomeExamTicketCell.h */,
+				27EF3F2C24C0384E002068A2 /* HomeExamTicketCell.m */,
+				27EF3F2D24C0384E002068A2 /* HomeExamTicketCell.xib */,
 			);
 			path = View;
 			sourceTree = "<group>";
@@ -1363,8 +1414,8 @@
 		2747700F24BC0C0100181362 /* Controller */ = {
 			isa = PBXGroup;
 			children = (
-				2747701024BC0C0100181362 /* ClassroomViewController.m */,
 				2747701124BC0C0100181362 /* ClassroomViewController.h */,
+				2747701024BC0C0100181362 /* ClassroomViewController.m */,
 			);
 			path = Controller;
 			sourceTree = "<group>";
@@ -1372,6 +1423,8 @@
 		2747701224BC0C0100181362 /* Model */ = {
 			isa = PBXGroup;
 			children = (
+				27EF3F0824C02B3F002068A2 /* LocalRenderManager.h */,
+				27EF3F0724C02B3E002068A2 /* LocalRenderManager.m */,
 				2747701324BC0C0100181362 /* RoomLoginHelper.h */,
 				2747701424BC0C0100181362 /* RoomLoginHelper.m */,
 			);
@@ -1381,6 +1434,12 @@
 		2747701524BC0C0100181362 /* View */ = {
 			isa = PBXGroup;
 			children = (
+				27EF3F1D24C02B68002068A2 /* MainContainer */,
+				27EF3F1A24C02B68002068A2 /* MainToolbar */,
+				27EF3F0D24C02B67002068A2 /* NormalAlertView */,
+				27EF3F0A24C02B67002068A2 /* TipsView */,
+				27EF3F1024C02B67002068A2 /* TitleView */,
+				27EF3F1324C02B67002068A2 /* VideoList */,
 			);
 			path = View;
 			sourceTree = "<group>";
@@ -1388,11 +1447,11 @@
 		2747701624BC0C0100181362 /* Service */ = {
 			isa = PBXGroup;
 			children = (
-				2747701724BC0C0100181362 /* KSRemoteUserManager.h */,
-				2747701824BC0C0100181362 /* RTCService */,
 				2747701B24BC0C0100181362 /* Classroom */,
-				2747705224BC0C0100181362 /* KSRemoteUserManager.m */,
 				2747705324BC0C0100181362 /* IM */,
+				2747701724BC0C0100181362 /* KSRemoteUserManager.h */,
+				2747705224BC0C0100181362 /* KSRemoteUserManager.m */,
+				2747701824BC0C0100181362 /* RTCService */,
 			);
 			path = Service;
 			sourceTree = "<group>";
@@ -1464,8 +1523,6 @@
 		2747702A24BC0C0100181362 /* View */ = {
 			isa = PBXGroup;
 			children = (
-				2747702B24BC0C0100181362 /* KSNormalAlertView.h */,
-				2747702C24BC0C0100181362 /* KSNormalAlertView.m */,
 			);
 			path = View;
 			sourceTree = "<group>";
@@ -1540,6 +1597,8 @@
 			children = (
 				27544CF624BC337D00EF58AF /* ExamTicketViewController.h */,
 				27544CF724BC337D00EF58AF /* ExamTicketViewController.m */,
+				27EF3EF924BEFC79002068A2 /* WaitExamViewController.h */,
+				27EF3EFA24BEFC79002068A2 /* WaitExamViewController.m */,
 			);
 			path = Controller;
 			sourceTree = "<group>";
@@ -1547,6 +1606,10 @@
 		2747705824BC0C0100181362 /* Model */ = {
 			isa = PBXGroup;
 			children = (
+				27EF3F0124BF0F11002068A2 /* TicketListModel.h */,
+				27EF3F0224BF0F12002068A2 /* TicketListModel.m */,
+				27EF3F0524BFF8A1002068A2 /* TicketDetailModel.h */,
+				27EF3F0424BFF8A0002068A2 /* TicketDetailModel.m */,
 			);
 			path = Model;
 			sourceTree = "<group>";
@@ -1554,6 +1617,12 @@
 		2747705924BC0C0100181362 /* View */ = {
 			isa = PBXGroup;
 			children = (
+				27EF3EF424BEF1DA002068A2 /* TicketBodyView.h */,
+				27EF3EF524BEF1DA002068A2 /* TicketBodyView.m */,
+				27EF3EF724BEF1E8002068A2 /* TicketBodyView.xib */,
+				27EF3EFC24BF015A002068A2 /* WaitExamBodyView.h */,
+				27EF3EFD24BF015A002068A2 /* WaitExamBodyView.m */,
+				27EF3EFF24BF016B002068A2 /* WaitExamBodyView.xib */,
 			);
 			path = View;
 			sourceTree = "<group>";
@@ -1663,14 +1732,14 @@
 		2747707524BC0C0200181362 /* Manager */ = {
 			isa = PBXGroup;
 			children = (
-				2747707624BC0C0200181362 /* OnlineRoomManager.m */,
 				2747707724BC0C0200181362 /* KSRCIMDataSource.h */,
-				2747707824BC0C0200181362 /* RCConnectionManager.h */,
-				2747707924BC0C0200181362 /* KSUpdateManager.h */,
 				2747707A24BC0C0200181362 /* KSRCIMDataSource.m */,
+				2747707924BC0C0200181362 /* KSUpdateManager.h */,
+				2747707D24BC0C0200181362 /* KSUpdateManager.m */,
 				2747707B24BC0C0200181362 /* OnlineRoomManager.h */,
+				2747707624BC0C0200181362 /* OnlineRoomManager.m */,
+				2747707824BC0C0200181362 /* RCConnectionManager.h */,
 				2747707C24BC0C0200181362 /* RCConnectionManager.m */,
-				2747707D24BC0C0200181362 /* KSUpdateManager.m */,
 			);
 			path = Manager;
 			sourceTree = "<group>";
@@ -2294,6 +2363,64 @@
 			path = View;
 			sourceTree = "<group>";
 		};
+		27EF3F0A24C02B67002068A2 /* TipsView */ = {
+			isa = PBXGroup;
+			children = (
+				27EF3F0B24C02B67002068A2 /* KSTipsView.m */,
+				27EF3F0C24C02B67002068A2 /* KSTipsView.h */,
+			);
+			path = TipsView;
+			sourceTree = "<group>";
+		};
+		27EF3F0D24C02B67002068A2 /* NormalAlertView */ = {
+			isa = PBXGroup;
+			children = (
+				27EF3F0E24C02B67002068A2 /* KSNormalAlertView.h */,
+				27EF3F0F24C02B67002068A2 /* KSNormalAlertView.m */,
+			);
+			path = NormalAlertView;
+			sourceTree = "<group>";
+		};
+		27EF3F1024C02B67002068A2 /* TitleView */ = {
+			isa = PBXGroup;
+			children = (
+				27EF3F1124C02B67002068A2 /* ClassTitleView.h */,
+				27EF3F1224C02B67002068A2 /* ClassTitleView.m */,
+			);
+			path = TitleView;
+			sourceTree = "<group>";
+		};
+		27EF3F1324C02B67002068A2 /* VideoList */ = {
+			isa = PBXGroup;
+			children = (
+				27EF3F1524C02B68002068A2 /* ClassVideoListCell.h */,
+				27EF3F1924C02B68002068A2 /* ClassVideoListCell.m */,
+				27EF3F1424C02B67002068A2 /* ClassVideoListView.h */,
+				27EF3F1724C02B68002068A2 /* ClassVideoListView.m */,
+			);
+			path = VideoList;
+			sourceTree = "<group>";
+		};
+		27EF3F1A24C02B68002068A2 /* MainToolbar */ = {
+			isa = PBXGroup;
+			children = (
+				27EF3F1B24C02B68002068A2 /* MainToolView.m */,
+				27EF3F1C24C02B68002068A2 /* MainToolView.h */,
+			);
+			path = MainToolbar;
+			sourceTree = "<group>";
+		};
+		27EF3F1D24C02B68002068A2 /* MainContainer */ = {
+			isa = PBXGroup;
+			children = (
+				27EF3F1E24C02B68002068A2 /* ClassroomMainContainer.h */,
+				27EF3F1F24C02B68002068A2 /* ClassroomMainContainer.m */,
+				27EF3F2924C02DE9002068A2 /* EmptyView.h */,
+				27EF3F2824C02DE8002068A2 /* EmptyView.m */,
+			);
+			path = MainContainer;
+			sourceTree = "<group>";
+		};
 		3765A35FA865CF0E79E4496A /* Pods */ = {
 			isa = PBXGroup;
 			children = (
@@ -2457,6 +2584,7 @@
 				2747718A24BC0C0500181362 /* WMPlayer.bundle in Resources */,
 				27476F5624BBFB5C00181362 /* LaunchScreen.storyboard in Resources */,
 				2747727F24BC0C7C00181362 /* RCColor.plist in Resources */,
+				27EF3EF824BEF1E8002068A2 /* TicketBodyView.xib in Resources */,
 				27A008F924BDB6310002452B /* UserBodyView.xib in Resources */,
 				2794D1C424BD60E900BAF6F3 /* UserCenterBodyView.xib in Resources */,
 				2794D1B224BC605600BAF6F3 /* VefiBodyView.xib in Resources */,
@@ -2477,7 +2605,9 @@
 				2747728024BC0C7C00181362 /* RongCloudKit.strings in Resources */,
 				27476F5124BBFB5900181362 /* Main.storyboard in Resources */,
 				27EF3EF324BEE885002068A2 /* NotifyMessageCell.xib in Resources */,
+				27EF3F0024BF016B002068A2 /* WaitExamBodyView.xib in Resources */,
 				27A008A924BD96C50002452B /* KSNetworkAlert.xib in Resources */,
+				27EF3F2F24C0384E002068A2 /* HomeExamTicketCell.xib in Resources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -2609,6 +2739,7 @@
 				2747719A24BC0C0500181362 /* TZPhotoPreviewCell.m in Sources */,
 				274771E324BC0C0500181362 /* KSIMService.m in Sources */,
 				2747721D24BC0C0500181362 /* UIButton+Property.m in Sources */,
+				27EF3F2024C02B68002068A2 /* KSTipsView.m in Sources */,
 				27A008B824BD98170002452B /* AudioPlayManager.m in Sources */,
 				2747725224BC0C0500181362 /* JXCategoryBaseCell.m in Sources */,
 				2747723824BC0C0500181362 /* JXCategoryIndicatorImageView.m in Sources */,
@@ -2618,11 +2749,13 @@
 				2747722E24BC0C0500181362 /* KSAudioRecordManager.m in Sources */,
 				274771B024BC0C0500181362 /* UIView+Animation.m in Sources */,
 				274771CA24BC0C0500181362 /* RoomLoginHelper.m in Sources */,
+				27EF3F0924C02B3F002068A2 /* LocalRenderManager.m in Sources */,
 				2794D1C724BD62FB00BAF6F3 /* ModifyBodyView.m in Sources */,
 				274771A624BC0C0500181362 /* MSSBrowseActionSheet.m in Sources */,
 				2747720124BC0C0500181362 /* UIDevice+zhDeviceType.m in Sources */,
 				274771F124BC0C0500181362 /* KSUpdateManager.m in Sources */,
 				2747721424BC0C0500181362 /* NSDate+Transform.m in Sources */,
+				27EF3F2324C02B68002068A2 /* ClassVideoListView.m in Sources */,
 				27A008DD24BDA6950002452B /* PhoneCheckBodyView.m in Sources */,
 				2747724824BC0C0500181362 /* JXCategoryImageView.m in Sources */,
 				2747723424BC0C0500181362 /* JXPagerView.m in Sources */,
@@ -2681,22 +2814,24 @@
 				274771F424BC0C0500181362 /* KSLRUManager.m in Sources */,
 				2747718C24BC0C0500181362 /* FastForwardView.m in Sources */,
 				2747718724BC0C0500181362 /* JYEqualCellSpaceFlowLayout.m in Sources */,
-				274771D024BC0C0500181362 /* KSNormalAlertView.m in Sources */,
 				274771E624BC0C0500181362 /* LoginViewController.m in Sources */,
 				2747726424BC0C0500181362 /* LLPhoto.m in Sources */,
 				27A008C924BD9D550002452B /* KSNetTypeManager.m in Sources */,
+				27EF3F2124C02B68002068A2 /* KSNormalAlertView.m in Sources */,
 				2747723524BC0C0500181362 /* JXPagerListRefreshView.m in Sources */,
 				274771A824BC0C0500181362 /* UIImage+MSSScale.m in Sources */,
 				27A008F724BDB6220002452B /* UserBodyView.m in Sources */,
 				274771D224BC0C0500181362 /* DisplayCommandMessage.m in Sources */,
 				2747722F24BC0C0500181362 /* KSRecordPowerAnimationView.m in Sources */,
 				2747723C24BC0C0500181362 /* JXCategoryIndicatorCellModel.m in Sources */,
+				27EF3F0624BFF8A1002068A2 /* TicketDetailModel.m in Sources */,
 				274771FD24BC0C0500181362 /* NSObject+KSImpChangeTool.m in Sources */,
 				2747718B24BC0C0500181362 /* WMLightView.m in Sources */,
 				2747721024BC0C0500181362 /* UIView+Hints.m in Sources */,
 				274771A424BC0C0500181362 /* MSSBrowseCollectionViewCell.m in Sources */,
 				274771AF24BC0C0500181362 /* UIView+MSSLayout.m in Sources */,
 				27A008E124BDA6B40002452B /* FeedbackViewController.m in Sources */,
+				27EF3EF624BEF1DA002068A2 /* TicketBodyView.m in Sources */,
 				274771CC24BC0C0500181362 /* UIView+MBProgressHUD.m in Sources */,
 				274771F624BC0C0500181362 /* KSDiskCache.m in Sources */,
 				2747721224BC0C0500181362 /* NSDate+Extension.m in Sources */,
@@ -2724,6 +2859,7 @@
 				274771CD24BC0C0500181362 /* RolePortraitView.m in Sources */,
 				2747722024BC0C0500181362 /* UIImage+Property.m in Sources */,
 				274771C624BC0C0500181362 /* KSTabBarController.m in Sources */,
+				27EF3F2A24C02DE9002068A2 /* EmptyView.m in Sources */,
 				2747724524BC0C0500181362 /* JXCategoryTitleCellModel.m in Sources */,
 				2747720B24BC0C0500181362 /* UIView+Dealloc.m in Sources */,
 				2747724024BC0C0500181362 /* JXCategoryDotView.m in Sources */,
@@ -2733,6 +2869,7 @@
 				2747719324BC0C0500181362 /* NSBundle+TZImagePicker.m in Sources */,
 				27A008B524BD97FE0002452B /* AudioRecordManager.m in Sources */,
 				2747721924BC0C0500181362 /* UIImage+Color.m in Sources */,
+				27EF3F2E24C0384E002068A2 /* HomeExamTicketCell.m in Sources */,
 				2747722B24BC0C0500181362 /* KSAudioRecordFileManager.m in Sources */,
 				27A008D424BDA67F0002452B /* ModifyPhoneCheckController.m in Sources */,
 				2747726224BC0C0500181362 /* LLImageCache.m in Sources */,
@@ -2747,6 +2884,7 @@
 				2794D1B024BC604800BAF6F3 /* VefiBodyView.m in Sources */,
 				274771C824BC0C0500181362 /* HomeViewController.m in Sources */,
 				27544CFB24BC338900EF58AF /* UserCenterViewController.m in Sources */,
+				27EF3EFB24BEFC79002068A2 /* WaitExamViewController.m in Sources */,
 				274771B124BC0C0500181362 /* ZKCycleScrollView.m in Sources */,
 				274771A724BC0C0500181362 /* MSSBrowseNetworkViewController.m in Sources */,
 				2747724924BC0C0500181362 /* JXCategoryImageCell.m in Sources */,
@@ -2768,6 +2906,7 @@
 				2747725424BC0C0500181362 /* MBProgressHUD+NJ.m in Sources */,
 				27A008D324BDA67F0002452B /* ModifyPhoneChangeController.m in Sources */,
 				2747724A24BC0C0500181362 /* JXCategoryImageCellModel.m in Sources */,
+				27EF3F0324BF0F12002068A2 /* TicketListModel.m in Sources */,
 				2747721C24BC0C0500181362 /* UrlDecode.m in Sources */,
 				2747724724BC0C0500181362 /* JXCategoryTitleView.m in Sources */,
 				274771F724BC0C0500181362 /* KSCacheManager.m in Sources */,
@@ -2781,6 +2920,7 @@
 				274771AB24BC0C0500181362 /* MSSBrowseRemindView.m in Sources */,
 				274771A924BC0C0500181362 /* MSSBrowseBaseViewController.m in Sources */,
 				27A008A524BD96C50002452B /* NetworkingCheckController.m in Sources */,
+				27EF3EFE24BF015A002068A2 /* WaitExamBodyView.m in Sources */,
 				274771C724BC0C0500181362 /* KSRequestManager.m in Sources */,
 				2747722824BC0C0500181362 /* GRCreateManager.m in Sources */,
 				2747720724BC0C0500181362 /* UITextView+ZWLimitCounter.m in Sources */,
@@ -2799,6 +2939,7 @@
 				27EF3EEE24BEE35E002068A2 /* MessageListModel.m in Sources */,
 				2747725824BC0C0500181362 /* SkipTextView.m in Sources */,
 				2747725E24BC0C0500181362 /* KSInputView.m in Sources */,
+				27EF3F2224C02B68002068A2 /* ClassTitleView.m in Sources */,
 				2747719C24BC0C0500181362 /* TZLocationManager.m in Sources */,
 				2747720524BC0C0500181362 /* CALayer+Color.m in Sources */,
 				274771D124BC0C0500181362 /* ApplySpeechMessage.m in Sources */,
@@ -2818,7 +2959,9 @@
 				2747719424BC0C0500181362 /* UIView+Layout.m in Sources */,
 				2747719D24BC0C0500181362 /* TZGifPhotoPreviewController.m in Sources */,
 				2747724E24BC0C0500181362 /* JXCategoryFactory.m in Sources */,
+				27EF3F2524C02B68002068A2 /* ClassVideoListCell.m in Sources */,
 				2747722524BC0C0500181362 /* UIButton+EnlargeEdge.m in Sources */,
+				27EF3F2724C02B68002068A2 /* ClassroomMainContainer.m in Sources */,
 				2747722724BC0C0500181362 /* UIImage+Resize.m in Sources */,
 				274771EE24BC0C0500181362 /* OnlineRoomManager.m in Sources */,
 				2747721F24BC0C0500181362 /* UIColor+Hex.m in Sources */,
@@ -2832,6 +2975,7 @@
 				2747725724BC0C0500181362 /* UITextView_Toolbar.m in Sources */,
 				274771DA24BC0C0500181362 /* MemberChangeMessage.m in Sources */,
 				2747721824BC0C0500181362 /* NSObject+ReadDocument.m in Sources */,
+				27EF3F2624C02B68002068A2 /* MainToolView.m in Sources */,
 				2747718F24BC0C0500181362 /* TZImageCropManager.m in Sources */,
 				2747725924BC0C0500181362 /* GRScanManager.m in Sources */,
 				2747724224BC0C0500181362 /* JXCategoryTitleImageView.m in Sources */,

+ 22 - 0
MusicGradeExam/MusicGradeExam/Assets.xcassets/Home/count_bg.imageset/Contents.json

@@ -7,11 +7,33 @@
     {
       "filename" : "count_bg@2x.png",
       "idiom" : "universal",
+      "resizing" : {
+        "cap-insets" : {
+          "left" : 46,
+          "right" : 141
+        },
+        "center" : {
+          "mode" : "tile",
+          "width" : 24
+        },
+        "mode" : "3-part-horizontal"
+      },
       "scale" : "2x"
     },
     {
       "filename" : "count_bg@3x.png",
       "idiom" : "universal",
+      "resizing" : {
+        "cap-insets" : {
+          "left" : 68,
+          "right" : 240
+        },
+        "center" : {
+          "mode" : "tile",
+          "width" : 9
+        },
+        "mode" : "3-part-horizontal"
+      },
       "scale" : "3x"
     }
   ],

+ 22 - 0
MusicGradeExam/MusicGradeExam/Assets.xcassets/Home/home_top.imageset/Contents.json

@@ -0,0 +1,22 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "home_top@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "home_top@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN=BIN
MusicGradeExam/MusicGradeExam/Assets.xcassets/Home/home_top.imageset/home_top@2x.png


BIN=BIN
MusicGradeExam/MusicGradeExam/Assets.xcassets/Home/home_top.imageset/home_top@3x.png


+ 22 - 0
MusicGradeExam/MusicGradeExam/Assets.xcassets/wd_img_zwsj.imageset/Contents.json

@@ -0,0 +1,22 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "wd_img_zwsj@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "wd_img_zwsj@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN=BIN
MusicGradeExam/MusicGradeExam/Assets.xcassets/wd_img_zwsj.imageset/wd_img_zwsj@2x.png


BIN=BIN
MusicGradeExam/MusicGradeExam/Assets.xcassets/wd_img_zwsj.imageset/wd_img_zwsj@3x.png


+ 1 - 0
MusicGradeExam/MusicGradeExam/Base/KSBaseViewController.m

@@ -571,6 +571,7 @@
 - (NSInteger) calcDaysFromBegin:(NSDate *)beginDate end:(NSDate *)endDate{
     //创建日期格式化对象
     NSDateFormatter *dateFormatter=[[NSDateFormatter alloc] init];
+    dateFormatter.timeZone = [NSTimeZone systemTimeZone];
     [dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm"];
     
     //取两个日期对象的时间间隔:

+ 30 - 3
MusicGradeExam/MusicGradeExam/KSRequestManager.h

@@ -183,7 +183,7 @@ NS_ASSUME_NONNULL_BEGIN
 + (void)logoutRequest:(NSString *)post success:(void(^)(NSDictionary *dic))success faliure:(void(^)(NSError *error))faliure;
 
 #pragma mark ---- 消息相关
-// /api-student/sysMessage/list
+// /api-auth/sysMessage/list
 
 /// 消息列表
 /// @param get get
@@ -193,7 +193,7 @@ NS_ASSUME_NONNULL_BEGIN
 /// @param faliure 失败
 + (void)sysMessageRequest:(NSString *)get page:(NSString *)page rows:(NSString *)rows success:(void(^)(NSDictionary *dic))success faliure:(void(^)(NSError *error))faliure;
 
-// /api-student/sysMessage/setRead
+// /api-auth/sysMessage/setRead
 
 /// 消息设置已读
 /// @param post post
@@ -202,7 +202,7 @@ NS_ASSUME_NONNULL_BEGIN
 /// @param faliure 失败
 + (void)sysMessageSetReadRequest:(NSString *)post messageId:(NSString *)messageId success:(void(^)(NSDictionary *dic))success faliure:(void(^)(NSError *error))faliure;
 
-// /api-student/sysMessage/queryCountOfUnread
+// /api-auth/sysMessage/queryCountOfUnread
 
 /// 获取未读消息条数
 /// @param get get
@@ -280,6 +280,33 @@ NS_ASSUME_NONNULL_BEGIN
 /// @param faliure 失败
 + (void)refreshImTokenRequest:(NSString *)post success:(void(^)(NSDictionary *dic))success faliure:(void(^)(NSError *error))faliure;
 
+
+#pragma mark ----- 准考证服务
+// /api-user/examCertification/queryCertificationPage
+
+/// 获取学员准考证列表
+/// @param get get
+/// @param success 成功
+/// @param faliure 失败
++ (void)queryCertificationPageRequest:(NSString *)get success:(void(^)(NSDictionary *dic))success faliure:(void(^)(NSError *error))faliure;
+
+// /api-user/examCertification/needCheckingDetail
+
+/// 待考详情
+/// @param get get
+/// @param examRegistrationId 报名编号
+/// @param success 成功
+/// @param faliure 失败
++ (void)needCheckingDetailRequest:(NSString *)get examRegistrationId:(NSString *)examRegistrationId success:(void(^)(NSDictionary *dic))success faliure:(void(^)(NSError *error))faliure;
+
+// /api-user/examRoomStudentRelation/signIn
+
+/// 签到
+/// @param post post
+/// @param examRegistrationId 报名编号
+/// @param success 成功
+/// @param faliure 失败
++ (void)signInRequest:(NSString *)post examRegistrationId:(NSString *)examRegistrationId success:(void(^)(NSDictionary *dic))success faliure:(void(^)(NSError *error))faliure;
 @end
 
 NS_ASSUME_NONNULL_END

+ 49 - 7
MusicGradeExam/MusicGradeExam/KSRequestManager.m

@@ -479,7 +479,7 @@
 }
 
 #pragma mark --- 消息相关
-// /api-user/sysMessage/list
+// /api-auth/sysMessage/list
 
 /// 消息列表
 /// @param get get
@@ -488,14 +488,14 @@
 /// @param success 成功
 /// @param faliure 失败
 + (void)sysMessageRequest:(NSString *)get page:(NSString *)page rows:(NSString *)rows success:(void (^)(NSDictionary * _Nonnull))success faliure:(void (^)(NSError * _Nonnull))faliure {
-    NSString *url = [NSString stringWithFormat:@"%@%@",hostURL, @"/api-user/sysMessage/list"];
+    NSString *url = [NSString stringWithFormat:@"%@%@",hostURL, @"/api-auth/sysMessage/list"];
     NSMutableDictionary *parm = [NSMutableDictionary dictionary];
     [parm setValue:page forKey:@"page"];
     [parm setValue:rows forKey:@"rows"];
     [self request:get url:url parm:parm success:success faliure:faliure];
 }
 
-// /api-user/sysMessage/setRead
+// /api-auth/sysMessage/setRead
 
 /// 消息设置已读
 /// @param post post
@@ -504,19 +504,19 @@
 /// @param faliure 失败
 + (void)sysMessageSetReadRequest:(NSString *)post messageId:(NSString *)messageId success:(void (^)(NSDictionary * _Nonnull))success faliure:(void (^)(NSError * _Nonnull))faliure {
     [self configRequestMethodForm];
-    NSString *url = [NSString stringWithFormat:@"%@%@",hostURL, @"/api-user/sysMessage/setRead"];
+    NSString *url = [NSString stringWithFormat:@"%@%@",hostURL, @"/api-auth/sysMessage/setRead"];
     NSMutableDictionary *parm = [NSMutableDictionary dictionary];
     [parm setValue:messageId forKey:@"id"];
     [self request:post url:url parm:parm success:success faliure:faliure];
 }
-// /api-user/sysMessage/queryCountOfUnread
+// /api-auth/sysMessage/queryCountOfUnread
 
 /// 获取未读消息条数
 /// @param get get
 /// @param success 成功
 /// @param faliure 失败
 + (void)queryCountOfUnreadRequest:(NSString *)get success:(void (^)(NSDictionary * _Nonnull))success faliure:(void (^)(NSError * _Nonnull))faliure {
-    NSString *url = [NSString stringWithFormat:@"%@%@", hostURL, @"/api-user/sysMessage/queryCountOfUnread"];
+    NSString *url = [NSString stringWithFormat:@"%@%@", hostURL, @"/api-auth/sysMessage/queryCountOfUnread"];
     [self request:get url:url parm:nil success:success faliure:faliure];
 }
 #pragma mark --- 个人信息
@@ -554,7 +554,7 @@
     [parm setValue:gender forKey:@"gender"];
     [parm setValue:birthdate forKey:@"birthdate"];
     [parm setValue:nation forKey:@"nation"];
-    [parm setValue:avatar forKey:@"avatar"];
+    [parm setValue:avatar forKey:@"certificatePhoto"];
     NSString *url = [NSString stringWithFormat:@"%@%@",hostURL, @"/api-user/student/updateInfo"];
     [self request:post url:url parm:parm success:success faliure:faliure];
 }
@@ -628,4 +628,46 @@
     [self request:post url:url parm:parm success:success faliure:faliure];
 }
 
+#pragma mark ----- 准考证服务
+// /api-user/examCertification/queryCertificationPage
+
+/// 获取学员准考证列表
+/// @param get get
+/// @param success 成功
+/// @param faliure 失败
++ (void)queryCertificationPageRequest:(NSString *)get success:(void(^)(NSDictionary *dic))success faliure:(void(^)(NSError *error))faliure {
+    
+    NSString *url = [NSString stringWithFormat:@"%@%@",hostURL, @"/api-user/examCertification/queryCertificationPage"];
+    [self request:get url:url parm:nil success:success faliure:faliure];
+}
+
+// /api-user/examCertification/needCheckingDetail
+
+/// 待考详情
+/// @param get get
+/// @param examRegistrationId 报名编号
+/// @param success 成功
+/// @param faliure 失败
++ (void)needCheckingDetailRequest:(NSString *)get examRegistrationId:(NSString *)examRegistrationId success:(void(^)(NSDictionary *dic))success faliure:(void(^)(NSError *error))faliure {
+    NSString *url = [NSString stringWithFormat:@"%@%@",hostURL, @"/api-user/examCertification/needCheckingDetail"];
+    NSMutableDictionary *parm = [NSMutableDictionary dictionary];
+    [parm setValue:examRegistrationId forKey:@"examRegistrationId"];
+    [self request:get url:url parm:parm success:success faliure:faliure];
+}
+
+// /api-user/examRoomStudentRelation/signIn
+
+/// 签到
+/// @param post post
+/// @param examRegistrationId 报名编号
+/// @param success 成功
+/// @param faliure 失败
++ (void)signInRequest:(NSString *)post examRegistrationId:(NSString *)examRegistrationId success:(void(^)(NSDictionary *dic))success faliure:(void(^)(NSError *error))faliure {
+    [self configRequestMethodForm];
+    NSString *url = [NSString stringWithFormat:@"%@%@",hostURL, @"/api-user/examRoomStudentRelation/signIn"];
+    NSMutableDictionary *parm = [NSMutableDictionary dictionary];
+    [parm setValue:examRegistrationId forKey:@"examRegistrationId"];
+    [self request:post url:url parm:parm success:success faliure:faliure];
+}
+
 @end

+ 1 - 3
MusicGradeExam/MusicGradeExam/Manager/OnlineRoomManager.h

@@ -13,9 +13,7 @@ NS_ASSUME_NONNULL_BEGIN
 
 @interface OnlineRoomManager : NSObject
 
-- (void)joinRoomWithId:(NSString *)roomId subjectName:(NSString *)subjectName courseScheduleId:(NSString *)courseScheduleId classEndTime:(NSString *)classEndTime quitEndTime:(NSInteger)quitRoomTime inViewController:(KSBaseViewController *)ctrl;
-
-- (void)joinRoomWithId:(NSString *)roomId subjectName:(NSString *)subjectName courseScheduleId:(NSString *)courseScheduleId classEndTime:(NSString *)classEndTime inViewController:(KSBaseViewController *)ctrl;
+- (void)joinRoomWithId:(NSString *)roomId inViewController:(KSBaseViewController *)ctrl;
 
 @end
 

+ 28 - 60
MusicGradeExam/MusicGradeExam/Manager/OnlineRoomManager.m

@@ -7,23 +7,21 @@
 //
 
 #import "OnlineRoomManager.h"
+#import "ClassroomViewController.h"
+#import "RoomLoginHelper.h"
+#import "ClassroomService.h"
+#import <RongIMLib/RongIMLib.h>
+#import "RTCService.h"
+#import "ClassroomService.h"
+#import "KSNormalAlertView.h"
+#import "RCConnectionManager.h"
 
-@interface OnlineRoomManager ()
+@interface OnlineRoomManager ()<ClassroomHelperDelegate>
 
 @property (nonatomic, strong) NSString *roomId;
 // 防止循环引用
 @property (nonatomic, weak) KSBaseViewController *baseCtrl;
 
-@property (nonatomic, strong) NSString *subjectName;
-
-@property (nonatomic, assign) BOOL isRotationImage;
-
-@property (nonatomic, strong) NSString *classEndTime;
-
-@property (nonatomic, strong) NSString *courseScheduleId;
-
-@property (nonatomic, assign) NSInteger quitRoomTime;
-
 @end
 
 @implementation OnlineRoomManager
@@ -32,34 +30,17 @@
 {
     self = [super init];
     if (self) {
-//         [LoginHelper sharedInstance].delegate = self;
+        [RoomLoginHelper sharedInstance].delegate = self;
     }
     return self;
 }
 
-- (void)joinRoomWithId:(NSString *)roomId subjectName:(NSString *)subjectName courseScheduleId:(NSString *)courseScheduleId classEndTime:(NSString *)classEndTime quitEndTime:(NSInteger)quitRoomTime inViewController:(KSBaseViewController *)ctrl {
+- (void)joinRoomWithId:(NSString *)roomId inViewController:(KSBaseViewController *)ctrl {
     self.roomId = roomId;
-    self.subjectName = subjectName;
     self.baseCtrl = ctrl;
-    self.classEndTime = classEndTime;
-    self.courseScheduleId = courseScheduleId;
-    self.quitRoomTime = quitRoomTime;
     [self joinRoom];
 }
 
-- (void)joinRoomWithId:(NSString *)roomId subjectName:(NSString *)subjectName courseScheduleId:(NSString *)courseScheduleId classEndTime:(NSString *)classEndTime inViewController:(KSBaseViewController *)ctrl {
-    [self joinRoomWithId:roomId subjectName:subjectName courseScheduleId:courseScheduleId classEndTime:classEndTime quitEndTime:0 inViewController:ctrl];
-}
-
-- (void)setSubjectName:(NSString *)subjectName {
-    _subjectName = subjectName;
-    if ([subjectName isEqualToString:@"长笛"] || [subjectName isEqualToString:@"小军鼓"]) {
-        _isRotationImage = NO;
-    }
-    else {
-        _isRotationImage = YES;
-    }
-}
 
 
 - (void)joinRoom {
@@ -67,22 +48,14 @@
     [self login:NO];
 }
 
-//#pragma mark - ClassroomHelperDelegate
-//- (void)classroomDidJoin:(Classroom *)classroom{
-//    if ([self.baseCtrl.navigationController.topViewController isKindOfClass:[self.baseCtrl class]]) {
-//        [self.baseCtrl removehub];
-//        [RCConnectionManager shareManager].isNeedShowMessage = YES;
-////        // 加入RTC成功反馈给后端
-////        [KSRequestManager roomSignInRequest:KS_POST roomId:self.roomId success:^(NSDictionary * _Nonnull dic) {
-////            if ([dic integerValueForKey:@"code"] == 200 && [dic boolValueForKey:@"status"]) {
-////                NSLog(@"成功");
-////            }
-////        } faliure:^(NSError * _Nonnull error) {
-////            NSLog(@"---");
-////        }];
-//        [self pushToClassroom];
-//    }
-//}
+#pragma mark - ClassroomHelperDelegate
+- (void)classroomDidJoin:(Classroom *)classroom{
+    if ([self.baseCtrl.navigationController.topViewController isKindOfClass:[self.baseCtrl class]]) {
+        [self.baseCtrl removehub];
+        [RCConnectionManager shareManager].isNeedShowMessage = YES;
+        [self pushToClassroom];
+    }
+}
 
 
 - (void)classroomDidJoinFailCode:(NSNumber *)code {
@@ -99,26 +72,21 @@
 
 - (void)classroomDidOverMaxUserCount{
     [self.baseCtrl removehub];
-//    [KSNormalAlertView ks_showAlertWithTitle:@"课堂学员人数已满!" confirmTitle:@"确认" confirm:^{
-////        [self login:YES];
-//    }];
+    [KSNormalAlertView ks_showAlertWithTitle:@"课堂学员人数已满!" confirmTitle:@"确认" confirm:^{
+    }];
 }
 
 - (void)pushToClassroom {
     
-//    NewClassRoomViewController *vc = [[NewClassRoomViewController alloc] init];
-//    vc.courseScheduleId = self.roomId;
-//    vc.isRorationImage = self.isRotationImage;
-//    vc.classEndTime = self.classEndTime;
-//    vc.courseId = self.courseScheduleId;
-//    vc.quitRoomTime = self.quitRoomTime;
-//    vc.modalPresentationStyle = UIModalPresentationFullScreen;
-//    [self.baseCtrl presentViewController:vc animated:YES completion:nil];
+    ClassroomViewController *vc = [[ClassroomViewController alloc] init];
+    vc.roomId = self.roomId;
+    vc.modalPresentationStyle = UIModalPresentationFullScreen;
+    [self.baseCtrl presentViewController:vc animated:YES completion:nil];
 }
 
 - (void)login:(BOOL)isAudience {
-//    NSString *roomId = [self.roomId stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
-//    NSString *userName = [UserDefault(NicknameKey) stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
-//    [[LoginHelper sharedInstance] login:roomId user:userName isAudience:isAudience];
+    NSString *roomId = [self.roomId stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
+    NSString *userName = [UserDefault(NicknameKey) stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
+    [[RoomLoginHelper sharedInstance] login:roomId user:userName isAudience:isAudience];
 }
 @end

+ 2 - 0
MusicGradeExam/MusicGradeExam/UI/Classroom/Controller/ClassroomViewController.h

@@ -12,6 +12,8 @@ NS_ASSUME_NONNULL_BEGIN
 
 @interface ClassroomViewController : UIViewController
 
+@property (nonatomic, strong) NSString *roomId;
+
 @end
 
 NS_ASSUME_NONNULL_END

+ 789 - 1
MusicGradeExam/MusicGradeExam/UI/Classroom/Controller/ClassroomViewController.m

@@ -7,18 +7,806 @@
 //
 
 #import "ClassroomViewController.h"
+#import "ClassTitleView.h"
+#import "ClassroomMainContainer.h"
+#import "ClassVideoListView.h"
+#import "KSNormalAlertView.h"
 
-@interface ClassroomViewController ()
+#import "RTCService.h"
+#import "Classroom.h"
+#import "ClassroomService.h"
+#import <MBProgressHUD/MBProgressHUD.h>
+#import "AppDelegate.h"
+#import "UIDevice+TFDevice.h"
+#import "KSRemoteUserManager.h"
+#import "LocalRenderManager.h"
+#import "NSDate+Transform.h"
+
+#import "UIViewController+zhStatusBarStyle.h"
+#import "KSTipsView.h"
+#import "RoomLoginHelper.h"
+
+#define TitleViewHeight  (64)
+
+@interface ClassroomViewController ()<ClassTitleViewDelegate, RongRTCRoomDelegate, ClassroomDelegate, ClassroomMainContainerDelegate, UIGestureRecognizerDelegate, RongRTCActivityMonitorDelegate, RTCServiceDelegate, ClassVideoListViewDelegate>
+
+@property (nonatomic, strong) ClassTitleView *titleView;
+@property (nonatomic, strong) ClassroomMainContainer *containerView;
+@property (nonatomic, strong) ClassVideoListView *videoListView;
+@property (nonatomic, strong) MBProgressHUD *hud;
+@property (nonatomic, strong) NSMutableArray<NSNumber*>* packetLossStore;
+
+@property (nonatomic, strong) UILabel *networkLabel;
+
+@property (nonatomic, assign) BOOL cancleAlert;
+
+@property (nonatomic, assign) BOOL isQuitRoom; // 是否正在退出房间
+
+@property (nonatomic, assign) BOOL hasShowAlert;
+
+@property (nonatomic, assign) BOOL isSwitchLine; // 是否线路切换
+
+@property (nonatomic, strong) KSTipsView *tipsView;
 
 @end
 
 @implementation ClassroomViewController
 
+#pragma mark - Life cycle
+
+- (void)viewWillAppear:(BOOL)animated {
+    [super viewWillAppear:animated];
+    self.zh_statusBarHidden = YES;
+    // 切换到横屏
+    AppDelegate* delegate = (AppDelegate*)[UIApplication sharedApplication].delegate;
+    delegate.allowAutoRotate = YES;
+    [UIDevice switchNewOrientation:UIInterfaceOrientationLandscapeRight];
+    [[UIApplication sharedApplication] setIdleTimerDisabled:YES];
+    [RongRTCEngine sharedEngine].monitorDelegate = self;
+}
+
+- (void)viewWillDisappear:(BOOL)animated {
+    [super viewWillDisappear:animated];
+    // 竖屏
+    AppDelegate* delegate = (AppDelegate*)[UIApplication sharedApplication].delegate;
+    delegate.allowAutoRotate = NO;
+    [UIDevice switchNewOrientation:UIInterfaceOrientationPortrait];
+    [[UIApplication sharedApplication] setIdleTimerDisabled:NO];
+    self.zh_statusBarHidden = NO;
+}
+
 - (void)viewDidLoad {
     [super viewDidLoad];
     // Do any additional setup after loading the view.
+    
+    self.view.backgroundColor = [UIColor colorWithHexString:@"141414" alpha:1];
+    
+    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(leaveRoomAction) name:@"classroomLogout" object:nil];
+    // 切换到横屏
+    AppDelegate* delegate = (AppDelegate*)[UIApplication sharedApplication].delegate;
+    delegate.allowAutoRotate = YES;
+    [UIDevice switchNewOrientation:UIInterfaceOrientationLandscapeRight];
+    [self addSubviews];
+    [self bindDelegates];
+    [self publishStream];
+    [self renderMainContainerView];
+    [[RTCService sharedInstance] useSpeaker:YES];
+    UITapGestureRecognizer *tapGes = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapGesture:)];
+    [self.view addGestureRecognizer:tapGes];
+    tapGes.delegate = self;
+    [self showRoleHud];
+    self.isQuitRoom = NO;
+    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(backgroundQuitRoomAction) name:@"backgroundQuit" object:nil];
+}
+
+- (void)backgroundQuitRoomAction {
+    self.isQuitRoom = YES;
+    self.isSwitchLine = NO;
+    [[RoomLoginHelper sharedInstance] logout:^{
+    } error:^(RongRTCCode code) {
+    }];
+    self.cancleAlert = YES;
+}
+
+#pragma mark ----- 账号异地登陆
+- (void)leaveRoomAction {
+    [self showMessage:@"该账号在其他地方登录"];
+    self.isQuitRoom = YES;
+    self.isSwitchLine = NO;
+    [[RoomLoginHelper sharedInstance] logout:^{
+    } error:^(RongRTCCode code) {
+    }];
+    self.cancleAlert = YES;
+    [[NSNotificationCenter defaultCenter] postNotificationName:@"backLoginView" object:nil];
+}
+
+// 提示信息
+- (void)showMessage:(NSString *)message {
+    MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
+    [self.view bringSubviewToFront:hud];
+    hud.removeFromSuperViewOnHide =YES;
+    hud.mode = MBProgressHUDModeText;
+    hud.label.text = message;
+    hud.minSize = CGSizeMake(132.f, 60.0f);
+    hud.label.textColor = [UIColor whiteColor];
+    hud.bezelView.backgroundColor = [UIColor colorWithHexString:@"#000000" alpha:0.8];
+    [hud hideAnimated:YES afterDelay:2];
+}
+
+
+- (void)tapGesture: (UITapGestureRecognizer *)tapGesture {
+    self.titleView.isDisplay = YES;
+}
+-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
+    if ([touch.view isDescendantOfView:self.videoListView]) {
+        return NO;
+    }
+    if ([touch.view isDescendantOfView:self.containerView]) {
+        [self tapGesture:(UITapGestureRecognizer *)gestureRecognizer];
+    }
+    return YES;
+}
+
+- (void)showRoleHud {
+    Role role =  [ClassroomService sharedService].currentRoom.currentMember.role;
+    if(role == RoleAudience) {
+        [self.tipsView showTipsMessage:@"你当前身份是旁听人,其他人看/听不见你" inView:self.view];
+        
+        [self performSelector:@selector(showOnlyYouHUD) withObject:nil afterDelay:5.0f];
+    }else{
+        [self showOnlyYouHUD];
+    }
+}
+
+- (void)showOnlyYouHUD {
+    if ([ClassroomService sharedService].currentRoom.memberList.count == 1) {
+        [self.tipsView showTipsMessage:@"当前课堂只有你一人,你可以等待或离开" inView:self.view];
+    }
+}
+
+#pragma mark --- ClassTitleViewDelegate
+- (void)classTitleView:(UIButton *)button didTapAtTag:(ClassTitleViewActionTag)tag {
+    switch (tag) {
+        case ClassTitleViewActionTagMicrophone:
+        {
+            if (button.selected == NO) {
+                [KSNormalAlertView ks_showAlertWithTitle:@"确认关闭麦克风吗?" leftTitle:@"取消" rightTitle:@"确认" cancel:^{
+                } confirm:^{
+                    button.selected = YES;
+                    [[ClassroomService sharedService] enableDevice:NO withType:DeviceTypeMicrophone];
+                }];
+
+            }
+            else {
+                button.selected = NO;
+                [[ClassroomService sharedService] enableDevice:YES withType:DeviceTypeMicrophone];
+            }
+        }
+            break;
+        case ClassTitleViewActionTagCamera:
+        {
+            if (button.selected == NO) {
+                [KSNormalAlertView ks_showAlertWithTitle:@"确认关闭摄像头吗?" leftTitle:@"取消" rightTitle:@"确认" cancel:^{
+                } confirm:^{
+                    button.selected = YES;
+                    [[ClassroomService sharedService] enableDevice:NO withType:DeviceTypeCamera];
+                }];
+            }
+            else {
+                button.selected = NO;
+                [[ClassroomService sharedService] enableDevice:YES withType:DeviceTypeCamera];
+            }
+        }
+            break;
+        case ClassTitleViewActionTagSwitchLine:  // 切换线路
+        {
+            [KSNormalAlertView ks_showAlertWithTitle:@"确认切换线路吗?" leftTitle:@"取消" rightTitle:@"确认" cancel:^{
+            } confirm:^{
+                [self restartRoom];
+            }];
+        }
+            break;
+        case ClassTitleViewActionTagSwitchCamera:
+        {
+            [[RTCService sharedInstance] switchCamera];
+        }
+            break;
+        case ClassTitleViewActionTagHangup:
+        {
+            [KSNormalAlertView ks_showAlertWithTitle:@"确认退出课堂吗?" leftTitle:@"取消" rightTitle:@"确认" cancel:^{
+            } confirm:^{
+                self.isQuitRoom = YES;
+                SealClassLog(@"ActionTagHangup");
+                self.hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
+                [[RoomLoginHelper sharedInstance] logout:^{
+                } error:^(RongRTCCode code) {
+                    [self.hud hideAnimated:YES];
+                }];
+            }];
+        }
+            break;
+        default:
+            break;
+    }
+}
+
+// 判断是否需要退出房间
+- (void)refreshClassStatus {
+    /*
+    NSString *userType = UserDefault(UserTypeKey);
+    if (![userType isEqualToString:@"1"] && self.isQuitRoom == NO) {
+        NSString *formatString = @"yyyy-MM-dd HH:mm:ss";
+        NSDate *endDate = [NSDate dateFromString:self.classEndTime format:formatString];
+        NSTimeInterval endDuration = [[NSDate date] timeIntervalSinceDate:endDate];
+        if (endDuration >= self.quitRoomTime * 60) {
+            self.isQuitRoom = YES;
+            // 退出房间
+            [self showMessage:@"课程已结束"];
+            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
+                // 课程结束退出房间
+                SealClassLog(@"ClassEnd!");
+                self.hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
+                [[LoginHelper sharedInstance] logout:^{
+                } error:^(RongRTCCode code) {
+                    [self.hud hideAnimated:YES];
+                }];
+            });
+        }
+    }
+     */
+}
+
+#pragma mark ------ 退出RTC房间并重新加入
+- (void)restartRoom {
+    FwLogI(RC_Type_RTC, @"L-SwitchLine-S", @"UserId:%@", [ClassroomService sharedService].currentRoom.currentMemberId);
+    self.isSwitchLine = YES;
+    self.hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
+    [[RoomLoginHelper sharedInstance] logout:^{
+    } error:^(RongRTCCode code) {
+    }];
+}
+
+- (void)reJoinRTCRoom {
+    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
+        NSString *roomId = [RoomLoginHelper sharedInstance].roomId;
+        NSString *userName = [RoomLoginHelper sharedInstance].userName;
+        [[ClassroomService sharedService] joinClassroom:roomId userName:userName isAudience:NO success:^(Classroom * _Nonnull classroom) {
+            self.isSwitchLine = NO;
+            [self restartRTCConnection];
+        } error:^(ErrorCode errorCode) {
+            [self showMessage:@"切换线路失败"];
+            [self.hud hideAnimated:YES];
+            self.isSwitchLine = NO;
+        }];
+    });
+}
+
+- (void)restartRTCConnection {
+    
+    [[RTCService sharedInstance] joinRongRTCRoom:[ClassroomService sharedService].currentRoom.roomId success:^(RongRTCRoom * _Nonnull room) {
+        [self.hud hideAnimated:YES];
+        [self showMessage:@"切换线路成功"];
+        // 重置UI 重新发布音视频流
+        [LocalRenderManager shareInstance].hadRenderMainView = NO;
+        [self publishStream];
+        [self bindDelegates];
+        [self.titleView refreshTitleView];
+        [self.containerView refreshToolView];
+        [self.hud hideAnimated:YES];
+        [RongRTCEngine sharedEngine].monitorDelegate = self;
+        [[RTCService sharedInstance] useSpeaker:YES];
+        [self renderMainContainerView];
+        [self.videoListView reloadVideoList];
+        
+    } error:^(RongRTCCode code) {
+        [self.hud hideAnimated:YES];
+        [self showMessage:@"切换线路失败!"];
+    }];
+}
+
+#pragma mark - ClassroomDelegate
+- (void)roomDidLeave {
+    NSLog(@"roomDidLeave");
+    if (_isSwitchLine) {
+        if ([RTCService sharedInstance].rtcRoom) {
+            [[KSRemoteUserManager shareInstance] removeAllUser];
+            [[RTCService sharedInstance] leaveRongRTCRoom:[ClassroomService sharedService].currentRoom.roomId success:^{
+                [self reJoinRTCRoom];
+            } error:^(RongRTCCode code) {
+                [self.hud hideAnimated:YES];
+                [self showMessage:@"切换线路失败"];
+                self.isSwitchLine = NO;
+            }];
+        }
+    }
+    else {
+        [[KSRemoteUserManager shareInstance] removeAllUser];
+        if ([RTCService sharedInstance].rtcRoom) {
+            [[RTCService sharedInstance] leaveRongRTCRoom:[ClassroomService sharedService].currentRoom.roomId success:^{
+            } error:^(RongRTCCode code) {
+            }];
+        }
+        [self dismissClassroom];
+    }
+}
+
+- (void)dismissClassroom {
+    [self.hud hideAnimated:YES];
+    [ClassroomService sharedService].currentRoom = nil;
+    [self dismissViewControllerAnimated:NO completion:^{
+        [self.titleView stopDurationTimer];
+    }];
+}
+
+- (void)memberDidJoin:(RoomMember *)member {
+    NSLog(@"memberDidJoin %@",member);
+    if(member.role != RoleAudience) {
+        [self.videoListView reloadVideoList];
+    }
+    if (member.role == RoleTeacher || member.role == RoleAssistant) {
+        [self.tipsView showTipsMessage:[NSString stringWithFormat:@"老师%@已经进入教室", member.name] inView:self.view];
+    }
+}
+
+- (void)memberDidLeave:(RoomMember *)member {
+    NSLog(@"memberDidLeave %@",member);
+    // 此处移除
+    [[KSRemoteUserManager shareInstance] removeUser:member.userId];
+    
+    if(member.role != RoleAudience) {
+        [self.videoListView reloadVideoList];
+    }
+}
+
+- (void)memberDidKick:(RoomMember *)member {
+    NSLog(@"memberDidKick %@",member);
+    [[KSRemoteUserManager shareInstance] removeUser:member.userId];
+    if ([ClassroomService sharedService].currentRoom.currentMember.role == RoleTeacher) {
+        [self.tipsView showTipsMessage:[NSString stringWithFormat:@"你已将%@移出课堂", member.name] inView:self.view];
+    }
+    [self.videoListView reloadVideoList];
+    if (self.containerView.member.role == member.role) {
+        [self.containerView cancelRenderView];
+    }
+}
+
+- (void)errorDidOccur:(ErrorCode)code {
+    NSLog(@"errorDidOccur %@",@(code));
+    [self.hud hideAnimated:YES];
+    if (code != ErrorCodeSuccess) {
+        if (code == ErrorCodeOverMaxUserCount) {
+            [self.tipsView showTipsMessage:@"超过人数限制" inView:self.view];
+        }else {
+            [self.tipsView showTipsMessage:@"操作失败,请稍后再试" inView:self.view];
+        }
+    }
+}
+
+- (void)deviceDidEnable:(BOOL)enable type:(DeviceType)type forUser:(RoomMember *)member operator:(nonnull NSString *)operatorId {
+    NSLog(@"deviceDidEnable devicetype:%@ enable:%@ memeber:%@",@(type),@(enable),member);
+    RoomMember *curMember = [ClassroomService sharedService].currentRoom.currentMember;
+    NSString *hudMessage = @"";
+    //只有老师和自己才有提示
+    if (curMember.role == RoleTeacher && ![curMember.userId isEqualToString:operatorId]) {
+        if (type == DeviceTypeCamera) {
+            hudMessage = !enable ? [NSString stringWithFormat:NSLocalizedStringFromTable(@"SetCameraClose", @"SealClass", nil),member.name] : [NSString stringWithFormat:NSLocalizedStringFromTable(@"SetCameraOpen", @"SealClass", nil),member.name];
+        } else if (type == DeviceTypeMicrophone) {
+            hudMessage = !enable ? [NSString stringWithFormat:NSLocalizedStringFromTable(@"SetMicorophoneClose", @"SealClass", nil),member.name] : [NSString stringWithFormat:NSLocalizedStringFromTable(@"SetMicorophoneOpen", @"SealClass", nil),member.name];
+        }
+        else if (type == DeviceTypeMusicMode) {
+            hudMessage = !enable ? [NSString stringWithFormat:@"%@的音乐模式已关闭",member.name] : [NSString stringWithFormat:@"%@的音乐模式已开启",member.name];
+        }
+        [self.tipsView showTipsMessage:hudMessage inView:self.view];
+    } else {
+        if ([curMember.userId isEqualToString:member.userId]) {
+            if (type == DeviceTypeCamera) { // 摄像头
+                // 非自己操作才需要做提示
+                if(![curMember.userId isEqualToString:operatorId]){
+                    hudMessage = !enable ? @"老师已关闭你的摄像头" : NSLocalizedStringFromTable(@"CameraOpend", @"SealClass", nil);
+                    [self.tipsView showTipsMessage:hudMessage inView:self.view];
+                }
+                
+                [self.containerView refreshToolView];
+                [[RTCService sharedInstance] setCameraDisable:!enable];
+            } else if (type == DeviceTypeMicrophone) { // 麦克风
+                // 非自己操作才需要做提示
+                if(![curMember.userId isEqualToString:operatorId]){
+                    hudMessage = !enable ? @"老师已关闭你的麦克风" : NSLocalizedStringFromTable(@"MicorophoneOpend", @"SealClass", nil);
+                    [self.tipsView showTipsMessage:hudMessage inView:self.view];
+                }
+                
+                [self.containerView refreshToolView];
+                [[RTCService sharedInstance] setMicrophoneDisable:!enable];
+                
+            }
+            else if (type == DeviceTypeMusicMode) {
+                // 非自己操作才需要做提示
+                if(![curMember.userId isEqualToString:operatorId]) {
+//                    hudMessage = !enable ? NSLocalizedStringFromTable(@"老师关闭了你的音乐模式", @"SealClass", nil) : NSLocalizedStringFromTable(@"音乐模式已打开", @"SealClass", nil);
+//                    [self.tipsView showTipsMessage:hudMessage inView:self.view];
+                }
+                
+                [self.containerView refreshToolView];
+                [self changeModeAction:enable];
+            }
+            else if (type == DeviceTypeHandup) {
+//                if ([curMember.userId isEqualToString:operatorId]) {
+//                    if (enable) {
+//                        [self.tipsView showTipsMessage:@"您已举手" inView:self.view];
+//                    }
+//                }
+            }
+        }
+    }
+    if ([ClassroomService sharedService].currentRoom.currentDisplayType != DisplayWhiteboard && type == DeviceTypeCamera) {
+        // 延迟调用,防止刷新出现灰屏
+        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
+            [self renderMainContainerView];
+        });
+    }
+    [self.titleView refreshTitleView];
+    [self.containerView refreshToolView];
+    
+}
+
+//助教请求用户打开设备,助教关闭用户设备没有回调。
+- (void)deviceDidInviteEnable:(DeviceType)type ticket:(NSString *)ticket {
+    NSLog(@"deviceDidInviteEnable devicetype:%@ ticket:%@ ",@(type),ticket);
+    if (type == DeviceTypeCamera) {
+        [KSNormalAlertView ks_showAlertWithTitle:@"老师邀请你打开摄像头" leftTitle:@"拒绝" rightTitle:@"同意" cancel:^{
+            [[ClassroomService sharedService] rejectEnableDevice:ticket];
+        } confirm:^{
+            [[ClassroomService sharedService] approveEnableDevice:ticket];
+        }];
+    }else if (type == DeviceTypeMicrophone) {
+        [KSNormalAlertView ks_showAlertWithTitle:@"老师邀请你打开麦克风" leftTitle:@"拒绝" rightTitle:@"同意" cancel:^{
+            [[ClassroomService sharedService] rejectEnableDevice:ticket];
+        } confirm:^{
+            [[ClassroomService sharedService] approveEnableDevice:ticket];
+        }];
+    }
+    else {
+        [[ClassroomService sharedService] approveEnableDevice:ticket];
+    }
+}
+
+//只有助教能收到这个回调,可以不在这里处理文字,因为设备的回调还会走
+- (void)deviceInviteEnableDidApprove:(RoomMember *)member type:(DeviceType)type {
+    NSLog(@"deviceInviteEnableDidApprove devicetype:%@ member:%@ ",@(type),member);
+}
+
+//拒绝只有助教能收到这个回调,且只走这个回调
+- (void)deviceInviteEnableDidReject:(RoomMember *)member type:(DeviceType)type {
+    NSLog(@"deviceInviteEnableDidReject devicetype:%@ member:%@ ",@(type),member);
+    [self.tipsView showTipsMessage:[NSString stringWithFormat:@"%@拒绝了你的邀请",member.name] inView:self.view];
+}
+
+#pragma mark - private method
+- (void)bindDelegates {
+    self.titleView.delegate = self;
+    [[RTCService sharedInstance] setRTCRoomDelegate:self];
+    [RTCService sharedInstance].delegate = self;
+    [ClassroomService sharedService].classroomDelegate = self;
+}
+
+- (void)addSubviews {
+    [self.view addSubview:self.containerView];
+    [self.view addSubview:self.titleView];
+    [self.view addSubview:self.videoListView];
+    [self.view bringSubviewToFront:self.titleView];
+}
+
+- (CGRect)mainContainerViewFrame {
+    CGFloat x = iPhoneXSafeTopMargin;
+    CGFloat y = 0;
+    CGFloat width = UIScreenWidth - x - iPhoneXSafeBottomMargin;
+    CGFloat height = UIScreenHeight - y;
+    return CGRectMake(x, y, width, height);
+}
+
+
+- (CGRect)getWBoardFrame {
+    CGRect mainVideoFrame = self.containerView.currentVideoFrame;
+    mainVideoFrame.origin.x += iPhoneXSafeTopMargin;
+    mainVideoFrame.origin.y = 10;
+    return mainVideoFrame;
 }
 
+
+- (void)publishStream {
+    if ([ClassroomService sharedService].currentRoom.currentMember.role != RoleAudience) {
+        [[RTCService sharedInstance] publishLocalUserDefaultAVStream];
+    }
+}
+
+- (void)renderMainContainerView {
+    self.titleView.onShowLabel.hidden = YES;
+    RoomMember *teacher = [ClassroomService sharedService].currentRoom.teacher;
+    if ([ClassroomService sharedService].currentRoom.currentDisplayType == DisplayTypeStudent) {
+        // 将之前订阅的流切换成小流
+        [self changeLastDisplayToTinyStream];
+        // 有打开的屏幕
+        for (RoomMember *displayMember in [ClassroomService sharedService].currentRoom.memberList) {
+            if ([displayMember.userId isEqualToString:[ClassroomService sharedService].currentRoom.currentDisplayURI]) {
+                if ([displayMember.userId isEqualToString:[ClassroomService sharedService].currentRoom.currentMember.userId]) {
+                    self.titleView.onShowLabel.hidden = NO;
+                }
+                [self.containerView containerViewRenderView:displayMember];
+                [self.videoListView showTeacherPrompt:NO];
+                break;
+            }
+        }
+        
+    }
+    else {
+        // 将之前订阅的流切换成小流
+        [self changeLastDisplayToTinyStream];
+        
+        if (([ClassroomService sharedService].currentRoom.currentDisplayType == DisplayTeacher && teacher.cameraEnable)) {
+            NSLog(@"%@",[ClassroomService sharedService].currentRoom.currentDisplayURI);
+            [self.containerView containerViewRenderView:[ClassroomService sharedService].currentRoom.teacher];
+            [self.videoListView showTeacherPrompt:YES];
+        } else if ([ClassroomService sharedService].currentRoom.currentDisplayType == DisplayWhiteboard) {
+            [self.containerView cancelRenderView];
+            [self.videoListView showTeacherPrompt:NO];
+        } else if (([ClassroomService sharedService].currentRoom.currentDisplayType == DisplaySharedScreen)) {
+            [[RTCService sharedInstance] renderUserSharedScreenOnView:self.containerView.videoView forUser:[ClassroomService sharedService].currentRoom.currentDisplayURI];
+            // 分享视频
+            [self.videoListView showTeacherPrompt:NO];
+        } else {
+            [self.containerView cancelRenderView];
+        }
+    }
+}
+
+- (void)changeLastDisplayToTinyStream {
+    RoomMember *lastMember = self.containerView.member;
+    if (![NSString isEmptyString:lastMember.userId] && ![lastMember.userId isEqualToString:[ClassroomService sharedService].currentRoom.currentDisplayURI] && ![lastMember.userId isEqualToString:[ClassroomService sharedService].currentRoom.currentMember.userId]) {
+        [[RTCService sharedInstance] exchangeRemoteUserAVStreamToTinySteam:lastMember.userId callback:^{
+            
+        }];
+    }
+}
+
+- (void)whiteboardDidDisplay:(NSString *)boardId {
+    NSLog(@"whiteboardDidDisplay %@ ",boardId);
+    [self renderMainContainerView];
+}
+
+- (void)sharedScreenDidDisplay:(NSString *)userId {
+    NSLog(@"sharedScreenDidDisplay %@ ",userId);
+    [self renderMainContainerView];
+}
+
+- (void)studentDidDisplay:(NSString *)studentId {
+    NSLog(@"studentDidDisplay %@", studentId);
+    [self renderMainContainerView];
+    
+}
+- (void)teacherDidDisplay {
+    NSLog(@"teacherDidDisplay %@ ",[ClassroomService sharedService].currentRoom.teacher);
+    [self renderMainContainerView];
+}
+
+
+- (void)noneDidDisplay {
+    NSLog(@"noneDidDisplay");
+    [self renderMainContainerView];
+}
+
+- (void)checkNodePlay {
+    
+}
+
+// 播放节拍器的回调
+- (void)playNodeAction:(BOOL)enable userId:(NSString *)userId rate:(int)rate beatType:(BeatType)type volume:(NSInteger)volume {
+    NSLog(@"playNodeAction userId:%@ enable:%@ beatType:%@ rate:%@ volume:%@",userId,@(enable),@(type), @(rate), @(volume));
+    RoomMember *curMember = [ClassroomService sharedService].currentRoom.currentMember;
+    if (curMember.role == RoleStudent) {
+
+    }
+}
+
+
+
+#pragma mark --- RTCServiceDelegate  自己发布流成功回调
+- (void)successPublishLocalStream {
+    
+//    [self checkNodePlay];
+}
+
+#pragma mark - RongRTCRoomDelegate
+- (void)didPublishStreams:(NSArray <RongRTCAVInputStream *>*)streams {
+    
+    NSString *displayUserId;
+    if (([ClassroomService sharedService].currentRoom.currentDisplayType == DisplayAssistant)) {
+        displayUserId = [ClassroomService sharedService].currentRoom.assistant.userId;
+    } else if (([ClassroomService sharedService].currentRoom.currentDisplayType == DisplayTeacher)) {
+        displayUserId = [ClassroomService sharedService].currentRoom.teacher.userId;
+    }
+    else if ([ClassroomService sharedService].currentRoom.currentDisplayType == DisplayTypeStudent) {
+        displayUserId = [ClassroomService sharedService].currentRoom.currentDisplayURI;
+    }
+    // 当用户非正常退出的时候,如果再次进入需要重新订阅流
+    RongRTCAVInputStream *firstStream = [streams lastObject];
+    if ([[KSRemoteUserManager shareInstance] isContentUser:firstStream.userId]) {
+        [[KSRemoteUserManager shareInstance] removeUser:firstStream.userId];
+    }
+    
+    for (RongRTCAVInputStream *stream in streams) {
+        if (stream.streamType == RTCMediaTypeVideo) {
+            if ([stream.userId isEqualToString:displayUserId]) {
+                [self renderMainContainerView];
+            }
+            [self.videoListView updateUserVideo:stream.userId];
+        }
+    }
+}
+
+- (void)didConnectToStream:(RongRTCAVInputStream *)stream {
+    if (stream.streamId && stream.streamId.length > 0) {
+        NSLog(@"didConnectToStream userId:%@ streamID:%@",stream.userId,stream.streamId);
+    }
+}
+
+- (void)didReportFirstKeyframe:(RongRTCAVInputStream *)stream {
+    if (stream.streamId && stream.streamId.length > 0) {
+        FwLogI(RC_Type_RTC, @"A-DYReportFirstKeyFrame-R", @"DYReportFirstKeyFrame:%@,userID:%@",stream.streamId,stream.userId);
+    }
+}
+
+
+#pragma mark ----- ClassVideoListViewDelegate
+- (void)videoListView:(ClassVideoListView *)view didTap:(RoomMember *)member {
+    /*
+    // 将视频投到主屏幕上
+    NSString *currentDisplayUserId = member.userId;
+    NSString *lastDisplayUserId = [ClassroomService sharedService].currentRoom.currentDisplayURI;
+    
+    if([currentDisplayUserId isEqualToString:lastDisplayUserId]) {
+        [self.videoListView updateUserVideo:lastDisplayUserId];
+        return;
+    }
+    DisplayType type = member.role == RoleTeacher ? DisplayTeacher : DisplayTypeStudent;
+    [ClassroomService sharedService].currentRoom.currentDisplayURI = currentDisplayUserId;
+    [ClassroomService sharedService].currentRoom.currentDisplayType = type;
+    // 如果之前有切换成大流的视频
+    if (![NSString isEmptyString:lastDisplayUserId] && ![lastDisplayUserId isEqualToString:[ClassroomService sharedService].currentRoom.currentMemberId]) {
+        [[RTCService sharedInstance] exchangeRemoteUserAVStreamToTinySteam:lastDisplayUserId];
+    }
+    
+    if(![[ClassroomService sharedService].currentRoom.currentMemberId isEqualToString:member.userId]) {
+        // 记录当前播放音频的学生
+        [ClassroomService sharedService].currentRoom.currentPlayUserId = currentDisplayUserId;
+        [[RTCService sharedInstance] exchangeRemoteUserAVStreamToNomalSteam:member.userId];
+    }
+    [self renderMainContainerView];
+     */
+}
+
+#pragma mark - Getters & setters
+- (KSTipsView *)tipsView {
+    if (!_tipsView) {
+        _tipsView = [[KSTipsView alloc] initWithFrame:CGRectZero];
+    }
+    return _tipsView;
+}
+
+- (ClassTitleView *)titleView {
+    if (!_titleView) {
+        _titleView = [[ClassTitleView alloc] initWithFrame:CGRectMake(iPhoneXSafeTopMargin, 0, kScreenWidth - iPhoneXSafeTopMargin - iPhoneXSafeBottomMargin, TitleViewHeight)];
+    }
+    return _titleView;
+}
+
+- (ClassVideoListView *)videoListView {
+    if(!_videoListView) {
+        CGFloat width = 0.0f;
+        CGFloat height = 0.0f;
+        height = kScreenHeight - 10;
+        width = height / 3.0f * 4;
+        if (width > kScreenWidth - 160) {
+            width = kScreenWidth - 160;
+        }
+        _videoListView = [[ClassVideoListView alloc] initWithFrame:CGRectMake(width + iPhoneXSafeTopMargin + 5, 10, kScreenWidth -  (width + 10 + iPhoneXSafeTopMargin) - iPhoneXSafeBottomMargin, height)];
+        _videoListView.delegate = self;
+    }
+    return _videoListView;
+}
+
+- (ClassroomMainContainer *)containerView {
+    if (!_containerView) {
+        _containerView = [[ClassroomMainContainer alloc] initWithFrame:[self mainContainerViewFrame]];
+        _containerView.delegate = self;
+    }
+    return _containerView;
+}
+
+#pragma mark ---- 网络监测
+- (void)didReportStatForm:(RongRTCStatisticalForm *)form {
+    const CGFloat lossRateBase = 0.15;
+    if (self.packetLossStore.count + form.sendStats.count > 10) {
+        NSInteger diff = (self.packetLossStore.count + form.sendStats.count) - 10;
+        [self.packetLossStore removeObjectsInRange:NSMakeRange(0,diff)];
+    }
+    
+    for (RongRTCStreamStat* stat  in form.sendStats) {
+        [self.packetLossStore addObject:@(stat.packetLoss)];
+    }
+    
+    float totalSum = 0;
+    for (NSNumber* lossRate in self.packetLossStore) {
+        totalSum += [lossRate floatValue];
+    }
+    
+    if (totalSum > 0 && self.packetLossStore.count) {
+        float avgLoss = totalSum / self.packetLossStore.count;
+        [self showNetworkingStatus:avgLoss];
+        if (avgLoss > lossRateBase) { // 网络质量不佳
+            
+            dispatch_async(dispatch_get_main_queue(), ^{
+                [self.view addSubview:self.networkLabel];
+                [self.view bringSubviewToFront:self.networkLabel];
+                self.networkLabel.hidden = NO;
+                self.networkLabel.text = @"当前通话连接质量不佳";
+            });
+        }
+        else { // 正常
+            dispatch_async(dispatch_get_main_queue(), ^{
+                self.networkLabel.hidden = YES;
+                self.networkLabel.text = @"";
+                [self.networkLabel removeFromSuperview];
+            });
+        }
+    }
+}
+
+- (void)showNetworkingStatus:(float)packageLose {
+    dispatch_async(dispatch_get_main_queue(), ^{
+        if (packageLose < 0.05) {
+            self.titleView.netStatus = NetWorkingStatus_Good;
+        }
+        else if (packageLose <= 0.1) {
+            self.titleView.netStatus = NetWorkingStatus_Well;
+        }
+        else if (packageLose <= 0.15) {
+            self.titleView.netStatus = NetWorkingStatus_Bad;
+        }
+        else {
+            self.titleView.netStatus = NetWorkingStatus_Poor;
+        }
+    });
+}
+
+
+- (NSMutableArray<NSNumber*>*)packetLossStore {
+    if (!_packetLossStore) {
+        _packetLossStore = [[NSMutableArray alloc] initWithCapacity:10];
+    }
+    return _packetLossStore;
+}
+
+- (UILabel *)networkLabel {
+    if (!_networkLabel) {
+        _networkLabel = [[UILabel alloc] initWithFrame:CGRectMake((kScreenWidth - 200) / 2.0f, (kScreenHeight - 60) / 2.0f, 200, 60)];
+        _networkLabel.backgroundColor = HexRGBAlpha(0x000000, 0.8f);
+        _networkLabel.textColor = HexRGB(0xffffff);
+        _networkLabel.textAlignment = NSTextAlignmentCenter;
+        _networkLabel.font = [UIFont systemFontOfSize:14.0f];
+        _networkLabel.layer.cornerRadius = 5.0f;
+    }
+    return _networkLabel;
+}
+
+#pragma mark ---- 修改声音模式
+- (void)changeModeAction:(BOOL)enable {
+    if (enable) {
+        [[RongRTCAudioCapturer sharedInstance] changeMusicPlayMode:RongRTCAudioScenarioMusicSingleNotePlay];
+    }
+    else {
+        [[RongRTCAudioCapturer sharedInstance] changeMusicPlayMode:RongRTCAudioScenarioMusicNomalPlay];
+    }
+}
 /*
 #pragma mark - Navigation
 

+ 21 - 0
MusicGradeExam/MusicGradeExam/UI/Classroom/Model/LocalRenderManager.h

@@ -0,0 +1,21 @@
+//
+//  LocalRenderManager.h
+//  StudentDaya
+//
+//  Created by Kyle on 2020/6/12.
+//  Copyright © 2020 DayaMusic. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface LocalRenderManager : NSObject
+
+@property (nonatomic, assign) BOOL hadRenderMainView;
+
++ (instancetype)shareInstance;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 22 - 0
MusicGradeExam/MusicGradeExam/UI/Classroom/Model/LocalRenderManager.m

@@ -0,0 +1,22 @@
+//
+//  LocalRenderManager.m
+//  StudentDaya
+//
+//  Created by Kyle on 2020/6/12.
+//  Copyright © 2020 DayaMusic. All rights reserved.
+//
+
+#import "LocalRenderManager.h"
+
+@implementation LocalRenderManager
+
++ (instancetype)shareInstance {
+    static LocalRenderManager *manager = nil;
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        manager = [[LocalRenderManager alloc] init];
+    });
+    return manager;
+}
+
+@end

+ 78 - 0
MusicGradeExam/MusicGradeExam/UI/Classroom/Model/RoomLoginHelper.m

@@ -7,7 +7,85 @@
 //
 
 #import "RoomLoginHelper.h"
+#import <RongIMKit/RongIMKit.h>
+#import "RCConnectionManager.h"
+
+@interface RoomLoginHelper()<RongRTCRoomDelegate>
+
+@property (nonatomic, strong) Classroom *classroom;
+
+@end
 
 @implementation RoomLoginHelper
 
++ (instancetype)sharedInstance {
+    static RoomLoginHelper *service = nil;
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        service = [[self alloc] init];
+
+    });
+    return service;
+}
+
+#pragma mark - Api
+- (void)login:(NSString *)roomId user:(NSString *)userName isAudience:(BOOL)audience{
+    NSLog(@"login start");
+    self.roomId = roomId;
+    self.userName = userName;
+    [[ClassroomService sharedService] joinClassroom:roomId userName:userName isAudience:audience success:^(Classroom * _Nonnull classroom) {
+        NSLog(@"login classroom success");
+        self.classroom = classroom;
+        NSLog(@"connect im start");
+        [RCConnectionManager shareManager].isNeedJoin = YES;
+    } error:^(ErrorCode errorCode){
+        NSLog(@"login classroom error:%@",@(errorCode));
+            if (errorCode == ErrorCodeOverMaxUserCount) {
+                if(self.delegate && [self.delegate respondsToSelector:@selector(classroomDidOverMaxUserCount)]){
+                    [self.delegate classroomDidOverMaxUserCount];
+                }
+            }else{
+                if(self.delegate && [self.delegate respondsToSelector:@selector(classroomDidJoinFailCode:)]){
+                    [self.delegate classroomDidJoinFailCode:@(errorCode)];
+                }
+            }
+    }];
+}
+
+- (void)logout:(void (^)(void))success error:(void (^)(RongRTCCode code))error {
+    NSLog(@"logout start");
+    [self leaveRoom];
+}
+
+- (void)leaveRoom {
+    [RCConnectionManager shareManager].isNeedJoin = NO;
+    [[ClassroomService sharedService] leaveClassroom];
+}
+
+#pragma mark - Helper
+- (void)joinRongRTCRoom{
+    NSLog(@"join rtc room start");
+    [[RTCService sharedInstance] joinRongRTCRoom:self.classroom.roomId success:^(RongRTCRoom * _Nonnull room) {
+        NSLog(@"join rtc room success");
+        dispatch_main_async_safe(^{
+            if(self.delegate && [self.delegate respondsToSelector:@selector(classroomDidJoin:)]){
+                [self.delegate classroomDidJoin:self.classroom];
+            }
+        });
+    } error:^(RongRTCCode code) {
+        NSLog(@"join rtc room error:%@",@(code));
+        dispatch_main_async_safe(^{
+            if (code == RongRTCCodeJoinToSameRoom) {
+                if(self.delegate && [self.delegate respondsToSelector:@selector(classroomDidJoin:)]){
+                    [self.delegate classroomDidJoin:self.classroom];
+                }
+            }else{
+                if(self.delegate && [self.delegate respondsToSelector:@selector(classroomDidJoinFailRTC:)]){
+                    [self.delegate classroomDidJoinFailRTC:@(code)];
+                }
+            }
+        });
+    }];
+}
+
 @end

+ 51 - 0
MusicGradeExam/MusicGradeExam/UI/Classroom/View/MainContainer/ClassroomMainContainer.h

@@ -0,0 +1,51 @@
+//
+//  ClassroomMainContainer.h
+//  StudentDaya
+//
+//  Created by Kyle on 2020/6/12.
+//  Copyright © 2020 DayaMusic. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+#import "ClassroomService.h"
+#import "EmptyView.h"
+#import "RoomMember.h"
+#import "MainToolView.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@class ClassroomMainContainer;
+
+@protocol ClassroomMainContainerDelegate <NSObject>
+
+- (void)mainContainerOpertionMember:(RoomMember *)member tapButton:(UIButton *)button;
+
+@end
+
+@interface ClassroomMainContainer : UIView
+
+@property (nonatomic, strong) UIView *videoBackView;
+
+@property (nonatomic, strong) UIView *videoView;
+
+@property (nonatomic, strong) EmptyView *emptyView;
+
+@property (nonatomic, strong) RoomMember *member;
+
+@property (nonatomic, weak) id<ClassroomMainContainerDelegate> delegate;
+
+@property (nonatomic, assign, readonly) BOOL isFullScreen;
+
+@property (nonatomic, readonly) CGRect currentVideoFrame;
+
+- (void)didChangeRole:(Role)role;
+
+- (void)containerViewRenderView:(RoomMember *)member;
+
+- (void)cancelRenderView;
+
+- (void)refreshToolView;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 200 - 0
MusicGradeExam/MusicGradeExam/UI/Classroom/View/MainContainer/ClassroomMainContainer.m

@@ -0,0 +1,200 @@
+//
+//  ClassroomMainContainer.m
+//  StudentDaya
+//
+//  Created by Kyle on 2020/6/12.
+//  Copyright © 2020 DayaMusic. All rights reserved.
+//
+
+#import "ClassroomMainContainer.h"
+#import "RTCService.h"
+#import "LocalRenderManager.h"
+
+@interface ClassroomMainContainer ()<MainToolViewDelegate>
+
+@property (nonatomic, strong) UIView *tapGestureView;
+@property (nonatomic) CGRect currentVideoFrame;
+@property (nonatomic) CGRect originVideoFrame;
+@property (nonatomic, strong) UILabel *nameLabel;
+
+@property (nonatomic, strong) MainToolView *toolView;
+
+@end
+
+@implementation ClassroomMainContainer
+
+- (instancetype)initWithFrame:(CGRect)frame {
+    self = [super initWithFrame:frame];
+    if (self) {
+        self.backgroundColor = [UIColor colorWithHexString:@"141414" alpha:1];
+        [self addSubview:self.emptyView];
+        [self addSubview:self.videoBackView];
+        [self.videoBackView addSubview:self.videoView];
+        self.originVideoFrame = self.videoBackView.frame;
+        self.currentVideoFrame = self.originVideoFrame;
+        [self.videoView addSubview:self.toolView];
+        [self.videoView addSubview:self.nameLabel];
+
+    }
+    return self;
+}
+
+- (void)refreshToolView {
+    RoomMember *curMember = [ClassroomService sharedService].currentRoom.currentMember;
+    if ([self.member.userId isEqualToString:curMember.userId]) {
+        self.toolView.hidden = YES;
+    }
+    else {
+        self.toolView.hidden = NO;
+        self.toolView.shareButton.selected = curMember.handUpOn;
+    }
+}
+
+- (void)didChangeRole:(Role)role {
+    [self.emptyView changeRole:role];
+}
+
+- (void)containerViewRenderView:(RoomMember *)member {
+    self.videoBackView.hidden = NO;
+    self.videoView.hidden = NO;
+    if([[ClassroomService sharedService].currentRoom.currentMemberId isEqualToString:member.userId]) {
+        RoomMember *curMemeber =[ClassroomService sharedService].currentRoom.currentMember;
+        [[RTCService sharedInstance] renderLocalVideoOnView:self.videoView cameraEnable:curMemeber.cameraEnable];
+        [LocalRenderManager shareInstance].hadRenderMainView = YES;
+    }else {
+        [LocalRenderManager shareInstance].hadRenderMainView = NO;
+        [[RTCService sharedInstance] exchangeRemoteUserAVStreamToNomalSteam:member.userId callback:^{
+            [[RTCService sharedInstance] renderRemoteVideoOnView:self.videoView forUser:member.userId];
+        }];
+    }
+    self.member = member;
+    if ([member.userId isEqualToString:[ClassroomService sharedService].currentRoom.currentMember.userId]) {
+        self.nameLabel.text = @"我";
+    }
+    else {
+        self.nameLabel.text = [NSString returnNoNullStringWithString:member.name];
+    }
+    
+    // 判断显示是否是老师
+    if (member.role == RoleTeacher) {
+        self.toolView.status = MainToolStatusTeacher;
+    }
+    else {
+        self.toolView.status = MainToolStatusStudent;
+    }
+    [self refreshToolView];
+}
+
+- (void)cancelRenderView {
+    [LocalRenderManager shareInstance].hadRenderMainView = NO;
+    // 如何关闭了摄像头,可能导致videoView 遮挡控制事件
+    [self updateVideoViewFrame:NO];
+    self.videoBackView.hidden = YES;
+}
+
+#pragma mark ---- tool view delegate
+- (void)mainToolViewDidTap:(UIButton *)button {
+    if (self.delegate && [self.delegate respondsToSelector:@selector(mainContainerOpertionMember:tapButton:)]) {
+        [self.delegate mainContainerOpertionMember:[ClassroomService sharedService].currentRoom.currentMember tapButton:button];
+    }
+}
+
+#pragma mark - private method
+
+- (void)updateVideoViewFrame:(BOOL)isFull {
+    self.currentVideoFrame = self.frame;
+    if(isFull) {
+        [self.superview addSubview:self.videoBackView];
+        [self.superview bringSubviewToFront:self.videoBackView];
+        CGFloat width = 0.0f;
+        CGFloat height = 0.0f;
+        
+        height = kScreen_Width / 4.0f * 3;
+        if (height > kScreenHeight) {
+            height = kScreenHeight;
+        }
+        width = height / 3.0f * 4;
+
+        self.videoView.frame = CGRectMake((kScreenWidth - width) / 2.0f, 0, width, height);
+    }else {
+        [self.videoBackView removeFromSuperview];
+        [self addSubview:self.videoBackView];
+        self.currentVideoFrame = self.originVideoFrame;
+        self.videoView.frame = CGRectMake(0, 0, CGRectGetWidth(self.currentVideoFrame), CGRectGetHeight(self.currentVideoFrame));
+    }
+    self.videoBackView.frame = self.currentVideoFrame;
+}
+
+#pragma mark - getter
+- (UIView *)videoBackView {
+    if (!_videoBackView) {
+        CGFloat width = 0.0f;
+        CGFloat height = 0.0f;
+        height = kScreenHeight - 20;
+        width = height / 3.0f * 4;
+        if (width > kScreenWidth - 180) {
+            width = kScreenWidth - 180;
+            height = width / 4.0f * 3;
+        }
+        _videoBackView = [[UIView alloc] initWithFrame:CGRectMake(10, 10, width, height)];
+        _videoBackView.backgroundColor = [UIColor colorWithHexString:@"141414" alpha:1];
+    }
+    return _videoBackView;
+}
+
+
+- (UIView *)videoView {
+    if(!_videoView) {
+        CGFloat width = 0.0f;
+        CGFloat height = 0.0f;
+        height = kScreenHeight - 20;
+        width = height / 3.0f * 4;
+        if (width > kScreenWidth - 180) {
+            width = kScreenWidth - 180;
+            height = width / 4.0f * 3;
+        }
+        _videoView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, width, height)];
+    }
+    return _videoView;
+}
+
+- (EmptyView *)emptyView {
+    if (!_emptyView) {
+        CGFloat width = 0.0f;
+        CGFloat height = 0.0f;
+        height = kScreenHeight - 20;
+        width = height / 3.0f * 4;
+        if (width > kScreenWidth - 180) {
+            width = kScreenWidth - 180;
+            height = width / 4.0f * 3;
+        }
+        _emptyView = [[EmptyView alloc] initWithFrame:CGRectMake(10, 10, width, height) role:[ClassroomService sharedService].currentRoom.currentMember.role];
+    }
+    return _emptyView;
+}
+
+- (MainToolView *)toolView {
+    if (!_toolView) {
+        _toolView = [[MainToolView alloc] initWithFrame:CGRectMake(0, CGRectGetHeight(self.videoView.frame) - 50, CGRectGetWidth(self.videoView.frame), 50)];
+        _toolView.delegate = self;
+    }
+    return _toolView;
+}
+
+- (UILabel *)nameLabel {
+    if (!_nameLabel) {
+        _nameLabel = [[UILabel alloc] initWithFrame:CGRectMake(20, 20, 100, 20)];
+        _nameLabel.textColor = [UIColor whiteColor];
+        _nameLabel.font = [UIFont systemFontOfSize:16.0f];
+    }
+    return _nameLabel;
+}
+/*
+// Only override drawRect: if you perform custom drawing.
+// An empty implementation adversely affects performance during animation.
+- (void)drawRect:(CGRect)rect {
+    // Drawing code
+}
+*/
+
+@end

+ 26 - 0
MusicGradeExam/MusicGradeExam/UI/Classroom/View/MainContainer/EmptyView.h

@@ -0,0 +1,26 @@
+//
+//  EmptyView.h
+//  SealClass
+//
+//  Created by Zhaoqianyu on 2019/3/13.
+//  Copyright © 2019 RongCloud. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+#import "RoomMember.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface EmptyView : UIView
+- (instancetype)initWithFrame:(CGRect)frame role:(Role)role;
+
+/**
+ 切换角色
+
+ @param role 当前角色
+ @discussion 不同角色显示不同
+ */
+- (void)changeRole:(Role)role;
+@end
+
+NS_ASSUME_NONNULL_END

+ 80 - 0
MusicGradeExam/MusicGradeExam/UI/Classroom/View/MainContainer/EmptyView.m

@@ -0,0 +1,80 @@
+//
+//  EmptyView.m
+//  SealClass
+//
+//  Created by Zhaoqianyu on 2019/3/13.
+//  Copyright © 2019 RongCloud. All rights reserved.
+//
+
+#import "EmptyView.h"
+#import <Masonry/Masonry.h>
+
+#define ImageWidth 111
+#define ImageHeight 82
+
+@interface EmptyView()
+
+@property(nonatomic, assign) Role currentRole;
+@property(nonatomic, strong) UIImageView *emptyImageView;
+@property(nonatomic, strong) UILabel *emptyLabel;
+
+@end
+
+@implementation EmptyView
+
+- (instancetype)initWithFrame:(CGRect)frame role:(Role)role; {
+    self = [super initWithFrame:frame];
+    if (self) {
+        
+        [self addSubview:self.emptyImageView];
+        [self addSubview:self.emptyLabel];
+        [self.emptyImageView mas_makeConstraints:^(MASConstraintMaker *make) {
+            make.edges.mas_equalTo(self);
+        }];
+        [self.emptyLabel mas_makeConstraints:^(MASConstraintMaker *make) {
+            make.top.mas_equalTo(self).mas_offset((self.bounds.size.height-ImageHeight)/2+ImageHeight-10);
+            make.centerX.mas_equalTo(self);
+            make.height.mas_equalTo(9);
+        }];
+        [self changeRole:role];
+    }
+    return self;
+}
+
+- (void)changeRole:(Role)role {
+    self.currentRole = role;
+    switch (role) {
+        case RoleAssistant:
+        case RoleTeacher:
+            self.emptyImageView.image = [UIImage imageNamed:@"empty_teacher"];
+            self.emptyLabel.text = @"当前无共享内容,您可以新建共享内容";
+            break;
+        default:
+            self.emptyImageView.image = [UIImage imageNamed:@"empty_student"];
+            self.emptyLabel.text = @"当前无共享内容,请耐心等待";
+            break;
+    }
+}
+
+#pragma mark - Getters & setters
+
+- (UIImageView *)emptyImageView {
+    if (!_emptyImageView) {
+        _emptyImageView = [UIImageView new];
+        _emptyImageView.contentMode = UIViewContentModeCenter;
+        self.backgroundColor = HexRGB(0xffffff);
+    }
+    return _emptyImageView;
+}
+
+- (UILabel *)emptyLabel {
+    if (!_emptyLabel) {
+        _emptyLabel = [UILabel new];
+        _emptyLabel.textColor = HexRGB(0xb2b2b2);
+        _emptyLabel.font = [UIFont systemFontOfSize:6];
+        _emptyLabel.textAlignment = NSTextAlignmentCenter;
+    }
+    return _emptyLabel;
+}
+
+@end

+ 38 - 0
MusicGradeExam/MusicGradeExam/UI/Classroom/View/MainToolbar/MainToolView.h

@@ -0,0 +1,38 @@
+//
+//  MainToolView.h
+//  StudentDaya
+//
+//  Created by Kyle on 2020/6/12.
+//  Copyright © 2020 DayaMusic. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+typedef NS_ENUM(NSInteger, MainToolStatus) {
+    MainToolStatusTeacher,  // 主屏幕显示老师
+    MainToolStatusStudent,  // 主屏幕显示学生
+};
+
+
+NS_ASSUME_NONNULL_BEGIN
+
+@class MainToolView;
+
+@protocol MainToolViewDelegate <NSObject>
+
+- (void)mainToolViewDidTap:(UIButton *)button;
+
+@end
+
+
+@interface MainToolView : UIView
+
+@property (nonatomic, weak) id<MainToolViewDelegate> delegate;
+
+@property (nonatomic, assign) MainToolStatus status;
+
+@property (nonatomic, strong) UIButton *shareButton;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 70 - 0
MusicGradeExam/MusicGradeExam/UI/Classroom/View/MainToolbar/MainToolView.m

@@ -0,0 +1,70 @@
+//
+//  MainToolView.m
+//  StudentDaya
+//
+//  Created by Kyle on 2020/6/12.
+//  Copyright © 2020 DayaMusic. All rights reserved.
+//
+
+#import "MainToolView.h"
+
+@interface MainToolView ()
+
+
+
+@end
+
+@implementation MainToolView
+
+- (instancetype)initWithFrame:(CGRect)frame {
+    self = [super initWithFrame:frame];
+    if (self) {
+        self.backgroundColor = [UIColor clearColor];
+        [self addSubviews];
+    }
+    return self;
+}
+
+- (void)addSubviews {
+    [self addSubview:self.shareButton];
+    [self.shareButton mas_makeConstraints:^(MASConstraintMaker *make) {
+        make.top.mas_equalTo(self).offset(5);
+        make.right.mas_equalTo(self.mas_right).offset(-10);
+        make.width.mas_equalTo(107);
+        make.height.mas_equalTo(34);
+    }];
+}
+
+- (void)tapEvent:(UIButton *)sender {
+    if ([self.delegate respondsToSelector:@selector(mainToolViewDidTap:)]) {
+        [self.delegate mainToolViewDidTap:sender];
+    }
+}
+
+- (UIButton *)shareButton {
+    if (!_shareButton) {
+        _shareButton = [UIButton buttonWithType:UIButtonTypeCustom];
+        _shareButton.enabled = YES;
+        [_shareButton addTarget:self action:@selector(tapEvent:) forControlEvents:UIControlEventTouchUpInside];
+        [_shareButton setTitleColor:HexRGB(0xf9f9f9) forState:UIControlStateNormal];
+        [_shareButton setTitle:@" 举手" forState:UIControlStateNormal];
+        [_shareButton setBackgroundColor:HexRGB(0x2c2c2c)];
+        _shareButton.layer.cornerRadius = 17.0f;
+        [_shareButton.titleLabel setFont:[UIFont systemFontOfSize:14.0f]];
+        [_shareButton setImage:[UIImage imageNamed:@"handup_image"] forState:UIControlStateNormal];
+        
+        [_shareButton setTitle:@"已举手" forState:UIControlStateSelected];
+        [_shareButton setImage:[UIImage new] forState:UIControlStateSelected];
+    }
+    return _shareButton;
+}
+
+/*
+// Only override drawRect: if you perform custom drawing.
+// An empty implementation adversely affects performance during animation.
+- (void)drawRect:(CGRect)rect {
+    // Drawing code
+}
+*/
+
+@end

+ 0 - 0
MusicGradeExam/MusicGradeExam/UI/Classroom/Service/Classroom/Util/View/KSNormalAlertView.h → MusicGradeExam/MusicGradeExam/UI/Classroom/View/NormalAlertView/KSNormalAlertView.h


+ 0 - 0
MusicGradeExam/MusicGradeExam/UI/Classroom/Service/Classroom/Util/View/KSNormalAlertView.m → MusicGradeExam/MusicGradeExam/UI/Classroom/View/NormalAlertView/KSNormalAlertView.m


+ 19 - 0
MusicGradeExam/MusicGradeExam/UI/Classroom/View/TipsView/KSTipsView.h

@@ -0,0 +1,19 @@
+//
+//  KSTipsView.h
+//  StudentDaya
+//
+//  Created by Kyle on 2020/6/16.
+//  Copyright © 2020 DayaMusic. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface KSTipsView : UIView
+
+- (void)showTipsMessage:(NSString *)message inView:(UIView *)view;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 108 - 0
MusicGradeExam/MusicGradeExam/UI/Classroom/View/TipsView/KSTipsView.m

@@ -0,0 +1,108 @@
+//
+//  KSTipsView.m
+//  StudentDaya
+//
+//  Created by Kyle on 2020/6/16.
+//  Copyright © 2020 DayaMusic. All rights reserved.
+//
+
+#import "KSTipsView.h"
+
+CGFloat const TipsShowTime = 2.0f;
+@interface KSTipsView ()
+
+@property (nonatomic, strong) UILabel *messageLabel;
+
+@property (nonatomic, assign) BOOL isShow;
+
+@property (nonatomic, weak) NSTimer *hideDelayTimer;
+
+@end
+
+@implementation KSTipsView
+
+
+
+
+- (instancetype)initWithFrame:(CGRect)frame {
+    self = [super initWithFrame:frame];
+    if (self) {
+        [self configUI];
+    }
+    return self;
+}
+
+- (void)configUI {
+    self.backgroundColor = HexRGB(0x3D3D3D);
+    self.autoresizingMask = UIViewAutoresizingFlexibleWidth;
+    self.layer.cornerRadius = 17;
+    UIImageView *tipsImage = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"mb_notice"]];
+    [self addSubview:tipsImage];
+    [tipsImage mas_makeConstraints:^(MASConstraintMaker *make) {
+        make.left.mas_equalTo(self.mas_left).offset(21);
+        make.width.height.mas_equalTo(17);
+        make.centerY.mas_equalTo(0);
+    }];
+    [self addSubview:self.messageLabel];
+    [self.messageLabel mas_makeConstraints:^(MASConstraintMaker *make) {
+        make.left.mas_equalTo(tipsImage.mas_right).offset(8);
+        make.height.mas_equalTo(self.mas_height);
+        make.top.mas_equalTo(self.mas_top);
+        make.right.mas_equalTo(self.mas_right).offset(-19);
+    }];
+}
+
+
+- (void)showTipsMessage:(NSString *)message inView:(UIView *)view {
+    if (self.isShow) {
+        [self removeFromSuperview];
+        [self.hideDelayTimer invalidate];
+        self.hideDelayTimer = nil;
+    }
+    self.messageLabel.text = message;
+    [view addSubview:self];
+    [self mas_updateConstraints:^(MASConstraintMaker *make) {
+        make.height.mas_equalTo(34);
+        make.centerX.mas_equalTo(view.mas_centerX);
+        make.top.mas_equalTo(view.mas_top).offset(65);
+    }];
+    [self hideAfterDelay:TipsShowTime];
+}
+
+- (void)hideAfterDelay:(NSTimeInterval)delay {
+    NSTimer *timer = [NSTimer timerWithTimeInterval:delay target:self selector:@selector(handleHideTimer) userInfo:nil repeats:NO];
+    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
+    self.hideDelayTimer = timer;
+}
+
+- (void)handleHideTimer {
+    [self removeFromSuperview];
+    [self.hideDelayTimer invalidate];
+    self.hideDelayTimer = nil;
+}
+- (UILabel *)messageLabel {
+    if (!_messageLabel) {
+        _messageLabel = [[UILabel alloc] init];
+        _messageLabel.textColor = HexRGB(0xf9f9f9);
+        _messageLabel.textAlignment = NSTextAlignmentCenter;
+        _messageLabel.font = [UIFont systemFontOfSize:14.0f];
+        _messageLabel.numberOfLines = 1;
+    }
+    return _messageLabel;
+}
+
+- (void)dealloc {
+    if (self.hideDelayTimer) {
+        [self.hideDelayTimer invalidate];
+        self.hideDelayTimer = nil;
+    }
+}
+/*
+// Only override drawRect: if you perform custom drawing.
+// An empty implementation adversely affects performance during animation.
+- (void)drawRect:(CGRect)rect {
+    // Drawing code
+}
+*/
+
+@end

+ 72 - 0
MusicGradeExam/MusicGradeExam/UI/Classroom/View/TitleView/ClassTitleView.h

@@ -0,0 +1,72 @@
+//
+//  ClassTitleView.h
+//  StudentDaya
+//
+//  Created by Kyle on 2020/6/12.
+//  Copyright © 2020 DayaMusic. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+typedef NS_ENUM(NSInteger, ClassTitleViewActionTag) {
+    ClassTitleViewActionTagSwitchLine,
+    ClassTitleViewActionTagSwitchCamera,
+    ClassTitleViewActionTagMicrophone,
+    ClassTitleViewActionTagCamera,
+    ClassTitleViewActionTagChat,
+    ClassTitleViewActionTagHangup,
+};
+
+typedef NS_ENUM(NSInteger, NetWorkingStatus) {
+    NetWorkingStatus_Good = 1,
+    NetWorkingStatus_Well = 2,
+    NetWorkingStatus_Bad = 3,
+    NetWorkingStatus_Poor = 4,
+};
+
+NS_ASSUME_NONNULL_BEGIN
+
+@class ClassTitleView;
+
+@protocol ClassTitleViewDelegate <NSObject>
+
+- (void)classTitleView:(UIButton *)button didTapAtTag:(ClassTitleViewActionTag)tag;
+
+- (void)refreshClassStatus;
+
+@end
+
+@interface ClassTitleView : UIView
+
+@property (nonatomic, weak) id<ClassTitleViewDelegate> delegate;
+
+@property (nonatomic, strong) UIImageView *signalImageView;
+
+@property (nonatomic, strong) UIButton *switchCameraBtn;
+
+@property (nonatomic, strong) UIButton *microphoneBtn;
+
+@property (nonatomic, strong) UIButton *cameraBtn;
+
+@property (nonatomic, strong) UIButton *switchLineBtn;
+
+@property (nonatomic, strong) UIButton *chatBtn;
+
+@property (nonatomic, strong) UIButton *hangupBtn;
+
+@property (nonatomic, assign) BOOL isDisplay;
+
+@property (nonatomic, strong) UILabel *onShowLabel;
+
+@property (nonatomic, assign) NetWorkingStatus netStatus;
+
+- (void)refreshTitleView;
+
+- (void)stopDurationTimer;
+
+- (void)isCameraAvailable:(void (^)(bool avilable))successBlock;
+
+-(void)isMicrophoneAvailable:(void (^)(bool avilable))successBlock;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 549 - 0
MusicGradeExam/MusicGradeExam/UI/Classroom/View/TitleView/ClassTitleView.m

@@ -0,0 +1,549 @@
+//
+//  ClassTitleView.m
+//  StudentDaya
+//
+//  Created by Kyle on 2020/6/12.
+//  Copyright © 2020 DayaMusic. All rights reserved.
+//
+
+#import "ClassTitleView.h"
+#import "ClassroomService.h"
+#import "Masonry.h"
+#import <AVFoundation/AVFoundation.h>
+#import "RTCService.h"
+
+#define TSignalImageViewWidth  (24)
+#define TTimeLableWidth   (70)
+#define TTimeLableHeight  (14)
+
+#define StateButtonWidth (34)
+#define TButtonWidth (24)
+#define TButtonSpace (20)
+
+#define THiddenDuration (3)
+
+@interface ClassTitleView ()
+
+@property (nonatomic, strong) UILabel *timeLable;
+@property (nonatomic, strong) NSTimer *timeTimer;
+@property (nonatomic, strong) NSMutableArray *buttonArray;
+@property (nonatomic, strong) NSArray *buttonImageArray;
+@property (nonatomic, strong) NSArray *buttonHighlightedImageArray;
+@property (nonatomic, strong) NSArray *buttonCloseImageArray;
+@property (nonatomic, assign) NSInteger duration;
+
+@property (nonatomic, assign) NSInteger hiddenDuration;
+
+@property (nonatomic, assign) BOOL isCountDown;
+
+@end
+
+@implementation ClassTitleView
+
+- (instancetype)initWithFrame:(CGRect)frame
+{
+    self = [super initWithFrame:frame];
+    if (self) {
+        
+        self.backgroundColor = [HexRGB(0x141414) colorWithAlphaComponent:0.74];
+        [self.buttonArray addObjectsFromArray:@[self.switchLineBtn,self.switchCameraBtn,self.microphoneBtn,self.cameraBtn,self.chatBtn,self.hangupBtn]];
+        [self addSubviews];
+        self.isCountDown = NO;
+        self.duration = [ClassroomService sharedService].currentRoom.surplusTime;
+        [self refreshTitleView];
+        [self.timeTimer setFireDate:[NSDate distantPast]];
+        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onReceiveMessageNotification:) name:OnReceiveMessageNotification object:nil];
+    }
+    return self;
+}
+
+
+- (void)onReceiveMessageNotification:(NSNotification *)notification{
+    NSDictionary *dic = notification.object;
+    RCMessage *message = dic[@"message"];
+    Classroom *currentRoom = [ClassroomService sharedService].currentRoom;
+    if ([message.targetId isEqualToString:currentRoom.roomId]) {
+        dispatch_main_async_safe(^{
+            [self updateClassNewsButton];
+        });
+    }
+}
+
+- (void)updateClassNewsButton{
+    Classroom *currentRoom = [ClassroomService sharedService].currentRoom;
+    int unreadCount = [[RCIMClient sharedRCIMClient] getUnreadCount:ConversationType_GROUP targetId:currentRoom.roomId];
+    UIButton *button = (UIButton *)[self viewWithTag:ClassTitleViewActionTagChat];
+    if (unreadCount > 0 && !button.selected) {
+        [button setBackgroundImage:[UIImage imageNamed:@"classnews_unreadStatus"] forState:UIControlStateNormal];
+        [button setBackgroundImage:[UIImage imageNamed:@"classnews_unreadStatus"] forState:UIControlStateHighlighted];
+    }else{
+        [button setBackgroundImage:[UIImage imageNamed:@"classnews_nomal"] forState:UIControlStateNormal];
+        [button setBackgroundImage:[UIImage imageNamed:@"classnews_nomal"] forState:UIControlStateHighlighted];
+    }
+}
+
+- (void)addSubviews {
+    [self addSubview:self.signalImageView];
+    [self addSubview:self.timeLable];
+    [self addSubview:self.switchLineBtn];
+    [self addSubview:self.switchCameraBtn];
+    [self addSubview:self.microphoneBtn];
+    [self addSubview:self.cameraBtn];
+    [self addSubview:self.onShowLabel];
+    
+    [self addSubview:self.chatBtn];
+    [self addSubview:self.hangupBtn];
+    CGFloat topOffset = (self.bounds.size.height - TButtonWidth) / 2.0;
+    
+    [self.signalImageView mas_makeConstraints:^(MASConstraintMaker *make) {
+        make.left.mas_equalTo(self.mas_left).offset(20);
+        make.centerY.mas_equalTo(self.mas_centerY);
+        make.width.mas_equalTo(TSignalImageViewWidth);
+        make.height.mas_equalTo(TSignalImageViewWidth);
+    }];
+    [self.timeLable mas_makeConstraints:^(MASConstraintMaker *make) {
+        make.left.equalTo(self.signalImageView.mas_right).offset(20);
+        make.centerY.equalTo(self.mas_centerY);
+        make.width.equalTo(@TTimeLableWidth);
+        make.height.equalTo(@TTimeLableHeight);
+    }];
+    
+    // 切换线路
+    [self.switchLineBtn mas_makeConstraints:^(MASConstraintMaker *make) {
+        make.left.mas_equalTo(self.timeLable.mas_right).offset(10);
+        make.width.mas_equalTo(TButtonWidth);
+        make.height.mas_equalTo(TButtonWidth);
+        make.top.mas_equalTo(self.mas_top).offset(topOffset);
+    }];
+    
+    // 切换摄像头
+    [self.switchCameraBtn mas_makeConstraints:^(MASConstraintMaker *make) {
+        make.left.mas_equalTo(self.switchLineBtn.mas_right).offset(TButtonSpace);
+        make.width.mas_equalTo(TButtonWidth);
+        make.height.mas_equalTo(TButtonWidth);
+        make.top.mas_equalTo(self.mas_top).offset(topOffset);
+    }];
+    // 麦克风
+    [self.microphoneBtn mas_makeConstraints:^(MASConstraintMaker *make) {
+        make.left.mas_equalTo(self.switchCameraBtn.mas_right).offset(TButtonSpace);
+        make.width.mas_equalTo(TButtonWidth);
+        make.height.mas_equalTo(TButtonWidth);
+        make.top.mas_equalTo(self.mas_top).offset(topOffset);
+    }];
+    // 摄像头
+    [self.cameraBtn mas_makeConstraints:^(MASConstraintMaker *make) {
+        make.left.mas_equalTo(self.microphoneBtn.mas_right).offset(TButtonSpace);
+        make.width.mas_equalTo(TButtonWidth);
+        make.height.mas_equalTo(TButtonWidth);
+        make.top.mas_equalTo(self.mas_top).offset(topOffset);
+    }];
+    
+    // 演示label
+    [self.onShowLabel mas_makeConstraints:^(MASConstraintMaker *make) {
+        make.left.mas_equalTo(self.cameraBtn.mas_right).offset(TButtonSpace * 2);
+        make.width.mas_equalTo(120);
+        make.height.mas_equalTo(TButtonWidth);
+        make.top.mas_equalTo(self.mas_top).offset(topOffset);
+    }];
+    
+    
+    // 退出
+    [self.hangupBtn mas_makeConstraints:^(MASConstraintMaker *make) {
+        make.right.mas_equalTo(self.mas_right).offset(-TButtonSpace);
+        make.width.mas_equalTo(TButtonWidth);
+        make.height.mas_equalTo(TButtonWidth);
+        make.top.mas_equalTo(self.mas_top).offset(topOffset);
+    }];
+    
+    // 聊天
+    [self.chatBtn mas_makeConstraints:^(MASConstraintMaker *make) {
+        make.right.mas_equalTo(self.hangupBtn.mas_left).offset(-TButtonSpace);
+        make.width.mas_equalTo(TButtonWidth);
+        make.height.mas_equalTo(TButtonWidth);
+        make.top.mas_equalTo(self.mas_top).offset(topOffset);
+    }];
+    self.isDisplay = YES;
+}
+
+- (void)clearUnreadMessage{
+    Classroom *currentRoom = [ClassroomService sharedService].currentRoom;
+    [[RCIMClient sharedRCIMClient] clearMessagesUnreadStatus:ConversationType_GROUP targetId:currentRoom.roomId];
+    UIButton *button = (UIButton *)[self viewWithTag:ClassTitleViewActionTagChat];
+    [button setBackgroundImage:[UIImage imageNamed:@"classnews_nomal"] forState:UIControlStateNormal];
+    [button setBackgroundImage:[UIImage imageNamed:@"classnews_nomal"] forState:UIControlStateHighlighted];
+}
+
+
+
+- (void)refreshTitleView {
+    [self setDefaultButtons];
+    RoomMember *curMember = [ClassroomService sharedService].currentRoom.currentMember;
+    // 判断显示状态
+    [self isCameraAvailable:^(bool avilable) {
+        if (avilable) {
+            self.cameraBtn.selected = !curMember.cameraEnable;
+        }else {
+            self.cameraBtn.selected = YES;
+        }
+    }];
+    [self isMicrophoneAvailable:^(bool avilable) {
+        if (avilable) {
+            self.microphoneBtn.selected = !curMember.microphoneEnable;
+        }else {
+            self.microphoneBtn.selected = YES;
+        }
+    }];
+}
+
+- (void)setDuration:(NSInteger)duration {
+    _duration = duration;
+    if (_duration < 300 && self.isCountDown == NO) {
+        self.isCountDown = YES;
+        self.timeLable.textColor = HexRGB(0x2DC7AA);
+        [self.timeLable.layer addAnimation:[self opacityForeverAnimation:0.5f] forKey:nil];
+    }
+}
+
+- (void)timeFunction:(NSTimer *)timer {
+    // 隐藏titleView
+    if (self.isDisplay) {
+        if (self.hiddenDuration > 0) {
+            self.hiddenDuration -= 1;
+        }
+        else {
+            self.hiddenDuration = 0;
+            self.isDisplay = NO;
+            [self hiddenView];
+        }
+    }
+    
+    self.timeLable.text = [self formatJoinTime];
+    if (self.delegate && [self.delegate respondsToSelector:@selector(refreshClassStatus)]) {
+        [self.delegate refreshClassStatus];
+    }
+}
+
+- (void)stopDurationTimer {
+    [self.timeTimer setFireDate:[NSDate distantFuture]];
+    if (self.timeTimer.valid) {
+        [self.timeTimer invalidate];
+        self.timeTimer = nil;
+    }
+}
+
+
+- (void)tapEvent:(UIButton *)btn {
+    
+    if (btn.tag == ClassTitleViewActionTagMicrophone) {
+        [self isMicrophoneAvailable:^(bool avilable) {
+            if (!avilable) {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+                UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:nil message:NSLocalizedStringFromTable(@"microphoneAvailable", @"SealClass", nil) delegate:self cancelButtonTitle:NSLocalizedStringFromTable(@"Cancel", @"SealClass", nil) otherButtonTitles:@"设置", nil];
+                [alertView show];
+#pragma clang diagnostic pop
+                return;
+            }else {
+                if(self.delegate && [self.delegate respondsToSelector:@selector(classTitleView:didTapAtTag:)]) {
+                    [self.delegate classTitleView:btn didTapAtTag:btn.tag];
+                }
+                
+            }
+        }];
+    }
+    else if (btn.tag == ClassTitleViewActionTagCamera) {
+        [self isCameraAvailable:^(bool avilable) {
+            if (!avilable) {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+                UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:nil message:NSLocalizedStringFromTable(@"cameraAvailable", @"SealClass", nil) delegate:self cancelButtonTitle:NSLocalizedStringFromTable(@"Cancel", @"SealClass", nil) otherButtonTitles:@"设置", nil];
+                [alertView show];
+#pragma clang diagnostic pop
+                return;
+            }else {
+                if(self.delegate && [self.delegate respondsToSelector:@selector(classTitleView:didTapAtTag:)]) {
+                    [self.delegate classTitleView:btn didTapAtTag:btn.tag];
+                }
+            }
+        }];
+    }
+    else {
+        if (btn.tag == ClassTitleViewActionTagChat) {
+            [self clearUnreadMessage];
+        }
+        if(self.delegate && [self.delegate respondsToSelector:@selector(classTitleView:didTapAtTag:)]) {
+            [self.delegate classTitleView:btn didTapAtTag:btn.tag];
+        }
+    }
+}
+
+- (void)setDefaultButtons {
+    self.buttonImageArray = @[@"line_switch",@"camera_switch", @"userMic_on", @"userCamera_on", @"classnews_nomal", @"class_hangUp"];
+    self.buttonCloseImageArray = @[@"line_switch",@"camera_switch", @"userMic_off", @"userCamera_off", @"classnews_nomal", @"class_hangUp"];
+
+    for(int i = 0; i < self.buttonArray.count; i++) {
+        UIButton *button = [self.buttonArray objectAtIndex:i];
+        button.enabled = YES;
+        [button setBackgroundImage:[UIImage imageNamed:[self.buttonImageArray objectAtIndex:i]] forState:UIControlStateNormal];
+        [button setBackgroundImage:[UIImage imageNamed:[self.buttonCloseImageArray objectAtIndex:i]]  forState:UIControlStateSelected];
+    }
+}
+
+- (NSString *)formatJoinTime {
+    if (self.duration <= 0) {
+        return @"00:00:00";
+    }
+    NSInteger durationInteger = self.duration--;
+    NSInteger durationS = durationInteger % 60;
+    NSInteger durationM = ((durationInteger - durationS) / 60) % 60;
+    NSInteger durationH = (durationInteger - durationS - 60 * durationM) / 3600;
+    NSMutableArray * durationArr = [NSMutableArray new];
+    [durationArr addObject:[NSString stringWithFormat:@"%02ld", durationH]];
+    [durationArr addObject:[NSString stringWithFormat:@"%02ld", durationM]];
+    [durationArr addObject:[NSString stringWithFormat:@"%02ld", durationS]];
+    return [durationArr componentsJoinedByString:@":"];
+}
+
+- (UIImageView *)signalImageView {
+    if(!_signalImageView) {
+        _signalImageView = [[UIImageView alloc] init];
+        _signalImageView.image = [UIImage imageNamed:@"network_status1"];
+    }
+    return _signalImageView;
+}
+
+- (UILabel *)timeLable {
+    if(!_timeLable) {
+        _timeLable = [[UILabel alloc] init];
+        _timeLable.font = [UIFont systemFontOfSize:14];
+        _timeLable.textColor = [UIColor colorWithHexString:@"FFFFFF" alpha:1];
+    }
+    return _timeLable;
+}
+
+- (UIButton *)switchLineBtn {
+    if (!_switchLineBtn) {
+        _switchLineBtn = [[UIButton alloc] init];
+        _switchLineBtn.enabled = YES;
+        _switchLineBtn.tag = ClassTitleViewActionTagSwitchLine;
+        [_switchLineBtn addTarget:self action:@selector(tapEvent:) forControlEvents:UIControlEventTouchUpInside];
+    }
+    return _switchLineBtn;
+}
+
+
+- (UIButton *)switchCameraBtn {
+    if(!_switchCameraBtn) {
+        _switchCameraBtn = [[UIButton alloc] init];
+        _switchCameraBtn.enabled = YES;
+        _switchCameraBtn.tag = ClassTitleViewActionTagSwitchCamera;
+        [_switchCameraBtn addTarget:self action:@selector(tapEvent:) forControlEvents:UIControlEventTouchUpInside];
+        
+    }
+    return _switchCameraBtn;
+}
+
+- (UIButton *)microphoneBtn {
+    if(!_microphoneBtn) {
+        _microphoneBtn = [[UIButton alloc] init];
+        _microphoneBtn.enabled = YES;
+        _microphoneBtn.tag =  ClassTitleViewActionTagMicrophone;
+        [_microphoneBtn addTarget:self action:@selector(tapEvent:) forControlEvents:UIControlEventTouchUpInside];
+        
+    }
+    return _microphoneBtn;
+}
+
+
+- (UIButton *)cameraBtn {
+    if (!_cameraBtn) {
+        _cameraBtn = [[UIButton alloc] init];
+        _cameraBtn.enabled = YES;
+        _cameraBtn.tag = ClassTitleViewActionTagCamera;
+        [_cameraBtn addTarget:self action:@selector(tapEvent:) forControlEvents:UIControlEventTouchUpInside];
+    }
+    return _cameraBtn;
+}
+
+- (UILabel *)onShowLabel {
+    if (!_onShowLabel) {
+        _onShowLabel = [[UILabel alloc] init];
+        _onShowLabel.textColor = HexRGB(0xf9f9f9);
+        _onShowLabel.font = [UIFont systemFontOfSize:14.0f];
+        _onShowLabel.text = @"您已进入演示模式";
+    }
+    return _onShowLabel;
+}
+
+- (UIButton *)chatBtn {
+    if (!_chatBtn) {
+        _chatBtn = [[UIButton alloc] init];
+        _chatBtn.enabled = YES;
+        _chatBtn.tag =  ClassTitleViewActionTagChat;
+        [_chatBtn addTarget:self action:@selector(tapEvent:) forControlEvents:UIControlEventTouchUpInside];
+    }
+    return _chatBtn;
+}
+
+- (UIButton *)hangupBtn {
+    if(!_hangupBtn) {
+        _hangupBtn = [[UIButton alloc] init];
+        _hangupBtn.enabled = YES;
+        _hangupBtn.tag =  ClassTitleViewActionTagHangup;
+        [_hangupBtn addTarget:self action:@selector(tapEvent:) forControlEvents:UIControlEventTouchUpInside];
+        
+    }
+    return _hangupBtn;
+}
+
+- (NSMutableArray *)buttonArray {
+    if (!_buttonArray) {
+        _buttonArray = [[NSMutableArray alloc] init];
+    }
+    return _buttonArray;
+}
+
+- (NSTimer *)timeTimer {
+    if (_timeTimer == nil) {
+        
+        _timeTimer = [NSTimer scheduledTimerWithTimeInterval:1
+                                                      target:self
+                                                    selector:@selector(timeFunction:)
+                                                    userInfo:nil
+                                                     repeats:YES];
+        [[NSRunLoop currentRunLoop] addTimer:_timeTimer forMode:NSRunLoopCommonModes];
+    }
+    
+    return _timeTimer;
+}
+
+- (void)setNetStatus:(NetWorkingStatus)netStatus {
+    _netStatus = netStatus;
+    NSString *displayImg = @"network_status1";
+    switch (netStatus) {
+        case NetWorkingStatus_Good:
+            displayImg = @"network_status1";
+            break;
+        case NetWorkingStatus_Well:
+            displayImg = @"network_status2";
+            break;
+        case NetWorkingStatus_Bad:
+            displayImg = @"network_status3";
+            break;
+        case NetWorkingStatus_Poor:
+            displayImg = @"network_status4";
+            break;
+        default:
+            break;
+    }
+    self.signalImageView.image = [UIImage imageNamed:displayImg];
+}
+
+#pragma mark ----- 显示和隐藏title
+- (void)displayView {
+    CGRect frame = self.frame;
+    self.hiddenDuration = THiddenDuration;
+    if (frame.origin.y == 0) {
+        return;
+    }
+    
+    [UIView animateWithDuration:0.3f animations:^{
+        CGRect frame = self.frame;
+        frame.origin.y = 0;
+        self.frame = frame;
+    }];
+}
+
+- (void)hiddenView {
+    CGRect frame = self.frame;
+    if (frame.origin.y < 0) {
+        return;
+    }
+    [UIView animateWithDuration:0.3f animations:^{
+        CGRect frame = self.frame;
+        frame.origin.y = -frame.size.height;
+        self.frame = frame;
+    }];
+}
+
+- (void)setIsDisplay:(BOOL)isDisplay {
+    _isDisplay = isDisplay;
+    if (isDisplay) {
+        [self displayView];
+    }
+}
+
+#pragma mark === 永久闪烁的动画 ======
+- (CABasicAnimation *)opacityForeverAnimation:(CGFloat)time {
+    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
+    animation.fromValue = [NSNumber numberWithFloat:1.0f];
+    animation.toValue = [NSNumber numberWithFloat:0.0f];//这是透明度。
+    animation.autoreverses = YES;
+    animation.duration = time;
+    animation.repeatCount = MAXFLOAT;
+    animation.removedOnCompletion = NO;
+    animation.fillMode = kCAFillModeForwards;
+    animation.timingFunction=[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
+    return animation;
+}
+
+#pragma mark - UIAlertViewDelegate
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
+    if (buttonIndex == 1) { // 去设置界面,开启相机访问权限
+        [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]];
+    }
+}
+#pragma clang diagnostic pop
+
+- (void)isCameraAvailable:(void (^)(bool avilable))successBlock {
+    NSString *mediaType = AVMediaTypeVideo;                                                         //读取媒体类型
+    AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:mediaType]; //读取设备授权状态
+    if (AVAuthorizationStatusAuthorized == authStatus) {
+        successBlock(YES);
+    } else if(authStatus == AVAuthorizationStatusNotDetermined) {
+        [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo
+                                 completionHandler:^(BOOL granted) {
+                                     dispatch_async(dispatch_get_main_queue(), ^{
+                                         if (granted) {
+                                             successBlock(YES);
+                                         } else {
+                                             successBlock(NO);
+                                         }
+                                     });
+                                 }];
+    }else {
+        successBlock(NO);
+    }
+}
+
+-(void)isMicrophoneAvailable:(void (^)(bool avilable))successBlock  {
+    AVAudioSessionRecordPermission authStatus = [[AVAudioSession sharedInstance] recordPermission];
+    if (AVAudioSessionRecordPermissionGranted == authStatus) {
+        successBlock(YES);
+    } else if(authStatus == AVAudioSessionRecordPermissionUndetermined){
+        [[AVAudioSession sharedInstance] requestRecordPermission:^(BOOL granted) {
+            dispatch_async(dispatch_get_main_queue(), ^{
+                if (granted) {
+                    successBlock(YES);
+                } else {
+                    successBlock(NO);
+                    
+                }
+            });
+        }];
+    }else {
+        successBlock(NO);
+    }
+}
+/*
+// Only override drawRect: if you perform custom drawing.
+// An empty implementation adversely affects performance during animation.
+- (void)drawRect:(CGRect)rect {
+    // Drawing code
+}
+*/
+
+@end

+ 29 - 0
MusicGradeExam/MusicGradeExam/UI/Classroom/View/VideoList/ClassVideoListCell.h

@@ -0,0 +1,29 @@
+//
+//  ClassVideoListCell.h
+//  StudentDaya
+//
+//  Created by Kyle on 2020/6/12.
+//  Copyright © 2020 DayaMusic. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+#import "RoomMember.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface ClassVideoListCell : UITableViewCell
+
+@property (nonatomic, strong) UIView *videoView;
+
+@property (nonatomic, assign) CGSize cellSize;
+
+- (void)setModel:(RoomMember *)member showTeacherPrompt:(BOOL)teacherPrompt;
+
+- (void)renderVideo:(RoomMember *)member;
+
+- (void)cancelVideo;
+
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 255 - 0
MusicGradeExam/MusicGradeExam/UI/Classroom/View/VideoList/ClassVideoListCell.m

@@ -0,0 +1,255 @@
+//
+//  ClassVideoListCell.m
+//  StudentDaya
+//
+//  Created by Kyle on 2020/6/12.
+//  Copyright © 2020 DayaMusic. All rights reserved.
+//
+
+#import "ClassVideoListCell.h"
+#import "RTCService.h"
+#import "ClassroomService.h"
+#import "LocalRenderManager.h"
+
+@interface ClassVideoListCell ()
+
+@property (nonatomic, strong) UIView *backGroundView;
+@property (nonatomic, strong) UILabel *roleLable;
+@property (nonatomic, strong) UILabel *nameLable;
+@property (nonatomic, strong) UILabel *WaitLable;
+@property (nonatomic, strong) UILabel *promptLable;
+@end
+
+@implementation ClassVideoListCell
+
+- (void)awakeFromNib {
+    [super awakeFromNib];
+    // Initialization code
+}
+
+- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
+{
+    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
+    if (self) {
+        self.selectionStyle = UITableViewCellSelectionStyleNone;
+        self.backgroundColor = [UIColor clearColor];
+        [self addSubViews];
+    }
+    return self;
+}
+
+- (void)addSubViews {
+    [self.contentView addSubview:self.backGroundView];
+    [self.contentView addSubview:self.videoView];
+    [self.contentView addSubview:self.roleLable];
+    [self.contentView addSubview:self.nameLable];
+    [self.contentView addSubview:self.WaitLable];
+    [self.contentView addSubview:self.promptLable];
+    
+    self.videoView.frame = CGRectMake(0, 0, self.contentView.width, self.contentView.height);
+    [self.backGroundView mas_makeConstraints:^(MASConstraintMaker *make) {
+        make.left.equalTo(self.contentView.mas_left).offset(0);
+        make.top.equalTo(self.contentView.mas_top).offset(0);
+        make.bottom.equalTo(self.contentView.mas_bottom).offset(-10);
+        make.right.equalTo(self.contentView.mas_right).offset(0);
+    }];
+    
+    [self.WaitLable mas_makeConstraints:^(MASConstraintMaker *make) {
+        make.centerY.equalTo(self.videoView.mas_centerY);
+        make.centerX.equalTo(self.videoView.mas_centerX);
+        make.height.equalTo(@25);
+        make.width.equalTo(@84);
+    }];
+    [self.promptLable mas_makeConstraints:^(MASConstraintMaker *make) {
+        make.centerY.equalTo(self.videoView.mas_centerY);
+        make.centerX.equalTo(self.videoView.mas_centerX);
+        make.height.equalTo(@25);
+        make.width.equalTo(@84);
+    }];
+    
+    [self.roleLable mas_makeConstraints:^(MASConstraintMaker *make) {
+        make.left.equalTo(self.contentView.mas_left).offset(4);
+        make.top.equalTo(self.contentView.mas_top).offset(4);
+        make.height.equalTo(@18);
+        make.width.equalTo(@24);
+    }];
+    
+    [self.nameLable mas_makeConstraints:^(MASConstraintMaker *make) {
+        make.left.equalTo(self.contentView.mas_left).offset(10);
+        make.bottom.equalTo(self.contentView.mas_bottom).offset(-20);
+        make.height.equalTo(@22);
+        make.right.mas_equalTo(self.contentView.mas_right).offset(-5);
+    }];
+}
+
+- (void)setCellSize:(CGSize)cellSize {
+    _cellSize = cellSize;
+    self.videoView.size = cellSize;
+}
+
+- (void)setModel:(RoomMember *)member showTeacherPrompt:(BOOL)teacherPrompt {
+    [self resetDefaultStyle];
+    if (member == nil) {
+        return;
+    }
+    
+    if (member.userId == nil) {
+        self.nameLable.hidden = YES;
+        self.roleLable.hidden = YES;
+        self.videoView.hidden = YES;
+        self.promptLable.hidden = YES;
+        self.WaitLable.hidden = NO;
+    }
+    else {
+        if ([member.userId isEqualToString:[ClassroomService sharedService].currentRoom.currentMember.userId]) {
+            self.nameLable.text = @"我";
+            self.roleLable.hidden = YES;
+        }
+        else {
+            [self remakeNameLable:member];
+            
+            if ([member.userId isEqualToString:[ClassroomService sharedService].currentRoom.teacher.userId]) {
+                if (teacherPrompt) {
+                    self.promptLable.hidden = NO;
+                }
+                self.roleLable.hidden = NO;
+            }
+            else {
+                self.roleLable.hidden = YES;
+            }
+        }
+        if ([[ClassroomService sharedService].currentRoom.currentDisplayURI isEqualToString:member.userId]) {
+            self.videoView.hidden = YES;
+        }
+        else {
+            self.videoView.hidden = NO;
+        }
+        [self renderVideo:member];
+    }
+}
+- (void)resetDefaultStyle {
+    self.nameLable.text = nil;
+    self.roleLable.hidden = NO;
+    self.nameLable.hidden = NO;
+    self.videoView.hidden = NO;
+    self.WaitLable.hidden = YES;
+    self.promptLable.hidden = YES;
+    [self cancelVideo];
+}
+
+- (void)renderVideo:(RoomMember *)member {
+    if(![self canRenderVideo:member]) {
+        return;
+    }
+    // 涉及到请求返回比较慢的情况 当前用户已在主屏渲染
+    if ([[ClassroomService sharedService].currentRoom.currentMemberId isEqualToString:member.userId] && [LocalRenderManager shareInstance].hadRenderMainView) {
+        return;
+    }
+    if([[ClassroomService sharedService].currentRoom.currentMemberId isEqualToString:member.userId]) {
+        RoomMember *curMemeber =[ClassroomService sharedService].currentRoom.currentMember;
+        [[RTCService sharedInstance] renderLocalVideoOnView:self.videoView cameraEnable:curMemeber.cameraEnable];
+    } else {
+        [[RTCService sharedInstance] renderRemoteVideoOnView:self.videoView forUser:member.userId];
+    }
+}
+
+- (void)cancelVideo {
+    [[RTCService sharedInstance] cancelRenderVideoInView:self.videoView];
+}
+
+- (void)remakeNameLable:(RoomMember *)member {
+    NSString * nameTxt = [[NSString alloc] init];
+    nameTxt =  member.name.length > 5 ? [NSString stringWithFormat:@"%@...",[member.name substringToIndex:5]] : member.name;
+    self.nameLable.text = nameTxt;
+}
+
+#pragma mark - private method
+//当前的 cell 是否可以渲染视频
+- (BOOL)canRenderVideo:(RoomMember *)member {
+    //主视频区渲染了该用户的视频,则 视频列表 cell 不显示视频
+    //旁观者不显示视频
+    //主视频区蒙版渲染了该用户的视频,则 视频列表 cell 不显示视频
+    Classroom *room = [ClassroomService sharedService].currentRoom;
+    if([room.currentDisplayURI isEqualToString:member.userId] || member.role == RoleAudience || [room.currentMaskUserId isEqualToString:member.userId]) {
+        return NO;
+    }
+    return YES;
+}
+
+- (UIView *)backGroundView {
+    if(!_backGroundView) {
+        _backGroundView = [[UIView alloc] init];
+        _backGroundView.backgroundColor = [UIColor colorWithHexString:@"3D4041" alpha:1];
+        _backGroundView.hidden = NO;
+    }
+    return _backGroundView;
+}
+
+- (UIView *)videoView {
+    if(!_videoView) {
+        _videoView = [[UIView alloc] init];
+        _videoView.backgroundColor = [UIColor colorWithHexString:@"3D4041" alpha:1];
+    }
+    return _videoView;
+}
+
+- (UILabel *)roleLable {
+    if(!_roleLable) {
+        _roleLable = [[UILabel alloc] init];
+        _roleLable.font = [UIFont systemFontOfSize:10];
+        _roleLable.textAlignment = NSTextAlignmentCenter;
+        _roleLable.textColor = [UIColor colorWithHexString:@"FFFFFF" alpha:1];
+        _roleLable.backgroundColor = HexRGB(0xFF5500);
+        _roleLable.text = @"讲师";
+        _roleLable.layer.cornerRadius = 4;
+        _roleLable.layer.masksToBounds = YES;
+    }
+    return _roleLable;
+}
+
+- (UILabel *)nameLable {
+    if(!_nameLable) {
+        _nameLable = [[UILabel alloc] init];
+        _nameLable.font = [UIFont systemFontOfSize:16.0];
+        _nameLable.numberOfLines = 1;
+        _nameLable.textAlignment = NSTextAlignmentLeft;
+        _nameLable.textColor = [UIColor colorWithHexString:@"FFFFFF" alpha:1];
+    }
+    return _nameLable;
+}
+
+- (UILabel *)WaitLable {
+    if(!_WaitLable) {
+        _WaitLable = [[UILabel alloc] init];
+        _WaitLable.font = [UIFont systemFontOfSize:9];
+        _WaitLable.numberOfLines = 2;
+        _WaitLable.textAlignment = NSTextAlignmentCenter;
+        _WaitLable.backgroundColor = [UIColor clearColor];
+        _WaitLable.textColor = [UIColor colorWithHexString:@"FFFFFF" alpha:1];
+        _WaitLable.text =  @"讲师正在赶来的路上\n请耐心等待";
+        _WaitLable.hidden = YES;
+    }
+    return _WaitLable;
+}
+
+- (UILabel *)promptLable {
+    if(!_promptLable) {
+        _promptLable = [[UILabel alloc] init];
+        _promptLable.font = [UIFont systemFontOfSize:12];
+        _promptLable.numberOfLines = 1;
+        _promptLable.textAlignment = NSTextAlignmentCenter;
+        _promptLable.backgroundColor = [UIColor clearColor];
+        _promptLable.textColor = [UIColor colorWithHexString:@"FFFFFF" alpha:1];
+        _promptLable.text =  @"讲师正在授课";
+        _promptLable.hidden = YES;
+    }
+    return _promptLable;
+}
+
+- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
+    [super setSelected:selected animated:animated];
+
+    // Configure the view for the selected state
+}
+
+@end

+ 34 - 0
MusicGradeExam/MusicGradeExam/UI/Classroom/View/VideoList/ClassVideoListView.h

@@ -0,0 +1,34 @@
+//
+//  ClassVideoListView.h
+//  StudentDaya
+//
+//  Created by Kyle on 2020/6/12.
+//  Copyright © 2020 DayaMusic. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+@class ClassVideoListView,RoomMember;
+@protocol ClassVideoListViewDelegate <NSObject>
+
+- (void)videoListView:(ClassVideoListView *_Nullable)view didTap:(RoomMember *_Nullable)member;
+
+@end
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface ClassVideoListView : UIView
+
+@property (nonatomic, assign) BOOL showTeacherPrompt;
+
+@property (nonatomic, weak) id<ClassVideoListViewDelegate> delegate;
+
+- (void)updateUserVideo:(NSString *)userId;
+
+- (void)reloadVideoList;
+
+- (void)showTeacherPrompt:(BOOL)showTeacherPrompt;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 205 - 0
MusicGradeExam/MusicGradeExam/UI/Classroom/View/VideoList/ClassVideoListView.m

@@ -0,0 +1,205 @@
+//
+//  ClassVideoListView.m
+//  StudentDaya
+//
+//  Created by Kyle on 2020/6/12.
+//  Copyright © 2020 DayaMusic. All rights reserved.
+//
+
+#import "ClassVideoListView.h"
+#import "ClassVideoListCell.h"
+#import "ClassroomService.h"
+#import "RTCService.h"
+
+@interface ClassVideoListView ()<UITableViewDelegate, UITableViewDataSource>
+
+@property (nonatomic, strong) UITableView *videoListTableView;
+@property (nonatomic, strong) NSMutableArray *videoDataSource;
+@property (nonatomic, strong) dispatch_queue_t videoListQueue;
+@end
+
+
+
+@implementation ClassVideoListView
+
+- (instancetype)initWithFrame:(CGRect)frame
+{
+    self = [super initWithFrame:frame];
+    if (self) {
+        self.backgroundColor = [UIColor clearColor];
+        [self addSubview:self.videoListTableView];
+        [self getDataSource];
+        self.showTeacherPrompt = NO;
+    }
+    return self;
+}
+
+- (void)getDataSource {
+    // 自己第一,讲师第二
+    dispatch_async(self.videoListQueue, ^{
+        NSArray *tempArray = [ClassroomService sharedService].currentRoom.memberList;
+        if (tempArray.count <= 0) {
+            return;
+        }
+        NSSortDescriptor * des = [[NSSortDescriptor alloc] initWithKey:@"joinTime" ascending:YES];
+        NSMutableArray *sortArray = [[tempArray sortedArrayUsingDescriptors:@[des]] mutableCopy];
+        RoomMember *currentMember = [ClassroomService sharedService].currentRoom.currentMember;
+        RoomMember *teacher = [ClassroomService sharedService].currentRoom.teacher;
+        __block NSInteger teacherIdx = -1;
+        __block  NSInteger meIdx = -1;
+        NSMutableIndexSet *set = [[NSMutableIndexSet alloc]init];
+        
+        for (int i = 0; i < [sortArray count]; i++ ) {
+            RoomMember *member = (RoomMember *)[sortArray objectAtIndex:i];
+            if ([member.userId isEqualToString:currentMember.userId]) {
+                meIdx = i;
+            }else {
+                if (member.role == RoleTeacher) {
+                    teacherIdx = i;
+                }
+                if (member.role == RoleAudience) {
+                    [set addIndex:i];//旁观者不能被任何他人看见,如果自己是旁观者自己可以看见自己
+                }
+            }
+        }
+        
+        NSMutableArray *lastArray = [[NSMutableArray alloc] init];
+        if (teacherIdx >= 0) {
+            [lastArray addObject:teacher];
+            [set addIndex:teacherIdx];
+        }
+        if (meIdx >= 0) {
+            [lastArray addObject:currentMember];
+            [set addIndex:meIdx];
+        }
+        if (set.count >0) {
+            [sortArray removeObjectsAtIndexes:set];
+        }
+        
+        [lastArray addObjectsFromArray:sortArray];
+        /*
+        // 移除掉主屏显示成员
+        NSString *display = [ClassroomService sharedService].currentRoom.currentDisplayURI;
+        for (RoomMember *member in lastArray) {
+            if ([member.userId isEqualToString:display] && [ClassroomService sharedService].currentRoom.currentDisplayType != DisplayWhiteboard) {
+                [lastArray removeObject:member];
+                break;
+            }
+        }
+        */
+        if (teacher == nil) {
+            RoomMember *member = [[RoomMember alloc] init];
+            [lastArray insertObject:member atIndex:0];
+        }
+        dispatch_async(dispatch_get_main_queue(), ^{
+            self.videoDataSource = [lastArray mutableCopy];
+            [self.videoListTableView reloadData];
+        });
+    });
+
+}
+
+- (void)updateUserVideo:(NSString *)userId {
+    dispatch_async(self.videoListQueue, ^{
+        for(int i=0;i<self.videoDataSource.count;i++) {
+            RoomMember *member = self.videoDataSource[i];
+            if([member.userId isEqualToString:userId]) {
+                NSIndexPath *index = [NSIndexPath indexPathForRow:i inSection:0];
+                dispatch_async(dispatch_get_main_queue(), ^{
+                    ClassVideoListCell * cell = [self.videoListTableView cellForRowAtIndexPath:index];
+                    if (cell) {
+                        [self.videoListTableView reloadRowsAtIndexPaths:@[index] withRowAnimation:UITableViewRowAnimationNone];
+                    }
+                });
+            }
+        }
+    });
+}
+
+- (void)reloadVideoList {
+    [self getDataSource];
+}
+
+- (void)showTeacherPrompt:(BOOL)showTeacherPrompt {
+    self.showTeacherPrompt = showTeacherPrompt;
+    [self reloadVideoList];
+}
+
+
+#pragma mark - tableViewDelegate
+-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
+    static NSString * cellName = @"VideoListCell";
+    ClassVideoListCell * cell = [tableView dequeueReusableCellWithIdentifier:cellName];
+    if (cell == nil) {
+        cell = [[ClassVideoListCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellName];
+    }
+    cell.cellSize = CGSizeMake(CGRectGetWidth(self.frame), (CGRectGetWidth(self.frame) - 20) / 4.0 * 3);
+//    cell.contentView.transform = CGAffineTransformMakeScale (1,-1);
+    if (self.videoDataSource.count > 0) {
+        [cell setModel:[self.videoDataSource objectAtIndex:indexPath.row] showTeacherPrompt:self.showTeacherPrompt];
+    }
+    return cell;
+}
+
+- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
+    RoomMember *mem = [self.videoDataSource objectAtIndex:indexPath.row];
+    if([self.delegate respondsToSelector:@selector(videoListView:didTap:)]) {
+        [self.delegate videoListView:self didTap:mem];
+    }
+}
+
+- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
+    
+    return (CGRectGetWidth(self.frame) - 20) / 4.0 * 3 + 10;
+}
+
+-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
+    if (self.videoDataSource != nil) {
+        return self.videoDataSource.count;
+    }
+    return 0;
+}
+
+- (UITableView *)videoListTableView {
+    if(!_videoListTableView) {
+        CGSize size = self.bounds.size;
+        _videoListTableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, size.width, size.height) style:UITableViewStylePlain];
+        _videoListTableView.backgroundColor = [UIColor clearColor];
+        _videoListTableView.delegate = self;
+        _videoListTableView.dataSource = self;
+        _videoListTableView.bounces = NO;
+        if (@available(iOS 11.0, *)) {
+            _videoListTableView.insetsContentViewsToSafeArea = NO;
+        }
+        _videoListTableView.separatorColor=[UIColor clearColor];
+        _videoListTableView.showsVerticalScrollIndicator = NO;
+        UIView *footerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, size.width, 10)];
+        footerView.backgroundColor = [UIColor clearColor];
+        _videoListTableView.tableFooterView = footerView;
+    }
+    return _videoListTableView;
+}
+
+- (NSArray *)videoDataSource {
+    if(!_videoDataSource) {
+        _videoDataSource = [[NSMutableArray alloc] init];
+    }
+    return _videoDataSource;
+}
+
+- (dispatch_queue_t)videoListQueue {
+    if(!_videoListQueue) {
+        _videoListQueue = dispatch_queue_create("cn.rongcloud.sealclass.videolist", DISPATCH_QUEUE_SERIAL);
+    }
+    return _videoListQueue;
+}
+
+/*
+// Only override drawRect: if you perform custom drawing.
+// An empty implementation adversely affects performance during animation.
+- (void)drawRect:(CGRect)rect {
+    // Drawing code
+}
+*/
+
+@end

+ 64 - 0
MusicGradeExam/MusicGradeExam/UI/Exam/Controller/ExamTicketViewController.m

@@ -7,9 +7,16 @@
 //
 
 #import "ExamTicketViewController.h"
+#import "TicketBodyView.h"
+#import "WaitExamViewController.h"
+#import "TicketListModel.h"
 
 @interface ExamTicketViewController ()
 
+@property (nonatomic, strong) TicketBodyView *bodyView;
+
+@property (nonatomic, strong) TicketListModel *sourceModel;
+
 @end
 
 @implementation ExamTicketViewController
@@ -17,6 +24,63 @@
 - (void)viewDidLoad {
     [super viewDidLoad];
     // Do any additional setup after loading the view.
+    self.ks_prefersNavigationBarHidden = YES;
+    self.view.backgroundColor = HexRGB(0xf3f4f8);
+    [self configUI];
+}
+
+- (void)viewWillAppear:(BOOL)animated {
+    [super viewWillAppear:animated];
+    [self requestData];
+}
+
+- (void)requestData {
+    [KSRequestManager queryCertificationPageRequest:KS_GET success:^(NSDictionary * _Nonnull dic) {
+        if ([dic integerValueForKey:@"code"] == 200 && [dic boolValueForKey:@"status"]) {
+            NSArray *source = [dic arrayValueForKey:@"data"];
+            if (source.count) {
+                self.sourceModel = [[TicketListModel alloc] initWithDictionary:[source firstObject]];
+            }
+            else {
+                self.sourceModel = nil;
+            }
+        }
+        else {
+            [self MBPShow:MESSAGEKEY];
+        }
+        [self refreshUI];
+    } faliure:^(NSError * _Nonnull error) {
+        
+    }];
+}
+
+
+- (void)refreshUI {
+    if (self.sourceModel) {
+        [self.bodyView configMessage:self.sourceModel];
+    }
+    else {
+        
+    }
+}
+
+- (void)configUI {
+    _bodyView = [TicketBodyView shareInstance];
+    CGFloat viewHeight = 412 + 22 + 25 + 30 + iPhoneXSafeTopMargin + 18 + 50 + 20;
+    viewHeight = viewHeight > kScreenHeight - kTabBarHeight ? viewHeight : kScreenHeight - kTabBarHeight;
+    _bodyView.frame = CGRectMake(0, 0, kScreenWidth, viewHeight);
+    MJWeakSelf;
+    [_bodyView joinRoomCallback:^(TicketListModel * _Nonnull source) {
+        [weakSelf joinRoomAction:[NSString stringWithFormat:@"%.0f",source.examRegistrationId]];
+    }];
+    [self.scrollView addSubview:_bodyView];
+    [self.scrollView setContentSize:CGSizeMake(kScreenWidth, viewHeight)];
+}
+
+- (void)joinRoomAction:(NSString *)examRegistrationId {
+    WaitExamViewController *waitCtrl = [[WaitExamViewController alloc] init];
+    waitCtrl.examRegistrationId = examRegistrationId;
+    [self.navigationController pushViewController:waitCtrl animated:YES];
 }
 
 /*

+ 19 - 0
MusicGradeExam/MusicGradeExam/UI/Exam/Controller/WaitExamViewController.h

@@ -0,0 +1,19 @@
+//
+//  WaitExamViewController.h
+//  MusicGradeExam
+//
+//  Created by Kyle on 2020/7/15.
+//  Copyright © 2020 DayaMusic. All rights reserved.
+//
+
+#import "KSBaseViewController.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface WaitExamViewController : KSBaseViewController
+
+@property (nonatomic, strong) NSString *examRegistrationId;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 133 - 0
MusicGradeExam/MusicGradeExam/UI/Exam/Controller/WaitExamViewController.m

@@ -0,0 +1,133 @@
+//
+//  WaitExamViewController.m
+//  MusicGradeExam
+//
+//  Created by Kyle on 2020/7/15.
+//  Copyright © 2020 DayaMusic. All rights reserved.
+//
+
+#import "WaitExamViewController.h"
+#import "WaitExamBodyView.h"
+#import "TicketDetailModel.h"
+#import "OnlineRoomManager.h"
+
+@interface WaitExamViewController ()
+
+@property (nonatomic, strong) WaitExamBodyView *bodyView;
+
+@property (nonatomic, strong) TicketDetailModel *sourceModel;
+
+@property (nonatomic, strong) OnlineRoomManager *classManager;
+
+@end
+
+@implementation WaitExamViewController
+
+- (void)viewDidLoad {
+    [super viewDidLoad];
+    // Do any additional setup after loading the view.
+    [self allocTitle:@"待考"];
+    self.view.backgroundColor = HexRGB(0xf3f4f8);
+    [self configUI];
+}
+
+- (void)viewWillAppear:(BOOL)animated {
+    [super viewWillAppear:animated];
+    [self requestData];
+}
+
+- (void)requestData {
+    [self showhud];
+    [KSRequestManager needCheckingDetailRequest:KS_GET examRegistrationId:self.examRegistrationId success:^(NSDictionary * _Nonnull dic) {
+        [self removehub];
+        if ([dic integerValueForKey:@"code"] == 200 && [dic boolValueForKey:@"status"]) {
+            self.sourceModel = [[TicketDetailModel alloc] initWithDictionary:[dic dictionaryValueForKey:@"data"]];
+            [self.bodyView configMessageSource:self.sourceModel];
+        }
+        else {
+            [self MBPShow:MESSAGEKEY];
+        }
+    } faliure:^(NSError * _Nonnull error) {
+        [self removehub];
+    }];
+}
+
+- (void)configUI {
+    self.scrollView.backgroundColor = HexRGB(0xf3f4f8);
+    self.scrollView.frame = CGRectMake(0, 0, kScreenWidth, kScreenHeight - kTabBarHeight - iPhoneXSafeBottomMargin);
+    _bodyView = [WaitExamBodyView shareInstance];
+    CGFloat viewHeight = 206 + 200 + 19 + 204 + 50 + 21 + 9 + 50 + 27;
+    viewHeight = viewHeight > kScreenHeight - kTabBarHeight - iPhoneXSafeBottomMargin ? viewHeight : kScreenHeight - kTabBarHeight - iPhoneXSafeBottomMargin;
+    _bodyView.frame = CGRectMake(0, 0, kScreenWidth, viewHeight);
+    MJWeakSelf;
+    [_bodyView operationCallback:^(JOINROOMACTION action, TicketDetailModel * _Nullable source) {
+        [weakSelf opreationAction:action source:source];
+    }];
+    [self.scrollView addSubview:_bodyView];
+    [self.scrollView setContentSize:CGSizeMake(kScreenWidth, viewHeight)];
+    
+}
+
+- (void)opreationAction:(JOINROOMACTION)action source:(TicketDetailModel *)source {
+    switch (action) {
+        case JOINROOMACTION_SIGN:  // 签到
+        {
+            [self signAction];
+        }
+            break;
+        case JOINROOMACTION_GUIDE: // 引导页
+        {
+            
+        }
+            break;
+        case JOINROOMACTION_JOIN:  // 加入房间
+        {
+            NSString *roomId = [NSString stringWithFormat:@"%.0f", source.examRoomId];
+            [self joinRoomAction:roomId];
+        }
+            break;
+        default:
+            break;
+    }
+}
+
+
+
+#pragma mark ----- 签到
+- (void)signAction {
+    [self showhud];
+    [KSRequestManager signInRequest:KS_POST examRegistrationId:self.examRegistrationId success:^(NSDictionary * _Nonnull dic) {
+        [self removehub];
+        if ([dic integerValueForKey:@"code"] == 200 && [dic boolValueForKey:@"status"]) {
+            self.bodyView.isSign = YES;
+        }
+        else {
+            [self MBPShow:MESSAGEKEY];
+        }
+    } faliure:^(NSError * _Nonnull error) {
+        [self removehub];
+    }];
+}
+
+#pragma mark ----- 加入房间
+- (void)joinRoomAction:(NSString *)roomId {
+    [self.classManager joinRoomWithId:roomId inViewController:self];
+}
+
+- (OnlineRoomManager *)classManager {
+    if (!_classManager) {
+        _classManager = [[OnlineRoomManager alloc] init];
+    }
+    return _classManager;
+}
+/*
+#pragma mark - Navigation
+
+// In a storyboard-based application, you will often want to do a little preparation before navigation
+- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
+    // Get the new view controller using [segue destinationViewController].
+    // Pass the selected object to the new view controller.
+}
+*/
+
+@end

+ 31 - 0
MusicGradeExam/MusicGradeExam/UI/Exam/Model/TicketDetailModel.h

@@ -0,0 +1,31 @@
+//
+//  TicketDetailModel.h
+//
+//  Created by   on 2020/7/16
+//  Copyright (c) 2020 __MyCompanyName__. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+
+
+@interface TicketDetailModel : NSObject <NSCoding, NSCopying>
+
+@property (nonatomic, assign) double examRoomId;
+@property (nonatomic, strong) NSString *baseExamName;
+@property (nonatomic, strong) NSString *examStartTime;
+@property (nonatomic, assign) double finishedExam;
+@property (nonatomic, strong) NSString *desc;
+@property (nonatomic, assign) double openFlag;
+@property (nonatomic, assign) double classroomSwitch;
+@property (nonatomic, strong) NSString *signInTime;
+@property (nonatomic, assign) double waitNum;
+@property (nonatomic, strong) NSString *examEndTime;
+@property (nonatomic, assign) double examRegistrationId;
+@property (nonatomic, assign) double examRoomStudentRelationId;
+
++ (instancetype)modelObjectWithDictionary:(NSDictionary *)dict;
+- (instancetype)initWithDictionary:(NSDictionary *)dict;
+- (NSDictionary *)dictionaryRepresentation;
+
+@end

+ 172 - 0
MusicGradeExam/MusicGradeExam/UI/Exam/Model/TicketDetailModel.m

@@ -0,0 +1,172 @@
+//
+//  TicketDetailModel.m
+//
+//  Created by   on 2020/7/16
+//  Copyright (c) 2020 __MyCompanyName__. All rights reserved.
+//
+
+#import "TicketDetailModel.h"
+
+
+NSString *const kTicketDetailModelExamRoomId = @"examRoomId";
+NSString *const kTicketDetailModelBaseExamName = @"baseExamName";
+NSString *const kTicketDetailModelExamStartTime = @"examStartTime";
+NSString *const kTicketDetailModelFinishedExam = @"finishedExam";
+NSString *const kTicketDetailModelDesc = @"desc";
+NSString *const kTicketDetailModelOpenFlag = @"openFlag";
+NSString *const kTicketDetailModelClassroomSwitch = @"classroomSwitch";
+NSString *const kTicketDetailModelSignInTime = @"signInTime";
+NSString *const kTicketDetailModelWaitNum = @"waitNum";
+NSString *const kTicketDetailModelExamEndTime = @"examEndTime";
+NSString *const kTicketDetailModelExamRegistrationId = @"examRegistrationId";
+NSString *const kTicketDetailModelExamRoomStudentRelationId = @"examRoomStudentRelationId";
+
+
+@interface TicketDetailModel ()
+
+- (id)objectOrNilForKey:(id)aKey fromDictionary:(NSDictionary *)dict;
+
+@end
+
+@implementation TicketDetailModel
+
+@synthesize examRoomId = _examRoomId;
+@synthesize baseExamName = _baseExamName;
+@synthesize examStartTime = _examStartTime;
+@synthesize finishedExam = _finishedExam;
+@synthesize desc = _desc;
+@synthesize openFlag = _openFlag;
+@synthesize classroomSwitch = _classroomSwitch;
+@synthesize signInTime = _signInTime;
+@synthesize waitNum = _waitNum;
+@synthesize examEndTime = _examEndTime;
+@synthesize examRegistrationId = _examRegistrationId;
+@synthesize examRoomStudentRelationId = _examRoomStudentRelationId;
+
+
++ (instancetype)modelObjectWithDictionary:(NSDictionary *)dict
+{
+    return [[self alloc] initWithDictionary:dict];
+}
+
+- (instancetype)initWithDictionary:(NSDictionary *)dict
+{
+    self = [super init];
+    
+    // This check serves to make sure that a non-NSDictionary object
+    // passed into the model class doesn't break the parsing.
+    if(self && [dict isKindOfClass:[NSDictionary class]]) {
+            self.examRoomId = [[self objectOrNilForKey:kTicketDetailModelExamRoomId fromDictionary:dict] doubleValue];
+            self.baseExamName = [self objectOrNilForKey:kTicketDetailModelBaseExamName fromDictionary:dict];
+            self.examStartTime = [self objectOrNilForKey:kTicketDetailModelExamStartTime fromDictionary:dict];
+            self.finishedExam = [[self objectOrNilForKey:kTicketDetailModelFinishedExam fromDictionary:dict] doubleValue];
+            self.desc = [self objectOrNilForKey:kTicketDetailModelDesc fromDictionary:dict];
+            self.openFlag = [[self objectOrNilForKey:kTicketDetailModelOpenFlag fromDictionary:dict] doubleValue];
+            self.classroomSwitch = [[self objectOrNilForKey:kTicketDetailModelClassroomSwitch fromDictionary:dict] doubleValue];
+            self.signInTime = [self objectOrNilForKey:kTicketDetailModelSignInTime fromDictionary:dict];
+            self.waitNum = [[self objectOrNilForKey:kTicketDetailModelWaitNum fromDictionary:dict] doubleValue];
+            self.examEndTime = [self objectOrNilForKey:kTicketDetailModelExamEndTime fromDictionary:dict];
+            self.examRegistrationId = [[self objectOrNilForKey:kTicketDetailModelExamRegistrationId fromDictionary:dict] doubleValue];
+            self.examRoomStudentRelationId = [[self objectOrNilForKey:kTicketDetailModelExamRoomStudentRelationId fromDictionary:dict] doubleValue];
+
+    }
+    
+    return self;
+    
+}
+
+- (NSDictionary *)dictionaryRepresentation
+{
+    NSMutableDictionary *mutableDict = [NSMutableDictionary dictionary];
+    [mutableDict setValue:[NSNumber numberWithDouble:self.examRoomId] forKey:kTicketDetailModelExamRoomId];
+    [mutableDict setValue:self.baseExamName forKey:kTicketDetailModelBaseExamName];
+    [mutableDict setValue:self.examStartTime forKey:kTicketDetailModelExamStartTime];
+    [mutableDict setValue:[NSNumber numberWithDouble:self.finishedExam] forKey:kTicketDetailModelFinishedExam];
+    [mutableDict setValue:self.desc forKey:kTicketDetailModelDesc];
+    [mutableDict setValue:[NSNumber numberWithDouble:self.openFlag] forKey:kTicketDetailModelOpenFlag];
+    [mutableDict setValue:[NSNumber numberWithDouble:self.classroomSwitch] forKey:kTicketDetailModelClassroomSwitch];
+    [mutableDict setValue:self.signInTime forKey:kTicketDetailModelSignInTime];
+    [mutableDict setValue:[NSNumber numberWithDouble:self.waitNum] forKey:kTicketDetailModelWaitNum];
+    [mutableDict setValue:self.examEndTime forKey:kTicketDetailModelExamEndTime];
+    [mutableDict setValue:[NSNumber numberWithDouble:self.examRegistrationId] forKey:kTicketDetailModelExamRegistrationId];
+    [mutableDict setValue:[NSNumber numberWithDouble:self.examRoomStudentRelationId] forKey:kTicketDetailModelExamRoomStudentRelationId];
+
+    return [NSDictionary dictionaryWithDictionary:mutableDict];
+}
+
+- (NSString *)description 
+{
+    return [NSString stringWithFormat:@"%@", [self dictionaryRepresentation]];
+}
+
+#pragma mark - Helper Method
+- (id)objectOrNilForKey:(id)aKey fromDictionary:(NSDictionary *)dict
+{
+    id object = [dict objectForKey:aKey];
+    return [object isEqual:[NSNull null]] ? nil : object;
+}
+
+
+#pragma mark - NSCoding Methods
+
+- (id)initWithCoder:(NSCoder *)aDecoder
+{
+    self = [super init];
+
+    self.examRoomId = [aDecoder decodeDoubleForKey:kTicketDetailModelExamRoomId];
+    self.baseExamName = [aDecoder decodeObjectForKey:kTicketDetailModelBaseExamName];
+    self.examStartTime = [aDecoder decodeObjectForKey:kTicketDetailModelExamStartTime];
+    self.finishedExam = [aDecoder decodeDoubleForKey:kTicketDetailModelFinishedExam];
+    self.desc = [aDecoder decodeObjectForKey:kTicketDetailModelDesc];
+    self.openFlag = [aDecoder decodeDoubleForKey:kTicketDetailModelOpenFlag];
+    self.classroomSwitch = [aDecoder decodeDoubleForKey:kTicketDetailModelClassroomSwitch];
+    self.signInTime = [aDecoder decodeObjectForKey:kTicketDetailModelSignInTime];
+    self.waitNum = [aDecoder decodeDoubleForKey:kTicketDetailModelWaitNum];
+    self.examEndTime = [aDecoder decodeObjectForKey:kTicketDetailModelExamEndTime];
+    self.examRegistrationId = [aDecoder decodeDoubleForKey:kTicketDetailModelExamRegistrationId];
+    self.examRoomStudentRelationId = [aDecoder decodeDoubleForKey:kTicketDetailModelExamRoomStudentRelationId];
+    return self;
+}
+
+- (void)encodeWithCoder:(NSCoder *)aCoder
+{
+
+    [aCoder encodeDouble:_examRoomId forKey:kTicketDetailModelExamRoomId];
+    [aCoder encodeObject:_baseExamName forKey:kTicketDetailModelBaseExamName];
+    [aCoder encodeObject:_examStartTime forKey:kTicketDetailModelExamStartTime];
+    [aCoder encodeDouble:_finishedExam forKey:kTicketDetailModelFinishedExam];
+    [aCoder encodeObject:_desc forKey:kTicketDetailModelDesc];
+    [aCoder encodeDouble:_openFlag forKey:kTicketDetailModelOpenFlag];
+    [aCoder encodeDouble:_classroomSwitch forKey:kTicketDetailModelClassroomSwitch];
+    [aCoder encodeObject:_signInTime forKey:kTicketDetailModelSignInTime];
+    [aCoder encodeDouble:_waitNum forKey:kTicketDetailModelWaitNum];
+    [aCoder encodeObject:_examEndTime forKey:kTicketDetailModelExamEndTime];
+    [aCoder encodeDouble:_examRegistrationId forKey:kTicketDetailModelExamRegistrationId];
+    [aCoder encodeDouble:_examRoomStudentRelationId forKey:kTicketDetailModelExamRoomStudentRelationId];
+}
+
+- (id)copyWithZone:(NSZone *)zone
+{
+    TicketDetailModel *copy = [[TicketDetailModel alloc] init];
+    
+    if (copy) {
+
+        copy.examRoomId = self.examRoomId;
+        copy.baseExamName = [self.baseExamName copyWithZone:zone];
+        copy.examStartTime = [self.examStartTime copyWithZone:zone];
+        copy.finishedExam = self.finishedExam;
+        copy.desc = [self.desc copyWithZone:zone];
+        copy.openFlag = self.openFlag;
+        copy.classroomSwitch = self.classroomSwitch;
+        copy.signInTime = [self.signInTime copyWithZone:zone];
+        copy.waitNum = self.waitNum;
+        copy.examEndTime = [self.examEndTime copyWithZone:zone];
+        copy.examRegistrationId = self.examRegistrationId;
+        copy.examRoomStudentRelationId = self.examRoomStudentRelationId;
+    }
+    
+    return copy;
+}
+
+
+@end

+ 29 - 0
MusicGradeExam/MusicGradeExam/UI/Exam/Model/TicketListModel.h

@@ -0,0 +1,29 @@
+//
+//  TicketListModel.h
+//
+//  Created by   on 2020/7/15
+//  Copyright (c) 2020 __MyCompanyName__. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+
+
+@interface TicketListModel : NSObject <NSCoding, NSCopying>
+
+@property (nonatomic, assign) double gender;
+@property (nonatomic, strong) NSString *cardNo;
+@property (nonatomic, strong) NSString *certificatePhoto;
+@property (nonatomic, strong) NSString *examTime;
+@property (nonatomic, assign) double examRegistrationId;
+@property (nonatomic, assign) double level;
+@property (nonatomic, strong) NSString *examAddress;
+@property (nonatomic, assign) double subjectId;
+@property (nonatomic, strong) NSString *subjectName;
+@property (nonatomic, strong) NSString *realName;
+
++ (instancetype)modelObjectWithDictionary:(NSDictionary *)dict;
+- (instancetype)initWithDictionary:(NSDictionary *)dict;
+- (NSDictionary *)dictionaryRepresentation;
+
+@end

+ 158 - 0
MusicGradeExam/MusicGradeExam/UI/Exam/Model/TicketListModel.m

@@ -0,0 +1,158 @@
+//
+//  TicketListModel.m
+//
+//  Created by   on 2020/7/15
+//  Copyright (c) 2020 __MyCompanyName__. All rights reserved.
+//
+
+#import "TicketListModel.h"
+
+
+NSString *const kTicketListModelGender = @"gender";
+NSString *const kTicketListModelCardNo = @"cardNo";
+NSString *const kTicketListModelCertificatePhoto = @"certificatePhoto";
+NSString *const kTicketListModelExamTime = @"examTime";
+NSString *const kTicketListModelExamRegistrationId = @"examRegistrationId";
+NSString *const kTicketListModelLevel = @"level";
+NSString *const kTicketListModelExamAddress = @"examAddress";
+NSString *const kTicketListModelSubjectId = @"subjectId";
+NSString *const kTicketListModelSubjectName = @"subjectName";
+NSString *const kTicketListModelRealName = @"realName";
+
+
+@interface TicketListModel ()
+
+- (id)objectOrNilForKey:(id)aKey fromDictionary:(NSDictionary *)dict;
+
+@end
+
+@implementation TicketListModel
+
+@synthesize gender = _gender;
+@synthesize cardNo = _cardNo;
+@synthesize certificatePhoto = _certificatePhoto;
+@synthesize examTime = _examTime;
+@synthesize examRegistrationId = _examRegistrationId;
+@synthesize level = _level;
+@synthesize examAddress = _examAddress;
+@synthesize subjectId = _subjectId;
+@synthesize subjectName = _subjectName;
+@synthesize realName = _realName;
+
+
++ (instancetype)modelObjectWithDictionary:(NSDictionary *)dict
+{
+    return [[self alloc] initWithDictionary:dict];
+}
+
+- (instancetype)initWithDictionary:(NSDictionary *)dict
+{
+    self = [super init];
+    
+    // This check serves to make sure that a non-NSDictionary object
+    // passed into the model class doesn't break the parsing.
+    if(self && [dict isKindOfClass:[NSDictionary class]]) {
+            self.gender = [[self objectOrNilForKey:kTicketListModelGender fromDictionary:dict] doubleValue];
+            self.cardNo = [self objectOrNilForKey:kTicketListModelCardNo fromDictionary:dict];
+            self.certificatePhoto = [self objectOrNilForKey:kTicketListModelCertificatePhoto fromDictionary:dict];
+            self.examTime = [self objectOrNilForKey:kTicketListModelExamTime fromDictionary:dict];
+            self.examRegistrationId = [[self objectOrNilForKey:kTicketListModelExamRegistrationId fromDictionary:dict] doubleValue];
+            self.level = [[self objectOrNilForKey:kTicketListModelLevel fromDictionary:dict] doubleValue];
+            self.examAddress = [self objectOrNilForKey:kTicketListModelExamAddress fromDictionary:dict];
+            self.subjectId = [[self objectOrNilForKey:kTicketListModelSubjectId fromDictionary:dict] doubleValue];
+            self.subjectName = [self objectOrNilForKey:kTicketListModelSubjectName fromDictionary:dict];
+            self.realName = [self objectOrNilForKey:kTicketListModelRealName fromDictionary:dict];
+
+    }
+    
+    return self;
+    
+}
+
+- (NSDictionary *)dictionaryRepresentation
+{
+    NSMutableDictionary *mutableDict = [NSMutableDictionary dictionary];
+    [mutableDict setValue:[NSNumber numberWithDouble:self.gender] forKey:kTicketListModelGender];
+    [mutableDict setValue:self.cardNo forKey:kTicketListModelCardNo];
+    [mutableDict setValue:self.certificatePhoto forKey:kTicketListModelCertificatePhoto];
+    [mutableDict setValue:self.examTime forKey:kTicketListModelExamTime];
+    [mutableDict setValue:[NSNumber numberWithDouble:self.examRegistrationId] forKey:kTicketListModelExamRegistrationId];
+    [mutableDict setValue:[NSNumber numberWithDouble:self.level] forKey:kTicketListModelLevel];
+    [mutableDict setValue:self.examAddress forKey:kTicketListModelExamAddress];
+    [mutableDict setValue:[NSNumber numberWithDouble:self.subjectId] forKey:kTicketListModelSubjectId];
+    [mutableDict setValue:self.subjectName forKey:kTicketListModelSubjectName];
+    [mutableDict setValue:self.realName forKey:kTicketListModelRealName];
+
+    return [NSDictionary dictionaryWithDictionary:mutableDict];
+}
+
+- (NSString *)description 
+{
+    return [NSString stringWithFormat:@"%@", [self dictionaryRepresentation]];
+}
+
+#pragma mark - Helper Method
+- (id)objectOrNilForKey:(id)aKey fromDictionary:(NSDictionary *)dict
+{
+    id object = [dict objectForKey:aKey];
+    return [object isEqual:[NSNull null]] ? nil : object;
+}
+
+
+#pragma mark - NSCoding Methods
+
+- (id)initWithCoder:(NSCoder *)aDecoder
+{
+    self = [super init];
+
+    self.gender = [aDecoder decodeDoubleForKey:kTicketListModelGender];
+    self.cardNo = [aDecoder decodeObjectForKey:kTicketListModelCardNo];
+    self.certificatePhoto = [aDecoder decodeObjectForKey:kTicketListModelCertificatePhoto];
+    self.examTime = [aDecoder decodeObjectForKey:kTicketListModelExamTime];
+    self.examRegistrationId = [aDecoder decodeDoubleForKey:kTicketListModelExamRegistrationId];
+    self.level = [aDecoder decodeDoubleForKey:kTicketListModelLevel];
+    self.examAddress = [aDecoder decodeObjectForKey:kTicketListModelExamAddress];
+    self.subjectId = [aDecoder decodeDoubleForKey:kTicketListModelSubjectId];
+    self.subjectName = [aDecoder decodeObjectForKey:kTicketListModelSubjectName];
+    self.realName = [aDecoder decodeObjectForKey:kTicketListModelRealName];
+    return self;
+}
+
+- (void)encodeWithCoder:(NSCoder *)aCoder
+{
+
+    [aCoder encodeDouble:_gender forKey:kTicketListModelGender];
+    [aCoder encodeObject:_cardNo forKey:kTicketListModelCardNo];
+    [aCoder encodeObject:_certificatePhoto forKey:kTicketListModelCertificatePhoto];
+    [aCoder encodeObject:_examTime forKey:kTicketListModelExamTime];
+    [aCoder encodeDouble:_examRegistrationId forKey:kTicketListModelExamRegistrationId];
+    [aCoder encodeDouble:_level forKey:kTicketListModelLevel];
+    [aCoder encodeObject:_examAddress forKey:kTicketListModelExamAddress];
+    [aCoder encodeDouble:_subjectId forKey:kTicketListModelSubjectId];
+    [aCoder encodeObject:_subjectName forKey:kTicketListModelSubjectName];
+    [aCoder encodeObject:_realName forKey:kTicketListModelRealName];
+}
+
+- (id)copyWithZone:(NSZone *)zone
+{
+    TicketListModel *copy = [[TicketListModel alloc] init];
+    
+    if (copy) {
+
+        copy.gender = self.gender;
+        copy.cardNo = [self.cardNo copyWithZone:zone];
+        copy.certificatePhoto = [self.certificatePhoto copyWithZone:zone];
+        copy.examTime = [self.examTime copyWithZone:zone];
+        copy.examRegistrationId = self.examRegistrationId;
+        copy.level = self.level;
+        copy.examAddress = [self.examAddress copyWithZone:zone];
+        copy.subjectId = self.subjectId;
+        copy.subjectName = [self.subjectName copyWithZone:zone];
+        copy.realName = [self.realName copyWithZone:zone];
+    }
+    
+    return copy;
+}
+
+
+@end

+ 25 - 0
MusicGradeExam/MusicGradeExam/UI/Exam/View/TicketBodyView.h

@@ -0,0 +1,25 @@
+//
+//  TicketBodyView.h
+//  MusicGradeExam
+//
+//  Created by Kyle on 2020/7/15.
+//  Copyright © 2020 DayaMusic. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+#import "TicketListModel.h"
+typedef void(^JoinCallback)(TicketListModel * _Nonnull source);
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface TicketBodyView : UIView
+
++ (instancetype)shareInstance;
+
+- (void)configMessage:(TicketListModel *)message;
+
+- (void)joinRoomCallback:(JoinCallback)callback;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 133 - 0
MusicGradeExam/MusicGradeExam/UI/Exam/View/TicketBodyView.m

@@ -0,0 +1,133 @@
+//
+//  TicketBodyView.m
+//  MusicGradeExam
+//
+//  Created by Kyle on 2020/7/15.
+//  Copyright © 2020 DayaMusic. All rights reserved.
+//
+
+#import "TicketBodyView.h"
+#import "UserInfoManager.h"
+
+@interface TicketBodyView ()
+
+@property (weak, nonatomic) IBOutlet UIView *topView;
+
+@property (weak, nonatomic) IBOutlet UIImageView *certificateImage;
+
+@property (weak, nonatomic) IBOutlet UILabel *cardNo;
+
+@property (weak, nonatomic) IBOutlet UILabel *userName;
+
+@property (weak, nonatomic) IBOutlet UILabel *userSex;
+
+@property (weak, nonatomic) IBOutlet UILabel *subjectLabel;
+
+@property (weak, nonatomic) IBOutlet UILabel *examLevel;
+
+@property (weak, nonatomic) IBOutlet UILabel *examTime;
+
+@property (weak, nonatomic) IBOutlet UILabel *classLocation;
+
+@property (nonatomic, copy) JoinCallback callback;
+
+@property (nonatomic, strong) CAGradientLayer *gradientLayer;
+
+@property (nonatomic, strong) TicketListModel *sourceModel;
+
+@end
+
+@implementation TicketBodyView
+
+- (void)awakeFromNib {
+    [super awakeFromNib];
+    [self.topView.layer addSublayer:self.gradientLayer];
+    self.topView.layer.masksToBounds = YES;
+}
+
++ (instancetype)shareInstance {
+    TicketBodyView *view = [[[NSBundle mainBundle] loadNibNamed:@"TicketBodyView" owner:nil options:nil] firstObject];
+    return view;
+}
+
+- (void)layoutSubviews {
+    [super layoutSubviews];
+    //在这里获取frame
+    _gradientLayer.frame = CGRectMake(0, 0, CGRectGetWidth(self.topView.frame), CGRectGetHeight(self.topView.frame));
+}
+
+- (void)configMessage:(TicketListModel *)message {
+    // 赋值
+    self.sourceModel = message;
+    [self.certificateImage sd_setImageWithURL:[NSURL URLWithString:self.sourceModel.certificatePhoto]];
+    self.cardNo.text = [NSString stringWithFormat:@"准考证号:%@",[NSString returnNoNullStringWithString:self.sourceModel.cardNo]];
+    self.userName.text = [NSString stringWithFormat:@"考试名字:%@",[NSString returnNoNullStringWithString:self.sourceModel.realName]];
+    self.userSex.text = self.sourceModel.gender == 1 ? @"考生性别:男" : @"考生性别:女";
+    self.subjectLabel.text = [NSString stringWithFormat:@"报考级别:%@",[NSString returnNoNullStringWithString:self.sourceModel.subjectName]];
+    self.examLevel.text = [NSString stringWithFormat:@"报考级别:%@",[self getLevelName:self.sourceModel.level]];
+    self.examTime.text = [NSString stringWithFormat:@"考试时间:%@",[NSString returnNoNullStringWithString:self.sourceModel.examTime]];
+    self.classLocation.text = [NSString isEmptyString:self.sourceModel.examAddress] ? @"考试地点:网络教室" : [NSString stringWithFormat:@"考试地点%@",self.sourceModel.examAddress];
+}
+
+- (NSString *)getLevelName:(NSUInteger)level {
+    switch (level) {
+        case 1:
+            return @"壹级";
+        case 2:
+            return @"贰级";
+        case 3:
+            return @"叁级";
+        case 4:
+            return @"肆级";
+        case 5:
+            return @"伍级";
+        case 6:
+            return @"陆级";
+        case 7:
+            return @"柒级";
+        case 8:
+            return @"捌级";
+        case 9:
+            return @"玖级";
+        case 10:
+            return @"拾级";
+        default:
+            return @"";
+            break;
+    }
+}
+
+- (void)joinRoomCallback:(JoinCallback)callback {
+    if (callback) {
+        self.callback = callback;
+    }
+}
+
+- (IBAction)joinRoom:(id)sender {
+    if (self.callback) {
+        self.callback(self.sourceModel);
+    }
+}
+
+- (CAGradientLayer *)gradientLayer {
+    if (!_gradientLayer) {
+        _gradientLayer = [CAGradientLayer layer];
+        _gradientLayer.startPoint = CGPointMake(1, 0.77);
+        _gradientLayer.endPoint = CGPointMake(0.02, 0.06);
+        _gradientLayer.locations = @[@(0),@(1.0)];//渐变点
+        UIColor *startColor = HexRGB(0x59e5d5);
+        UIColor *endColor = HexRGB(0x2dc7aa);
+        [_gradientLayer setColors:@[(id)(startColor.CGColor),(id)(endColor.CGColor)]];//渐变数组
+    }
+    return _gradientLayer;
+}
+
+/*
+// Only override drawRect: if you perform custom drawing.
+// An empty implementation adversely affects performance during animation.
+- (void)drawRect:(CGRect)rect {
+    // Drawing code
+}
+*/
+
+@end

+ 232 - 0
MusicGradeExam/MusicGradeExam/UI/Exam/View/TicketBodyView.xib

@@ -0,0 +1,232 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="16097" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
+    <device id="retina6_1" orientation="portrait" appearance="light"/>
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
+        <capability name="Safe area layout guides" minToolsVersion="9.0"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </dependencies>
+    <objects>
+        <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
+        <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
+        <view contentMode="scaleToFill" id="iN0-l3-epB" customClass="TicketBodyView">
+            <rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
+            <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+            <subviews>
+                <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="yUJ-P8-V4z">
+                    <rect key="frame" x="0.0" y="0.0" width="414" height="190"/>
+                    <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
+                    <constraints>
+                        <constraint firstAttribute="height" constant="190" id="onF-wb-CV9"/>
+                    </constraints>
+                </view>
+                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="准考证详情" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Sfp-A6-TYV">
+                    <rect key="frame" x="161" y="61.5" width="92" height="25"/>
+                    <constraints>
+                        <constraint firstAttribute="height" constant="25" id="wNi-aE-MU2"/>
+                    </constraints>
+                    <fontDescription key="fontDescription" type="system" weight="medium" pointSize="18"/>
+                    <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                    <nil key="highlightedColor"/>
+                </label>
+                <imageView hidden="YES" userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="login_back" translatesAutoresizingMaskIntoConstraints="NO" id="MQl-y7-vxz">
+                    <rect key="frame" x="17" y="64" width="11" height="20"/>
+                    <constraints>
+                        <constraint firstAttribute="width" constant="11" id="1v3-Uj-fpq"/>
+                        <constraint firstAttribute="height" constant="20" id="XU4-gE-Yd9"/>
+                    </constraints>
+                </imageView>
+                <button hidden="YES" opaque="NO" tag="1005" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Vgy-lZ-7Kl">
+                    <rect key="frame" x="0.0" y="54" width="40" height="40"/>
+                    <constraints>
+                        <constraint firstAttribute="height" constant="40" id="1hK-eK-a63"/>
+                        <constraint firstAttribute="width" constant="40" id="o2n-Cn-mbm"/>
+                    </constraints>
+                </button>
+                <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="yYc-dX-DlQ">
+                    <rect key="frame" x="16" y="116" width="382" height="412"/>
+                    <subviews>
+                        <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Cj7-w9-VFo">
+                            <rect key="frame" x="11" y="11" width="360" height="390"/>
+                            <subviews>
+                                <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="A1y-sO-QGA">
+                                    <rect key="frame" x="130" y="26" width="100" height="131"/>
+                                    <color key="backgroundColor" red="0.89803921568627454" green="0.83529411764705885" blue="0.83529411764705885" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                                    <constraints>
+                                        <constraint firstAttribute="width" constant="100" id="BVy-Mc-nfH"/>
+                                        <constraint firstAttribute="height" constant="131" id="ScM-W4-Jg7"/>
+                                    </constraints>
+                                </imageView>
+                                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="准考证号:" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="KFg-DG-VJj">
+                                    <rect key="frame" x="75" y="181" width="275" height="20"/>
+                                    <constraints>
+                                        <constraint firstAttribute="height" constant="20" id="4jJ-Hb-l6c"/>
+                                    </constraints>
+                                    <fontDescription key="fontDescription" type="system" pointSize="14"/>
+                                    <color key="textColor" red="0.10196078431372549" green="0.10196078431372549" blue="0.10196078431372549" alpha="1" colorSpace="calibratedRGB"/>
+                                    <nil key="highlightedColor"/>
+                                </label>
+                                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="考生名字:" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="hj2-Sz-yoI">
+                                    <rect key="frame" x="75" y="209" width="275" height="20"/>
+                                    <constraints>
+                                        <constraint firstAttribute="height" constant="20" id="Qse-p4-2qW"/>
+                                    </constraints>
+                                    <fontDescription key="fontDescription" type="system" pointSize="14"/>
+                                    <color key="textColor" red="0.1019607843" green="0.1019607843" blue="0.1019607843" alpha="1" colorSpace="calibratedRGB"/>
+                                    <nil key="highlightedColor"/>
+                                </label>
+                                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="考生性别:" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="59a-SL-h8z">
+                                    <rect key="frame" x="75" y="237" width="275" height="20"/>
+                                    <constraints>
+                                        <constraint firstAttribute="height" constant="20" id="fLE-r4-pJ8"/>
+                                    </constraints>
+                                    <fontDescription key="fontDescription" type="system" pointSize="14"/>
+                                    <color key="textColor" red="0.1019607843" green="0.1019607843" blue="0.1019607843" alpha="1" colorSpace="calibratedRGB"/>
+                                    <nil key="highlightedColor"/>
+                                </label>
+                                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="报考专业:" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Yik-4s-H4Q">
+                                    <rect key="frame" x="75" y="265" width="275" height="20"/>
+                                    <constraints>
+                                        <constraint firstAttribute="height" constant="20" id="goO-9e-RAZ"/>
+                                    </constraints>
+                                    <fontDescription key="fontDescription" type="system" pointSize="14"/>
+                                    <color key="textColor" red="0.1019607843" green="0.1019607843" blue="0.1019607843" alpha="1" colorSpace="calibratedRGB"/>
+                                    <nil key="highlightedColor"/>
+                                </label>
+                                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="报考级别:" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="baH-bx-beF">
+                                    <rect key="frame" x="75" y="293" width="275" height="20"/>
+                                    <constraints>
+                                        <constraint firstAttribute="height" constant="20" id="bVh-7M-K79"/>
+                                    </constraints>
+                                    <fontDescription key="fontDescription" type="system" pointSize="14"/>
+                                    <color key="textColor" red="0.1019607843" green="0.1019607843" blue="0.1019607843" alpha="1" colorSpace="calibratedRGB"/>
+                                    <nil key="highlightedColor"/>
+                                </label>
+                                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="考试时间:" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="pgz-3o-9OJ">
+                                    <rect key="frame" x="75" y="321" width="275" height="20"/>
+                                    <constraints>
+                                        <constraint firstAttribute="height" constant="20" id="55S-oJ-IwL"/>
+                                    </constraints>
+                                    <fontDescription key="fontDescription" type="system" pointSize="14"/>
+                                    <color key="textColor" red="0.1019607843" green="0.1019607843" blue="0.1019607843" alpha="1" colorSpace="calibratedRGB"/>
+                                    <nil key="highlightedColor"/>
+                                </label>
+                                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="考试地点:" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="sl8-xL-Pp2">
+                                    <rect key="frame" x="75" y="349" width="275" height="20"/>
+                                    <constraints>
+                                        <constraint firstAttribute="height" constant="20" id="1fz-eA-rgH"/>
+                                    </constraints>
+                                    <fontDescription key="fontDescription" type="system" pointSize="14"/>
+                                    <color key="textColor" red="0.1019607843" green="0.1019607843" blue="0.1019607843" alpha="1" colorSpace="calibratedRGB"/>
+                                    <nil key="highlightedColor"/>
+                                </label>
+                            </subviews>
+                            <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
+                            <constraints>
+                                <constraint firstItem="KFg-DG-VJj" firstAttribute="leading" secondItem="A1y-sO-QGA" secondAttribute="leading" constant="-55" id="0Eg-4m-RtX"/>
+                                <constraint firstItem="sl8-xL-Pp2" firstAttribute="trailing" secondItem="KFg-DG-VJj" secondAttribute="trailing" id="6jc-aA-ndd"/>
+                                <constraint firstItem="59a-SL-h8z" firstAttribute="top" secondItem="hj2-Sz-yoI" secondAttribute="bottom" constant="8" id="7zt-hk-NWo"/>
+                                <constraint firstItem="sl8-xL-Pp2" firstAttribute="leading" secondItem="pgz-3o-9OJ" secondAttribute="leading" id="8OJ-g9-EwT"/>
+                                <constraint firstItem="pgz-3o-9OJ" firstAttribute="top" secondItem="baH-bx-beF" secondAttribute="bottom" constant="8" id="BB5-zQ-HxF"/>
+                                <constraint firstItem="hj2-Sz-yoI" firstAttribute="leading" secondItem="KFg-DG-VJj" secondAttribute="leading" id="C6E-5Y-Q9X"/>
+                                <constraint firstItem="Yik-4s-H4Q" firstAttribute="leading" secondItem="KFg-DG-VJj" secondAttribute="leading" id="Cq3-te-Mrn"/>
+                                <constraint firstAttribute="trailing" secondItem="KFg-DG-VJj" secondAttribute="trailing" constant="10" id="EhT-ZF-chp"/>
+                                <constraint firstItem="baH-bx-beF" firstAttribute="trailing" secondItem="KFg-DG-VJj" secondAttribute="trailing" id="EqX-RZ-Xjr"/>
+                                <constraint firstItem="Yik-4s-H4Q" firstAttribute="trailing" secondItem="KFg-DG-VJj" secondAttribute="trailing" id="HAg-jY-J0q"/>
+                                <constraint firstItem="pgz-3o-9OJ" firstAttribute="top" secondItem="baH-bx-beF" secondAttribute="bottom" constant="8" id="Hr2-4f-Q9I"/>
+                                <constraint firstItem="hj2-Sz-yoI" firstAttribute="trailing" secondItem="KFg-DG-VJj" secondAttribute="trailing" id="ImE-en-N7I"/>
+                                <constraint firstItem="59a-SL-h8z" firstAttribute="trailing" secondItem="KFg-DG-VJj" secondAttribute="trailing" id="L5b-mH-b6z"/>
+                                <constraint firstItem="baH-bx-beF" firstAttribute="top" secondItem="Yik-4s-H4Q" secondAttribute="bottom" constant="8" id="N4O-io-7xo"/>
+                                <constraint firstItem="pgz-3o-9OJ" firstAttribute="leading" secondItem="KFg-DG-VJj" secondAttribute="leading" id="U0A-6l-VQq"/>
+                                <constraint firstItem="A1y-sO-QGA" firstAttribute="top" secondItem="Cj7-w9-VFo" secondAttribute="top" constant="26" id="WHa-wq-Mya"/>
+                                <constraint firstItem="A1y-sO-QGA" firstAttribute="centerX" secondItem="Cj7-w9-VFo" secondAttribute="centerX" id="aJ2-dI-Zni"/>
+                                <constraint firstItem="hj2-Sz-yoI" firstAttribute="top" secondItem="KFg-DG-VJj" secondAttribute="bottom" constant="8" id="hf3-4I-v6O"/>
+                                <constraint firstItem="pgz-3o-9OJ" firstAttribute="trailing" secondItem="KFg-DG-VJj" secondAttribute="trailing" id="ncn-Up-FOP"/>
+                                <constraint firstItem="KFg-DG-VJj" firstAttribute="top" secondItem="A1y-sO-QGA" secondAttribute="bottom" constant="24" id="oCW-M2-rLg"/>
+                                <constraint firstItem="sl8-xL-Pp2" firstAttribute="top" secondItem="pgz-3o-9OJ" secondAttribute="bottom" constant="8" id="rs5-ve-iPZ"/>
+                                <constraint firstItem="59a-SL-h8z" firstAttribute="leading" secondItem="KFg-DG-VJj" secondAttribute="leading" id="tKn-og-oY6"/>
+                                <constraint firstItem="baH-bx-beF" firstAttribute="leading" secondItem="KFg-DG-VJj" secondAttribute="leading" id="ySH-z0-YeH"/>
+                                <constraint firstItem="Yik-4s-H4Q" firstAttribute="top" secondItem="59a-SL-h8z" secondAttribute="bottom" constant="8" id="yfS-CE-CT2"/>
+                            </constraints>
+                            <userDefinedRuntimeAttributes>
+                                <userDefinedRuntimeAttribute type="color" keyPath="borderColor">
+                                    <color key="value" red="0.1764705882352941" green="0.7803921568627451" blue="0.66666666666666663" alpha="1" colorSpace="calibratedRGB"/>
+                                </userDefinedRuntimeAttribute>
+                                <userDefinedRuntimeAttribute type="number" keyPath="borderWidth">
+                                    <real key="value" value="2"/>
+                                </userDefinedRuntimeAttribute>
+                                <userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
+                                    <real key="value" value="4"/>
+                                </userDefinedRuntimeAttribute>
+                            </userDefinedRuntimeAttributes>
+                        </view>
+                    </subviews>
+                    <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
+                    <constraints>
+                        <constraint firstItem="Cj7-w9-VFo" firstAttribute="top" secondItem="yYc-dX-DlQ" secondAttribute="top" constant="11" id="1D1-kX-ufB"/>
+                        <constraint firstAttribute="trailing" secondItem="Cj7-w9-VFo" secondAttribute="trailing" constant="11" id="2mi-cU-eKm"/>
+                        <constraint firstAttribute="bottom" secondItem="Cj7-w9-VFo" secondAttribute="bottom" constant="11" id="eaw-hT-Y4g"/>
+                        <constraint firstItem="Cj7-w9-VFo" firstAttribute="leading" secondItem="yYc-dX-DlQ" secondAttribute="leading" constant="11" id="fdO-C5-ycn"/>
+                        <constraint firstAttribute="height" constant="412" id="xLK-Po-Iif"/>
+                    </constraints>
+                    <userDefinedRuntimeAttributes>
+                        <userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
+                            <real key="value" value="4"/>
+                        </userDefinedRuntimeAttribute>
+                    </userDefinedRuntimeAttributes>
+                </view>
+                <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Zcb-sN-Ifk">
+                    <rect key="frame" x="17" y="546" width="380" height="50"/>
+                    <color key="backgroundColor" red="0.1764705882" green="0.78039215689999997" blue="0.66666666669999997" alpha="1" colorSpace="calibratedRGB"/>
+                    <constraints>
+                        <constraint firstAttribute="height" constant="50" id="u1K-X7-Iyg"/>
+                    </constraints>
+                    <state key="normal" title="进入教室"/>
+                    <userDefinedRuntimeAttributes>
+                        <userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
+                            <real key="value" value="25"/>
+                        </userDefinedRuntimeAttribute>
+                    </userDefinedRuntimeAttributes>
+                    <connections>
+                        <action selector="joinRoom:" destination="iN0-l3-epB" eventType="touchUpInside" id="RDY-fN-nBu"/>
+                    </connections>
+                </button>
+            </subviews>
+            <color key="backgroundColor" red="0.95294117647058818" green="0.95686274509803915" blue="0.97254901960784312" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+            <constraints>
+                <constraint firstItem="MQl-y7-vxz" firstAttribute="top" secondItem="vUN-kp-3ea" secondAttribute="top" constant="20" id="08Q-dd-bTs"/>
+                <constraint firstItem="MQl-y7-vxz" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" constant="17" id="0tL-fQ-SAl"/>
+                <constraint firstItem="yUJ-P8-V4z" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" id="3VO-CP-af9"/>
+                <constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="yYc-dX-DlQ" secondAttribute="trailing" constant="16" id="Cw1-Xk-enl"/>
+                <constraint firstItem="Zcb-sN-Ifk" firstAttribute="top" secondItem="yYc-dX-DlQ" secondAttribute="bottom" constant="18" id="Ss8-OU-Dkn"/>
+                <constraint firstItem="Sfp-A6-TYV" firstAttribute="centerY" secondItem="MQl-y7-vxz" secondAttribute="centerY" id="bHk-yh-6ho"/>
+                <constraint firstItem="yYc-dX-DlQ" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" constant="16" id="bJs-Ea-J83"/>
+                <constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="Zcb-sN-Ifk" secondAttribute="trailing" constant="17" id="ca6-sH-DLl"/>
+                <constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="yUJ-P8-V4z" secondAttribute="trailing" id="d2b-h3-nIu"/>
+                <constraint firstItem="yUJ-P8-V4z" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" id="k06-0l-UVr"/>
+                <constraint firstItem="Sfp-A6-TYV" firstAttribute="centerX" secondItem="iN0-l3-epB" secondAttribute="centerX" id="lDa-t9-YtU"/>
+                <constraint firstItem="Vgy-lZ-7Kl" firstAttribute="centerY" secondItem="MQl-y7-vxz" secondAttribute="centerY" id="lU4-lI-JNL"/>
+                <constraint firstItem="yYc-dX-DlQ" firstAttribute="top" secondItem="Vgy-lZ-7Kl" secondAttribute="bottom" constant="22" id="sof-XS-6Eo"/>
+                <constraint firstItem="Zcb-sN-Ifk" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" constant="17" id="usd-nl-uzu"/>
+                <constraint firstItem="Vgy-lZ-7Kl" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" id="xKa-qP-nBj"/>
+            </constraints>
+            <viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
+            <connections>
+                <outlet property="cardNo" destination="KFg-DG-VJj" id="RaK-7n-Wbj"/>
+                <outlet property="certificateImage" destination="A1y-sO-QGA" id="1hP-L1-7bc"/>
+                <outlet property="classLocation" destination="sl8-xL-Pp2" id="lJl-Bf-X7B"/>
+                <outlet property="examLevel" destination="baH-bx-beF" id="9MK-50-ube"/>
+                <outlet property="examTime" destination="pgz-3o-9OJ" id="S2z-he-SzZ"/>
+                <outlet property="subjectLabel" destination="Yik-4s-H4Q" id="FGR-yT-aw3"/>
+                <outlet property="topView" destination="yUJ-P8-V4z" id="diP-7H-kVh"/>
+                <outlet property="userName" destination="hj2-Sz-yoI" id="jnb-bW-tMP"/>
+                <outlet property="userSex" destination="59a-SL-h8z" id="ihp-h0-nSj"/>
+            </connections>
+            <point key="canvasLocation" x="131.8840579710145" y="101.78571428571428"/>
+        </view>
+    </objects>
+    <resources>
+        <image name="login_back" width="12" height="21"/>
+    </resources>
+</document>

+ 32 - 0
MusicGradeExam/MusicGradeExam/UI/Exam/View/WaitExamBodyView.h

@@ -0,0 +1,32 @@
+//
+//  WaitExamBodyView.h
+//  MusicGradeExam
+//
+//  Created by Kyle on 2020/7/15.
+//  Copyright © 2020 DayaMusic. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+#import "TicketDetailModel.h"
+typedef NS_ENUM(NSInteger, JOINROOMACTION) {
+    JOINROOMACTION_SIGN,   // 签到
+    JOINROOMACTION_GUIDE,  // 引导
+    JOINROOMACTION_JOIN,   // 进入房间
+};
+typedef void(^JoinRoomAction)(JOINROOMACTION action, TicketDetailModel * _Nullable source);
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface WaitExamBodyView : UIView
+
+@property (nonatomic, assign) BOOL isSign;
+
++ (instancetype)shareInstance;
+
+- (void)configMessageSource:(TicketDetailModel *)source;
+
+- (void)operationCallback:(JoinRoomAction)callback;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 150 - 0
MusicGradeExam/MusicGradeExam/UI/Exam/View/WaitExamBodyView.m

@@ -0,0 +1,150 @@
+//
+//  WaitExamBodyView.m
+//  MusicGradeExam
+//
+//  Created by Kyle on 2020/7/15.
+//  Copyright © 2020 DayaMusic. All rights reserved.
+//
+
+#import "WaitExamBodyView.h"
+
+@interface WaitExamBodyView ()
+
+@property (weak, nonatomic) IBOutlet UIButton *signButton;
+
+@property (weak, nonatomic) IBOutlet UILabel *signDescLabel;
+
+@property (weak, nonatomic) IBOutlet UILabel *classDate;
+
+@property (weak, nonatomic) IBOutlet UILabel *subjectLabel;
+
+@property (weak, nonatomic) IBOutlet UILabel *classTime;
+
+@property (weak, nonatomic) IBOutlet UILabel *waitLabel;
+
+@property (weak, nonatomic) IBOutlet UIButton *joinButton;
+
+@property (nonatomic, copy) JoinRoomAction callback;
+
+@property (nonatomic, strong) TicketDetailModel *sourceModel;
+
+@property (nonatomic, strong) CAGradientLayer *gradientLayer;
+
+@end
+
+@implementation WaitExamBodyView
+- (void)awakeFromNib {
+    [super awakeFromNib];
+    [self.signButton.layer insertSublayer:self.gradientLayer atIndex:0];
+    self.signButton.layer.masksToBounds = YES;
+}
+
+- (void)layoutSubviews {
+    [super layoutSubviews];
+    //在这里获取frame
+    if (_gradientLayer) {
+        _gradientLayer.frame = CGRectMake(0, 0, CGRectGetWidth(self.signButton.frame), CGRectGetHeight(self.signButton.frame));
+    }
+}
+
++ (instancetype)shareInstance {
+    WaitExamBodyView *view = [[[NSBundle mainBundle] loadNibNamed:@"WaitExamBodyView" owner:nil options:nil] firstObject];
+    return view;
+}
+
+- (void)configMessageSource:(TicketDetailModel *)source {
+    self.sourceModel = source;
+    if ([NSString isEmptyString:source.signInTime]) {
+        self.isSign = NO;
+        self.signDescLabel.text = @"您还未签到,签到后可进入教室";
+    }
+    else {
+        self.isSign = YES;
+        self.signDescLabel.text = @"您已签到,请点击下方按钮进入教室";
+    }
+    
+    self.classDate.text = [[self.sourceModel.examStartTime componentsSeparatedByString:@" "] firstObject];
+    self.subjectLabel.text = [NSString returnNoNullStringWithString:self.sourceModel.baseExamName];
+
+    self.waitLabel.text = [NSString stringWithFormat:@"%.0f位考生",self.sourceModel.waitNum];
+    
+    // 时间
+    NSDateFormatter *dateFormatter=[[NSDateFormatter alloc] init];
+    dateFormatter.timeZone = [NSTimeZone systemTimeZone];
+    [dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
+    NSDate *beginDate = [dateFormatter dateFromString:source.examStartTime];
+    NSDate *endDate = [dateFormatter dateFromString:source.examEndTime];
+    [dateFormatter setDateFormat:@"HH:mm"];
+    NSString *beginStr = [NSString returnNoNullStringWithString:[dateFormatter stringFromDate:beginDate]];
+    NSString *endStr = [NSString returnNoNullStringWithString:[dateFormatter stringFromDate:endDate]];
+    self.classTime.text = [NSString stringWithFormat:@"%@-%@", beginStr, endStr];
+}
+
+- (void)setIsSign:(BOOL)isSign {
+    _isSign = isSign;
+    if (isSign) {
+        [self.gradientLayer removeFromSuperlayer];
+        _gradientLayer = nil;
+        self.signButton.userInteractionEnabled = NO;
+        [self.signButton setTitle:@"已签到" forState:UIControlStateNormal];
+        self.joinButton.userInteractionEnabled = YES;
+        [self.joinButton setBackgroundColor:THEMECOLOR];
+    }
+    else {
+        [self.signButton.layer insertSublayer:self.gradientLayer atIndex:0];
+        self.signButton.userInteractionEnabled = YES;
+        [self.signButton setTitle:@"签到" forState:UIControlStateNormal];
+        self.joinButton.userInteractionEnabled = NO;
+        [self.joinButton setBackgroundColor:HexRGB(0xcccccc)];
+    }
+}
+
+- (void)operationCallback:(JoinRoomAction)callback {
+    if (callback) {
+        self.callback = callback;
+    }
+}
+
+
+
+- (IBAction)signAction:(id)sender {
+    if (self.callback) {
+        self.callback(JOINROOMACTION_SIGN, self.sourceModel);
+    }
+}
+
+
+- (IBAction)guideAction:(id)sender {
+    if (self.callback) {
+        self.callback(JOINROOMACTION_GUIDE, nil);
+    }
+}
+
+- (IBAction)joinAction:(id)sender {
+    if (self.callback) {
+        self.callback(JOINROOMACTION_JOIN, self.sourceModel);
+    }
+}
+
+- (CAGradientLayer *)gradientLayer {
+    if (!_gradientLayer) {
+        _gradientLayer = [CAGradientLayer layer];
+        _gradientLayer.startPoint = CGPointMake(0.5, 0.02);
+        _gradientLayer.endPoint = CGPointMake(0.5, 0.97);
+        _gradientLayer.locations = @[@(0),@(1.0)];//渐变点
+        UIColor *startColor = HexRGB(0xffd1b0);
+        UIColor *endColor = HexRGB(0xffb378);
+        [_gradientLayer setColors:@[(id)(startColor.CGColor),(id)(endColor.CGColor)]];//渐变数组
+    }
+    return _gradientLayer;
+}
+
+/*
+// Only override drawRect: if you perform custom drawing.
+// An empty implementation adversely affects performance during animation.
+- (void)drawRect:(CGRect)rect {
+    // Drawing code
+}
+*/
+
+@end

+ 260 - 0
MusicGradeExam/MusicGradeExam/UI/Exam/View/WaitExamBodyView.xib

@@ -0,0 +1,260 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="16097" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
+    <device id="retina6_1" orientation="portrait" appearance="light"/>
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
+        <capability name="Safe area layout guides" minToolsVersion="9.0"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </dependencies>
+    <objects>
+        <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
+        <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
+        <view contentMode="scaleToFill" id="iN0-l3-epB" customClass="WaitExamBodyView">
+            <rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
+            <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+            <subviews>
+                <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="UYp-Af-6p1">
+                    <rect key="frame" x="0.0" y="0.0" width="414" height="206"/>
+                    <subviews>
+                        <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="WEa-ER-E4J">
+                            <rect key="frame" x="142" y="21" width="130" height="130"/>
+                            <color key="backgroundColor" red="0.80000000000000004" green="0.80000000000000004" blue="0.80000000000000004" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                            <constraints>
+                                <constraint firstAttribute="height" constant="130" id="MG0-I2-lcH"/>
+                                <constraint firstAttribute="width" constant="130" id="yIg-pH-8vg"/>
+                            </constraints>
+                            <fontDescription key="fontDescription" type="system" weight="medium" pointSize="20"/>
+                            <state key="normal" title="签到"/>
+                            <userDefinedRuntimeAttributes>
+                                <userDefinedRuntimeAttribute type="size" keyPath="shadowOffset">
+                                    <size key="value" width="0.0" height="4"/>
+                                </userDefinedRuntimeAttribute>
+                                <userDefinedRuntimeAttribute type="color" keyPath="shadowUIColor">
+                                    <color key="value" red="0.8862745098" green="0.62745098040000002" blue="0.41960784309999999" alpha="0.47999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
+                                </userDefinedRuntimeAttribute>
+                                <userDefinedRuntimeAttribute type="number" keyPath="shadowOpacity">
+                                    <real key="value" value="1"/>
+                                </userDefinedRuntimeAttribute>
+                                <userDefinedRuntimeAttribute type="number" keyPath="shadowRadius">
+                                    <real key="value" value="13"/>
+                                </userDefinedRuntimeAttribute>
+                                <userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
+                                    <real key="value" value="65"/>
+                                </userDefinedRuntimeAttribute>
+                                <userDefinedRuntimeAttribute type="boolean" keyPath="maskToBounces" value="NO"/>
+                            </userDefinedRuntimeAttributes>
+                            <connections>
+                                <action selector="signAction:" destination="iN0-l3-epB" eventType="touchUpInside" id="hmj-oo-ksM"/>
+                            </connections>
+                        </button>
+                        <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="您还未签到,签到后可进入教室" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="O3Y-5U-nUH">
+                            <rect key="frame" x="107" y="165" width="200" height="17"/>
+                            <fontDescription key="fontDescription" type="system" pointSize="14"/>
+                            <color key="textColor" red="0.50196078430000002" green="0.50196078430000002" blue="0.50196078430000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                            <nil key="highlightedColor"/>
+                        </label>
+                    </subviews>
+                    <color key="backgroundColor" red="0.95294117649999999" green="0.95686274510000002" blue="0.97254901959999995" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                    <constraints>
+                        <constraint firstItem="O3Y-5U-nUH" firstAttribute="centerX" secondItem="UYp-Af-6p1" secondAttribute="centerX" id="E8W-oR-bXW"/>
+                        <constraint firstItem="WEa-ER-E4J" firstAttribute="centerX" secondItem="UYp-Af-6p1" secondAttribute="centerX" id="f1Q-Hm-BNr"/>
+                        <constraint firstItem="WEa-ER-E4J" firstAttribute="top" secondItem="UYp-Af-6p1" secondAttribute="top" constant="21" id="r33-76-GI3"/>
+                        <constraint firstItem="O3Y-5U-nUH" firstAttribute="top" secondItem="WEa-ER-E4J" secondAttribute="bottom" constant="14" id="ss3-dm-TvK"/>
+                        <constraint firstAttribute="height" constant="206" id="yii-Oa-qGV"/>
+                    </constraints>
+                </view>
+                <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Pa9-Km-YvF">
+                    <rect key="frame" x="0.0" y="206" width="414" height="200"/>
+                    <subviews>
+                        <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="日期:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="qtd-ei-Gfc">
+                            <rect key="frame" x="16" y="25" width="110" height="21"/>
+                            <constraints>
+                                <constraint firstAttribute="height" constant="21" id="GaS-lx-E5H"/>
+                            </constraints>
+                            <fontDescription key="fontDescription" type="system" pointSize="15"/>
+                            <color key="textColor" red="0.1019607843" green="0.1019607843" blue="0.1019607843" alpha="1" colorSpace="calibratedRGB"/>
+                            <nil key="highlightedColor"/>
+                        </label>
+                        <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="考试名字:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="OCV-Hb-Bhx">
+                            <rect key="frame" x="16" y="67" width="110" height="21"/>
+                            <constraints>
+                                <constraint firstAttribute="height" constant="21" id="32S-MN-pjK"/>
+                            </constraints>
+                            <fontDescription key="fontDescription" type="system" pointSize="15"/>
+                            <color key="textColor" red="0.1019607843" green="0.1019607843" blue="0.1019607843" alpha="1" colorSpace="calibratedRGB"/>
+                            <nil key="highlightedColor"/>
+                        </label>
+                        <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="预计考试时间:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="XNA-1W-zWF">
+                            <rect key="frame" x="16" y="109" width="110" height="21"/>
+                            <constraints>
+                                <constraint firstAttribute="height" constant="21" id="ejT-14-htA"/>
+                                <constraint firstAttribute="width" constant="110" id="nTw-Up-leg"/>
+                            </constraints>
+                            <fontDescription key="fontDescription" type="system" pointSize="15"/>
+                            <color key="textColor" red="0.1019607843" green="0.1019607843" blue="0.1019607843" alpha="1" colorSpace="calibratedRGB"/>
+                            <nil key="highlightedColor"/>
+                        </label>
+                        <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="当前需等待:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="tW6-ZA-2q9">
+                            <rect key="frame" x="16" y="151" width="110" height="21"/>
+                            <constraints>
+                                <constraint firstAttribute="height" constant="21" id="79W-AN-hFy"/>
+                            </constraints>
+                            <fontDescription key="fontDescription" type="system" pointSize="15"/>
+                            <color key="textColor" red="0.1019607843" green="0.1019607843" blue="0.1019607843" alpha="1" colorSpace="calibratedRGB"/>
+                            <nil key="highlightedColor"/>
+                            <userDefinedRuntimeAttributes>
+                                <userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
+                                    <real key="value" value="10"/>
+                                </userDefinedRuntimeAttribute>
+                            </userDefinedRuntimeAttributes>
+                        </label>
+                        <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="2020-05-20" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="qLV-gz-q1k">
+                            <rect key="frame" x="143" y="25" width="86.5" height="21"/>
+                            <constraints>
+                                <constraint firstAttribute="height" constant="21" id="NOA-LS-pbs"/>
+                            </constraints>
+                            <fontDescription key="fontDescription" type="system" pointSize="15"/>
+                            <color key="textColor" red="0.1019607843" green="0.1019607843" blue="0.1019607843" alpha="1" colorSpace="calibratedRGB"/>
+                            <nil key="highlightedColor"/>
+                        </label>
+                        <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="钢琴" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="cOI-UA-7JY">
+                            <rect key="frame" x="143" y="67" width="31" height="21"/>
+                            <constraints>
+                                <constraint firstAttribute="height" constant="21" id="iU4-3Y-IMh"/>
+                            </constraints>
+                            <fontDescription key="fontDescription" type="system" pointSize="15"/>
+                            <color key="textColor" red="0.1019607843" green="0.1019607843" blue="0.1019607843" alpha="1" colorSpace="calibratedRGB"/>
+                            <nil key="highlightedColor"/>
+                        </label>
+                        <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="9:00-12:00" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Czx-Hb-wZ8">
+                            <rect key="frame" x="143" y="109" width="77.5" height="21"/>
+                            <constraints>
+                                <constraint firstAttribute="height" constant="21" id="80L-ol-dr7"/>
+                            </constraints>
+                            <fontDescription key="fontDescription" type="system" pointSize="15"/>
+                            <color key="textColor" red="0.1019607843" green="0.1019607843" blue="0.1019607843" alpha="1" colorSpace="calibratedRGB"/>
+                            <nil key="highlightedColor"/>
+                        </label>
+                        <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="12 位考生" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="KQk-Oi-2gF">
+                            <rect key="frame" x="143" y="151" width="65.5" height="21"/>
+                            <constraints>
+                                <constraint firstAttribute="height" constant="21" id="AvQ-vK-bnf"/>
+                            </constraints>
+                            <fontDescription key="fontDescription" type="system" pointSize="15"/>
+                            <color key="textColor" red="0.1019607843" green="0.1019607843" blue="0.1019607843" alpha="1" colorSpace="calibratedRGB"/>
+                            <nil key="highlightedColor"/>
+                        </label>
+                    </subviews>
+                    <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
+                    <constraints>
+                        <constraint firstItem="tW6-ZA-2q9" firstAttribute="width" secondItem="qtd-ei-Gfc" secondAttribute="width" id="03D-3v-oA5"/>
+                        <constraint firstItem="XNA-1W-zWF" firstAttribute="top" secondItem="OCV-Hb-Bhx" secondAttribute="bottom" constant="21" id="3FF-sh-Cqn"/>
+                        <constraint firstItem="cOI-UA-7JY" firstAttribute="leading" secondItem="qLV-gz-q1k" secondAttribute="leading" id="3MN-PZ-dFh"/>
+                        <constraint firstItem="XNA-1W-zWF" firstAttribute="leading" secondItem="qtd-ei-Gfc" secondAttribute="leading" id="6P2-Oj-G9R"/>
+                        <constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="cOI-UA-7JY" secondAttribute="trailing" constant="16" id="6T7-nb-Lab"/>
+                        <constraint firstItem="OCV-Hb-Bhx" firstAttribute="leading" secondItem="qtd-ei-Gfc" secondAttribute="leading" id="9Jz-LY-exP"/>
+                        <constraint firstItem="qLV-gz-q1k" firstAttribute="centerY" secondItem="qtd-ei-Gfc" secondAttribute="centerY" id="9qw-jn-l5h"/>
+                        <constraint firstItem="KQk-Oi-2gF" firstAttribute="centerY" secondItem="tW6-ZA-2q9" secondAttribute="centerY" id="BAf-DH-X9y"/>
+                        <constraint firstItem="qtd-ei-Gfc" firstAttribute="top" secondItem="Pa9-Km-YvF" secondAttribute="top" constant="25" id="BnX-c4-Gt9"/>
+                        <constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="Czx-Hb-wZ8" secondAttribute="trailing" constant="16" id="C6S-hw-grJ"/>
+                        <constraint firstItem="qtd-ei-Gfc" firstAttribute="leading" secondItem="Pa9-Km-YvF" secondAttribute="leading" constant="16" id="HOo-6Z-Kgt"/>
+                        <constraint firstItem="OCV-Hb-Bhx" firstAttribute="width" secondItem="qtd-ei-Gfc" secondAttribute="width" id="M0m-Nb-unT"/>
+                        <constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="KQk-Oi-2gF" secondAttribute="trailing" constant="16" id="RAw-rj-oip"/>
+                        <constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="qLV-gz-q1k" secondAttribute="trailing" constant="16" id="WAO-th-Xen"/>
+                        <constraint firstItem="qLV-gz-q1k" firstAttribute="leading" secondItem="qtd-ei-Gfc" secondAttribute="trailing" constant="17" id="We3-Qx-gdZ"/>
+                        <constraint firstItem="KQk-Oi-2gF" firstAttribute="leading" secondItem="qLV-gz-q1k" secondAttribute="leading" id="bKC-gi-atQ"/>
+                        <constraint firstItem="Czx-Hb-wZ8" firstAttribute="centerY" secondItem="XNA-1W-zWF" secondAttribute="centerY" id="clu-bH-TUg"/>
+                        <constraint firstItem="OCV-Hb-Bhx" firstAttribute="top" secondItem="qtd-ei-Gfc" secondAttribute="bottom" constant="21" id="hG2-lP-JZq"/>
+                        <constraint firstItem="cOI-UA-7JY" firstAttribute="centerY" secondItem="OCV-Hb-Bhx" secondAttribute="centerY" id="icw-Y1-VzF"/>
+                        <constraint firstItem="XNA-1W-zWF" firstAttribute="width" secondItem="qtd-ei-Gfc" secondAttribute="width" id="jxC-bT-Z6h"/>
+                        <constraint firstItem="tW6-ZA-2q9" firstAttribute="top" secondItem="XNA-1W-zWF" secondAttribute="bottom" constant="21" id="nMi-x8-UAm"/>
+                        <constraint firstItem="Czx-Hb-wZ8" firstAttribute="leading" secondItem="qLV-gz-q1k" secondAttribute="leading" id="pUg-OJ-6gK"/>
+                        <constraint firstItem="tW6-ZA-2q9" firstAttribute="leading" secondItem="qtd-ei-Gfc" secondAttribute="leading" id="sWl-LO-y1T"/>
+                        <constraint firstAttribute="height" constant="200" id="w0j-Ad-QsE"/>
+                    </constraints>
+                </view>
+                <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="rEr-rK-lTU">
+                    <rect key="frame" x="0.0" y="425" width="414" height="204"/>
+                    <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
+                    <constraints>
+                        <constraint firstAttribute="height" constant="204" id="wUu-d4-Eaa"/>
+                    </constraints>
+                    <userDefinedRuntimeAttributes>
+                        <userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
+                            <real key="value" value="10"/>
+                        </userDefinedRuntimeAttribute>
+                    </userDefinedRuntimeAttributes>
+                </view>
+                <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="8bl-8o-KNJ">
+                    <rect key="frame" x="17" y="650" width="380" height="50"/>
+                    <constraints>
+                        <constraint firstAttribute="height" constant="50" id="gcf-YI-Gnf"/>
+                    </constraints>
+                    <state key="normal" title="查看引导">
+                        <color key="titleColor" red="0.1764705882" green="0.78039215689999997" blue="0.66666666669999997" alpha="1" colorSpace="calibratedRGB"/>
+                    </state>
+                    <userDefinedRuntimeAttributes>
+                        <userDefinedRuntimeAttribute type="number" keyPath="borderWidth">
+                            <real key="value" value="1"/>
+                        </userDefinedRuntimeAttribute>
+                        <userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
+                            <real key="value" value="25"/>
+                        </userDefinedRuntimeAttribute>
+                        <userDefinedRuntimeAttribute type="color" keyPath="borderColor">
+                            <color key="value" red="0.1764705882" green="0.78039215689999997" blue="0.66666666669999997" alpha="1" colorSpace="calibratedRGB"/>
+                        </userDefinedRuntimeAttribute>
+                    </userDefinedRuntimeAttributes>
+                    <connections>
+                        <action selector="guideAction:" destination="iN0-l3-epB" eventType="touchUpInside" id="szD-ot-0hU"/>
+                    </connections>
+                </button>
+                <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="I8H-IY-JIX">
+                    <rect key="frame" x="16" y="710" width="382" height="50"/>
+                    <color key="backgroundColor" red="0.80000000000000004" green="0.80000000000000004" blue="0.80000000000000004" alpha="1" colorSpace="calibratedRGB"/>
+                    <constraints>
+                        <constraint firstAttribute="height" constant="50" id="fRv-bF-dQZ"/>
+                    </constraints>
+                    <state key="normal" title="进入教室"/>
+                    <userDefinedRuntimeAttributes>
+                        <userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
+                            <real key="value" value="25"/>
+                        </userDefinedRuntimeAttribute>
+                    </userDefinedRuntimeAttributes>
+                    <connections>
+                        <action selector="joinAction:" destination="iN0-l3-epB" eventType="touchUpInside" id="Zoc-Q5-JHh"/>
+                    </connections>
+                </button>
+            </subviews>
+            <color key="backgroundColor" red="0.95294117647058818" green="0.95686274509803915" blue="0.97254901960784312" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+            <constraints>
+                <constraint firstItem="8bl-8o-KNJ" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" constant="17" id="0WS-Iz-tK7"/>
+                <constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="UYp-Af-6p1" secondAttribute="trailing" id="8pk-5f-VC7"/>
+                <constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="I8H-IY-JIX" secondAttribute="trailing" constant="16" id="8yH-6Q-Qdg"/>
+                <constraint firstItem="Pa9-Km-YvF" firstAttribute="top" secondItem="UYp-Af-6p1" secondAttribute="bottom" id="CWe-JG-6hK"/>
+                <constraint firstItem="Pa9-Km-YvF" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" id="HHn-iH-eae"/>
+                <constraint firstItem="I8H-IY-JIX" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" constant="16" id="Jdm-1A-QVo"/>
+                <constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="8bl-8o-KNJ" secondAttribute="trailing" constant="17" id="MoY-DY-LWf"/>
+                <constraint firstItem="UYp-Af-6p1" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" id="Q88-JQ-nCz"/>
+                <constraint firstItem="UYp-Af-6p1" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" id="RVB-yk-KHo"/>
+                <constraint firstItem="rEr-rK-lTU" firstAttribute="top" secondItem="Pa9-Km-YvF" secondAttribute="bottom" constant="19" id="Ryv-aW-hgO"/>
+                <constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="rEr-rK-lTU" secondAttribute="trailing" id="UBZ-Qd-R5C"/>
+                <constraint firstItem="I8H-IY-JIX" firstAttribute="top" secondItem="8bl-8o-KNJ" secondAttribute="bottom" constant="10" id="alG-yH-Dan"/>
+                <constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="Pa9-Km-YvF" secondAttribute="trailing" id="jzt-B0-L8V"/>
+                <constraint firstItem="rEr-rK-lTU" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" id="ktB-06-Zxi"/>
+                <constraint firstItem="8bl-8o-KNJ" firstAttribute="top" secondItem="rEr-rK-lTU" secondAttribute="bottom" constant="21" id="t2z-bu-S2A"/>
+            </constraints>
+            <viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
+            <connections>
+                <outlet property="classDate" destination="qLV-gz-q1k" id="deW-pB-idj"/>
+                <outlet property="classTime" destination="Czx-Hb-wZ8" id="irw-eT-XKg"/>
+                <outlet property="joinButton" destination="I8H-IY-JIX" id="cAC-Ad-PuS"/>
+                <outlet property="signButton" destination="WEa-ER-E4J" id="Ft8-JM-Rw6"/>
+                <outlet property="signDescLabel" destination="O3Y-5U-nUH" id="tBU-Y1-qvd"/>
+                <outlet property="subjectLabel" destination="cOI-UA-7JY" id="95e-BG-AVB"/>
+                <outlet property="waitLabel" destination="KQk-Oi-2gF" id="m6Y-Ea-Z5h"/>
+            </connections>
+            <point key="canvasLocation" x="132" y="102"/>
+        </view>
+    </objects>
+</document>

+ 134 - 2
MusicGradeExam/MusicGradeExam/UI/Home/Controller/HomeViewController.m

@@ -10,11 +10,20 @@
 #import "HomeBodyView.h"
 #import "NotifyMessageViewController.h"
 #import "KSBaseWKWebViewController.h"
+#import "TicketListModel.h"
+#import "TYCyclePagerView.h"
+#import "TYPageControl.h"
+#import "HomeExamTicketCell.h"
+#import "WaitExamViewController.h"
 
-@interface HomeViewController ()
+@interface HomeViewController ()<TYCyclePagerViewDataSource,TYCyclePagerViewDelegate>
 
 @property (nonatomic, strong) HomeBodyView *bodyView;
 
+@property (nonatomic, strong) TYPageControl *pageControl;
+
+@property (nonatomic, strong) TYCyclePagerView *pagerView; // 乐团数量
+
 @end
 
 @implementation HomeViewController
@@ -34,14 +43,74 @@
 - (void)requestMessage {
     // 获取信息
     // 1. 获取未读数量
+    [self queryUnReadMessageCount];
     // 2. 获取最近考试信息
+    [self queryTicketMessage];
+}
+
+
+- (void)queryTicketMessage {
+    [KSRequestManager queryCertificationPageRequest:KS_GET success:^(NSDictionary * _Nonnull dic) {
+        if ([dic integerValueForKey:@"code"] == 200 && [dic boolValueForKey:@"status"]) {
+            NSArray *source = [dic arrayValueForKey:@"data"];
+            NSMutableArray *ticketArray = [NSMutableArray array];
+            for (NSDictionary *parm in source) {
+                TicketListModel *model = [[TicketListModel alloc] initWithDictionary:parm];
+                [ticketArray addObject:model];
+            }
+            self.dataArray = [NSMutableArray arrayWithArray:ticketArray];
+        }
+        else {
+            [self MBPShow:MESSAGEKEY];
+        }
+        [self refreshUI];
+    } faliure:^(NSError * _Nonnull error) {
+        
+    }];
+}
+
+- (void)refreshUI {
+    if (self.dataArray.count == 0) {
+        [self.pageControl removeFromSuperview];
+        self.bodyView.hasExam = NO;
+    }
+    else {
+        if (self.dataArray.count > 1) {
+            [self.pagerView addSubview:self.pageControl];
+            _pageControl.numberOfPages = self.dataArray.count;
+        }
+        else {
+            [self.pageControl removeFromSuperview];
+        }
+        self.bodyView.hasExam = YES;
+        [self.pagerView reloadData];
+    }
+}
+
+- (void)queryUnReadMessageCount {
+    [KSRequestManager queryCountOfUnreadRequest:KS_GET success:^(NSDictionary * _Nonnull dic) {
+        [self.scrollView.mj_header endRefreshing];
+        if ([dic integerValueForKey:@"code"] == 200 && [dic boolValueForKey:@"status"]) {
+            NSDictionary *dataDic = [dic dictionaryValueForKey:@"data"];
+            if (dataDic == 0) {
+                self.bodyView.hasNoReadCount = NO;
+            }
+            else {
+                self.bodyView.hasNoReadCount = YES;
+            }
+        }
+        
+    } faliure:^(NSError * _Nonnull error) {
+        [self.scrollView.mj_header endRefreshing];
+    }];
 }
 
 - (void)configUI {
     self.scrollView.backgroundColor = HexRGB(0xf3f4f8);
+    self.scrollView.frame = CGRectMake(0, 0, kScreenWidth, kScreenHeight - kTabBarHeight);
     _bodyView = [HomeBodyView shareInstance];
     CGFloat recordHeight = (kScreenWidth - 24) / 13 * 4;
-    CGFloat viewHeight = recordHeight * 3 + 30 + kScreenWidth / 375 * 158 + 73 + 15;
+    CGFloat viewHeight = recordHeight * 3 + 45 + (kScreenWidth - 32) / 191 * 73 + 15 + iPhoneXSafeTopMargin + 83;
     viewHeight = kScreenHeight - kTabBarHeight ? viewHeight : kScreenHeight - kTabBarHeight;
     _bodyView.frame = CGRectMake(0, 0, kScreenWidth, viewHeight);
     MJWeakSelf;
@@ -56,6 +125,14 @@
         // Fallback on earlier versions
         self.automaticallyAdjustsScrollViewInsets = NO;
     }
+    [self addPageView];
+    self.scrollView.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{
+        [weakSelf requestMessage];
+    }];
+}
+
+- (void)addPageView {
+    [_bodyView.scollBgView addSubview:self.pagerView];
 }
 
 - (void)opreationAction:(HOMETYPE)type {
@@ -95,6 +172,61 @@
     }
 }
 
+#pragma mark ----  TYCyclePagerViewDataSource代理
+- (NSInteger)numberOfItemsInPagerView:(TYCyclePagerView *)pageView {
+    return self.dataArray.count;
+}
+
+- (UICollectionViewCell *)pagerView:(TYCyclePagerView *)pagerView cellForItemAtIndex:(NSInteger)index {
+    TicketListModel *model = self.dataArray[index];
+    HomeExamTicketCell *cell = [pagerView dequeueReusableCellWithReuseIdentifier:@"HomeExamTicketCell" forIndex:index];
+    [cell configCellWithSource:model];
+    return cell;
+}
+
+- (TYCyclePagerViewLayout *)layoutForPagerView:(TYCyclePagerView *)pageView {
+    TYCyclePagerViewLayout *layout = [[TYCyclePagerViewLayout alloc]init];
+    layout.itemSize = CGSizeMake(CGRectGetWidth(pageView.frame), CGRectGetHeight(pageView.frame));
+    layout.itemSpacing = 0;
+    return layout;
+    
+}
+
+- (void)pagerView:(TYCyclePagerView *)pageView didScrollFromIndex:(NSInteger)fromIndex toIndex:(NSInteger)toIndex {
+    [self.pageControl setCurrentPage:toIndex animate:YES];
+}
+
+- (void)pagerView:(TYCyclePagerView *)pageView didSelectedItemCell:(__kindof UICollectionViewCell *)cell atIndex:(NSInteger)index {
+    TicketListModel *model = self.dataArray[index];
+    WaitExamViewController *waitCtrl = [[WaitExamViewController alloc] init];
+    waitCtrl.examRegistrationId = [NSString stringWithFormat:@"%.0f",model.examRegistrationId];
+    [self.navigationController pushViewController:waitCtrl animated:YES];
+}
+
+#pragma mark ---- lazying
+- (TYCyclePagerView *)pagerView {
+    if (!_pagerView) {
+        _pagerView = [[TYCyclePagerView alloc] init];
+        _pagerView.frame = CGRectMake(0, 0, kScreenWidth - 32, (kScreenWidth - 32) / 191 * 73);
+        _pagerView.isInfiniteLoop = NO;
+        _pagerView.autoScrollInterval = 0;
+        _pagerView.dataSource = self;
+        _pagerView.delegate = self;
+        [_pagerView registerNib:[UINib nibWithNibName:@"HomeExamTicketCell" bundle:nil] forCellWithReuseIdentifier:@"HomeExamTicketCell"];
+    }
+    return _pagerView;
+}
+
+- (TYPageControl *)pageControl {
+    if (!_pageControl) {
+        _pageControl = [[TYPageControl alloc] init];
+        _pageControl.frame = CGRectMake(0, (kScreenWidth - 32) / 191 * 73 - 10, kScreenWidth - 32, 10);
+        _pageControl.currentPageIndicatorTintColor = THEMECOLOR;
+        _pageControl.pageIndicatorTintColor = HexRGB(0xd8d8d8);
+    }
+    return _pageControl;
+}
+
 /*
 #pragma mark - Navigation
 

+ 4 - 0
MusicGradeExam/MusicGradeExam/UI/Home/View/HomeBodyView.h

@@ -25,6 +25,10 @@ NS_ASSUME_NONNULL_BEGIN
 
 @property (weak, nonatomic) IBOutlet UIView *scollBgView;
 
+@property (nonatomic, assign) BOOL hasNoReadCount;
+
+@property (nonatomic, assign) BOOL hasExam;
+
 + (instancetype)shareInstance;
 
 - (void)buttonClickCallback:(HomeButtonClick)callback;

+ 26 - 0
MusicGradeExam/MusicGradeExam/UI/Home/View/HomeBodyView.m

@@ -12,6 +12,8 @@
 
 @property (nonatomic, copy) HomeButtonClick callback;
 
+@property (weak, nonatomic) IBOutlet NSLayoutConstraint *topMargin;
+
 @property (weak, nonatomic) IBOutlet NSLayoutConstraint *bgHeight;
 
 @end
@@ -20,6 +22,30 @@
 
 - (void)awakeFromNib {
     [super awakeFromNib];
+    _topMargin.constant = 34 + iPhoneXSafeTopMargin;
+    _bgHeight.constant = (kScreenWidth - 32) / 191 * 73;
+}
+
+- (void)setHasNoReadCount:(BOOL)hasNoReadCount {
+    _hasNoReadCount = hasNoReadCount;
+    if (hasNoReadCount) {
+        [self.noticeButton setImage:[UIImage imageNamed:@"notice_read"] forState:UIControlStateNormal];
+    }
+    else {
+        [self.noticeButton setImage:[UIImage imageNamed:@"notice_unread"] forState:UIControlStateNormal];
+    }
+}
+
+- (void)setHasExam:(BOOL)hasExam {
+    _hasExam = hasExam;
+    if (_hasExam) {
+        _bgHeight.constant = (kScreenWidth - 32) / 191 * 73;
+        self.scollBgView.hidden = NO;
+    }
+    else {
+        _bgHeight.constant = 0;
+        self.scollBgView.hidden = YES;
+    }
 }
 
 + (instancetype)shareInstance {

+ 35 - 31
MusicGradeExam/MusicGradeExam/UI/Home/View/HomeBodyView.xib

@@ -1,10 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="16097" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="16097" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
     <device id="retina6_1" orientation="portrait" appearance="light"/>
     <dependencies>
         <deployment identifier="iOS"/>
         <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
-        <capability name="Safe area layout guides" minToolsVersion="9.0"/>
         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
     </dependencies>
     <objects>
@@ -14,17 +13,18 @@
             <rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
             <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
             <subviews>
-                <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="UE7-CX-F3s">
+                <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="home_top" translatesAutoresizingMaskIntoConstraints="NO" id="UE7-CX-F3s">
                     <rect key="frame" x="0.0" y="0.0" width="414" height="174.5"/>
                     <constraints>
                         <constraint firstAttribute="width" secondItem="UE7-CX-F3s" secondAttribute="height" multiplier="375:158" id="spA-lC-yEg"/>
                     </constraints>
                 </imageView>
                 <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="deZ-CC-Tlw">
-                    <rect key="frame" x="16" y="101.5" width="382" height="146"/>
+                    <rect key="frame" x="16" y="90.5" width="382" height="146"/>
                     <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
                     <constraints>
-                        <constraint firstAttribute="height" constant="146" id="tdc-1f-NK3"/>
+                        <constraint firstAttribute="height" constant="146" id="2Ow-0T-1Ld"/>
+                        <constraint firstAttribute="width" secondItem="deZ-CC-Tlw" secondAttribute="height" multiplier="191:73" id="oCK-Gi-Vza"/>
                     </constraints>
                     <userDefinedRuntimeAttributes>
                         <userDefinedRuntimeAttribute type="size" keyPath="shadowOffset">
@@ -45,7 +45,7 @@
                     </userDefinedRuntimeAttributes>
                 </view>
                 <button opaque="NO" tag="1001" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="pm2-TK-yzh">
-                    <rect key="frame" x="364" y="46.5" width="40" height="40"/>
+                    <rect key="frame" x="364" y="30.5" width="40" height="40"/>
                     <constraints>
                         <constraint firstAttribute="height" constant="40" id="C4X-wk-y2Q"/>
                         <constraint firstAttribute="width" constant="40" id="S9o-kq-zZV"/>
@@ -56,13 +56,16 @@
                     </connections>
                 </button>
                 <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="酷乐秀" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="qJ1-Pm-qsl">
-                    <rect key="frame" x="16" y="52" width="73" height="29"/>
+                    <rect key="frame" x="16" y="34" width="73" height="33"/>
+                    <constraints>
+                        <constraint firstAttribute="height" constant="33" id="SxQ-jk-5Uj"/>
+                    </constraints>
                     <fontDescription key="fontDescription" type="system" weight="medium" pointSize="24"/>
                     <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
                     <nil key="highlightedColor"/>
                 </label>
                 <imageView clipsSubviews="YES" tag="1002" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="book_record" translatesAutoresizingMaskIntoConstraints="NO" id="FTX-tf-JYt">
-                    <rect key="frame" x="12" y="262.5" width="390" height="120"/>
+                    <rect key="frame" x="12" y="251.5" width="392" height="120.5"/>
                     <gestureRecognizers/>
                     <constraints>
                         <constraint firstAttribute="width" secondItem="FTX-tf-JYt" secondAttribute="height" multiplier="13:4" id="wNV-53-ocO"/>
@@ -72,7 +75,7 @@
                     </connections>
                 </imageView>
                 <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="报考记录" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="71n-fC-CqF">
-                    <rect key="frame" x="32" y="297.5" width="74" height="25"/>
+                    <rect key="frame" x="32" y="287" width="74" height="25"/>
                     <constraints>
                         <constraint firstAttribute="height" constant="25" id="MxA-yU-X9o"/>
                     </constraints>
@@ -81,14 +84,14 @@
                     <nil key="highlightedColor"/>
                 </label>
                 <imageView clipsSubviews="YES" tag="1003" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="exam_record" translatesAutoresizingMaskIntoConstraints="NO" id="X8m-zH-diL">
-                    <rect key="frame" x="12" y="397.5" width="390" height="120"/>
+                    <rect key="frame" x="12" y="387" width="392" height="120.5"/>
                     <gestureRecognizers/>
                     <connections>
                         <outletCollection property="gestureRecognizers" destination="g0m-kn-yMe" appends="YES" id="J0N-dX-A4b"/>
                     </connections>
                 </imageView>
                 <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="您即将进行的考试" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="y0E-93-7ac">
-                    <rect key="frame" x="32" y="464.5" width="114.5" height="20"/>
+                    <rect key="frame" x="32" y="454.5" width="114.5" height="20"/>
                     <constraints>
                         <constraint firstAttribute="height" constant="20" id="eD7-7U-5gB"/>
                     </constraints>
@@ -97,7 +100,7 @@
                     <nil key="highlightedColor"/>
                 </label>
                 <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="考试记录" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="g1C-Ty-W4n">
-                    <rect key="frame" x="32" y="432.5" width="74" height="25"/>
+                    <rect key="frame" x="32" y="422.5" width="74" height="25"/>
                     <constraints>
                         <constraint firstAttribute="height" constant="25" id="tTe-yW-e14"/>
                     </constraints>
@@ -106,10 +109,10 @@
                     <nil key="highlightedColor"/>
                 </label>
                 <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="record_detail" translatesAutoresizingMaskIntoConstraints="NO" id="lSQ-oa-URI">
-                    <rect key="frame" x="151.5" y="465.5" width="14" height="18"/>
+                    <rect key="frame" x="151.5" y="455.5" width="14" height="18"/>
                 </imageView>
                 <imageView clipsSubviews="YES" tag="1004" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="exam_record" translatesAutoresizingMaskIntoConstraints="NO" id="6jn-Ca-srp">
-                    <rect key="frame" x="12" y="532.5" width="390" height="120"/>
+                    <rect key="frame" x="12" y="522.5" width="392" height="121"/>
                     <gestureRecognizers/>
                     <constraints>
                         <constraint firstAttribute="width" secondItem="6jn-Ca-srp" secondAttribute="height" multiplier="13:4" id="We3-pT-D82"/>
@@ -119,7 +122,7 @@
                     </connections>
                 </imageView>
                 <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="进入考级曲库" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Gd1-H6-mZc">
-                    <rect key="frame" x="32" y="599.5" width="86" height="20"/>
+                    <rect key="frame" x="32" y="590" width="86" height="20"/>
                     <constraints>
                         <constraint firstAttribute="height" constant="20" id="oVm-Ho-adW"/>
                     </constraints>
@@ -128,7 +131,7 @@
                     <nil key="highlightedColor"/>
                 </label>
                 <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="考级曲库" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ylI-OI-Itq">
-                    <rect key="frame" x="32" y="567.5" width="74" height="25"/>
+                    <rect key="frame" x="32" y="558" width="74" height="25"/>
                     <constraints>
                         <constraint firstAttribute="height" constant="25" id="ClN-i1-S5V"/>
                     </constraints>
@@ -137,10 +140,10 @@
                     <nil key="highlightedColor"/>
                 </label>
                 <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="record_detail" translatesAutoresizingMaskIntoConstraints="NO" id="3du-vq-z5T">
-                    <rect key="frame" x="123" y="600.5" width="14" height="18"/>
+                    <rect key="frame" x="123" y="591" width="14" height="18"/>
                 </imageView>
                 <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="您的报考记录" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="aDg-7e-SV3">
-                    <rect key="frame" x="32" y="329.5" width="86" height="20"/>
+                    <rect key="frame" x="32" y="319" width="86" height="20"/>
                     <constraints>
                         <constraint firstAttribute="height" constant="20" id="LRW-oG-3IF"/>
                     </constraints>
@@ -149,17 +152,17 @@
                     <nil key="highlightedColor"/>
                 </label>
                 <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="record_detail" translatesAutoresizingMaskIntoConstraints="NO" id="J1M-Gy-55Y">
-                    <rect key="frame" x="123" y="330.5" width="14" height="18"/>
+                    <rect key="frame" x="123" y="320" width="14" height="18"/>
                 </imageView>
             </subviews>
             <color key="backgroundColor" red="0.95294117647058818" green="0.95686274509803915" blue="0.97254901960784312" alpha="1" colorSpace="calibratedRGB"/>
             <constraints>
                 <constraint firstItem="FTX-tf-JYt" firstAttribute="top" secondItem="deZ-CC-Tlw" secondAttribute="bottom" constant="15" id="0OE-L8-r85"/>
                 <constraint firstItem="y0E-93-7ac" firstAttribute="leading" secondItem="g1C-Ty-W4n" secondAttribute="leading" id="1hr-61-kkQ"/>
-                <constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="FTX-tf-JYt" secondAttribute="trailing" constant="12" id="2ha-2A-PGC"/>
-                <constraint firstItem="deZ-CC-Tlw" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" constant="16" id="3Kr-dw-VIu"/>
+                <constraint firstAttribute="trailing" secondItem="FTX-tf-JYt" secondAttribute="trailing" constant="10" id="2ha-2A-PGC"/>
+                <constraint firstItem="deZ-CC-Tlw" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="16" id="3Kr-dw-VIu"/>
                 <constraint firstItem="3du-vq-z5T" firstAttribute="leading" secondItem="Gd1-H6-mZc" secondAttribute="trailing" constant="5" id="4kX-qt-wMo"/>
-                <constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="UE7-CX-F3s" secondAttribute="trailing" id="7Bf-Td-8s6"/>
+                <constraint firstAttribute="trailing" secondItem="UE7-CX-F3s" secondAttribute="trailing" id="7Bf-Td-8s6"/>
                 <constraint firstItem="X8m-zH-diL" firstAttribute="width" secondItem="X8m-zH-diL" secondAttribute="height" multiplier="13:4" id="7Ow-SZ-xH1"/>
                 <constraint firstItem="X8m-zH-diL" firstAttribute="leading" secondItem="FTX-tf-JYt" secondAttribute="leading" id="8IJ-cL-MHB"/>
                 <constraint firstItem="lSQ-oa-URI" firstAttribute="centerY" secondItem="y0E-93-7ac" secondAttribute="centerY" id="9z9-g0-6Xn"/>
@@ -169,35 +172,35 @@
                 <constraint firstItem="g1C-Ty-W4n" firstAttribute="bottom" secondItem="X8m-zH-diL" secondAttribute="centerY" id="FYP-mc-9EX"/>
                 <constraint firstItem="6jn-Ca-srp" firstAttribute="leading" secondItem="FTX-tf-JYt" secondAttribute="leading" id="IZb-ZI-5x6"/>
                 <constraint firstItem="J1M-Gy-55Y" firstAttribute="leading" secondItem="aDg-7e-SV3" secondAttribute="trailing" constant="5" id="KkW-2E-C2m"/>
-                <constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="deZ-CC-Tlw" secondAttribute="trailing" constant="16" id="Nek-Xb-k8j"/>
+                <constraint firstAttribute="trailing" secondItem="deZ-CC-Tlw" secondAttribute="trailing" constant="16" id="Nek-Xb-k8j"/>
                 <constraint firstItem="g1C-Ty-W4n" firstAttribute="leading" secondItem="X8m-zH-diL" secondAttribute="leading" constant="20" id="Se2-In-qdn"/>
                 <constraint firstItem="Gd1-H6-mZc" firstAttribute="top" secondItem="ylI-OI-Itq" secondAttribute="bottom" constant="7" id="TEV-is-ZS3"/>
                 <constraint firstItem="71n-fC-CqF" firstAttribute="leading" secondItem="FTX-tf-JYt" secondAttribute="leading" constant="20" id="VpC-SF-2LZ"/>
                 <constraint firstItem="3du-vq-z5T" firstAttribute="centerY" secondItem="Gd1-H6-mZc" secondAttribute="centerY" id="WOA-Fd-oVt"/>
-                <constraint firstItem="qJ1-Pm-qsl" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" constant="16" id="XWG-Hi-LXu"/>
+                <constraint firstItem="qJ1-Pm-qsl" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="16" id="XWG-Hi-LXu"/>
                 <constraint firstItem="6jn-Ca-srp" firstAttribute="top" secondItem="X8m-zH-diL" secondAttribute="bottom" constant="15" id="Xdu-ZS-WnO"/>
-                <constraint firstItem="deZ-CC-Tlw" firstAttribute="centerY" secondItem="UE7-CX-F3s" secondAttribute="bottom" id="aJJ-pq-Ad0"/>
                 <constraint firstItem="lSQ-oa-URI" firstAttribute="leading" secondItem="y0E-93-7ac" secondAttribute="trailing" constant="5" id="duC-ZB-gBe"/>
                 <constraint firstItem="X8m-zH-diL" firstAttribute="trailing" secondItem="FTX-tf-JYt" secondAttribute="trailing" id="eLs-5L-Yai"/>
                 <constraint firstItem="ylI-OI-Itq" firstAttribute="leading" secondItem="6jn-Ca-srp" secondAttribute="leading" constant="20" id="gmk-5l-Qbf"/>
-                <constraint firstItem="UE7-CX-F3s" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" id="hKP-bb-VyG"/>
-                <constraint firstItem="deZ-CC-Tlw" firstAttribute="top" secondItem="pm2-TK-yzh" secondAttribute="bottom" constant="15" id="iLw-WW-Lf8"/>
+                <constraint firstItem="UE7-CX-F3s" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" id="hKP-bb-VyG"/>
+                <constraint firstItem="deZ-CC-Tlw" firstAttribute="top" secondItem="pm2-TK-yzh" secondAttribute="bottom" constant="20" id="iLw-WW-Lf8"/>
                 <constraint firstItem="J1M-Gy-55Y" firstAttribute="centerY" secondItem="aDg-7e-SV3" secondAttribute="centerY" id="oiT-pM-V6J"/>
-                <constraint firstItem="FTX-tf-JYt" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" constant="12" id="ond-pM-vCq"/>
+                <constraint firstItem="FTX-tf-JYt" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="12" id="ond-pM-vCq"/>
                 <constraint firstItem="aDg-7e-SV3" firstAttribute="top" secondItem="71n-fC-CqF" secondAttribute="bottom" constant="7" id="sZM-2r-jhV"/>
                 <constraint firstItem="y0E-93-7ac" firstAttribute="top" secondItem="g1C-Ty-W4n" secondAttribute="bottom" constant="7" id="tGA-jG-Bm1"/>
                 <constraint firstItem="71n-fC-CqF" firstAttribute="bottom" secondItem="FTX-tf-JYt" secondAttribute="centerY" id="upB-i4-JqE"/>
                 <constraint firstItem="Gd1-H6-mZc" firstAttribute="leading" secondItem="ylI-OI-Itq" secondAttribute="leading" id="wQf-uF-w9i"/>
-                <constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="pm2-TK-yzh" secondAttribute="trailing" constant="10" id="wWc-Ul-kmu"/>
+                <constraint firstAttribute="trailing" secondItem="pm2-TK-yzh" secondAttribute="trailing" constant="10" id="wWc-Ul-kmu"/>
                 <constraint firstItem="ylI-OI-Itq" firstAttribute="bottom" secondItem="6jn-Ca-srp" secondAttribute="centerY" id="xHE-4u-yL6"/>
                 <constraint firstItem="aDg-7e-SV3" firstAttribute="leading" secondItem="71n-fC-CqF" secondAttribute="leading" id="xNP-fv-2MS"/>
+                <constraint firstItem="qJ1-Pm-qsl" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" constant="34" id="z7t-8v-nwm"/>
                 <constraint firstItem="X8m-zH-diL" firstAttribute="top" secondItem="FTX-tf-JYt" secondAttribute="bottom" constant="15" id="zqL-CI-vbV"/>
             </constraints>
-            <viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
             <connections>
-                <outlet property="bgHeight" destination="tdc-1f-NK3" id="qls-C6-BA3"/>
+                <outlet property="bgHeight" destination="2Ow-0T-1Ld" id="wrY-ii-pAl"/>
                 <outlet property="noticeButton" destination="pm2-TK-yzh" id="D24-BB-F6h"/>
                 <outlet property="scollBgView" destination="deZ-CC-Tlw" id="3il-Dh-Bet"/>
+                <outlet property="topMargin" destination="z7t-8v-nwm" id="tlh-RE-qoY"/>
             </connections>
             <point key="canvasLocation" x="131.8840579710145" y="105.80357142857143"/>
         </view>
@@ -220,6 +223,7 @@
     <resources>
         <image name="book_record" width="351" height="120"/>
         <image name="exam_record" width="351" height="120"/>
+        <image name="home_top" width="375" height="158"/>
         <image name="notice_unread" width="21" height="22"/>
         <image name="record_detail" width="14" height="18"/>
     </resources>

+ 20 - 0
MusicGradeExam/MusicGradeExam/UI/Home/View/HomeExamTicketCell.h

@@ -0,0 +1,20 @@
+//
+//  HomeExamTicketCell.h
+//  MusicGradeExam
+//
+//  Created by Kyle on 2020/7/16.
+//  Copyright © 2020 DayaMusic. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+#import "TicketListModel.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface HomeExamTicketCell : UICollectionViewCell
+
+- (void)configCellWithSource:(TicketListModel *)model;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 92 - 0
MusicGradeExam/MusicGradeExam/UI/Home/View/HomeExamTicketCell.m

@@ -0,0 +1,92 @@
+//
+//  HomeExamTicketCell.m
+//  MusicGradeExam
+//
+//  Created by Kyle on 2020/7/16.
+//  Copyright © 2020 DayaMusic. All rights reserved.
+//
+
+#import "HomeExamTicketCell.h"
+
+@interface HomeExamTicketCell ()
+
+@property (weak, nonatomic) IBOutlet UILabel *examSubject;
+
+@property (weak, nonatomic) IBOutlet UILabel *classDate;
+
+@property (weak, nonatomic) IBOutlet UILabel *countLabel;
+
+@end
+
+@implementation HomeExamTicketCell
+
+- (void)awakeFromNib {
+    [super awakeFromNib];
+    // Initialization code
+}
+
+- (void)configCellWithSource:(TicketListModel *)model {
+    self.examSubject.text = [self getSubjectName:model.subjectName level:model.level];
+    if (![NSString isEmptyString:model.examTime]) {
+        self.classDate.text = [[model.examTime componentsSeparatedByString:@" "] firstObject];
+    }
+    else {
+        self.classDate.text = @"";
+    }
+    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
+    [formatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
+    formatter.timeZone = [NSTimeZone systemTimeZone];
+    NSDate *beginDate = [formatter dateFromString:model.examTime];
+    [formatter setDateFormat:@"yyyy-MM-dd"];
+    NSDate *currentDate = [NSDate date];
+    NSString *currentDay = [formatter stringFromDate:currentDate];
+    currentDate = [formatter dateFromString:currentDay];
+    NSString *beginDay = [formatter stringFromDate:beginDate];
+    NSDate *courseDate = [formatter dateFromString:beginDay];
+    
+    NSTimeInterval timeInterval = [courseDate timeIntervalSinceDate:beginDate];
+    NSInteger countDay = timeInterval / 86400 > 0 ? timeInterval / 86400 : 0;
+    self.countLabel.text = [NSString stringWithFormat:@"倒计时 %zd天", countDay];
+}
+
+- (NSString *)getSubjectName:(NSString *)name level:(NSInteger)level {
+    NSString *levelStr = nil;
+    switch (level) {
+        case 1:
+            levelStr = @"壹级";
+            break;
+        case 2:
+            levelStr = @"贰级";
+            break;
+        case 3:
+            levelStr = @"叁级";
+            break;
+        case 4:
+            levelStr = @"肆级";
+            break;
+        case 5:
+            levelStr = @"伍级";
+            break;
+        case 6:
+            levelStr = @"陆级";
+            break;
+        case 7:
+            levelStr = @"柒级";
+            break;
+        case 8:
+            levelStr = @"捌级";
+            break;
+        case 9:
+            levelStr = @"玖级";
+            break;
+        case 10:
+            levelStr = @"拾级";
+            break;
+        default:
+            levelStr = @"";
+            break;
+    }
+    return [NSString stringWithFormat:@"%@(%@)", name, levelStr];
+}
+
+@end

+ 120 - 0
MusicGradeExam/MusicGradeExam/UI/Home/View/HomeExamTicketCell.xib

@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="16097" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
+    <device id="retina6_1" orientation="portrait" appearance="light"/>
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
+        <capability name="Safe area layout guides" minToolsVersion="9.0"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </dependencies>
+    <objects>
+        <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
+        <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
+        <collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" id="gTV-IL-0wX" customClass="HomeExamTicketCell">
+            <rect key="frame" x="0.0" y="0.0" width="375" height="146"/>
+            <autoresizingMask key="autoresizingMask"/>
+            <view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
+                <rect key="frame" x="0.0" y="0.0" width="375" height="146"/>
+                <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+                <subviews>
+                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="最近考试" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="qvJ-vA-iTg">
+                        <rect key="frame" x="15" y="17" width="85.5" height="30"/>
+                        <constraints>
+                            <constraint firstAttribute="height" constant="30" id="lNh-Kp-LAR"/>
+                        </constraints>
+                        <fontDescription key="fontDescription" type="system" weight="medium" pointSize="21"/>
+                        <color key="textColor" red="0.1019607843" green="0.1019607843" blue="0.1019607843" alpha="1" colorSpace="calibratedRGB"/>
+                        <nil key="highlightedColor"/>
+                    </label>
+                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="专业等级:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="HFT-5Y-vVV">
+                        <rect key="frame" x="15" y="62" width="85" height="22"/>
+                        <constraints>
+                            <constraint firstAttribute="width" constant="85" id="Tol-vC-jDv"/>
+                            <constraint firstAttribute="height" constant="22" id="zlc-T4-DZF"/>
+                        </constraints>
+                        <fontDescription key="fontDescription" type="system" pointSize="16"/>
+                        <color key="textColor" red="0.59999999999999998" green="0.59999999999999998" blue="0.59999999999999998" alpha="1" colorSpace="calibratedRGB"/>
+                        <nil key="highlightedColor"/>
+                    </label>
+                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="考试时间:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="avN-QY-0J4">
+                        <rect key="frame" x="15" y="99" width="85" height="22"/>
+                        <constraints>
+                            <constraint firstAttribute="height" constant="22" id="qfB-ah-sVB"/>
+                            <constraint firstAttribute="width" constant="85" id="wQW-LW-B7q"/>
+                        </constraints>
+                        <fontDescription key="fontDescription" type="system" pointSize="16"/>
+                        <color key="textColor" red="0.59999999999999998" green="0.59999999999999998" blue="0.59999999999999998" alpha="1" colorSpace="calibratedRGB"/>
+                        <nil key="highlightedColor"/>
+                    </label>
+                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="钢琴(一级)" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="SBL-f7-w01">
+                        <rect key="frame" x="100" y="63" width="98" height="20"/>
+                        <fontDescription key="fontDescription" type="system" weight="medium" pointSize="16"/>
+                        <color key="textColor" red="0.1019607843" green="0.1019607843" blue="0.1019607843" alpha="1" colorSpace="calibratedRGB"/>
+                        <nil key="highlightedColor"/>
+                    </label>
+                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="2020-05-20" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Qoe-n1-s6Q">
+                        <rect key="frame" x="100" y="100" width="100" height="20"/>
+                        <constraints>
+                            <constraint firstAttribute="width" constant="100" id="fDI-bE-Rfk"/>
+                        </constraints>
+                        <fontDescription key="fontDescription" type="system" weight="medium" pointSize="16"/>
+                        <color key="textColor" red="0.1764705882" green="0.78039215689999997" blue="0.66666666669999997" alpha="1" colorSpace="calibratedRGB"/>
+                        <nil key="highlightedColor"/>
+                    </label>
+                    <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Ek5-fn-qTb">
+                        <rect key="frame" x="210" y="97" width="127" height="26"/>
+                        <subviews>
+                            <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="count_bg" translatesAutoresizingMaskIntoConstraints="NO" id="FZq-yB-D7G">
+                                <rect key="frame" x="0.0" y="0.0" width="127" height="26"/>
+                            </imageView>
+                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="倒计时 10天" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="cKe-X3-WHL">
+                                <rect key="frame" x="25" y="3.5" width="87" height="19.5"/>
+                                <fontDescription key="fontDescription" type="system" weight="medium" pointSize="16"/>
+                                <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                                <nil key="highlightedColor"/>
+                            </label>
+                        </subviews>
+                        <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
+                        <constraints>
+                            <constraint firstAttribute="width" relation="greaterThanOrEqual" constant="108" id="FNA-kM-50k"/>
+                            <constraint firstAttribute="height" constant="26" id="JsG-Ng-EHa"/>
+                            <constraint firstItem="cKe-X3-WHL" firstAttribute="leading" secondItem="Ek5-fn-qTb" secondAttribute="leading" constant="25" id="LX8-i9-beK"/>
+                            <constraint firstAttribute="trailing" secondItem="cKe-X3-WHL" secondAttribute="trailing" constant="15" id="MEC-ga-6Mw"/>
+                            <constraint firstAttribute="trailing" secondItem="FZq-yB-D7G" secondAttribute="trailing" id="WoF-sp-LAo"/>
+                            <constraint firstAttribute="bottom" secondItem="FZq-yB-D7G" secondAttribute="bottom" id="Ypb-wb-xiC"/>
+                            <constraint firstItem="FZq-yB-D7G" firstAttribute="leading" secondItem="Ek5-fn-qTb" secondAttribute="leading" id="doa-bj-dtb"/>
+                            <constraint firstItem="FZq-yB-D7G" firstAttribute="top" secondItem="Ek5-fn-qTb" secondAttribute="top" id="stl-Wb-9Pd"/>
+                            <constraint firstItem="cKe-X3-WHL" firstAttribute="centerY" secondItem="Ek5-fn-qTb" secondAttribute="centerY" id="xfk-IC-kmk"/>
+                        </constraints>
+                    </view>
+                </subviews>
+            </view>
+            <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+            <constraints>
+                <constraint firstItem="avN-QY-0J4" firstAttribute="top" secondItem="HFT-5Y-vVV" secondAttribute="bottom" constant="15" id="8h1-h2-ZMD"/>
+                <constraint firstItem="Ek5-fn-qTb" firstAttribute="centerY" secondItem="Qoe-n1-s6Q" secondAttribute="centerY" id="I6W-JO-UyJ"/>
+                <constraint firstItem="Qoe-n1-s6Q" firstAttribute="leading" secondItem="avN-QY-0J4" secondAttribute="trailing" id="Job-Ql-6Dd"/>
+                <constraint firstItem="avN-QY-0J4" firstAttribute="leading" secondItem="qvJ-vA-iTg" secondAttribute="leading" id="K1E-4H-cau"/>
+                <constraint firstItem="HFT-5Y-vVV" firstAttribute="leading" secondItem="qvJ-vA-iTg" secondAttribute="leading" id="PUl-Li-WeQ"/>
+                <constraint firstItem="qvJ-vA-iTg" firstAttribute="leading" secondItem="gTV-IL-0wX" secondAttribute="leading" constant="15" id="UEI-de-GBu"/>
+                <constraint firstItem="Qoe-n1-s6Q" firstAttribute="centerY" secondItem="avN-QY-0J4" secondAttribute="centerY" id="d9v-Ja-nbe"/>
+                <constraint firstItem="Ek5-fn-qTb" firstAttribute="leading" secondItem="Qoe-n1-s6Q" secondAttribute="trailing" constant="10" id="eDM-yl-H2a"/>
+                <constraint firstItem="SBL-f7-w01" firstAttribute="centerY" secondItem="HFT-5Y-vVV" secondAttribute="centerY" id="gYK-tb-wwy"/>
+                <constraint firstItem="HFT-5Y-vVV" firstAttribute="top" secondItem="qvJ-vA-iTg" secondAttribute="bottom" constant="15" id="iqb-2J-Vm7"/>
+                <constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="Ek5-fn-qTb" secondAttribute="trailing" constant="10" id="nm6-10-AIU"/>
+                <constraint firstItem="SBL-f7-w01" firstAttribute="leading" secondItem="HFT-5Y-vVV" secondAttribute="trailing" id="xwi-iW-PjM"/>
+                <constraint firstItem="HFT-5Y-vVV" firstAttribute="centerY" secondItem="gTV-IL-0wX" secondAttribute="centerY" id="zvR-yl-M0v"/>
+            </constraints>
+            <viewLayoutGuide key="safeArea" id="SEy-5g-ep8"/>
+            <connections>
+                <outlet property="classDate" destination="Qoe-n1-s6Q" id="pJb-Vj-mKT"/>
+                <outlet property="countLabel" destination="cKe-X3-WHL" id="XQh-33-job"/>
+                <outlet property="examSubject" destination="SBL-f7-w01" id="ohU-Wn-f8W"/>
+            </connections>
+            <point key="canvasLocation" x="131.15942028985509" y="108.48214285714285"/>
+        </collectionViewCell>
+    </objects>
+    <resources>
+        <image name="count_bg" width="105.5" height="25"/>
+    </resources>
+</document>

+ 1 - 1
MusicGradeExam/MusicGradeExam/UI/Login/Model/UserInfoManager.m

@@ -85,7 +85,7 @@
                 needConnect = YES;
             }
             UserDefaultSet(self.userInfo.imToken, RongTokenKey);
-            UserDefaultSet(self.userInfo.username, NicknameKey);
+            UserDefaultSet(self.userInfo.realName, NicknameKey);
             UserDefaultSet(self.userInfo.avatar, AvatarUrlKey);
             [[NSUserDefaults standardUserDefaults] synchronize];
             

+ 19 - 0
MusicGradeExam/MusicGradeExam/UI/NotiferMessage/Controller/NotifyMessageViewController.m

@@ -70,6 +70,25 @@
 
 - (void)configUI {
     [self.view addSubview:self.tableView];
+    [self setPromptString:@"无内容" imageName:@"wd_img_zwsj" inView:self.tableView];
+    MJWeakSelf;
+    self.tableView.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{
+        [weakSelf resetParamenter];
+        [weakSelf requestData];
+    }];
+    self.tableView.mj_footer = [MJRefreshBackNormalFooter footerWithRefreshingBlock:^{
+        if (weakSelf.isLoadMore) {
+            weakSelf.pages += 1;
+            [weakSelf requestData];
+        }
+        else {
+            [weakSelf.tableView.mj_footer endRefreshingWithNoMoreData];
+        }
+    }];
+}
+
+- (void)resetParamenter {
+    self.isLoadMore = YES;
     self.pages = 1;
     self.rows = 10;
     self.dataArray = [NSMutableArray array];

+ 8 - 1
MusicGradeExam/MusicGradeExam/UI/UserCenter/Controller/Mine/Controller/UserViewController.m

@@ -10,6 +10,7 @@
 #import "UserBodyView.h"
 #import "KSMediaManager.h"
 #import "UserInfoManager.h"
+#import <RongIMKit/RongIMKit.h>
 
 @interface UserViewController ()
 
@@ -152,6 +153,7 @@
 
 - (NSString *)formatBirthDayString:(NSString *)birthday {
     NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
+    dateFormatter.timeZone = [NSTimeZone systemTimeZone];
     [dateFormatter setDateFormat:@"yyyyMMdd"];
     NSDate *date = [dateFormatter dateFromString:birthday];
     [dateFormatter setDateFormat:@"yyyy-MM-dd"];
@@ -165,13 +167,18 @@
             UserInfo *info = USER_MANAGER.userInfo;
             
             if (![NSString isEmptyString:avatar]) {
-                info.avatar = avatar;
+                info.certificatePhoto = avatar;
             }
             else {
                 info.realName = realName;
                 info.idCardNo = idCardNo;
                 info.nation = nation;
                 info.birthdate = [[dic dictionaryValueForKey:@"data"] stringValueForKey:@"birthdate"];
+                UserDefaultSet(realName, NicknameKey);
+                // 设置个人信息
+                RCUserInfo *currentUserInfo =
+                [[RCUserInfo alloc] initWithUserId:UserDefault(UIDKey) name:UserDefault(NicknameKey) portrait:UserDefault(AvatarUrlKey)];
+                [RCIM sharedRCIM].currentUserInfo = currentUserInfo;
             }
             [self.bodyView refreshView];
         }

+ 2 - 2
MusicGradeExam/MusicGradeExam/UI/UserCenter/Controller/Mine/View/UserBodyView.m

@@ -70,11 +70,11 @@
     self.birthdayLabel.text = [NSString returnNoNullStringWithString:[[info.birthdate componentsSeparatedByString:@" "] firstObject]];
     self.nationLabel.text = [NSString returnNoNullStringWithString:info.nation];
     
-    if ([NSString isEmptyString:info.avatar]) {
+    if ([NSString isEmptyString:info.certificatePhoto]) {
         [self.userImage setImage:[UIImage imageNamed:@"image_upload"]];
     }
     else {
-        [self.userImage sd_setImageWithURL:[NSURL URLWithString:info.avatar] placeholderImage:[UIImage imageNamed:@"image_upload"]];
+        [self.userImage sd_setImageWithURL:[NSURL URLWithString:info.certificatePhoto] placeholderImage:[UIImage imageNamed:@"image_upload"]];
     }
 }
 

+ 2 - 2
MusicGradeExam/MusicGradeExam/UI/UserCenter/View/UserCenterBodyView.m

@@ -25,11 +25,11 @@
 - (void)configMessage {
     UserInfo *info = USER_MANAGER.userInfo;
     self.userName.text = [NSString returnNoNullStringWithString:info.realName];
-    if ([NSString isEmptyString:info.avatar]) {
+    if ([NSString isEmptyString:info.certificatePhoto]) {
         [self.userAvatar setImage:[UIImage imageNamed:USER_LOGO]];
     }
     else {
-        [self.userAvatar sd_setImageWithURL:[NSURL URLWithString:info.avatar] placeholderImage:[UIImage imageNamed:USER_LOGO]];
+        [self.userAvatar sd_setImageWithURL:[NSURL URLWithString:info.certificatePhoto] placeholderImage:[UIImage imageNamed:USER_LOGO]];
     }
     if ([NSString isEmptyString:info.birthdate]) {
         self.birthday.text = @"";