Browse Source

VIP定制课交互结构

Steven 8 months ago
parent
commit
157bf657d1
14 changed files with 1336 additions and 5 deletions
  1. 66 0
      KulexiuForTeacher/KulexiuForTeacher.xcodeproj/project.pbxproj
  2. 1 1
      KulexiuForTeacher/KulexiuForTeacher.xcodeproj/xcshareddata/xcschemes/KulexiuForTeacher.xcscheme
  3. 18 0
      KulexiuForTeacher/KulexiuForTeacher/Module/Course/AccompanyCourse/Controller/AccompanyCourseGroupViewController.h
  4. 125 0
      KulexiuForTeacher/KulexiuForTeacher/Module/Course/AccompanyCourse/Controller/AccompanyCourseGroupViewController.m
  5. 18 0
      KulexiuForTeacher/KulexiuForTeacher/Module/Course/MusicRoom/Controller/MusicRoomGroupViewController.h
  6. 124 0
      KulexiuForTeacher/KulexiuForTeacher/Module/Course/MusicRoom/Controller/MusicRoomGroupViewController.m
  7. 4 4
      KulexiuForTeacher/KulexiuForTeacher/Module/Course/MusicRoom/View/MusicRoomStudentCell.xib
  8. 19 0
      KulexiuForTeacher/KulexiuForTeacher/Module/Course/VIPCourse/Controller/VIPCourseGroupViewController.h
  9. 126 0
      KulexiuForTeacher/KulexiuForTeacher/Module/Course/VIPCourse/Controller/VIPCourseGroupViewController.m
  10. 25 0
      KulexiuForTeacher/KulexiuForTeacher/Module/Course/VIPCourse/Controller/VipCouseDetailViewController.h
  11. 523 0
      KulexiuForTeacher/KulexiuForTeacher/Module/Course/VIPCourse/Controller/VipCouseDetailViewController.m
  12. 27 0
      KulexiuForTeacher/KulexiuForTeacher/Module/Course/VIPCourse/View/VipCouseInfoCell.h
  13. 90 0
      KulexiuForTeacher/KulexiuForTeacher/Module/Course/VIPCourse/View/VipCouseInfoCell.m
  14. 170 0
      KulexiuForTeacher/KulexiuForTeacher/Module/Course/VIPCourse/View/VipCouseInfoCell.xib

+ 66 - 0
KulexiuForTeacher/KulexiuForTeacher.xcodeproj/project.pbxproj

@@ -1109,6 +1109,12 @@
 		BCF7EB352C856C53006AA38C /* ScanFailBodyView.m in Sources */ = {isa = PBXBuildFile; fileRef = BCF7EB2F2C856C52006AA38C /* ScanFailBodyView.m */; };
 		BCF7EB362C856C53006AA38C /* ScanOpenAlert.xib in Resources */ = {isa = PBXBuildFile; fileRef = BCF7EB302C856C53006AA38C /* ScanOpenAlert.xib */; };
 		BCF7EB372C856C53006AA38C /* ScanLoginBodyView.xib in Resources */ = {isa = PBXBuildFile; fileRef = BCF7EB312C856C53006AA38C /* ScanLoginBodyView.xib */; };
+		BCFB9B892CE4883100B66BC0 /* VipCouseDetailViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = BCFB9B882CE4883100B66BC0 /* VipCouseDetailViewController.m */; };
+		BCFB9B8D2CE48D3A00B66BC0 /* VipCouseInfoCell.m in Sources */ = {isa = PBXBuildFile; fileRef = BCFB9B8B2CE48D3A00B66BC0 /* VipCouseInfoCell.m */; };
+		BCFB9B8E2CE48D3A00B66BC0 /* VipCouseInfoCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = BCFB9B8C2CE48D3A00B66BC0 /* VipCouseInfoCell.xib */; };
+		BCFB9B912CE48F8400B66BC0 /* VIPCourseGroupViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = BCFB9B902CE48F8400B66BC0 /* VIPCourseGroupViewController.m */; };
+		BCFB9B942CE48FDB00B66BC0 /* AccompanyCourseGroupViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = BCFB9B932CE48FDB00B66BC0 /* AccompanyCourseGroupViewController.m */; };
+		BCFB9B972CE4903800B66BC0 /* MusicRoomGroupViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = BCFB9B962CE4903800B66BC0 /* MusicRoomGroupViewController.m */; };
 		BCFC09DD2C48E4A0009A727F /* UserVip.m in Sources */ = {isa = PBXBuildFile; fileRef = BCFC09DC2C48E4A0009A727F /* UserVip.m */; };
 		BCFE540928168DFF00AD6786 /* KSButtonStatusView.m in Sources */ = {isa = PBXBuildFile; fileRef = BCFE540828168DFF00AD6786 /* KSButtonStatusView.m */; };
 		BCFE541028178FF600AD6786 /* MyIncomeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = BCFE540F28178FF600AD6786 /* MyIncomeViewController.m */; };
