BJPChatMessageViewController.m 13.3 KB
Newer Older
辛亚鹏's avatar
辛亚鹏 committed
1 2
//
//  BJPChatMessageViewController.m
huangjie's avatar
huangjie committed
3
//  BJPlaybackUI
辛亚鹏's avatar
辛亚鹏 committed
4 5 6 7 8
//
//  Created by 辛亚鹏 on 2017/8/23.
//
//

huangjie's avatar
huangjie committed
9
#import <BJLiveBase/BJLiveBase+UIKit.h>
戴曦嘉's avatar
1.4.8  
戴曦嘉 committed
10
#import <BJLiveBase/UITableView+BJLHeightCache.h>
辛亚鹏's avatar
辛亚鹏 committed
11 12 13 14 15

#import "BJPChatMessageViewController.h"
#import "BJPChatMessageTableViewCell.h"
#import "BJPAppearance.h"

huangjie's avatar
huangjie committed
16 17
static NSInteger const pageSize = 50;

辛亚鹏's avatar
辛亚鹏 committed
18 19 20 21
NS_ASSUME_NONNULL_BEGIN

@interface BJPChatMessageViewController () <UITableViewDelegate, UITableViewDataSource>

huangjie's avatar
huangjie committed
22
@property (nonatomic, weak) BJVRoom *room;
凡义's avatar
凡义 committed
23 24
@property (nonatomic) NSMutableArray<BJLMessage *> *allMessages;
@property (nonatomic) NSMutableArray<BJLMessage *> *loadedMessages;
辛亚鹏's avatar
辛亚鹏 committed
25 26

@property (nonatomic, readwrite) UITableView *tableView;
凡义's avatar
凡义 committed
27
@property (nonatomic) BOOL wasAtTheBottomOfTableView;
huangjie's avatar
huangjie committed
28
@property (nonatomic) BOOL loadingMore;
辛亚鹏's avatar
辛亚鹏 committed
29 30 31 32 33 34 35

@end

@implementation BJPChatMessageViewController

- (void)viewDidLoad {
    [super viewDidLoad];
huangjie's avatar
huangjie committed
36
    [self setupSubviews];
辛亚鹏's avatar
辛亚鹏 committed
37 38
}

凡义's avatar
凡义 committed
39 40 41 42 43 44 45 46 47 48 49 50
- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    [self scrollToTheEndTableView];
}

- (void)viewWillLayoutSubviews {
    [super viewWillLayoutSubviews];
    self.wasAtTheBottomOfTableView = [self atTheBottomOfTableView];
}

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];
戴曦嘉's avatar
戴曦嘉 committed
51

huangjie's avatar
huangjie committed
52
    self.tableView.scrollIndicatorInsets = bjl_set(self.tableView.scrollIndicatorInsets, {
凡义's avatar
凡义 committed
53
        CGFloat adjustment = CGRectGetWidth(self.view.frame) - 8.5; // 8.5 = 2.5 + 3.0 * 2;
戴曦嘉's avatar
戴曦嘉 committed
54
        set.left = -adjustment;
凡义's avatar
凡义 committed
55 56
        set.right = adjustment;
    });
戴曦嘉's avatar
戴曦嘉 committed
57

凡义's avatar
凡义 committed
58 59 60 61 62
    if (self.wasAtTheBottomOfTableView && ![self atTheBottomOfTableView]) {
        [self scrollToTheEndTableView];
    }
}

辛亚鹏's avatar
辛亚鹏 committed
63
- (void)loadView {
戴曦嘉's avatar
戴曦嘉 committed
64
    self.view = [BJLHitTestView viewWithFrame:[UIScreen mainScreen].bounds hitTestBlock:^UIView *_Nullable(UIView *_Nullable hitView, CGPoint point, UIEvent *_Nullable event) {
huangjie's avatar
huangjie committed
65
        UITableViewCell *cell = [hitView bjl_closestViewOfClass:[UITableViewCell class] includeSelf:NO];
辛亚鹏's avatar
辛亚鹏 committed
66
        if (cell && hitView != cell.contentView) {
戴曦嘉's avatar
2.10.0  
戴曦嘉 committed
67
            //            hitView.clipsToBounds = YES;
辛亚鹏's avatar
辛亚鹏 committed
68 69 70 71 72 73
            return hitView;
        }
        return nil;
    }];
}

