ios-hooks/AppRunMan/server/FloatingWindow.m
2025-12-01 10:07:51 +08:00

433 lines
16 KiB
Objective-C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// 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 <Foundation/Foundation.h>
@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