// // WRBookmark.m // WeRead (读书) // Reverse-engineered implementation reconstruction // // Detailed pseudo-code based on binary analysis (many NSString ivars, // WRBook, WRMPReview, NSArray, NSDictionary, NSMutableSet) and // contextual knowledge of WeRead's bookmark/annotation system. // #import "WRBookmark.h" // --------------------------------------------------------------------------- // Color mapping // --------------------------------------------------------------------------- static NSDictionary *sColorMap = nil; @implementation WRBookmark #pragma mark - Class Initialization + (void)initialize { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sColorMap = @{ @"yellow" : [UIColor colorWithRed:1.0 green:0.9 blue:0.3 alpha:0.4], @"blue" : [UIColor colorWithRed:0.3 green:0.6 blue:1.0 alpha:0.4], @"red" : [UIColor colorWithRed:1.0 green:0.3 blue:0.3 alpha:0.4], @"green" : [UIColor colorWithRed:0.3 green:0.9 blue:0.4 alpha:0.4], @"purple" : [UIColor colorWithRed:0.7 green:0.3 blue:0.9 alpha:0.4], }; }); } #pragma mark - Factory Methods + (instancetype)bookmarkWithBookId:(NSString *)bookId chapterUid:(NSString *)chapterUid offset:(NSInteger)offset text:(NSString *)text type:(WRBookmarkType)type { WRBookmark *bm = [[WRBookmark alloc] init]; bm.bookId = bookId; bm.chapterUid = chapterUid; bm.chapterOffset = offset; bm.markText = text; bm.type = type; bm.createTime = [[NSDate date] timeIntervalSince1970]; bm.updateTime = bm.createTime; bm.isSynced = NO; // Generate a unique bookmark ID bm.bookmarkId = [[NSUUID UUID] UUIDString]; return bm; } + (instancetype)highlightWithBookId:(NSString *)bookId chapterUid:(NSString *)chapterUid startPos:(NSInteger)startPos endPos:(NSInteger)endPos text:(NSString *)text colorStyle:(NSString *)colorStyle { WRBookmark *bm = [self bookmarkWithBookId:bookId chapterUid:chapterUid offset:startPos text:text type:WRBookmarkTypeHighlight]; bm.startPos = startPos; bm.endPos = endPos; bm.rangeLength = endPos - startPos; bm.colorStyle = colorStyle ?: @"yellow"; return bm; } #pragma mark - Serialization - (NSDictionary *)toDictionary { NSMutableDictionary *dict = [NSMutableDictionary dictionary]; if (_bookmarkId) dict[@"bookmarkId"] = _bookmarkId; if (_bookId) dict[@"bookId"] = _bookId; if (_chapterUid) dict[@"chapterUid"] = _chapterUid; dict[@"chapterOffset"] = @(_chapterOffset); dict[@"chapterIndex"] = @(_chapterIndex); if (_markText) dict[@"markText"] = _markText; if (_noteContent) dict[@"noteContent"] = _noteContent; dict[@"type"] = @(_type); if (_colorStyle) dict[@"colorStyle"] = _colorStyle; dict[@"startPos"] = @(_startPos); dict[@"endPos"] = @(_endPos); dict[@"rangeLength"] = @(_rangeLength); if (_anchorId) dict[@"anchorId"] = _anchorId; if (_rangeKey) dict[@"rangeKey"] = _rangeKey; dict[@"createTime"] = @(_createTime); dict[@"updateTime"] = @(_updateTime); dict[@"isSynced"] = @(_isSynced); if (_syncKey) dict[@"syncKey"] = _syncKey; if (_rangeInfo) dict[@"rangeInfo"] = _rangeInfo; if (_extraMetadata) dict[@"extraMetadata"] = _extraMetadata; if (_tags.count > 0) { dict[@"tags"] = [_tags allObjects]; } return [dict copy]; } + (nullable instancetype)fromDictionary:(NSDictionary *)dict { if (!dict) return nil; WRBookmark *bm = [[WRBookmark alloc] init]; bm.bookmarkId = dict[@"bookmarkId"]; bm.bookId = dict[@"bookId"]; bm.chapterUid = dict[@"chapterUid"]; bm.chapterOffset = [dict[@"chapterOffset"] integerValue]; bm.chapterIndex = [dict[@"chapterIndex"] integerValue]; bm.markText = dict[@"markText"]; bm.noteContent = dict[@"noteContent"]; bm.type = [dict[@"type"] integerValue]; bm.colorStyle = dict[@"colorStyle"]; bm.startPos = [dict[@"startPos"] integerValue]; bm.endPos = [dict[@"endPos"] integerValue]; bm.rangeLength = [dict[@"rangeLength"] integerValue]; bm.anchorId = dict[@"anchorId"]; bm.rangeKey = dict[@"rangeKey"]; bm.createTime = [dict[@"createTime"] doubleValue]; bm.updateTime = [dict[@"updateTime"] doubleValue]; bm.isSynced = [dict[@"isSynced"] boolValue]; bm.syncKey = dict[@"syncKey"]; bm.rangeInfo = dict[@"rangeInfo"]; bm.extraMetadata = dict[@"extraMetadata"]; NSArray *tags = dict[@"tags"]; if (tags) { bm.tags = [NSMutableSet setWithArray:tags]; } return bm; } - (nullable NSData *)toJSONData { NSDictionary *dict = [self toDictionary]; return [NSJSONSerialization dataWithJSONObject:dict options:0 error:nil]; } + (nullable instancetype)fromJSONData:(NSData *)data { if (!data) return nil; NSError *error = nil; NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; if (!dict || ![dict isKindOfClass:[NSDictionary class]]) return nil; return [self fromDictionary:dict]; } #pragma mark - Display - (NSString *)displaySummary { switch (_type) { case WRBookmarkTypeHighlight: return [NSString stringWithFormat:@"[Highlight] %@", _markText ?: @""]; case WRBookmarkTypeUnderline: return [NSString stringWithFormat:@"[Underline] %@", _markText ?: @""]; case WRBookmarkTypeMark: return [NSString stringWithFormat:@"[Bookmark] Chapter %@", _chapterUid ?: @""]; case WRBookmarkTypeNote: return [NSString stringWithFormat:@"[Note] %@", _noteContent ?: _markText ?: @""]; case WRBookmarkTypePencil: return @"[Pencil Note]"; default: return _markText ?: @""; } } - (UIColor *)highlightUIColor { if (!_colorStyle) { return sColorMap[@"yellow"] ?: [UIColor yellowColor]; } return sColorMap[_colorStyle] ?: [UIColor yellowColor]; } #pragma mark - NSObject - (NSString *)description { return [NSString stringWithFormat:@"", _bookmarkId, (long)_type, _bookId, _chapterUid, [_markText substringToIndex:MIN(30, _markText.length)]]; } - (BOOL)isEqual:(id)object { if (self == object) return YES; if (![object isKindOfClass:[WRBookmark class]]) return NO; WRBookmark *other = (WRBookmark *)object; return [self.bookmarkId isEqualToString:other.bookmarkId]; } - (NSUInteger)hash { return self.bookmarkId.hash; } @end