huangjie's avatar
huangjie committed
74 75 76 77 78 79 80 81 82 83 84 85 86
#pragma mark - subviews

- (void)setupSubviews {
    [self.view addSubview:self.tableView];
    [self.tableView bjl_makeConstraints:^(BJLConstraintMaker *make) {
        make.edges.equalTo(self.view).priorityHigh();
    }];
}

#pragma mark - observers

- (void)setupObserversWithRoom:(BJVRoom *)room {
    self.room = room;
戴曦嘉's avatar
戴曦嘉 committed
87

huangjie's avatar
huangjie committed
88 89 90
    bjl_weakify(self);
    // 消息覆盖更新
    [self bjl_observe:BJLMakeMethod(room.messageVM, receivedMessagesDidOverwrite:)
凡义's avatar
凡义 committed
91
             observer:^BOOL(NSArray<BJLMessage *> *messageArray) {
戴曦嘉's avatar
戴曦嘉 committed
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
                 bjl_strongify(self);
                 if (self.room.playbackInfo || self.room.downloadItem.playInfo) {
                     BOOL isShowChatList = self.room.isLocalVideo ? self.room.downloadItem.playInfo.isShowChatList : self.room.playbackInfo.isShowChatList;
                     if (isShowChatList) {
                         // !!!: 回放消息数有限,且 key 是唯一的,重置列表时不需要清除高度缓存
                         self.allMessages = [messageArray mutableCopy];
                         [self resetLoadedMessages];
                         [self.tableView reloadData];
                         [self scrollToTheEndTableView];
                     }
                     else {
                         return NO;
                     }
                 }
                 return YES;
             }];

huangjie's avatar
huangjie committed
109 110
    // 消息增量更新
    [self bjl_observe:BJLMakeMethod(room.messageVM, didReceiveMessages:)
凡义's avatar
凡义 committed
111
             observer:^BOOL(NSArray<BJLMessage *> *messageArray) {
戴曦嘉's avatar
戴曦嘉 committed
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
                 bjl_strongify(self);
                 if (!messageArray.count) {
                     return YES;
                 }

                 if (self.room.playbackInfo || self.room.downloadItem.playInfo) {
                     BOOL isShowChatList = self.room.isLocalVideo ? self.room.downloadItem.playInfo.isShowChatList : self.room.playbackInfo.isShowChatList;
                     if (isShowChatList) {
                         [self.allMessages addObjectsFromArray:messageArray];
                         [self.loadedMessages addObjectsFromArray:messageArray];
                         BOOL wasAtTheBottom = [self atTheBottomOfTableView];
                         if (wasAtTheBottom && self.loadedMessages.count > pageSize) {
                             // !!!: 当前在 tableView 底部,reload 之后会再次滑动到底部,重置 self.loadedMessages,控制数目至最多 pageSize 条
                             [self resetLoadedMessages];
                         }
                         [self.tableView reloadData];
                         if (wasAtTheBottom) {
                             [self scrollToTheEndTableView];
                         }
                     }
                     else {
                         return NO;
                     }
                 }
                 return YES;
             }];
huangjie's avatar
huangjie committed
138 139
}

辛亚鹏's avatar
辛亚鹏 committed
140 141 142 143 144 145 146
#pragma mark - uitableview dataSource

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
huangjie's avatar
huangjie committed
147
    return self.loadedMessages.count;