@@ -2960,6 +2966,17 @@
 		BCF880EC2B91C7200007B8F0 /* Config-test.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Config-test.xcconfig"; sourceTree = "<group>"; };
 		BCF880EE2B91C7310007B8F0 /* Config-debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Config-debug.xcconfig"; sourceTree = "<group>"; };
 		BCF880F12B91C7580007B8F0 /* Config-release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Config-release.xcconfig"; sourceTree = "<group>"; };
+		BCFB9B872CE4883100B66BC0 /* VipCouseDetailViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VipCouseDetailViewController.h; sourceTree = "<group>"; };
+		BCFB9B882CE4883100B66BC0 /* VipCouseDetailViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = VipCouseDetailViewController.m; sourceTree = "<group>"; };
+		BCFB9B8A2CE48D3A00B66BC0 /* VipCouseInfoCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VipCouseInfoCell.h; sourceTree = "<group>"; };
+		BCFB9B8B2CE48D3A00B66BC0 /* VipCouseInfoCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = VipCouseInfoCell.m; sourceTree = "<group>"; };
+		BCFB9B8C2CE48D3A00B66BC0 /* VipCouseInfoCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = VipCouseInfoCell.xib; sourceTree = "<group>"; };
+		BCFB9B8F2CE48F8400B66BC0 /* VIPCourseGroupViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VIPCourseGroupViewController.h; sourceTree = "<group>"; };
+		BCFB9B902CE48F8400B66BC0 /* VIPCourseGroupViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = VIPCourseGroupViewController.m; sourceTree = "<group>"; };
+		BCFB9B922CE48FDB00B66BC0 /* AccompanyCourseGroupViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AccompanyCourseGroupViewController.h; sourceTree = "<group>"; };
+		BCFB9B932CE48FDB00B66BC0 /* AccompanyCourseGroupViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AccompanyCourseGroupViewController.m; sourceTree = "<group>"; };
+		BCFB9B952CE4903800B66BC0 /* MusicRoomGroupViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MusicRoomGroupViewController.h; sourceTree = "<group>"; };
+		BCFB9B962CE4903800B66BC0 /* MusicRoomGroupViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MusicRoomGroupViewController.m; sourceTree = "<group>"; };
 		BCFC09DB2C48E49F009A727F /* UserVip.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UserVip.h; sourceTree = "<group>"; };
 		BCFC09DC2C48E4A0009A727F /* UserVip.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UserVip.m; sourceTree = "<group>"; };
 		BCFE540728168DFF00AD6786 /* KSButtonStatusView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KSButtonStatusView.h; sourceTree = "<group>"; };
@@ -3902,6 +3919,7 @@
 		2779360627E32BBF0010E277 /* Course */ = {
 			isa = PBXGroup;
 			children = (
+				BCFB9B862CE487EA00B66BC0 /* VIPCourse */,
 				BC8B6E7E2858879300866917 /* MusicRoom */,
 				BCA9CE2827FD89CE00D558C6 /* AccompanyCourse */,
 				2779360727E32BBF0010E277 /* Controller */,
@@ -6409,6 +6427,8 @@
 		BC8B6E7F2858879300866917 /* Controller */ = {
 			isa = PBXGroup;
 			children = (
+				BCFB9B952CE4903800B66BC0 /* MusicRoomGroupViewController.h */,
+				BCFB9B962CE4903800B66BC0 /* MusicRoomGroupViewController.m */,
 				BC8B6E82285887A400866917 /* MusicRoomViewController.h */,
 				BC8B6E83285887A400866917 /* MusicRoomViewController.m */,
 			);
@@ -6623,6 +6643,8 @@
 		BCA9CE2927FD89CE00D558C6 /* Controller */ = {
 			isa = PBXGroup;
 			children = (
+				BCFB9B922CE48FDB00B66BC0 /* AccompanyCourseGroupViewController.h */,
+				BCFB9B932CE48FDB00B66BC0 /* AccompanyCourseGroupViewController.m */,
 				BC1191F2280EAB9600A716F7 /* AccompanyDetailViewController.h */,
 				BC1191F1280EAB9600A716F7 /* AccompanyDetailViewController.m */,
 			);
@@ -7226,6 +7248,44 @@
 			path = configuration;
 			sourceTree = "<group>";
 		};
+		BCFB9B832CE487EA00B66BC0 /* Controller */ = {
+			isa = PBXGroup;
+			children = (
+				BCFB9B8F2CE48F8400B66BC0 /* VIPCourseGroupViewController.h */,
+				BCFB9B902CE48F8400B66BC0 /* VIPCourseGroupViewController.m */,
+				BCFB9B872CE4883100B66BC0 /* VipCouseDetailViewController.h */,
+				BCFB9B882CE4883100B66BC0 /* VipCouseDetailViewController.m */,
+			);
+			path = Controller;
+			sourceTree = "<group>";
+		};
+		BCFB9B842CE487EA00B66BC0 /* Model */ = {
+			isa = PBXGroup;
+			children = (
+			);
+			path = Model;
+			sourceTree = "<group>";
+		};
+		BCFB9B852CE487EA00B66BC0 /* View */ = {
+			isa = PBXGroup;
+			children = (
+				BCFB9B8A2CE48D3A00B66BC0 /* VipCouseInfoCell.h */,
+				BCFB9B8B2CE48D3A00B66BC0 /* VipCouseInfoCell.m */,
+				BCFB9B8C2CE48D3A00B66BC0 /* VipCouseInfoCell.xib */,
+			);
+			path = View;
+			sourceTree = "<group>";
+		};
+		BCFB9B862CE487EA00B66BC0 /* VIPCourse */ = {
+			isa = PBXGroup;
+			children = (
+				BCFB9B832CE487EA00B66BC0 /* Controller */,
+				BCFB9B842CE487EA00B66BC0 /* Model */,
+				BCFB9B852CE487EA00B66BC0 /* View */,
+			);
+			path = VIPCourse;
+			sourceTree = "<group>";
+		};
 		BCFE540A28178BD100AD6786 /* Income */ = {
 			isa = PBXGroup;
 			children = (
@@ -7496,6 +7556,7 @@
 				27A54CEE27E9B986007309A3 /* ModifyNameBodyView.xib in Resources */,
 				BCECE2172B3D5F0800C0D555 /* FeedbackSortView.xib in Resources */,
 				BC66739F2BA8421100FC01A5 /* PrivacyInfo.xcprivacy in Resources */,
+				BCFB9B8E2CE48D3A00B66BC0 /* VipCouseInfoCell.xib in Resources */,
 				BC221FA128C72B9500F99802 /* MyStyleVideoView.xib in Resources */,
 				BCC583FA28A9FA8100BAB4CF /* cloud_animation_26.png in Resources */,
 				2708565E27EDA83100EC8E72 /* GroupMemberListCell.xib in Resources */,
@@ -8176,6 +8237,7 @@
 				273C759E27E9680C00F7C26F /* SettingBodyView.m in Sources */,
 				2780C92227E4902800A95A4F /* PasswordBodyView.m in Sources */,
 				BC38C4252AF900E100ABFCC2 /* KSNewAlertView.m in Sources */,
+				BCFB9B912CE48F8400B66BC0 /* VIPCourseGroupViewController.m in Sources */,
 				275E8A6927E18F2300DD3F6E /* AppDelegate.m in Sources */,
 				2779361E27E3338E0010E277 /* KSUpdateManager.m in Sources */,
 				BCC9F42827F69BD200647449 /* WhiteUtils.m in Sources */,
@@ -8199,6 +8261,7 @@
 				BC161CE72AE0FD590071530A /* TenantGroupSortView.m in Sources */,
 				2779321227E30FC30010E277 /* YKNodeModel.m in Sources */,
 				BC31BF8A2B219C5700F7D538 /* KSGaugeColorView.m in Sources */,
+				BCFB9B972CE4903800B66BC0 /* MusicRoomGroupViewController.m in Sources */,
 				27F9CAED27EC17AE003E0FE4 /* ChatAddressBodyView.m in Sources */,
 				BC4110492806706800800BD9 /* HomeworkListCell.m in Sources */,
 				BCA9CE2427FD792600D558C6 /* LiveCourseModel.m in Sources */,
@@ -8323,6 +8386,7 @@
 				BC9AA0C82ABC3C7B00CD954D /* KSPhotoChooseView.m in Sources */,
 				BCDF821F2A8A2F36005F8B82 /* KSSliderView.m in Sources */,
 				BCB9FA6B2872D57E005D766B /* LiveListModel.m in Sources */,
+				BCFB9B8D2CE48D3A00B66BC0 /* VipCouseInfoCell.m in Sources */,
 				BC9AA0D52ABC430B00CD954D /* KSCustomLoadingView.m in Sources */,
 				BC14E4762AB2E6C2000C4983 /* UnbindTenantViewController.m in Sources */,
 				BC14A61228A0AC820086395C /* MineTeachToolView.m in Sources */,
@@ -8462,6 +8526,7 @@
 				BC71DF2B2A8A0432003F165E /* KSWhiteboardRefreshView.m in Sources */,
 				BC9EFEB22C0480A500CFA7B1 /* GroupBanBodyView.m in Sources */,
 				BCD457B2286564DB0010B493 /* LiveRoomAlertView.m in Sources */,
+				BCFB9B892CE4883100B66BC0 /* VipCouseDetailViewController.m in Sources */,
 				BCB399AC27F946A200AFF376 /* CourseNavView.m in Sources */,
 				BCE06F2B2818146700234817 /* KSConfirmAlertView.m in Sources */,
 				275B16FA27EB08230081FDEF /* CreateFansGroupViewController.m in Sources */,
@@ -8537,6 +8602,7 @@
 				BCDE3594289B960100A9A560 /* HomeAlbumView.m in Sources */,
 				BCECE2192B3D5F0800C0D555 /* FeedbackListNavView.m in Sources */,
 				2779362C27E33C2B0010E277 /* LoginBodyView.m in Sources */,
+				BCFB9B942CE48FDB00B66BC0 /* AccompanyCourseGroupViewController.m in Sources */,
 				BC1191F3280EAB9600A716F7 /* AccompanyDetailViewController.m in Sources */,
 				BC31BF8B2B219C5700F7D538 /* KSGaugeSectorView.m in Sources */,
 				BC161CE22AE0DD6A0071530A /* TenantGroupChooseNavView.m in Sources */,

+ 1 - 1
KulexiuForTeacher/KulexiuForTeacher.xcodeproj/xcshareddata/xcschemes/KulexiuForTeacher.xcscheme

@@ -52,7 +52,7 @@
       </Testables>
    </TestAction>
    <LaunchAction
-      buildConfiguration = "Release"
+      buildConfiguration = "TEST"
       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
       launchStyle = "0"

+ 18 - 0
KulexiuForTeacher/KulexiuForTeacher/Module/Course/AccompanyCourse/Controller/AccompanyCourseGroupViewController.h

@@ -0,0 +1,18 @@
+//
+//  AccompanyCourseGroupViewController.h
+//  KulexiuForTeacher
+//
+//  Created by 王智 on 2024/11/13.
+//
+
+#import "KSBaseViewController.h"
+
+NS_ASSUME_NONNULL_BEGIN
+// 趣纠课--课程组
+@interface AccompanyCourseGroupViewController : KSBaseViewController
+
+@property (nonatomic, strong) NSString *courseGroupId; // 课程组ID
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 125 - 0
KulexiuForTeacher/KulexiuForTeacher/Module/Course/AccompanyCourse/Controller/AccompanyCourseGroupViewController.m

@@ -0,0 +1,125 @@
+//
+//  AccompanyCourseGroupViewController.m
+//  KulexiuForTeacher
+//
+//  Created by 王智 on 2024/11/13.
+//
+
+#import "AccompanyCourseGroupViewController.h"
+#import <RecordCheckManager.h>
+#import "KSPremissionAlert.h"
+#import "OnlineClassManager.h"
+#import "HomeworkDetailModel.h"
+
+@interface AccompanyCourseGroupViewController ()
+
+@property (nonatomic, strong) NSString *courseId;
+
+@property (nonatomic, strong) NSString *studentId;
+
+@property (nonatomic, assign) NSInteger joinRoomBeforeTime; // 上课开始时间
+
+@property (nonatomic, assign) NSInteger quitRomeEndTime;    // 下课截止时间
+
+@property (nonatomic, strong) HomeworkDetailModel *homeworkModel;
+
+@property (nonatomic, strong) OnlineClassManager *classManager;
+
+@end
+
+@implementation AccompanyCourseGroupViewController
+
+- (void)viewDidLoad {
+    [super viewDidLoad];
+    // Do any additional setup after loading the view.
+}
+
+
+- (void)tryJoinRoom {
+    
+    NSDateFormatter *dateFormatter = [NSObject getDateformatter];
+    [dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
+    NSDate *beginDate = [dateFormatter dateFromString:self.homeworkModel.startTime];
+    NSDate *endDate = [dateFormatter dateFromString:self.homeworkModel.endTime];
+    NSDate *currentDate = [NSDate date];
+    NSTimeInterval beginTimeInterval = [beginDate timeIntervalSinceDate:currentDate];
+    NSTimeInterval endTimeInterval = [currentDate timeIntervalSinceDate:endDate];
+    if (beginTimeInterval <= self.joinRoomBeforeTime * 60 && endTimeInterval < 0) {
+        [self joinClassRoom];
+    }
+    else if (endTimeInterval > 0) {
+        [LOADING_MANAGER MBShowAUTOHidingInWindow:@"该课程已结束"];
+    }
+    else {
+        NSString *tipsString = [NSString stringWithFormat:@"课程还未开始,请在上课前%zd分钟进入", self.joinRoomBeforeTime];
+        [LOADING_MANAGER MBShowAUTOHidingInWindow:tipsString];
+    }
+}
+
+// 加入房间
+- (void)joinClassRoom {
+    
+    // 加入房间前判断摄像头和麦克风逻辑
+    [RecordCheckManager checkCameraPremissionAvaiableCallback:^(PREMISSIONTYPE type) {
+        [self afterCheckCameraCheckMic:type];
+    }];
+}
+
+- (void)afterCheckCameraCheckMic:(PREMISSIONTYPE)cameraType {
+    [RecordCheckManager checkMicPermissionAvaiableCallback:^(PREMISSIONTYPE type) {
+        if (type == PREMISSIONTYPE_YES && cameraType == PREMISSIONTYPE_YES) {
+            // 判断是否进行课前检测
+            [self.classManager joinRoomWithId:self.courseId subjectName:self.homeworkModel.subjectName classEndTime:self.homeworkModel.endTime inViewController:self];
+        }
+        else {
+            NSString *content = @"";
+            CHECKDEVICETYPE checkType = CHECKDEVICETYPE_BOTH;
+            if (cameraType == PREMISSIONTYPE_NO && type == PREMISSIONTYPE_NO) {
+                content = @"请开启相机和麦克风访问权限";
+                checkType = CHECKDEVICETYPE_BOTH;
+            }
+            else if (cameraType == PREMISSIONTYPE_NO && type == PREMISSIONTYPE_YES) {
+                content =  @"请开启相机访问权限";
+                checkType = CHECKDEVICETYPE_CAMREA;
+            }
+            else if (cameraType == PREMISSIONTYPE_YES && type == PREMISSIONTYPE_NO) {
+                content = @"请开启麦克风访问权限";
+                checkType = CHECKDEVICETYPE_MIC;
+            }
+            [self showAlertWithMessage:content type:checkType];
+        }
+    }];
+    
+}
+
+- (void)showAlertWithMessage:(NSString *)message type:(CHECKDEVICETYPE)deviceType {
+    [KSPremissionAlert shareInstanceDisplayImage:deviceType message:message showInView:self.view cancel:^{
+        
+    } confirm:^{
+        [self openSettingView];
+    }];
+    
+}
+
+- (void)openSettingView {
+    [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString] options:@{} completionHandler:nil];
+}
+
+#pragma mark ----- lazying
+- (OnlineClassManager *)classManager {
+    if (!_classManager) {
+        _classManager = [[OnlineClassManager 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

+ 18 - 0
KulexiuForTeacher/KulexiuForTeacher/Module/Course/MusicRoom/Controller/MusicRoomGroupViewController.h

@@ -0,0 +1,18 @@
+//
+//  MusicRoomGroupViewController.h
+//  KulexiuForTeacher
+//
+//  Created by 王智 on 2024/11/13.
+//
+
+#import "KSBaseViewController.h"
+
+NS_ASSUME_NONNULL_BEGIN
+// 琴房课--课程组
+@interface MusicRoomGroupViewController : KSBaseViewController
+
+@property (nonatomic, strong) NSString *courseGroupId; // 课程组ID
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 124 - 0
KulexiuForTeacher/KulexiuForTeacher/Module/Course/MusicRoom/Controller/MusicRoomGroupViewController.m

@@ -0,0 +1,124 @@
+//
+//  MusicRoomGroupViewController.m
+//  KulexiuForTeacher
+//
+//  Created by 王智 on 2024/11/13.
+//
+
+#import "MusicRoomGroupViewController.h"
+#import <RecordCheckManager.h>
+#import "KSPremissionAlert.h"
+#import "OnlineClassManager.h"
+#import "HomeworkDetailModel.h"
+
+@interface MusicRoomGroupViewController ()
+
+@property (nonatomic, strong) NSString *courseId;
+
+@property (nonatomic, strong) NSString *studentId;
+
+@property (nonatomic, assign) NSInteger joinRoomBeforeTime; // 上课开始时间
+
+@property (nonatomic, assign) NSInteger quitRomeEndTime;    // 下课截止时间
+
+@property (nonatomic, strong) HomeworkDetailModel *homeworkModel;
+
+@property (nonatomic, strong) OnlineClassManager *classManager;
+
+@end
+
+@implementation MusicRoomGroupViewController
+
+- (void)viewDidLoad {
+    [super viewDidLoad];
+    // Do any additional setup after loading the view.
+}
+
+- (void)tryJoinRoom {
+    
+    NSDateFormatter *dateFormatter = [NSObject getDateformatter];
+    [dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
+    NSDate *beginDate = [dateFormatter dateFromString:self.homeworkModel.startTime];
+    NSDate *endDate = [dateFormatter dateFromString:self.homeworkModel.endTime];
+    NSDate *currentDate = [NSDate date];
+    NSTimeInterval beginTimeInterval = [beginDate timeIntervalSinceDate:currentDate];
+    NSTimeInterval endTimeInterval = [currentDate timeIntervalSinceDate:endDate];
+    if (beginTimeInterval <= self.joinRoomBeforeTime * 60 && endTimeInterval < 0) {
+        [self joinClassRoom];
+    }
+    else if (endTimeInterval > 0) {
+        [LOADING_MANAGER MBShowAUTOHidingInWindow:@"该课程已结束"];
+    }
+    else {
+        NSString *tipsString = [NSString stringWithFormat:@"课程还未开始,请在上课前%zd分钟进入", self.joinRoomBeforeTime];
+        [LOADING_MANAGER MBShowAUTOHidingInWindow:tipsString];
+    }
+}
+
+// 加入房间
+- (void)joinClassRoom {
+    
+    // 加入房间前判断摄像头和麦克风逻辑
+    [RecordCheckManager checkCameraPremissionAvaiableCallback:^(PREMISSIONTYPE type) {
+        [self afterCheckCameraCheckMic:type];
+    }];
+}
+
+- (void)afterCheckCameraCheckMic:(PREMISSIONTYPE)cameraType {
+    [RecordCheckManager checkMicPermissionAvaiableCallback:^(PREMISSIONTYPE type) {
+        if (type == PREMISSIONTYPE_YES && cameraType == PREMISSIONTYPE_YES) {
+            // 判断是否进行课前检测
+            [self.classManager joinRoomWithId:self.courseId subjectName:self.homeworkModel.subjectName classEndTime:self.homeworkModel.endTime inViewController:self];
+        }
+        else {
+            NSString *content = @"";
+            CHECKDEVICETYPE checkType = CHECKDEVICETYPE_BOTH;
+            if (cameraType == PREMISSIONTYPE_NO && type == PREMISSIONTYPE_NO) {
+                content = @"请开启相机和麦克风访问权限";
+                checkType = CHECKDEVICETYPE_BOTH;
+            }
+            else if (cameraType == PREMISSIONTYPE_NO && type == PREMISSIONTYPE_YES) {
+                content =  @"请开启相机访问权限";
+                checkType = CHECKDEVICETYPE_CAMREA;
+            }
+            else if (cameraType == PREMISSIONTYPE_YES && type == PREMISSIONTYPE_NO) {
+                content = @"请开启麦克风访问权限";
+                checkType = CHECKDEVICETYPE_MIC;
+            }
+            [self showAlertWithMessage:content type:checkType];
+        }
+    }];
+    
+}
+
+- (void)showAlertWithMessage:(NSString *)message type:(CHECKDEVICETYPE)deviceType {
+    [KSPremissionAlert shareInstanceDisplayImage:deviceType message:message showInView:self.view cancel:^{
+        
+    } confirm:^{
+        [self openSettingView];
+    }];
+    
+}
+
+- (void)openSettingView {
+    [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString] options:@{} completionHandler:nil];
+}
+
+#pragma mark ----- lazying
+- (OnlineClassManager *)classManager {
+    if (!_classManager) {
+        _classManager = [[OnlineClassManager 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

+ 4 - 4
KulexiuForTeacher/KulexiuForTeacher/Module/Course/MusicRoom/View/MusicRoomStudentCell.xib

@@ -1,9 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="22505" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="23504" 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="22504"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23506"/>
         <capability name="Safe area layout guides" minToolsVersion="9.0"/>
         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
     </dependencies>
@@ -20,7 +20,7 @@
                     <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="P8b-0z-8QJ">
                         <rect key="frame" x="14" y="0.0" width="297" height="76"/>
                         <subviews>
-                            <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="user_default_avatal" translatesAutoresizingMaskIntoConstraints="NO" id="rmJ-FQ-1KP">
+                            <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="student_avatar" translatesAutoresizingMaskIntoConstraints="NO" id="rmJ-FQ-1KP">
                                 <rect key="frame" x="12" y="14" width="48" height="48"/>
                                 <constraints>
                                     <constraint firstAttribute="height" constant="48" id="WNP-yH-bmz"/>
@@ -124,6 +124,6 @@
     </objects>
     <resources>
         <image name="homework_next" width="17" height="16"/>
-        <image name="user_default_avatal" width="52" height="52"/>
+        <image name="student_avatar" width="150" height="150"/>
     </resources>
 </document>

+ 19 - 0
KulexiuForTeacher/KulexiuForTeacher/Module/Course/VIPCourse/Controller/VIPCourseGroupViewController.h

@@ -0,0 +1,19 @@
+//
+//  VIPCourseGroupViewController.h
+//  KulexiuForTeacher
+//
+//  Created by 王智 on 2024/11/13.
+//
+
+#import "KSBaseViewController.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+// VIP定制课--课程组
+@interface VIPCourseGroupViewController : KSBaseViewController
+
+@property (nonatomic, strong) NSString *courseGroupId; // 课程组ID
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 126 - 0
KulexiuForTeacher/KulexiuForTeacher/Module/Course/VIPCourse/Controller/VIPCourseGroupViewController.m

@@ -0,0 +1,126 @@
+//
+//  VIPCourseGroupViewController.m
+//  KulexiuForTeacher
+//
+//  Created by 王智 on 2024/11/13.
+//
+
+#import "VIPCourseGroupViewController.h"
+#import <RecordCheckManager.h>
+#import "KSPremissionAlert.h"
+#import "OnlineClassManager.h"
+#import "HomeworkDetailModel.h"
+
+@interface VIPCourseGroupViewController ()
+
+@property (nonatomic, strong) NSString *courseId;
+
+@property (nonatomic, strong) NSString *studentId;
+
+@property (nonatomic, assign) NSInteger joinRoomBeforeTime; // 上课开始时间
+
+@property (nonatomic, assign) NSInteger quitRomeEndTime;    // 下课截止时间
+
+@property (nonatomic, strong) HomeworkDetailModel *homeworkModel;
+
+@property (nonatomic, strong) OnlineClassManager *classManager;
+
+
+@end
+
+@implementation VIPCourseGroupViewController
+
+- (void)viewDidLoad {
+    [super viewDidLoad];
+    // Do any additional setup after loading the view.
+}
+
+
+- (void)tryJoinRoom {
+    
+    NSDateFormatter *dateFormatter = [NSObject getDateformatter];
+    [dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
+    NSDate *beginDate = [dateFormatter dateFromString:self.homeworkModel.startTime];
+    NSDate *endDate = [dateFormatter dateFromString:self.homeworkModel.endTime];
+    NSDate *currentDate = [NSDate date];
+    NSTimeInterval beginTimeInterval = [beginDate timeIntervalSinceDate:currentDate];
+    NSTimeInterval endTimeInterval = [currentDate timeIntervalSinceDate:endDate];
+    if (beginTimeInterval <= self.joinRoomBeforeTime * 60 && endTimeInterval < 0) {
+        [self joinClassRoom];
+    }
+    else if (endTimeInterval > 0) {
+        [LOADING_MANAGER MBShowAUTOHidingInWindow:@"该课程已结束"];
+    }
+    else {
+        NSString *tipsString = [NSString stringWithFormat:@"课程还未开始,请在上课前%zd分钟进入", self.joinRoomBeforeTime];
+        [LOADING_MANAGER MBShowAUTOHidingInWindow:tipsString];
+    }
+}
+
+// 加入房间
+- (void)joinClassRoom {
+    
+    // 加入房间前判断摄像头和麦克风逻辑
+    [RecordCheckManager checkCameraPremissionAvaiableCallback:^(PREMISSIONTYPE type) {
+        [self afterCheckCameraCheckMic:type];
+    }];
+}
+
+- (void)afterCheckCameraCheckMic:(PREMISSIONTYPE)cameraType {
+    [RecordCheckManager checkMicPermissionAvaiableCallback:^(PREMISSIONTYPE type) {
+        if (type == PREMISSIONTYPE_YES && cameraType == PREMISSIONTYPE_YES) {
+            // 判断是否进行课前检测
+            [self.classManager joinRoomWithId:self.courseId subjectName:self.homeworkModel.subjectName classEndTime:self.homeworkModel.endTime inViewController:self];
+        }
+        else {
+            NSString *content = @"";
+            CHECKDEVICETYPE checkType = CHECKDEVICETYPE_BOTH;
+            if (cameraType == PREMISSIONTYPE_NO && type == PREMISSIONTYPE_NO) {
+                content = @"请开启相机和麦克风访问权限";
+                checkType = CHECKDEVICETYPE_BOTH;
+            }
+            else if (cameraType == PREMISSIONTYPE_NO && type == PREMISSIONTYPE_YES) {
+                content =  @"请开启相机访问权限";
+                checkType = CHECKDEVICETYPE_CAMREA;
+            }
+            else if (cameraType == PREMISSIONTYPE_YES && type == PREMISSIONTYPE_NO) {
+                content = @"请开启麦克风访问权限";
+                checkType = CHECKDEVICETYPE_MIC;
+            }
+            [self showAlertWithMessage:content type:checkType];
+        }
+    }];
+    
+}
+
+- (void)showAlertWithMessage:(NSString *)message type:(CHECKDEVICETYPE)deviceType {
+    [KSPremissionAlert shareInstanceDisplayImage:deviceType message:message showInView:self.view cancel:^{
+        
+    } confirm:^{
+        [self openSettingView];
+    }];
+    
+}
+
+- (void)openSettingView {
+    [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString] options:@{} completionHandler:nil];
+}
+
+#pragma mark ----- lazying
+- (OnlineClassManager *)classManager {
+    if (!_classManager) {
+        _classManager = [[OnlineClassManager 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

+ 25 - 0
KulexiuForTeacher/KulexiuForTeacher/Module/Course/VIPCourse/Controller/VipCouseDetailViewController.h

@@ -0,0 +1,25 @@
+//
+//  VipCouseDetailViewController.h
+//  KulexiuForTeacher
+//
+//  Created by 王智 on 2024/11/13.
+//
+
+#import "KSBaseViewController.h"
+
+NS_ASSUME_NONNULL_BEGIN
+// VIP定制课详情
+@interface VipCouseDetailViewController : KSBaseViewController
+
+@property (nonatomic, strong) NSString *courseId;
+
+@property (nonatomic, strong) NSString *courseGroupId;
+
+@property (nonatomic, strong) NSString *studentId;
+
+@property (nonatomic, assign) NSInteger joinRoomBeforeTime; // 上课开始时间
+
+@property (nonatomic, assign) NSInteger quitRomeEndTime;    // 下课截止时间
+@end
+
+NS_ASSUME_NONNULL_END

+ 523 - 0
KulexiuForTeacher/KulexiuForTeacher/Module/Course/VIPCourse/Controller/VipCouseDetailViewController.m

@@ -0,0 +1,523 @@
+//
+//  VipCouseDetailViewController.m
+//  KulexiuForTeacher
+//
+//  Created by 王智 on 2024/11/13.
+//
+
+#import "VipCouseDetailViewController.h"
+#import "AccompanyNavView.h"
+#import "AccompanyAlertView.h"
+#import "VipCouseInfoCell.h"
+#import "AccompanyEvaluateCell.h"
+#import "AccompanyStudentEvaCell.h"
+#import "AccompanyArrangeCell.h"
+#import "AccompanyHomeworkCell.h"
+#import "AccompanyRemarkCell.h"
+#import "HomeworkDetailModel.h"
+#import "HomeworkDetailModel.h"
+#import "EvaluateDetailModel.h"
+#import "KSChatConversationViewController.h"
+#import <WMPlayer.h>
+#import <RecordCheckManager.h>
+#import "KSPremissionAlert.h"
+#import "OnlineClassManager.h"
+
+@interface VipCouseDetailViewController ()<UITableViewDelegate,UITableViewDataSource,WMPlayerDelegate>
+{
+    WMPlayer *_wmPlayer;
+    CGRect _playerFrame;
+}
+@property (nonatomic, strong) UIView *bgView;
+
+@property (nonatomic, assign) BOOL isRatation;
+
+@property (nonatomic, strong) AccompanyNavView *navView;
+
+@property (nonatomic, strong) UITableView *tableView;
+
+@property (nonatomic, strong) HomeworkDetailModel *homeworkModel;
+
+@property (nonatomic, strong) EvaluateDetailModel *evaluateModel;
+
+@property (nonatomic, strong) AccompanyAlertView *alertView;
+
+@property (nonatomic, strong) NSMutableArray *fileArray;
+
+@property (nonatomic, strong) OnlineClassManager *classManager;
+
+@end
+
+@implementation VipCouseDetailViewController
+
+- (void)viewDidLoad {
+    [super viewDidLoad];
+    // Do any additional setup after loading the view.
+    self.ks_prefersNavigationBarHidden = YES;
+    [self configUI];
+}
+
+- (void)requestCourseInfoMessage {
+    [self requestHomeworkMessage];
+    [self requestEvaluateMessage];
+    [self requestRoomConfig];
+}
+
+- (void)viewWillAppear:(BOOL)animated {
+    [super viewWillAppear:animated];
+    [self requestCourseInfoMessage];
+}
+
+- (void)requestRoomConfig {
+    [KSNetworkingManager selectRoomConfigRequest:KS_GET success:^(NSDictionary * _Nonnull dic) {
+        if ([dic ks_integerValueForKey:@"code"] == 200 && [dic ks_boolValueForKey:@"status"]) {
+            NSDictionary *result = [dic ks_dictionaryValueForKey:@"data"];
+            self.joinRoomBeforeTime = [result ks_integerValueForKey:@"practiceStartTime"];
+            self.quitRomeEndTime = [result ks_integerValueForKey:@"practiceEndTime"];
+        }
+        else {
+            [LOADING_MANAGER MBShowAUTOHidingInWindow:MESSAGEKEY];
+        }
+    } faliure:^(NSError * _Nonnull error) {
+        
+    }];
+}
+
+- (void)requestHomeworkMessage {
+    [KSNetworkingManager homeworkDetailRequest:KS_GET courseId:self.courseId studentId:self.studentId success:^(NSDictionary * _Nonnull dic) {
+        if ([dic ks_integerValueForKey:@"code"] == 200 && [dic ks_boolValueForKey:@"status"]) {
+            self.homeworkModel = [[HomeworkDetailModel alloc] initWithDictionary:[dic ks_dictionaryValueForKey:@"data"]];
+            if (self.homeworkModel.submitHomework == 1) {
+                if (![NSString isEmptyString:self.homeworkModel.studentAttachments]) {
+                    self.fileArray = [NSMutableArray arrayWithArray:[self.homeworkModel.studentAttachments componentsSeparatedByString:@","]];
+                }
+                else {
+                    self.fileArray = [NSMutableArray array];
+                }
+            }
+            else {
+                self.fileArray = [NSMutableArray array];
+            }
+        }
+        else {
+            [LOADING_MANAGER MBShowAUTOHidingInWindow:MESSAGEKEY];
+        }
+        [self.tableView reloadData];
+    } faliure:^(NSError * _Nonnull error) {
+        
+    }];
+}
+
+- (void)requestEvaluateMessage {
+    [KSNetworkingManager selectRepliedRequest:KS_POST courseGroupId:self.courseGroupId courseScheduleId:self.courseId studentId:self.studentId success:^(NSDictionary * _Nonnull dic) {
+        if ([dic ks_integerValueForKey:@"code"] == 200 && [dic ks_boolValueForKey:@"status"]) {
+            self.evaluateModel = [[EvaluateDetailModel alloc] initWithDictionary:[dic ks_dictionaryValueForKey:@"data"]];
+        }
+        else {
+            [LOADING_MANAGER MBShowAUTOHidingInWindow:MESSAGEKEY];
+        }
+        [self.tableView reloadData];
+    } faliure:^(NSError * _Nonnull error) {
+        
+    }];
+}
+
+- (void)configUI {
+    [self.scrollView removeFromSuperview];
+    [self.view addSubview:self.navView];
+    CGFloat height = [self.navView getViewHeight];
+    [self.navView mas_makeConstraints:^(MASConstraintMaker *make) {
+        make.left.right.top.mas_equalTo(self.view);
+        make.height.mas_equalTo(height);
+    }];
+    [self.view addSubview:self.tableView];
+    [self.tableView mas_makeConstraints:^(MASConstraintMaker *make) {
+        make.left.right.mas_equalTo(self.view);
+        make.top.mas_equalTo(self.view.mas_top).offset(kNaviBarHeight);
+        make.bottom.mas_equalTo(self.view.mas_bottom).offset(-iPhoneXSafeBottomMargin);
+    }];
+}
+
+- (void)evaluateWithStatusLabel:(UILabel *)statusLabel {
+    if ([self.homeworkModel.courseStatus isEqualToString:@"COMPLETE"]) {
+        statusLabel.text = @"已结束";
+        statusLabel.textColor = HexRGB(0x999999);
+    }
+    else if ([self.homeworkModel.courseStatus isEqualToString:@"ING"]) {
+        statusLabel.text = @"进行中";
+        statusLabel.textColor = THEMECOLOR;
+    }
+    else {
+        statusLabel.text = @"未开始";
+        statusLabel.textColor = HexRGB(0xff802c);
+    }
+}
+
+
+#pragma mark --- table data source
+- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
+    return 6;
+}
+
+- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
+    if (indexPath.row == 0) {
+        VipCouseInfoCell *cell = [tableView dequeueReusableCellWithIdentifier:@"VipCouseInfoCell"];
+        [self evaluateWithStatusLabel:cell.statusLabel];
+        [cell configWithStartTime:self.homeworkModel.startTime endTime:self.homeworkModel.endTime studentAvatar:self.homeworkModel.studentAvatar studentName:self.homeworkModel.studentName studentId:self.homeworkModel.studentId studentSubject:self.homeworkModel.subjectName];
+        cell.hideChatButton = NO;
+        MJWeakSelf;
+        [cell chatCalkback:^{
+            [weakSelf chatAction];
+        }];
+        
+        return cell;
+    }
+    else if (indexPath.row == 1) {
+        AccompanyEvaluateCell *cell = [tableView dequeueReusableCellWithIdentifier:@"AccompanyEvaluateCell"];
+        NSString *message = self.evaluateModel.teacherReplied;
+        BOOL hasEvaluate = [NSString isEmptyString:self.evaluateModel.teacherReplied] ? NO : YES;
+        MJWeakSelf;
+        [cell configWithEvaluateMessage:message hasEvaluate:hasEvaluate callback:^{
+            [weakSelf evaluateCourse];
+        }];
+        return cell;
+    }
+    else if (indexPath.row == 2) {
+        AccompanyStudentEvaCell *cell = [tableView dequeueReusableCellWithIdentifier:@"AccompanyStudentEvaCell"];
+        NSString *message = self.evaluateModel.studentReplied;
+        BOOL hasEvaluate = [NSString isEmptyString:self.evaluateModel.studentReplied] ? NO : YES;
+        [cell configWithEvaluateMessage:message starNum:self.evaluateModel.score hasEvaluate:hasEvaluate];
+        return cell;
+    }
+    else if (indexPath.row == 3) {
+        AccompanyArrangeCell *cell = [tableView dequeueReusableCellWithIdentifier:@"AccompanyArrangeCell"];
+        BOOL hasArrange = self.homeworkModel.decorateHomework == 1 ? YES : NO;
+        MJWeakSelf;
+        [cell configWithHomeworkContent:self.homeworkModel.content hasArrangeHomework:hasArrange callback:^{
+            [weakSelf arrangeHomework];
+        }];
+        return cell;
+    }
+    else if (indexPath.row == 4) {
+        BOOL hasSubmitHomework = self.homeworkModel.submitHomework == 1 ? YES : NO;
+        BOOL isExpired = self.homeworkModel.homeworkExpire == 1 ? YES : NO;
+        AccompanyHomeworkCell *cell = [tableView dequeueReusableCellWithIdentifier:@"AccompanyHomeworkCell"];
+        MJWeakSelf;
+        [cell configWithAttachmentArray:self.fileArray hasSubmit:hasSubmitHomework isExpired:isExpired callback:^(NSInteger viewIndex) {
+            [weakSelf playVideoIndex:viewIndex];
+        }];
+        return cell;
+    }
+    else {
+        AccompanyRemarkCell *cell = [tableView dequeueReusableCellWithIdentifier:@"AccompanyRemarkCell"];
+        BOOL hasEvaluate = self.homeworkModel.reviewHomework == 1? YES : NO;
+        MJWeakSelf;
+        [cell configWithRemarkMessage:self.homeworkModel.teacherReplied hasEvaluate:hasEvaluate callback:^{
+            [weakSelf evaluateHomework];
+        }];
+        return cell;
+    }
+}
+
+- (void)chatAction {
+    if (self.homeworkModel && ![NSString isEmptyString:self.homeworkModel.imUserId]) {
+        TUIChatConversationModel *model = [[TUIChatConversationModel alloc] init];
+        model.userID = self.homeworkModel.imUserId;
+        KSChatConversationViewController *ctrl = [[KSChatConversationViewController alloc] init];
+        ctrl.conversation = model;
+        [self.navigationController pushViewController:ctrl animated:YES];
+    }
+}
+
+- (void)evaluateCourse {
+    if (![self.homeworkModel.courseStatus isEqualToString:@"COMPLETE"]) {
+        [LOADING_MANAGER MBShowAUTOHidingInWindow:@"课程结束之后才可以评价哦~"];
+        return;
+    }
+    self.alertView = [AccompanyAlertView shareInstance];
+    self.alertView.alertTitle.text = @"评价学员";
+    self.alertView.tipsLabel.text = @"请输入您对本次课程学员表现的评价";
+    self.alertView.frame = CGRectMake(0, 0, kScreenWidth, kScreenHeight);
+    [self.alertView showInView:[UIApplication sharedApplication].keyWindow showStarView:NO];
+    MJWeakSelf;
+    [self.alertView sureCallback:^(NSString * _Nonnull content, NSInteger starNum) {
+        [weakSelf evaluateAction:content];
+    }];
+}
+
+- (void)evaluateAction:(NSString *)content {
+    if ([NSString isEmptyString:content]) {
+        [LOADING_MANAGER MBShowAUTOHidingInWindow:@"请输入评价内容"];
+        return;
+    }
+    [LOADING_MANAGER showHUD];
+    [KSNetworkingManager teacherCourseRepliedRequest:KS_POST courseScheduleId:self.courseId courseGroupId:self.courseGroupId studentId:self.studentId teacherReplied:content success:^(NSDictionary * _Nonnull dic) {
+        [LOADING_MANAGER removeHUD];
+        if ([dic ks_integerValueForKey:@"code"] == 200 && [dic ks_boolValueForKey:@"status"]) {
+            [LOADING_MANAGER MBShowAUTOHidingInWindow:@"评价成功"];
+            [self requestCourseInfoMessage];
+        }
+        else {
+            [LOADING_MANAGER MBShowAUTOHidingInWindow:MESSAGEKEY];
+        }
+    } faliure:^(NSError * _Nonnull error) {
+        [LOADING_MANAGER removeHUD];
+    }];
+}
+
+- (void)arrangeHomework {
+    if (![self.homeworkModel.courseStatus isEqualToString:@"COMPLETE"]) {
+        [LOADING_MANAGER MBShowAUTOHidingInWindow:@"课程结束之后才可以布置作业哦~"];
+        return;
+    }
+    self.alertView = [AccompanyAlertView shareInstance];
+    self.alertView.alertTitle.text = @"布置作业";
+    self.alertView.tipsLabel.text = @"请输入本次课程作业内容";
+    self.alertView.frame = CGRectMake(0, 0, kScreenWidth, kScreenHeight);
+    [self.alertView showInView:[UIApplication sharedApplication].keyWindow showStarView:NO];
+    MJWeakSelf;
+    [self.alertView sureCallback:^(NSString * _Nonnull content, NSInteger starNum) {
+        [weakSelf arrangeAction:content];
+    }];
+}
+
+- (void)arrangeAction:(NSString *)homeworkContent {
+    [LOADING_MANAGER showHUD];
+    [KSNetworkingManager homeworkDecorateRequest:KS_POST content:homeworkContent courseScheduleId:self.courseId success:^(NSDictionary * _Nonnull dic) {
+        [LOADING_MANAGER removeHUD];
+        if ([dic ks_integerValueForKey:@"code"] == 200 && [dic ks_boolValueForKey:@"status"]) {
+            [LOADING_MANAGER MBShowAUTOHidingInWindow:@"布置成功"];
+            [self requestCourseInfoMessage];
+        }
+        else {
+            [LOADING_MANAGER MBShowAUTOHidingInWindow:MESSAGEKEY];
+        }
+    } faliure:^(NSError * _Nonnull error) {
+        [LOADING_MANAGER removeHUD];
+    }];
+}
+
+// 评价作业
+- (void)evaluateHomework {
+    if (self.homeworkModel.decorateHomework == 0) {
+        [LOADING_MANAGER MBShowAUTOHidingInWindow:@"您还未布置作业"];
+        return;
+    }
+    else if (self.homeworkModel.submitHomework == 0) {
+        [LOADING_MANAGER MBShowAUTOHidingInWindow:@"学生还未提交作业"];
+        return;
+    }
+    self.alertView = [AccompanyAlertView shareInstance];
+    self.alertView.alertTitle.text = @"作业点评";
+    self.alertView.tipsLabel.text = @"请输入您对本次作业的点评";
+    self.alertView.frame = CGRectMake(0, 0, kScreenWidth, kScreenHeight);
+    [self.alertView showInView:[UIApplication sharedApplication].keyWindow showStarView:NO];
+    MJWeakSelf;
+    [self.alertView sureCallback:^(NSString * _Nonnull content, NSInteger starNum) {
+        [weakSelf commentAction:content];
+    }];
+}
+
+- (void)commentAction:(NSString *)commentMessage {
+    [LOADING_MANAGER showHUD];
+    [KSNetworkingManager homeworkReviewRequest:KS_POST courseScheduleId:self.courseId studentId:self.studentId review:commentMessage success:^(NSDictionary * _Nonnull dic) {
+        [LOADING_MANAGER removeHUD];
+        if ([dic ks_integerValueForKey:@"code"] == 200 && [dic ks_boolValueForKey:@"status"]) {
+            [LOADING_MANAGER MBShowAUTOHidingInWindow:@"点评成功"];
+            [self requestCourseInfoMessage];
+        }
+        else {
+            [LOADING_MANAGER MBShowAUTOHidingInWindow:MESSAGEKEY];
+        }
+    } faliure:^(NSError * _Nonnull error) {
+        [LOADING_MANAGER removeHUD];
+    }];
+}
+
+
+- (void)playVideoIndex:(NSInteger)index {
+    if (self.fileArray.count > index) {
+        NSString *fileUrl = self.fileArray[index];
+        [self playVideoWithUrl:fileUrl];
+    }
+}
+
+#pragma mark ------ WMPlayer
+- (void)playVideoWithUrl:(NSString *)fileUrl {
+    fileUrl = [fileUrl getUrlEndcodeString];
+    _playerFrame = CGRectMake(0, iPhoneXSafeTopMargin, kScreenWidth, kScreenHeight - iPhoneXSafeTopMargin - iPhoneXSafeBottomMargin);
+    _wmPlayer = [[WMPlayer alloc] initWithFrame:_playerFrame];
+    WMPlayerModel *playModel = [[WMPlayerModel alloc] init];
+    playModel.videoURL = [NSURL URLWithString:fileUrl];
+    _wmPlayer.playerModel = playModel;
+    _wmPlayer.delegate = self;
+    _bgView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, kScreenWidth, kScreenHeight)];
+    _bgView.backgroundColor = [UIColor blackColor];
+    [[UIApplication sharedApplication].keyWindow addSubview:_bgView];
+    [[UIApplication sharedApplication].keyWindow addSubview:_wmPlayer];
+    [[UIApplication sharedApplication].keyWindow bringSubviewToFront:_wmPlayer];
+    
+    [_wmPlayer play];
+}
+
+- (void)wmplayer:(WMPlayer *)wmplayer clickedCloseButton:(UIButton *)backBtn {
+    [wmplayer removePlayer];
+    [_bgView removeFromSuperview];
+    [self setNeedsStatusBarAppearanceUpdate];
+}
+
+- (void)wmplayer:(WMPlayer *)wmplayer clickedFullScreenButton:(UIButton *)fullScreenBtn {
+    self.isRatation = !self.isRatation;
+    
+    if (self.isRatation) {
+        [wmplayer removeFromSuperview];
+        [UIView animateWithDuration:1.0f animations:^{
+            wmplayer.transform = CGAffineTransformMakeRotation(M_PI_2);
+            
+        } completion:^(BOOL finished) {
+            wmplayer.frame = CGRectMake(0, iPhoneXSafeTopMargin, kScreenWidth, kScreenHeight - iPhoneXSafeTopMargin - iPhoneXSafeBottomMargin);
+            [[UIApplication sharedApplication].keyWindow addSubview:wmplayer];
+            [[UIApplication sharedApplication].keyWindow bringSubviewToFront:wmplayer];
+        }];
+    }
+    else {
+        [wmplayer removeFromSuperview];
+        
+        [UIView animateWithDuration:1.0f animations:^{
+            //        复原
+            wmplayer.transform = CGAffineTransformIdentity;
+            
+        } completion:^(BOOL finished) {
+            wmplayer.frame = CGRectMake(0, iPhoneXSafeTopMargin, kScreenWidth, kScreenHeight - iPhoneXSafeTopMargin - iPhoneXSafeBottomMargin);
+            [[UIApplication sharedApplication].keyWindow addSubview:wmplayer];
+            [[UIApplication sharedApplication].keyWindow bringSubviewToFront:wmplayer];
+        }];
+    }
+}
+
+#pragma mark --- lazying
+
+- (OnlineClassManager *)classManager {
+    if (!_classManager) {
+        _classManager = [[OnlineClassManager alloc] init];
+    }
+    return _classManager;
+}
+
+- (UITableView *)tableView {
+    if (!_tableView) {
+        _tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
+        _tableView.delegate = self;
+        _tableView.dataSource = self;
+        _tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
+        _tableView.showsVerticalScrollIndicator = NO;
+        _tableView.showsHorizontalScrollIndicator = NO;
+        _tableView.backgroundColor = [UIColor clearColor];
+        _tableView.rowHeight = UITableViewAutomaticDimension;
+        [_tableView registerNib:[UINib nibWithNibName:@"VipCouseInfoCell" bundle:[NSBundle mainBundle]] forCellReuseIdentifier:@"VipCouseInfoCell"];
+        [_tableView registerNib:[UINib nibWithNibName:@"AccompanyEvaluateCell" bundle:[NSBundle mainBundle]] forCellReuseIdentifier:@"AccompanyEvaluateCell"];
+        [_tableView registerNib:[UINib nibWithNibName:@"AccompanyStudentEvaCell" bundle:[NSBundle mainBundle]] forCellReuseIdentifier:@"AccompanyStudentEvaCell"];
+        [_tableView registerNib:[UINib nibWithNibName:@"AccompanyArrangeCell" bundle:[NSBundle mainBundle]] forCellReuseIdentifier:@"AccompanyArrangeCell"];
+        [_tableView registerNib:[UINib nibWithNibName:@"AccompanyHomeworkCell" bundle:[NSBundle mainBundle]] forCellReuseIdentifier:@"AccompanyHomeworkCell"];
+        [_tableView registerNib:[UINib nibWithNibName:@"AccompanyRemarkCell" bundle:[NSBundle mainBundle]] forCellReuseIdentifier:@"AccompanyRemarkCell"];
+        _tableView.estimatedRowHeight = 136.0f;
+    }
+    return _tableView;
+}
+
+- (AccompanyNavView *)navView {
+    if (!_navView) {
+        _navView = [AccompanyNavView shareInstance];
+        MJWeakSelf;
+        [_navView navCallback:^{
+            [weakSelf backAction];
+        }];
+    }
+    return _navView;
+}
+
+
+- (void)tryJoinRoom {
+    
+    NSDateFormatter *dateFormatter = [NSObject getDateformatter];
+    [dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
+    NSDate *beginDate = [dateFormatter dateFromString:self.homeworkModel.startTime];
+    NSDate *endDate = [dateFormatter dateFromString:self.homeworkModel.endTime];
+    NSDate *currentDate = [NSDate date];
+    NSTimeInterval beginTimeInterval = [beginDate timeIntervalSinceDate:currentDate];
+    NSTimeInterval endTimeInterval = [currentDate timeIntervalSinceDate:endDate];
+    if (beginTimeInterval <= self.joinRoomBeforeTime * 60 && endTimeInterval < 0) {
+        [self joinClassRoom];
+    }
+    else if (endTimeInterval > 0) {
+        [LOADING_MANAGER MBShowAUTOHidingInWindow:@"该课程已结束"];
+    }
+    else {
+        NSString *tipsString = [NSString stringWithFormat:@"课程还未开始,请在上课前%zd分钟进入", self.joinRoomBeforeTime];
+        [LOADING_MANAGER MBShowAUTOHidingInWindow:tipsString];
+    }
+}
+
+// 加入房间
+- (void)joinClassRoom {
+    
+    // 加入房间前判断摄像头和麦克风逻辑
+    [RecordCheckManager checkCameraPremissionAvaiableCallback:^(PREMISSIONTYPE type) {
+        [self afterCheckCameraCheckMic:type];
+    }];
+}
+
+- (void)afterCheckCameraCheckMic:(PREMISSIONTYPE)cameraType {
+    [RecordCheckManager checkMicPermissionAvaiableCallback:^(PREMISSIONTYPE type) {
+        if (type == PREMISSIONTYPE_YES && cameraType == PREMISSIONTYPE_YES) {
+            // 判断是否进行课前检测
+            [self.classManager joinRoomWithId:self.courseId subjectName:self.homeworkModel.subjectName classEndTime:self.homeworkModel.endTime inViewController:self];
+        }
+        else {
+            NSString *content = @"";
+            CHECKDEVICETYPE checkType = CHECKDEVICETYPE_BOTH;
+            if (cameraType == PREMISSIONTYPE_NO && type == PREMISSIONTYPE_NO) {
+                content = @"请开启相机和麦克风访问权限";
+                checkType = CHECKDEVICETYPE_BOTH;
+            }
+            else if (cameraType == PREMISSIONTYPE_NO && type == PREMISSIONTYPE_YES) {
+                content =  @"请开启相机访问权限";
+                checkType = CHECKDEVICETYPE_CAMREA;
+            }
+            else if (cameraType == PREMISSIONTYPE_YES && type == PREMISSIONTYPE_NO) {
+                content = @"请开启麦克风访问权限";
+                checkType = CHECKDEVICETYPE_MIC;
+            }
+            [self showAlertWithMessage:content type:checkType];
+        }
+    }];
+    
+}
+
+- (void)showAlertWithMessage:(NSString *)message type:(CHECKDEVICETYPE)deviceType {
+    [KSPremissionAlert shareInstanceDisplayImage:deviceType message:message showInView:self.view cancel:^{
+        
+    } confirm:^{
+        [self openSettingView];
+    }];
+    
+}
+
+- (void)openSettingView {
+    if (@available(iOS 10, *)) {
+        [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString] options:@{} completionHandler:nil];
+    } else {
+        [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]];
+    }
+}
+/*
+#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

+ 27 - 0
KulexiuForTeacher/KulexiuForTeacher/Module/Course/VIPCourse/View/VipCouseInfoCell.h

@@ -0,0 +1,27 @@
+//
+//  VipCouseInfoCell.h
+//  KulexiuForTeacher
+//
+//  Created by 王智 on 2024/11/13.
+//
+
+#import <UIKit/UIKit.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+typedef void(^VipChatCallback)(void);
+
+
+@interface VipCouseInfoCell : UITableViewCell
+
+@property (weak, nonatomic) IBOutlet UILabel *statusLabel;
+
+@property (nonatomic, assign) BOOL hideChatButton;
+
+- (void)configWithStartTime:(NSString *)beginTime endTime:(NSString *)endTime studentAvatar:(NSString *)studentAvatar studentName:(NSString *)studentName studentId:(NSString *)studentId studentSubject:(NSString *)studentSubject;
+
+- (void)chatCalkback:(VipChatCallback)callback;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 90 - 0
KulexiuForTeacher/KulexiuForTeacher/Module/Course/VIPCourse/View/VipCouseInfoCell.m

@@ -0,0 +1,90 @@
+//
+//  VipCouseInfoCell.m
+//  KulexiuForTeacher
+//
+//  Created by 王智 on 2024/11/13.
+//
+
+#import "VipCouseInfoCell.h"
+
+@interface VipCouseInfoCell ()
+
+@property (weak, nonatomic) IBOutlet UILabel *courseTime;
+@property (weak, nonatomic) IBOutlet UIImageView *studentAvatar;
+@property (weak, nonatomic) IBOutlet UILabel *studentName;
+@property (weak, nonatomic) IBOutlet UILabel *studentSubject;
+
+@property (weak, nonatomic) IBOutlet UIButton *chatButton;
+
+@property (nonatomic, strong) NSString *userId;
+
+@property (nonatomic, strong) NSString *userName;
+
+@property (nonatomic, copy) VipChatCallback callback;
+
+@end
+
+@implementation VipCouseInfoCell
+
+- (void)awakeFromNib {
+    [super awakeFromNib];
+    // Initialization code
+    self.selectionStyle = UITableViewCellSelectionStyleNone;
+}
+
+- (void)configWithStartTime:(NSString *)beginTime endTime:(NSString *)endTime studentAvatar:(NSString *)studentAvatar studentName:(NSString *)studentName studentId:(NSString *)studentId studentSubject:(NSString *)studentSubject {
+    // time
+    NSDateFormatter *formatter = [NSObject getDateformatter];
+    [formatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
+    NSDate *startDate = [formatter dateFromString:beginTime];
+    NSDate *endDate = [formatter dateFromString:endTime];
+    [formatter setDateFormat:@"yyyy/MM/dd HH:mm"];
+    NSString *lessonBegin = [formatter stringFromDate:startDate];
+    [formatter setDateFormat:@"HH:mm"];
+    NSString *lessonEnd = [formatter stringFromDate:endDate];
+    self.courseTime.text = [NSString stringWithFormat:@"%@~%@",[NSString returnNoNullStringWithString:lessonBegin],[NSString returnNoNullStringWithString:lessonEnd]];
+    if ([NSString isEmptyString:studentName]) {
+        self.studentName.text = [NSString stringWithFormat:@"游客%@",studentId];
+    }
+    else {
+        self.studentName.text = studentName;
+    }
+    self.studentName.text = [NSString returnNoNullStringWithString:studentName];
+    [self.studentAvatar sd_setImageWithURL:[NSURL URLWithString:[studentAvatar getUrlEndcodeString]] placeholderImage:[UIImage imageNamed:STUDENT_AVATAR]];
+    self.studentSubject.text = [NSString returnNoNullStringWithString:studentSubject];
+
+    self.userId = studentId;
+    self.userName = studentName;
+}
+
+- (void)setHideChatButton:(BOOL)hideChatButton {
+    _hideChatButton = hideChatButton;
+    if (hideChatButton) {
+        self.chatButton.hidden = YES;
+        self.chatButton.userInteractionEnabled = NO;
+    }
+    else {
+        self.chatButton.hidden = NO;
+        self.chatButton.userInteractionEnabled = YES;
+    }
+}
+
+- (void)chatCalkback:(VipChatCallback)callback {
+    if (callback) {
+        self.callback = callback;
+    }
+}
+
+- (IBAction)chatAction:(id)sender {
+    if (self.callback) {
+        self.callback();
+    }
+}
+
+- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
+    [super setSelected:selected animated:animated];
+
+    // Configure the view for the selected state
+}
+
+@end

+ 170 - 0
KulexiuForTeacher/KulexiuForTeacher/Module/Course/VIPCourse/View/VipCouseInfoCell.xib

@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="23504" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
+    <device id="retina6_12" orientation="portrait" appearance="light"/>
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23506"/>
+        <capability name="Safe area layout guides" minToolsVersion="9.0"/>
+        <capability name="System colors in document resources" minToolsVersion="11.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"/>
+        <tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" rowHeight="145" id="KGk-i7-Jjw" customClass="VipCouseInfoCell">
+            <rect key="frame" x="0.0" y="0.0" width="335" height="145"/>
+            <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+            <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM">
+                <rect key="frame" x="0.0" y="0.0" width="335" height="145"/>
+                <autoresizingMask key="autoresizingMask"/>
+                <subviews>
+                    <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="yVi-pw-eI6">
+                        <rect key="frame" x="14" y="10" width="307" height="123"/>
+                        <subviews>
+                            <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="3dh-xL-h1k">
+                                <rect key="frame" x="10" y="38" width="287" height="1"/>
+                                <color key="backgroundColor" red="0.94901960780000005" green="0.94901960780000005" blue="0.94901960780000005" alpha="1" colorSpace="calibratedRGB"/>
+                                <constraints>
+                                    <constraint firstAttribute="height" constant="1" id="h97-r3-fgZ"/>
+                                </constraints>
+                            </view>
+                            <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="course_time" translatesAutoresizingMaskIntoConstraints="NO" id="B1g-pw-mYI">
+                                <rect key="frame" x="11" y="12" width="16" height="16"/>
+                                <constraints>
+                                    <constraint firstAttribute="width" constant="16" id="Su3-Id-msD"/>
+                                    <constraint firstAttribute="height" constant="16" id="hr8-TJ-Ukd"/>
+                                </constraints>
+                            </imageView>
+                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="49U-qQ-4uN">
+                                <rect key="frame" x="34" y="11" width="0.0" height="18"/>
+                                <constraints>
+                                    <constraint firstAttribute="height" constant="18" id="f06-F1-3xZ"/>
+                                </constraints>
+                                <fontDescription key="fontDescription" type="system" pointSize="13"/>
+                                <color key="textColor" red="0.40000000000000002" green="0.40000000000000002" blue="0.40000000000000002" alpha="1" colorSpace="calibratedRGB"/>
+                                <nil key="highlightedColor"/>
+                            </label>
+                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="QvR-ZO-p6W">
+                                <rect key="frame" x="246" y="20" width="50" height="0.0"/>
+                                <constraints>
+                                    <constraint firstAttribute="width" constant="50" id="oKu-05-8Na"/>
+                                </constraints>
+                                <fontDescription key="fontDescription" type="system" weight="medium" pointSize="14"/>
+                                <color key="textColor" red="0.59999999999999998" green="0.59999999999999998" blue="0.59999999999999998" alpha="1" colorSpace="calibratedRGB"/>
+                                <nil key="highlightedColor"/>
+                            </label>
+                            <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="student_avatar" translatesAutoresizingMaskIntoConstraints="NO" id="3cO-85-Ox1">
+                                <rect key="frame" x="11" y="56" width="47" height="47"/>
+                                <constraints>
+                                    <constraint firstAttribute="height" constant="47" id="YsZ-Ob-XKz"/>
+                                    <constraint firstAttribute="width" constant="47" id="p7L-Dk-Ijn"/>
+                                </constraints>
+                                <userDefinedRuntimeAttributes>
+                                    <userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
+                                        <real key="value" value="5"/>
+                                    </userDefinedRuntimeAttribute>
+                                </userDefinedRuntimeAttributes>
+                            </imageView>
+                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="UrD-Un-gOT">
+                                <rect key="frame" x="68" y="56" width="0.0" height="24"/>
+                                <constraints>
+                                    <constraint firstAttribute="height" constant="24" id="DRM-MQ-t61"/>
+                                </constraints>
+                                <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>
+                            <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="IV2-zi-jmv">
+                                <rect key="frame" x="68" y="83" width="10" height="20"/>
+                                <subviews>
+                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="4gL-4k-HYe">
+                                        <rect key="frame" x="5" y="0.0" width="0.0" height="20"/>
+                                        <fontDescription key="fontDescription" type="system" pointSize="11"/>
+                                        <color key="textColor" red="1" green="0.54901960780000003" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
+                                        <nil key="highlightedColor"/>
+                                    </label>
+                                </subviews>
+                                <color key="backgroundColor" red="1" green="0.94509803920000002" blue="0.87058823529999996" alpha="1" colorSpace="calibratedRGB"/>
+                                <constraints>
+                                    <constraint firstAttribute="trailing" secondItem="4gL-4k-HYe" secondAttribute="trailing" constant="5" id="4Np-oM-yIe"/>
+                                    <constraint firstItem="4gL-4k-HYe" firstAttribute="top" secondItem="IV2-zi-jmv" secondAttribute="top" id="9Bt-0w-zaH"/>
+                                    <constraint firstAttribute="height" constant="20" id="a1L-GP-2lN"/>
+                                    <constraint firstAttribute="bottom" secondItem="4gL-4k-HYe" secondAttribute="bottom" id="q0F-8u-0Qg"/>
+                                    <constraint firstItem="4gL-4k-HYe" firstAttribute="leading" secondItem="IV2-zi-jmv" secondAttribute="leading" constant="5" id="t9E-CA-Jd2"/>
+                                </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="Yoe-Ih-DzZ">
+                                <rect key="frame" x="68" y="48" width="40" height="40"/>
+                                <constraints>
+                                    <constraint firstAttribute="width" constant="40" id="SJF-dV-iio"/>
+                                    <constraint firstAttribute="height" constant="40" id="aFY-li-BZY"/>
+                                </constraints>
+                                <inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
+                                <state key="normal" image="course_chat"/>
+                                <connections>
+                                    <action selector="chatAction:" destination="KGk-i7-Jjw" eventType="touchUpInside" id="Ume-RJ-nmk"/>
+                                </connections>
+                            </button>
+                        </subviews>
+                        <color key="backgroundColor" systemColor="systemBackgroundColor"/>
+                        <constraints>
+                            <constraint firstAttribute="bottom" secondItem="3cO-85-Ox1" secondAttribute="bottom" constant="20" id="5Uk-ie-H5V"/>
+                            <constraint firstItem="QvR-ZO-p6W" firstAttribute="centerY" secondItem="49U-qQ-4uN" secondAttribute="centerY" id="8KI-hF-cDG"/>
+                            <constraint firstItem="B1g-pw-mYI" firstAttribute="top" secondItem="yVi-pw-eI6" secondAttribute="top" constant="12" id="AAO-VS-NM0"/>
+                            <constraint firstItem="Yoe-Ih-DzZ" firstAttribute="leading" secondItem="UrD-Un-gOT" secondAttribute="trailing" id="BJb-7I-w1j"/>
+                            <constraint firstItem="3cO-85-Ox1" firstAttribute="leading" secondItem="yVi-pw-eI6" secondAttribute="leading" constant="11" id="JKe-4J-32l"/>
+                            <constraint firstItem="3dh-xL-h1k" firstAttribute="leading" secondItem="yVi-pw-eI6" secondAttribute="leading" constant="10" id="Pmq-eE-IH9"/>
+                            <constraint firstItem="B1g-pw-mYI" firstAttribute="leading" secondItem="yVi-pw-eI6" secondAttribute="leading" constant="11" id="SeV-ws-KO7"/>
+                            <constraint firstAttribute="trailing" secondItem="3dh-xL-h1k" secondAttribute="trailing" constant="10" id="TcN-xQ-24N"/>
+                            <constraint firstItem="Yoe-Ih-DzZ" firstAttribute="centerY" secondItem="UrD-Un-gOT" secondAttribute="centerY" id="Wyc-9Z-gYa"/>
+                            <constraint firstItem="QvR-ZO-p6W" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="49U-qQ-4uN" secondAttribute="trailing" constant="10" id="bRB-zu-1kI"/>
+                            <constraint firstItem="UrD-Un-gOT" firstAttribute="top" secondItem="3cO-85-Ox1" secondAttribute="top" id="bvX-WF-eM6"/>
+                            <constraint firstItem="IV2-zi-jmv" firstAttribute="leading" secondItem="UrD-Un-gOT" secondAttribute="leading" id="cnk-Zz-l60"/>
+                            <constraint firstAttribute="trailing" secondItem="QvR-ZO-p6W" secondAttribute="trailing" constant="11" id="fgk-vu-WAC"/>
+                            <constraint firstItem="3dh-xL-h1k" firstAttribute="top" secondItem="B1g-pw-mYI" secondAttribute="bottom" constant="10" id="hT9-dL-fWG"/>
+                            <constraint firstItem="UrD-Un-gOT" firstAttribute="leading" secondItem="3cO-85-Ox1" secondAttribute="trailing" constant="10" id="hdz-n1-iJc"/>
+                            <constraint firstItem="3cO-85-Ox1" firstAttribute="top" secondItem="3dh-xL-h1k" secondAttribute="bottom" constant="17" id="jTo-xF-3ih"/>
+                            <constraint firstItem="49U-qQ-4uN" firstAttribute="centerY" secondItem="B1g-pw-mYI" secondAttribute="centerY" id="qxD-34-ckW"/>
+                            <constraint firstItem="IV2-zi-jmv" firstAttribute="bottom" secondItem="3cO-85-Ox1" secondAttribute="bottom" id="rNx-Sz-vHU"/>
+                            <constraint firstItem="49U-qQ-4uN" firstAttribute="leading" secondItem="B1g-pw-mYI" secondAttribute="trailing" constant="7" id="sGV-5F-Php"/>
+                        </constraints>
+                        <userDefinedRuntimeAttributes>
+                            <userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
+                                <real key="value" value="10"/>
+                            </userDefinedRuntimeAttribute>
+                        </userDefinedRuntimeAttributes>
+                    </view>
+                </subviews>
+                <constraints>
+                    <constraint firstItem="yVi-pw-eI6" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="top" constant="10" id="62n-c0-8Ja"/>
+                    <constraint firstAttribute="trailing" secondItem="yVi-pw-eI6" secondAttribute="trailing" constant="14" id="GN1-88-SxQ"/>
+                    <constraint firstItem="yVi-pw-eI6" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leading" constant="14" id="kcc-Dh-wvc"/>
+                    <constraint firstAttribute="bottom" secondItem="yVi-pw-eI6" secondAttribute="bottom" constant="12" id="nGz-Cw-zKm"/>
+                </constraints>
+            </tableViewCellContentView>
+            <viewLayoutGuide key="safeArea" id="aW0-zy-SZf"/>
+            <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+            <connections>
+                <outlet property="chatButton" destination="Yoe-Ih-DzZ" id="gQx-81-zPe"/>
+                <outlet property="courseTime" destination="49U-qQ-4uN" id="iaX-q1-K4j"/>
+                <outlet property="studentAvatar" destination="3cO-85-Ox1" id="Eme-uy-gve"/>
+                <outlet property="studentName" destination="UrD-Un-gOT" id="dal-Eo-Oyd"/>
+                <outlet property="studentSubject" destination="4gL-4k-HYe" id="I41-1H-tE7"/>
+            </connections>
+            <point key="canvasLocation" x="67.938931297709928" y="64.436619718309856"/>
+        </tableViewCell>
+    </objects>
+    <resources>
+        <image name="course_chat" width="19" height="18"/>
+        <image name="course_time" width="16" height="16"/>
+        <image name="student_avatar" width="150" height="150"/>
+        <systemColor name="systemBackgroundColor">
+            <color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+        </systemColor>
+    </resources>
+</document>