You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

470 lines
19KB

  1. #import "Slide.h"
  2. #import "assets/MMMarkdown.h"
  3. static BOOL isSlideEnabled;
  4. static BOOL isSlideDeletedOnly;
  5. static CGFloat pushshiftRequestTimeoutValue;
  6. %group Slide
  7. @implementation FontGenerator
  8. +(UIFont *) fontOfSize:(CGFloat) size submission:(BOOL) isSubmission willOffset:(BOOL) willOffset{
  9. NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
  10. NSString *fontName;
  11. CGFloat fontSize = size;
  12. if (willOffset){
  13. if (isSubmission){
  14. fontSize += ([userDefaults objectForKey:@"POST_FONT_SIZE"] == nil) ? 0 : [userDefaults integerForKey:@"POST_FONT_SIZE"];
  15. } else {
  16. fontSize += ([userDefaults objectForKey:@"COMMENT_FONT_SIZE"] == nil) ? -2 : [userDefaults integerForKey:@"COMMENT_FONT_SIZE"];
  17. }
  18. }
  19. if ([userDefaults stringForKey:(isSubmission ? @"postfont" : @"commentfont")] == nil){
  20. fontName = isSubmission ? @"AvenirNext-DemiBold" : @"AvenirNext-Medium";
  21. } else {
  22. fontName = [userDefaults stringForKey:(isSubmission ? @"postfont" : @"commentfont")];
  23. }
  24. UIFont *font = [UIFont fontWithName:fontName size:fontSize];
  25. if (!font){
  26. font = [UIFont systemFontOfSize:fontSize];
  27. }
  28. return font;
  29. }
  30. +(UIFont *) boldFontOfSize:(CGFloat) size submission:(BOOL) isSubmission willOffset:(BOOL) willOffset {
  31. UIFont *font = [self fontOfSize:size submission:isSubmission willOffset:willOffset];
  32. if ([font.fontName isEqualToString:[UIFont systemFontOfSize:10].fontName]){
  33. return [UIFont boldSystemFontOfSize:font.pointSize];
  34. } else {
  35. UIFontDescriptor *desc = [font.fontDescriptor fontDescriptorWithSymbolicTraits:UIFontDescriptorTraitBold];
  36. if (desc == nil){
  37. return font;
  38. } else {
  39. return [UIFont fontWithDescriptor:desc size: 0];
  40. }
  41. }
  42. }
  43. +(UIFont *) italicFontOfSize:(CGFloat) size submission:(BOOL) isSubmission willOffset:(BOOL) willOffset {
  44. UIFont *font = [self fontOfSize:size submission:isSubmission willOffset:willOffset];
  45. if ([font.fontName isEqualToString:[UIFont systemFontOfSize:10].fontName]){
  46. return [UIFont italicSystemFontOfSize:font.pointSize];
  47. } else {
  48. UIFontDescriptor *desc = [font.fontDescriptor fontDescriptorWithSymbolicTraits:UIFontDescriptorTraitItalic];
  49. if (desc == nil){
  50. return font;
  51. } else {
  52. return [UIFont fontWithDescriptor:desc size: 0];
  53. }
  54. }
  55. }
  56. @end
  57. @implementation ColorUtil
  58. +(UIColor *) accentColorForSub:(NSString *) subreddit{
  59. NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
  60. NSData *colorData = [userDefaults dataForKey:[NSString stringWithFormat:@"accent+%@", subreddit]];
  61. UIColor *color = [NSKeyedUnarchiver unarchiveObjectWithData:colorData];
  62. if (color) {
  63. return color;
  64. } else {
  65. UIColor *baseAccentColor = [NSKeyedUnarchiver unarchiveObjectWithData:[userDefaults dataForKey:@"accentcolor"]];
  66. if (baseAccentColor){
  67. return baseAccentColor;
  68. } else {
  69. return [UIColor colorWithRed:0.161 green:0.475 blue:1.0 alpha:1.0];
  70. }
  71. }
  72. }
  73. +(UIColor *) fontColorForTheme:(NSString *)theme{
  74. UIColor *fontColor;
  75. NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
  76. //if only switch blocks worked with strings...
  77. if ([theme isEqualToString:@"light"]) {
  78. fontColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.87];
  79. } else if ([theme isEqualToString:@"dark"]) {
  80. fontColor = [UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:0.87];
  81. } else if ([theme isEqualToString:@"black"]) {
  82. fontColor = [UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:0.87];
  83. } else if ([theme isEqualToString:@"blue"]) {
  84. fontColor = [UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:0.87];
  85. } else if ([theme isEqualToString:@"sepia"]) {
  86. fontColor = [UIColor colorWithRed:0.243 green:.239 blue:.212 alpha:0.87];
  87. } else if ([theme isEqualToString:@"red"]) {
  88. fontColor = [UIColor colorWithRed:1.0 green:0.969 blue:0.929 alpha:0.87];
  89. } else if ([theme isEqualToString:@"deep"]) {
  90. fontColor = [UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:0.87];
  91. } else if ([theme isEqualToString:@"mint"]) {
  92. fontColor = [UIColor colorWithRed:0.035 green:0.212 blue:0.059 alpha:0.87];
  93. } else if ([theme isEqualToString:@"cream"]) {
  94. fontColor = [UIColor colorWithRed:0.267 green:0.255 blue:0.224 alpha:0.87];
  95. } else if ([theme isEqualToString:@"acontrast"]) {
  96. fontColor = [UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:0.87];
  97. } else if ([theme isEqualToString:@"pink"]) {
  98. fontColor = [UIColor colorWithRed:0.149 green:0.157 blue:0.267 alpha:0.87];
  99. } else if ([theme isEqualToString:@"solarize"]) {
  100. fontColor = [UIColor colorWithRed:0.514 green:0.580 blue:0.588 alpha:0.87];
  101. } else if (!theme) {
  102. fontColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.87];
  103. } else {
  104. NSString *customThemeString = [[userDefaults stringForKey:[NSString stringWithFormat:@"Theme+%@", theme]] stringByRemovingPercentEncoding];
  105. if (customThemeString) {
  106. NSString *customFontColorHex = [customThemeString componentsSeparatedByString:@"#"][4];
  107. fontColor = [UIColor colorWithHex:customFontColorHex];
  108. } else {
  109. fontColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.87];
  110. }
  111. }
  112. return fontColor;
  113. }
  114. +(UIColor *) backgroundColorForTheme:(NSString *) theme{
  115. UIColor *backgroundColor;
  116. NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
  117. if ([theme isEqualToString:@"light"]) {
  118. backgroundColor = [UIColor colorWithRed:0.352 green:0.352 blue:0.352 alpha:1.0];
  119. } else if ([theme isEqualToString:@"dark"]) {
  120. backgroundColor = [UIColor colorWithRed:0.051 green:0.051 blue:0.051 alpha:1.0];
  121. } else if ([theme isEqualToString:@"black"]) {
  122. backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:1.0];
  123. } else if ([theme isEqualToString:@"blue"]) {
  124. backgroundColor = [UIColor colorWithRed:0.071 green:0.094 blue:0.106 alpha:1.0];
  125. } else if ([theme isEqualToString:@"sepia"]) {
  126. backgroundColor = [UIColor colorWithRed:0.310 green:0.302 blue:0.267 alpha:1.0];
  127. } else if ([theme isEqualToString:@"red"]) {
  128. backgroundColor = [UIColor colorWithRed:0.075 green:0.055 blue:0.051 alpha:1.0];
  129. } else if ([theme isEqualToString:@"deep"]) {
  130. backgroundColor = [UIColor colorWithRed:0.035 green:0.035 blue:0.043 alpha:1.0];
  131. } else if ([theme isEqualToString:@"mint"]) {
  132. backgroundColor = [UIColor colorWithRed:0.364 green:0.376 blue:0.357 alpha:1.0];
  133. } else if ([theme isEqualToString:@"cream"]) {
  134. backgroundColor = [UIColor colorWithRed:0.322 green:0.314 blue:0.286 alpha:1.0];
  135. } else if ([theme isEqualToString:@"acontrast"]) {
  136. backgroundColor = [UIColor colorWithRed:0.027 green:0.027 blue:0.24 alpha:1.0];
  137. } else if ([theme isEqualToString:@"pink"]) {
  138. backgroundColor = [UIColor colorWithRed:1.0 green:0.376 blue:0.357 alpha:1.0];
  139. } else if ([theme isEqualToString:@"solarize"]) {
  140. backgroundColor = [UIColor colorWithRed:0.040 green:0.067 blue:0.082 alpha:1.0];
  141. } else if (!theme) {
  142. backgroundColor = [UIColor colorWithRed:0.352 green:0.352 blue:0.352 alpha:1.0];
  143. } else {
  144. NSString *customThemeString = [[userDefaults stringForKey:[NSString stringWithFormat:@"Theme+%@", theme]] stringByRemovingPercentEncoding];
  145. if (customThemeString) {
  146. NSString *customFontColorHex = [customThemeString componentsSeparatedByString:@"#"][3];
  147. backgroundColor = [UIColor colorWithHex:customFontColorHex];
  148. } else {
  149. backgroundColor = [UIColor colorWithRed:0.352 green:0.352 blue:0.352 alpha:1.0];
  150. }
  151. }
  152. return backgroundColor;
  153. }
  154. @end
  155. static UIButton * createUndeleteButton(){
  156. UIButton *undeleteButton = [UIButton buttonWithType:UIButtonTypeCustom];
  157. UIImage *undeleteImage = [UIImage imageWithContentsOfFile:@"/var/mobile/Library/Application Support/TFDidThatSay/eye160white.png"];
  158. UIGraphicsBeginImageContextWithOptions(CGSizeMake(20, 20), NO, 0);
  159. [undeleteImage drawInRect:CGRectMake(0, 0, 20, 20)];
  160. undeleteImage = UIGraphicsGetImageFromCurrentImageContext();
  161. UIGraphicsEndImageContext();
  162. UIGraphicsBeginImageContextWithOptions(CGSizeMake(35, 35), NO, 0);
  163. CGContextRef context = UIGraphicsGetCurrentContext();
  164. UIGraphicsPushContext(context);
  165. [undeleteImage drawAtPoint:CGPointMake(7.5, 7.5)];
  166. UIGraphicsPopContext();
  167. undeleteImage = UIGraphicsGetImageFromCurrentImageContext();
  168. UIGraphicsEndImageContext();
  169. [undeleteButton setImage:undeleteImage forState:UIControlStateNormal];
  170. undeleteButton.imageView.contentMode = UIViewContentModeScaleAspectFit;
  171. return undeleteButton;
  172. }
  173. //because it wont compile without this
  174. %hook RComment
  175. %end
  176. %hook UIColor
  177. %new
  178. +(UIColor *) colorWithHex:(NSString *) arg1 {
  179. if (!arg1){
  180. NSString *firstChar = [arg1 substringToIndex:1];
  181. if ([firstChar isEqualToString:@"#"]){
  182. arg1 = [arg1 substringWithRange:NSMakeRange(1, [arg1 length]-1)];
  183. }
  184. unsigned rgbValue = 0;
  185. NSScanner *scanner = [NSScanner scannerWithString:arg1];
  186. [scanner scanHexInt:&rgbValue];
  187. return [UIColor colorWithRed:((rgbValue & 0xFF0000) >> 16)/255.0 green:((rgbValue & 0xFF00) >> 8)/255.0 blue:(rgbValue & 0xFF)/255.0 alpha:0.87];
  188. } else {
  189. return nil;
  190. }
  191. }
  192. %new
  193. -(NSString *) hexString {
  194. const CGFloat *components = CGColorGetComponents(self.CGColor);
  195. CGFloat r = components[0];
  196. CGFloat g = components[1];
  197. CGFloat b = components[2];
  198. return [NSString stringWithFormat:@"#%02lX%02lX%02lX", lroundf(r * 255), lroundf(g * 255), lroundf(b * 255)];
  199. }
  200. %end
  201. %hook CommentDepthCell
  202. -(void) doShortClick{
  203. %orig;
  204. [self addUndeleteButtonToMenu];
  205. }
  206. -(void) doLongClick {
  207. %orig;
  208. [self addUndeleteButtonToMenu];
  209. }
  210. %new
  211. -(void) addUndeleteButtonToMenu{
  212. NSString *body = [MSHookIvar<id>(self, "comment") body];
  213. if ((isSlideDeletedOnly && ([body isEqualToString:@"[deleted]"] || [body isEqualToString:@"[removed]"])) || !isSlideDeletedOnly){
  214. id controller = MSHookIvar<id>(self, "parent");
  215. if (MSHookIvar<id>(controller, "menuCell")){
  216. UIStackView *menu = MSHookIvar<UIStackView *>(self, "menu");
  217. if (![[[[menu arrangedSubviews] lastObject] actionsForTarget:self forControlEvent:UIControlEventTouchUpInside] containsObject:@"handleUndeleteComment:"]){
  218. UIButton *undeleteButton = createUndeleteButton();
  219. [undeleteButton addTarget:self action:@selector(handleUndeleteComment:) forControlEvents:UIControlEventTouchUpInside];
  220. [menu addArrangedSubview:undeleteButton];
  221. }
  222. }
  223. }
  224. }
  225. %new
  226. -(void) handleUndeleteComment:(id) sender{
  227. [sender setEnabled:NO];
  228. NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
  229. id textStackDisplayView = MSHookIvar<id>(self, "commentBody");
  230. id comment = MSHookIvar<id>(self, "comment");
  231. NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
  232. NSOperationQueue *queue = [[NSOperationQueue alloc] init];
  233. [request setURL:[NSURL URLWithString:[NSString stringWithFormat:@"https://api.pushshift.io/reddit/search/comment/?ids=%@&fields=author,body",[[comment id] componentsSeparatedByString:@"_"][1]]]];
  234. [request setHTTPMethod:@"GET"];
  235. [request setTimeoutInterval:pushshiftRequestTimeoutValue];
  236. [NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
  237. NSString *author = @"[author]";
  238. NSString *body = @"[body]";
  239. if (data != nil && error == nil){
  240. id jsonData = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
  241. if ([[jsonData objectForKey:@"data"] count] != 0){
  242. author = [[jsonData objectForKey:@"data"][0] objectForKey:@"author"];
  243. body = [[jsonData objectForKey:@"data"][0] objectForKey:@"body"];
  244. if ([body isEqualToString:@"[deleted]"] || [body isEqualToString:@"[removed]"]){
  245. body = @"[pushshift was unable to archive this]";
  246. }
  247. } else {
  248. body = @"[pushshift has not archived this yet]";
  249. }
  250. } else if (error != nil || data == nil){
  251. body = [NSString stringWithFormat:@"[an error occured while attempting to contact pushshift api (%@)]", [error localizedDescription]];
  252. }
  253. //Attributed string generation rewrote from Slide_for_Reddit.TextDisplayStackView.createAttributedChunk(...)
  254. UIFont *font = [%c(FontGenerator) fontOfSize:MSHookIvar<CGFloat>(textStackDisplayView, "fontSize") submission:NO willOffset:YES];
  255. NSString *themeName = [userDefaults stringForKey:@"theme"];
  256. UIColor *fontColor = [%c(ColorUtil) fontColorForTheme:themeName];
  257. UIColor *accentColor = [%c(ColorUtil) accentColorForSub:[comment subreddit]];
  258. NSString *html = [%c(MMMarkdown) HTMLStringWithMarkdown:body extensions:MMMarkdownExtensionsGitHubFlavored error:nil];
  259. html = [[html stringByReplacingOccurrencesOfString:@"<sup>" withString:@"<font size=\"1\">"] stringByReplacingOccurrencesOfString:@"</sup>" withString:@"</font>"];
  260. html = [[html stringByReplacingOccurrencesOfString:@"<del>" withString:@"<font color=\"green\">"] stringByReplacingOccurrencesOfString:@"</del>" withString:@"</font>"];
  261. html = [[html stringByReplacingOccurrencesOfString:@"<code>" withString:@"<font color=\"blue\">"] stringByReplacingOccurrencesOfString:@"</code>" withString:@"</font>"];
  262. html = [html stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
  263. DTHTMLAttributedStringBuilder *dthtmlBuilder = [[%c(DTHTMLAttributedStringBuilder) alloc] initWithHTML:[html dataUsingEncoding:NSUTF8StringEncoding] options:@{@"DTUseiOS6Attributes": @YES, @"DTDefaultTextColor": fontColor, @"DTDefaultFontSize": @([font pointSize])} documentAttributes:nil];
  264. NSMutableAttributedString *htmlAttributedString = [[NSMutableAttributedString alloc] initWithAttributedString:[dthtmlBuilder generatedAttributedString]];
  265. NSRange htmlStringRange = NSMakeRange(0, [htmlAttributedString length]);
  266. [[htmlAttributedString mutableString] replaceOccurrencesOfString:@"\t•\t" withString:@" • " options:nil range: htmlStringRange];
  267. [[htmlAttributedString mutableString] replaceOccurrencesOfString:@"\t◦\t" withString:@"  ◦ " options:nil range: htmlStringRange];
  268. [[htmlAttributedString mutableString] replaceOccurrencesOfString:@"\t▪\t" withString:@" ▪ " options:nil range: htmlStringRange];
  269. [htmlAttributedString removeAttribute:@"CTForegroundColorFromContext" range:htmlStringRange];
  270. [htmlAttributedString enumerateAttributesInRange:htmlStringRange options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired usingBlock:^(NSDictionary *attributes, NSRange range, BOOL *stop) {
  271. for (NSString *key in attributes){
  272. if ([(UIColor *) attributes[key] isKindOfClass:[UIColor class]]){
  273. UIColor *attrColor = (UIColor *) attributes[key];
  274. if ([[attrColor hexString] isEqualToString:@"#0000FF"]){
  275. UIFont *tempFont = [UIFont fontWithName:@"Courier" size:font.pointSize];
  276. [htmlAttributedString setAttributes:@{NSForegroundColorAttributeName: accentColor, NSBackgroundColorAttributeName: [%c(ColorUtil) backgroundColorForTheme:themeName], NSFontAttributeName: (tempFont ? tempFont : font)} range:range];
  277. } else if ([[attrColor hexString] isEqualToString:@"#008000"]) {
  278. [htmlAttributedString setAttributes:@{NSForegroundColorAttributeName: fontColor, NSFontAttributeName:font} range:range];
  279. }
  280. } else if ([(NSURL *) attributes[key] isKindOfClass:[NSURL class]]){
  281. NSURL *attrUrl = (NSURL *)attributes[key];
  282. if (([userDefaults objectForKey:@"ENLARGE_LINKS"] == nil) ? YES : [userDefaults boolForKey:@"ENLARGE_LINKS"]){
  283. [htmlAttributedString addAttribute:NSFontAttributeName value:[%c(FontGenerator) boldFontOfSize:18 submission:NO willOffset:YES] range:range];
  284. }
  285. [htmlAttributedString addAttribute:NSForegroundColorAttributeName value:accentColor range:range];
  286. [htmlAttributedString addAttribute:NSUnderlineColorAttributeName value:[UIColor clearColor] range:range];
  287. //skipping showLinkContentType b/c not necessary and spoilers b/c MMMarkdown doesn't support them
  288. [htmlAttributedString yy_setTextHighlightRange:range color: accentColor backgroundColor:nil userInfo:@{@"url": attrUrl}];
  289. break;
  290. }
  291. }
  292. }];
  293. [htmlAttributedString beginEditing];
  294. [htmlAttributedString enumerateAttribute:NSFontAttributeName inRange:NSMakeRange(0, [htmlAttributedString length]) options:nil usingBlock:^(id value, NSRange range, BOOL *stop){
  295. UIFont *attrFont = (UIFont *)value;
  296. BOOL isBold = (attrFont.fontDescriptor.symbolicTraits & UIFontDescriptorTraitBold) != 0;
  297. BOOL isItalic = (attrFont.fontDescriptor.symbolicTraits & UIFontDescriptorTraitItalic) != 0;
  298. UIFont *newFont = font;
  299. if (isBold){
  300. newFont = [%c(FontGenerator) boldFontOfSize:attrFont.pointSize submission:NO willOffset:NO];
  301. } else if (isItalic){
  302. newFont = [%c(FontGenerator) italicFontOfSize:attrFont.pointSize submission:NO willOffset:NO];
  303. }
  304. [htmlAttributedString removeAttribute:NSFontAttributeName range:range];
  305. [htmlAttributedString addAttribute:NSFontAttributeName value:newFont range:range];
  306. }];
  307. [htmlAttributedString endEditing];
  308. NSMutableAttributedString *newCommentText = [MSHookIvar<NSMutableAttributedString *>(self, "cellContent") initWithAttributedString:htmlAttributedString];
  309. NSAttributedString *tempAttributedString = [[NSAttributedString alloc] initWithString:@""];
  310. [newCommentText appendAttributedString:tempAttributedString]; //to keep the compiler happy
  311. [comment setAuthor:author];
  312. [comment setBody:body];
  313. id controller = MSHookIvar<id>(self, "parent");
  314. [self performSelectorOnMainThread:@selector(showMenu:) withObject:nil waitUntilDone:YES];
  315. [MSHookIvar<id>(controller, "tableView") performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:NO];
  316. [sender setEnabled:YES];
  317. }];
  318. }
  319. %end
  320. %end
  321. static void loadPrefs(){
  322. NSMutableDictionary *prefs = [[NSMutableDictionary alloc] initWithContentsOfFile:@"/User/Library/Preferences/com.lint.undelete.prefs.plist"];
  323. if (prefs){
  324. if ([prefs objectForKey:@"isSlideEnabled"] != nil){
  325. isSlideEnabled = [[prefs objectForKey:@"isSlideEnabled"] boolValue];
  326. } else {
  327. isSlideEnabled = YES;
  328. }
  329. if ([prefs objectForKey:@"isSlideDeletedOnly"] != nil){
  330. isSlideDeletedOnly = [[prefs objectForKey:@"isSlideDeletedOnly"] boolValue];
  331. } else {
  332. isSlideDeletedOnly = YES;
  333. }
  334. if ([prefs objectForKey:@"requestTimeoutValue"] != nil){
  335. pushshiftRequestTimeoutValue = [[prefs objectForKey:@"requestTimeoutValue"] doubleValue];
  336. } else {
  337. pushshiftRequestTimeoutValue = 10;
  338. }
  339. } else {
  340. isSlideEnabled = YES;
  341. isSlideDeletedOnly = YES;
  342. pushshiftRequestTimeoutValue = 10;
  343. }
  344. }
  345. static void prefsChanged(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo) {
  346. loadPrefs();
  347. }
  348. %ctor {
  349. loadPrefs();
  350. NSString* processName = [[NSProcessInfo processInfo] processName];
  351. if ([processName isEqualToString:@"Slide for Reddit"]){
  352. if (isSlideEnabled){
  353. CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), NULL, prefsChanged, CFSTR("com.lint.undelete.prefs.changed"), NULL, CFNotificationSuspensionBehaviorDeliverImmediately);
  354. %init(Slide, CommentDepthCell = objc_getClass("Slide_for_Reddit.CommentDepthCell"), RComment = objc_getClass("Slide_for_Reddit.RSubmission"));
  355. }
  356. }
  357. }