辛亚鹏's avatar
辛亚鹏 committed
148 149 150
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
凡义's avatar
凡义 committed
151
    BJLMessage *message = bjl_as([self.loadedMessages bjl_objectAtIndex:indexPath.row], BJLMessage);
huangjie's avatar
source  
huangjie committed
152 153 154
    NSString *cellIdentifier = [BJPChatMessageTableViewCell cellIdentifierForMessageType:message.type];
    BJPChatMessageTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier
                                                                        forIndexPath:indexPath];
戴曦嘉's avatar
戴曦嘉 committed
155
    BOOL hidden = self.room.playbackInfo.enableHideStudentPhoneNumber && message.fromUser.isStudent;
huangjie's avatar
source  
huangjie committed
156 157
    [cell updateWithMessage:message
                placeholder:message.imageURLString ? [UIImage bjp_imageNamed:@"bjp_img_placeholder"] : nil
戴曦嘉's avatar
戴曦嘉 committed
158 159 160
             tableViewWidth:CGRectGetWidth(self.tableView.bounds)
          shouldHiddenPhone:hidden];

huangjie's avatar
source  
huangjie committed
161
    bjl_weakify(self);
戴曦嘉's avatar
戴曦嘉 committed
162
    cell.updateCellConstraintsCallback = cell.updateCellConstraintsCallback ?: ^(BJPChatMessageTableViewCell *_Nullable cell) {
huangjie's avatar
source  
huangjie committed
163 164 165 166
        bjl_strongify(self);
        NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
        if (indexPath) {
            BOOL wasAtTheBottomOfTableView = [self atTheBottomOfTableView];
戴曦嘉's avatar
2.5.1  
戴曦嘉 committed
167
            [self.tableView bjl_clearHeightCachesWithKey:[self keyWithIndexPath:indexPath message:message]];
huangjie's avatar
source  
huangjie committed
168 169 170
            [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
            if (wasAtTheBottomOfTableView) {
                [self scrollToTheEndTableView];
辛亚鹏's avatar
辛亚鹏 committed
171
            }
huangjie's avatar
source  
huangjie committed
172 173
        }
    };
戴曦嘉's avatar
戴曦嘉 committed
174

huangjie's avatar
source  
huangjie committed
175
    return cell;
辛亚鹏's avatar
辛亚鹏 committed
176 177 178 179 180 181 182 183
}

- (void)scrollToTheEndTableView {
    NSInteger section = 0;
    NSInteger numberOfRows = [self.tableView numberOfRowsInSection:section];
    if (numberOfRows <= 0) {
        return;
    }
戴曦嘉's avatar
戴曦嘉 committed
184

辛亚鹏's avatar
辛亚鹏 committed
185 186 187 188 189 190 191 192 193 194 195
    NSIndexPath *indexPath = [NSIndexPath indexPathForRow:numberOfRows - 1
                                                inSection:section];
    [self.tableView scrollToRowAtIndexPath:indexPath
                          atScrollPosition:UITableViewScrollPositionBottom
                                  animated:NO];
}

- (CGFloat)atTheTopOfTableView {
    CGFloat contentOffsetY = self.tableView.contentOffset.y;
    CGFloat top = self.tableView.contentInset.top;
    CGFloat topOffset = contentOffsetY + top;
huangjie's avatar
huangjie committed
196
    return topOffset <= 0.0;
辛亚鹏's avatar
辛亚鹏 committed
197 198 199 200 201 202 203 204
}

- (CGFloat)atTheBottomOfTableView {
    CGFloat contentOffsetY = self.tableView.contentOffset.y;
    CGFloat bottom = self.tableView.contentInset.bottom;
    CGFloat viewHeight = CGRectGetHeight(self.tableView.frame);
    CGFloat contentHeight = self.tableView.contentSize.height;
    CGFloat bottomOffset = contentOffsetY + viewHeight - bottom - contentHeight;
huangjie's avatar
huangjie committed
205
    return bottomOffset >= 0.0 - BJPViewSpaceS;
辛亚鹏's avatar
辛亚鹏 committed
206 207
}

