// // FloatingWindow.m // nochange // // Created by mac on 2024/7/29. // #import "FloatingWindow.h" #import "IPhoneHertbeat.h" #import "MyAdTask2.h" #import "MyEventBus.h" #import "UIView+Toast.h" #import "XSHelper.h" #import "XSPhoneConfig.h" #import "XSPhoneInfo.h" #import @interface FloatingWindow () { NSMutableArray *loadTimestamps; NSMutableArray *showTimestamps; NSTimer *updateTimer; } @end @implementation FloatingWindow - (instancetype)initWithFrame { CGRect screenBounds = [UIScreen mainScreen].bounds; CGFloat floatingWindowWidth = 180; CGFloat floatingWindowHeight = 160; CGFloat xPosition = 0; CGFloat yPosition = screenBounds.size.height - floatingWindowHeight; CGRect frame = CGRectMake(xPosition, yPosition, floatingWindowWidth, floatingWindowHeight); self = [super initWithFrame:frame]; if (self) { UIPanGestureRecognizer *panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(detectPan:)]; [self addGestureRecognizer:panRecognizer]; self.backgroundColor = [UIColor grayColor]; // 默认灰色背景 self.layer.cornerRadius = 10; self.clipsToBounds = YES; [self setupUI]; [self updateInfo]; self.http = [[XSHttpHelper alloc] init]; [[MyEventBus sharedInstance] registerSubscriber:self]; loadTimestamps = [[NSMutableArray alloc] init]; showTimestamps = [[NSMutableArray alloc] init]; [self startUpdatingCounts]; // 启动定时任务 } return self; } - (void)dealloc { [[MyEventBus sharedInstance] unregisterSubscriber:self]; [self stopUpdatingCounts]; // 停止定时任务 } // 定时任务方法 - (void)startUpdatingCounts { updateTimer = [NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:@selector(updateCounts) userInfo:nil repeats:YES]; [[NSRunLoop mainRunLoop] addTimer:updateTimer forMode:NSRunLoopCommonModes]; } // 停止定时任务 - (void)stopUpdatingCounts { if ([updateTimer isValid]) { [updateTimer invalidate]; updateTimer = nil; } } // 更新文件夹中的文件数量 - (void)updateCounts { // 将耗时的目录统计放到后台线程,避免阻塞主线程导致卡顿 dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{ NSString *loadDir = @"/User/Documents/ad/load"; NSString *showDir = @"/User/Documents/ad/show"; // 统计当前有效文件数量(不在此处删除) NSInteger loadCount = [self countValidFilesInDirectory:loadDir]; NSInteger showCount = [self countValidFilesInDirectory:showDir]; // 异步执行清理过期文件,避免与其他进程写入产生竞争 [self cleanupExpiredFilesInDirectory:loadDir]; [self cleanupExpiredFilesInDirectory:showDir]; // UI 更新在主线程 dispatch_async(dispatch_get_main_queue(), ^{ self.infoLabel.text = [NSString stringWithFormat:@"S:%@ / L:%@", @(showCount), @(loadCount)]; [self updateBackgroundColorByShowCount:showCount]; }); }); } // 用于统计有效(1小时内修改)的文件数量,跳过不可读/属性异常的文件 - (NSInteger)countValidFilesInDirectory:(NSString *)directoryPath { NSFileManager *fileManager = [NSFileManager defaultManager]; NSURL *dirURL = [NSURL fileURLWithPath:directoryPath isDirectory:YES]; NSDate *oneHourAgo = [NSDate dateWithTimeIntervalSinceNow:-3600]; __block NSInteger count = 0; // 使用目录枚举器,减少一次性加载全部文件列表的内存压力 NSDirectoryEnumerator *enumerator = [fileManager enumeratorAtURL:dirURL includingPropertiesForKeys:@[NSURLIsRegularFileKey, NSURLContentModificationDateKey] options:(NSDirectoryEnumerationSkipsSubdirectoryDescendants | NSDirectoryEnumerationSkipsHiddenFiles) errorHandler:^BOOL(NSURL *url, NSError *error) { // 读取错误时跳过该条目,保证健壮性 return YES; }]; for (NSURL *fileURL in enumerator) { // 仅统计普通文件 NSNumber *isRegular = nil; if (![fileURL getResourceValue:&isRegular forKey:NSURLIsRegularFileKey error:nil] || !isRegular.boolValue) { continue; } // 跳过不可读文件,避免其他进程占用导致阻塞/错误 if (![fileManager isReadableFileAtPath:fileURL.path]) { continue; } // 读取修改时间 NSDate *modDate = nil; if (![fileURL getResourceValue:&modDate forKey:NSURLContentModificationDateKey error:nil] || modDate == nil) { // 属性获取失败视为不统计 continue; } if ([modDate compare:oneHourAgo] == NSOrderedDescending) { count++; } } return count; } // 后台清理过期文件:与统计分离执行,删除失败忽略;仅删除普通文件 - (void)cleanupExpiredFilesInDirectory:(NSString *)directoryPath { dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), ^{ NSFileManager *fileManager = [NSFileManager defaultManager]; NSURL *dirURL = [NSURL fileURLWithPath:directoryPath isDirectory:YES]; NSDate *oneHourAgo = [NSDate dateWithTimeIntervalSinceNow:-3600]; NSDirectoryEnumerator *enumerator = [fileManager enumeratorAtURL:dirURL includingPropertiesForKeys:@[NSURLIsRegularFileKey, NSURLContentModificationDateKey] options:(NSDirectoryEnumerationSkipsSubdirectoryDescendants | NSDirectoryEnumerationSkipsHiddenFiles) errorHandler:^BOOL(NSURL *url, NSError *error) { return YES; }]; for (NSURL *fileURL in enumerator) { NSNumber *isRegular = nil; if (![fileURL getResourceValue:&isRegular forKey:NSURLIsRegularFileKey error:nil] || !isRegular.boolValue) { continue; } // 跳过不可读/不可删除的文件,减少与写入进程的竞争 if (![fileManager isReadableFileAtPath:fileURL.path]) { continue; } NSDate *modDate = nil; if (![fileURL getResourceValue:&modDate forKey:NSURLContentModificationDateKey error:nil] || modDate == nil) { continue; } if ([modDate compare:oneHourAgo] != NSOrderedDescending) { // 尝试删除过期文件,失败则忽略 NSError *removeError = nil; [fileManager removeItemAtURL:fileURL error:&removeError]; // 可选:根据需要打印错误日志 // if (removeError) { NSLog(@"Cleanup skip %@ error: %@", fileURL.path, removeError); } } } }); } // 原方法保留声明但不再使用,避免并发删除引发卡顿/统计异常 - (NSInteger)countAndCleanFilesInDirectory:(NSString *)directoryPath { // 已弃用:请使用 countValidFilesInDirectory 与 cleanupExpiredFilesInDirectory // 保留空实现或转发以兼容旧调用方 return [self countValidFilesInDirectory:directoryPath]; } // 添加清理过期数据的辅助方法 - (void)cleanExpiredTimestamps:(NSMutableArray *)timestamps { NSDate *now = [NSDate date]; NSTimeInterval oneHourAgo = [now timeIntervalSince1970] - 3600; // 3600秒 = 1小时 NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(NSDate *timestamp, NSDictionary *bindings) { return [timestamp timeIntervalSince1970] > oneHourAgo; }]; NSArray *validTimestamps = [timestamps filteredArrayUsingPredicate:predicate]; [timestamps removeAllObjects]; [timestamps addObjectsFromArray:validTimestamps]; } // 更新背景颜色 - (void)updateBackgroundColorByShowCount:(NSInteger)showCount { UIColor *color; if (showCount < 80) { // 蓝色 #07A6E4 color = [UIColor colorWithRed:7/255.0 green:166/255.0 blue:228/255.0 alpha:1.0]; } else if (showCount < 160) { // 红色 #D92727 color = [UIColor colorWithRed:217/255.0 green:39/255.0 blue:39/255.0 alpha:1.0]; } else if (showCount < 240) { // 橙黄 #EF811C color = [UIColor colorWithRed:239/255.0 green:129/255.0 blue:28/255.0 alpha:1.0]; } else { // 绿色 #08C951 color = [UIColor colorWithRed:8/255.0 green:201/255.0 blue:81/255.0 alpha:1.0]; } dispatch_async(dispatch_get_main_queue(), ^{ self.backgroundColor = color; }); } // 添加 onEventLoad 方法 - (void)onEventLoad:(id)data { @synchronized(loadTimestamps) { [loadTimestamps addObject:[NSDate date]]; [self cleanExpiredTimestamps:loadTimestamps]; NSInteger loadCount = loadTimestamps.count; NSInteger showCount = showTimestamps.count; dispatch_async(dispatch_get_main_queue(), ^{ self.infoLabel.text = [NSString stringWithFormat:@"S:%@ / L:%@", @(showCount), @(loadCount)]; }); NSLog(@"Load event: total in last hour = %ld", (long)loadCount); } } // 添加 onEventShow 方法 - (void)onEventShow:(id)data { @synchronized(showTimestamps) { [showTimestamps addObject:[NSDate date]]; [self cleanExpiredTimestamps:showTimestamps]; NSInteger showCount = showTimestamps.count; NSInteger loadCount = loadTimestamps.count; dispatch_async(dispatch_get_main_queue(), ^{ self.infoLabel.text = [NSString stringWithFormat:@"S:%@ / L:%@", @(showCount), @(loadCount)]; }); // 根据 show 数量更新背景颜色 [self updateBackgroundColorByShowCount:showCount]; NSLog(@"Show event: total in last hour = %ld", (long)showCount); } } - (void)setupUI { // Name Label self.nameLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 10, 180, 18)]; self.nameLabel.textColor = [UIColor whiteColor]; self.nameLabel.font = [UIFont systemFontOfSize:14.0]; [self addSubview:self.nameLabel]; // IP Label self.ipLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 35, 180, 20)]; [self addSubview:self.ipLabel]; self.infoLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 55, 180, 20)]; [self addSubview:self.infoLabel]; // Device Type Label self.deviceTypeLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 75, 180, 30)]; self.deviceTypeLabel.textColor = [UIColor whiteColor]; self.deviceTypeLabel.numberOfLines = 0; self.deviceTypeLabel.font = [UIFont systemFontOfSize:12.0]; [self addSubview:self.deviceTypeLabel]; // Action Button self.actionButton = [UIButton buttonWithType:UIButtonTypeSystem]; self.actionButton.frame = CGRectMake(10, 120, 70, 30); [self.actionButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; [self.actionButton setTitleColor:[UIColor redColor] forState:UIControlStateHighlighted]; // 'rgb(113, 201, 206)' self.actionButton.backgroundColor = [UIColor colorWithRed:113 / 255.0 green:201 / 255.0 blue:206 / 255.0 alpha:1.0]; // self.actionButton.layer.borderWidth = 1.0; // self.actionButton.layer.borderColor = [UIColor blueColor].CGColor; self.actionButton.layer.cornerRadius = 4.0; NSString *btnTitle = @"已停止"; [self.actionButton setTitle:btnTitle forState:UIControlStateNormal]; [self.actionButton addTarget:self action:@selector(actionButtonTapped:) forControlEvents:UIControlEventTouchUpInside]; [self addSubview:self.actionButton]; // Settings Button self.settingsButton = [UIButton buttonWithType:UIButtonTypeSystem]; self.settingsButton.frame = CGRectMake(90, 120, 70, 30); [self.settingsButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; [self.settingsButton setTitleColor:[UIColor redColor] forState:UIControlStateHighlighted]; self.settingsButton.backgroundColor = [UIColor colorWithRed:113 / 255.0 green:201 / 255.0 blue:206 / 255.0 alpha:1.0]; // self.actionButton.layer.borderWidth = 1.0; // self.actionButton.layer.borderColor = [UIColor blueColor].CGColor; self.settingsButton.layer.cornerRadius = 4.0; [self.settingsButton setTitle:@"刷新" forState:UIControlStateNormal]; [self.settingsButton addTarget:self action:@selector(settingsButtonTapped) forControlEvents:UIControlEventTouchUpInside]; [self addSubview:self.settingsButton]; self.infoLabel.text = @"S:0 / L:0"; // self.center = CGPointMake(90, self.superview.bounds.size.height - 100); } - (void)updateInfo { XSPhoneConfig *info = [XSPhoneConfig sharedInstance]; self.nameLabel.text = [NSString stringWithFormat:@"%@", [info IPhoneName]]; self.ipLabel.text = [NSString stringWithFormat:@"IP: %@", [[XSPhoneInfo sharedInstance] IPAddress]]; self.deviceTypeLabel.text = @"unknow"; } - (void)onEventUpdateStatus:(id)data { __weak typeof(self) weakSelf = self; dispatch_async(dispatch_get_main_queue(), ^{ weakSelf.deviceTypeLabel.text = [NSString stringWithFormat:@"%@", data]; }); } - (void)onEventUpdateRunStatus:(id)data { __weak typeof(self) weakSelf = self; dispatch_async(dispatch_get_main_queue(), ^{ // BOOL b = data; NSString *btnTitle = ([data isEqual:@(YES)] ? @"运行中" : @"已停止"); NSLog(@"onEventUpdateRunStatus: %@,%@", data, btnTitle); [weakSelf.actionButton setTitle:btnTitle forState:UIControlStateNormal]; }); } - (void)showMyToast:(NSString *)msg { __weak typeof(self) weakSelf = self; dispatch_async(dispatch_get_main_queue(), ^{ // UIView usage [weakSelf makeToast:msg]; }); } - (void)onEventUpdateName:(id)data { __weak typeof(self) weakSelf = self; dispatch_async(dispatch_get_main_queue(), ^{ XSPhoneConfig *info = [XSPhoneConfig sharedInstance]; weakSelf.nameLabel.text = [NSString stringWithFormat:@"%@", [info IPhoneName]]; }); } - (void)actionButtonTapped:(UIButton *)sender { NSLog(@"Action button tapped"); MyAdTask2Mangger *man = [MyAdTask2Mangger sharedInstance]; // MyAdTaskManager *man = [MyAdTaskManager sharedInstance]; // IPhoneHertbeat *hertBeat = [IPhoneHertbeat sharedInstance]; NSString *status = [man toggle]; // @"已停止"; /* if ([self.actionButton.titleLabel.text isEqual:@"运行中"]) { [man stop]; } else { [man start]; status = @"运行中"; } */ //[self.actionButton setTitle:status forState:UIControlStateNormal]; } - (void)settingsButtonTapped { NSLog(@"Settings button tapped"); [self updateInfo]; //[self appendLog:@"设置按钮被按下"]; } - (void)changeBackgroundColor { self.backgroundColor = [UIColor colorWithRed:arc4random_uniform(256) / 255.0 green:arc4random_uniform(256) / 255.0 blue:arc4random_uniform(256) / 255.0 alpha:1.0]; } - (void)appendLog:(NSString *)logMessage { NSString *timestamp = [NSDateFormatter localizedStringFromDate:[NSDate date] dateStyle:NSDateFormatterNoStyle timeStyle:NSDateFormatterMediumStyle]; NSString *fullLogMessage = [NSString stringWithFormat:@"%@: %@\n", timestamp, logMessage]; self.logTextView.text = [self.logTextView.text stringByAppendingString:fullLogMessage]; [self.logTextView scrollRangeToVisible:NSMakeRange(self.logTextView.text.length - 1, 1)]; } - (void)detectPan:(UIPanGestureRecognizer *)panGesture { UIWindow *floatingWindow = (UIWindow *)panGesture.view; CGPoint translation = [panGesture translationInView:floatingWindow]; floatingWindow.center = CGPointMake(floatingWindow.center.x + translation.x, floatingWindow.center.y + translation.y); [panGesture setTranslation:CGPointZero inView:floatingWindow]; } @end