戴曦嘉's avatar
戴曦嘉 committed
208
#pragma mark - delegate
辛亚鹏's avatar
辛亚鹏 committed
209

戴曦嘉's avatar
1.4.8  
戴曦嘉 committed
210
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
凡义's avatar
凡义 committed
211
    BJLMessage *message = bjl_as([self.loadedMessages bjl_objectAtIndex:indexPath.row], BJLMessage);
戴曦嘉's avatar
2.5.1  
戴曦嘉 committed
212
    NSString *key = [self keyWithIndexPath:indexPath message:message];
huangjie's avatar
huangjie committed
213
    NSString *identifier = [BJPChatMessageTableViewCell cellIdentifierForMessageType:message.type];
戴曦嘉's avatar
戴曦嘉 committed
214
    bjl_weakify(self);
huangjie's avatar
huangjie committed
215
    void (^configuration)(BJPChatMessageTableViewCell *cell) =
戴曦嘉's avatar
戴曦嘉 committed
216 217 218 219 220 221 222 223 224 225
        ^(BJPChatMessageTableViewCell *cell) {
            bjl_strongify(self);
            cell.bjl_autoSizing = YES;
            BOOL hidden = self.room.playbackInfo.enableHideStudentPhoneNumber && message.fromUser.isStudent;
            [cell updateWithMessage:message
                        placeholder:message.imageURLString ? [UIImage bjp_imageNamed:@"bjp_img_placeholder"] : nil
                     tableViewWidth:CGRectGetWidth(self.tableView.bounds)
                  shouldHiddenPhone:hidden];
        };

huangjie's avatar
huangjie committed
226 227 228 229
    return [tableView bjl_cellHeightWithKey:key identifier:identifier configuration:configuration];
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
凡义's avatar
凡义 committed
230 231
    BJLMessage *message = bjl_as([self.loadedMessages bjl_objectAtIndex:indexPath.row], BJLMessage);
    if (message && message.type == BJLMessageType_image) {
huangjie's avatar
huangjie committed
232 233 234 235 236 237 238
        BJPChatMessageTableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
        if (self.showImageBrowserCallback) {
            self.showImageBrowserCallback(cell.imgView);
        }
    }
}

戴曦嘉's avatar
戴曦嘉 committed
239
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
huangjie's avatar
huangjie committed
240 241 242
    if (!scrollView.dragging && !scrollView.decelerating) {
        return;
    }
戴曦嘉's avatar
戴曦嘉 committed
243

huangjie's avatar
huangjie committed
244 245 246
    if ([self atTheTopOfTableView] && !self.loadingMore) {
        [self loadMoreMessages];
    }
戴曦嘉's avatar
1.4.8  
戴曦嘉 committed
247 248
}

辛亚鹏's avatar
辛亚鹏 committed
249 250
#pragma mark - get set

凡义's avatar
凡义 committed
251
- (NSString *)keyWithIndexPath:(NSIndexPath *)indexPath message:(BJLMessage *)message {
戴曦嘉's avatar
2.5.1  
戴曦嘉 committed
252 253 254 255
    NSString *key = [NSString stringWithFormat:@"%@-%@-%td", message.ID, message.fromUser.ID, message.offsetTimestamp];
    return key;
}

凡义's avatar
凡义 committed
256
- (NSMutableArray<BJLMessage *> *)allMessages {
huangjie's avatar
huangjie committed
257 258 259 260 261 262
    if (!_allMessages) {
        _allMessages = [NSMutableArray array];
    }
    return _allMessages;
}

凡义's avatar
凡义 committed
263
- (NSMutableArray<BJLMessage *> *)loadedMessages {
huangjie's avatar
huangjie committed
264 265 266 267 268 269
    if (!_loadedMessages) {
        _loadedMessages = [NSMutableArray array];
    }
    return _loadedMessages;
}

辛亚鹏's avatar
辛亚鹏 committed
270 271
- (UITableView *)tableView {
    if (!_tableView) {
huangjie's avatar
huangjie committed
272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289
        _tableView = ({
            UITableView *tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
            tableView.delegate = self;
            tableView.dataSource = self;
            tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
            tableView.backgroundColor = [UIColor clearColor];
            tableView.indicatorStyle = UIScrollViewIndicatorStyleBlack;
            tableView.showsVerticalScrollIndicator = NO;
            tableView.translatesAutoresizingMaskIntoConstraints = NO;
            tableView.allowsSelection = YES;
            tableView.contentInset = bjl_set(tableView.contentInset, {
                set.top = set.bottom = BJPViewSpaceS;
            });
            tableView.scrollIndicatorInsets = bjl_set(tableView.contentInset, {
                set.top = set.bottom = BJPViewSpaceS;
            });
            for (NSString *cellIdentifier in [BJPChatMessageTableViewCell allCellIdentifiers]) {
                [tableView registerClass:[BJPChatMessageTableViewCell class]
戴曦嘉's avatar
戴曦嘉 committed
290
                    forCellReuseIdentifier:cellIdentifier];
huangjie's avatar
huangjie committed
291 292
            }
            tableView.rowHeight = UITableViewAutomaticDimension;
凡义's avatar
凡义 committed
293
            tableView.estimatedRowHeight = [BJPChatMessageTableViewCell estimatedRowHeightForMessageType:BJLMessageType_text];
huangjie's avatar
huangjie committed
294 295
            tableView;
        });
辛亚鹏's avatar
辛亚鹏 committed
296 297 298 299
    }
    return _tableView;
}

huangjie's avatar
huangjie committed
300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321
#pragma mark - private

- (void)resetLoadedMessages {
    [self.loadedMessages removeAllObjects];
    NSInteger allCount = self.allMessages.count;
    NSInteger loadCount = MIN(allCount, pageSize);
    NSInteger loadStartIndex = MAX(allCount - loadCount, 0);
    if (loadCount > 0) {
        self.loadedMessages = [[self.allMessages subarrayWithRange:NSMakeRange(loadStartIndex, loadCount)] mutableCopy];
        self.loadingMore = YES;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            self.loadingMore = NO;
        });
        // !!!: no reloadData
    }
}

- (void)loadMoreMessages {
    NSInteger loadedCount = self.loadedMessages.count;
    NSInteger allCount = self.allMessages.count;
    NSInteger loadCount = MIN(allCount - loadedCount, pageSize);
    NSInteger loadStartIndex = MAX(allCount - loadedCount - loadCount, 0);
戴曦嘉's avatar
戴曦嘉 committed
322

huangjie's avatar
huangjie committed
323 324 325 326 327 328 329
    if (loadCount > 0) {
        self.loadedMessages = [[self.allMessages subarrayWithRange:NSMakeRange(loadStartIndex, loadCount + loadedCount)] mutableCopy];
        // !!!: 0.5 秒之内只触发一次
        self.loadingMore = YES;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            self.loadingMore = NO;
        });
戴曦嘉's avatar
戴曦嘉 committed
330

huangjie's avatar
huangjie committed
331 332 333 334
        // !!!: reload 时不响应拖动,避免画面跳动
        self.tableView.scrollEnabled = NO;
        [self.tableView reloadData];
        self.tableView.scrollEnabled = YES;
戴曦嘉's avatar
戴曦嘉 committed
335

huangjie's avatar
huangjie committed
336 337 338 339 340 341
        // !!!: 滑动到加载前的消息处
        NSIndexPath *indexPath = [NSIndexPath indexPathForRow:loadCount inSection:0];
        [self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:NO];
    }
}

辛亚鹏's avatar
辛亚鹏 committed
342 343 344
@end

NS_ASSUME_NONNULL_END