Files
tildefriends/src/ios.m
2025-11-12 20:09:10 -05:00

257 lines
11 KiB
Objective-C

#import <CoreSpotlight/CSSearchableIndex.h>
#import <CoreSpotlight/CSSearchableItem.h>
#import <CoreSpotlight/CSSearchableItemAttributeSet.h>
#import <UIKit/UIKit.h>
#import <WebKit/WKDownload.h>
#import <WebKit/WKDownloadDelegate.h>
#import <WebKit/WKNavigationAction.h>
#import <WebKit/WKNavigationDelegate.h>
#import <WebKit/WKNavigationResponse.h>
#import <WebKit/WKUIDelegate.h>
#import <WebKit/WKWebView.h>
#import <WebKit/WKWebViewConfiguration.h>
#include "log.h"
#include "util.js.h"
#include <libgen.h>
#include <string.h>
void tf_run_thread_start(const char* zip_path);
@interface ViewController : UINavigationController <WKUIDelegate, WKNavigationDelegate, WKDownloadDelegate, UIDocumentPickerDelegate>
@property (strong, nonatomic) WKWebView* web_view;
@property bool initial_load_complete;
@property (retain) NSURL* download_url;
@end
static void _start_initial_load(WKWebView* web_view)
{
[web_view loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://localhost:12345/login/auto"]]];
}
@implementation ViewController : UINavigationController
- (void)viewDidLoad
{
[super viewDidLoad];
WKWebViewConfiguration* configuration = [[WKWebViewConfiguration alloc] init];
self.web_view = [[WKWebView alloc] initWithFrame:self.view.frame configuration:configuration];
self.web_view.UIDelegate = self;
self.web_view.navigationDelegate = self;
self.web_view.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
self.web_view.translatesAutoresizingMaskIntoConstraints = false;
self.web_view.allowsBackForwardNavigationGestures = true;
[self.view addSubview:self.web_view];
UIRefreshControl* refresh = [[UIRefreshControl alloc] init];
[refresh addTarget:self action:@selector(handleRefresh:) forControlEvents:UIControlEventValueChanged];
self.web_view.scrollView.refreshControl = refresh;
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.web_view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.web_view attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:-75]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.web_view attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view
attribute:NSLayoutAttributeLeft
multiplier:1.0
constant:0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.web_view attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.view
attribute:NSLayoutAttributeRight
multiplier:1.0
constant:0]];
_start_initial_load(self.web_view);
}
- (void)handleRefresh:(id)sender
{
tf_printf("refresh\n");
[self.web_view reload];
}
- (void)webView:(WKWebView*)webView didFinishNavigation:(WKNavigation*)navigation
{
if (!self.initial_load_complete)
{
tf_printf("initial load complete\n");
self.initial_load_complete = true;
}
self.navigationController.interactivePopGestureRecognizer.enabled = self.web_view.canGoBack;
[self.web_view.scrollView.refreshControl endRefreshing];
}
- (void)webView:(WKWebView*)webView didFailProvisionalNavigation:(WKNavigation*)navigation withError:(NSError*)error
{
if (!self.initial_load_complete)
{
_start_initial_load(self.web_view);
}
}
- (void)webView:(WKWebView*)webView
runJavaScriptConfirmPanelWithMessage:(NSString*)message
initiatedByFrame:(WKFrameInfo*)frame
completionHandler:(void (^)(BOOL result))completionHandler
{
UIAlertController* alertController = [UIAlertController alertControllerWithTitle:message message:nil preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction* action) { completionHandler(true); }]];
[alertController addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction* action) { completionHandler(false); }]];
[self presentViewController:alertController animated:YES completion:^ {}];
}
- (void)webView:(WKWebView*)webView runJavaScriptAlertPanelWithMessage:(NSString*)message initiatedByFrame:(WKFrameInfo*)frame completionHandler:(void (^)(void))completionHandler
{
UIAlertController* alertController = [UIAlertController alertControllerWithTitle:message message:nil preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction* action) { completionHandler(); }]];
[self presentViewController:alertController animated:YES completion:^ {}];
}
- (void)webView:(WKWebView*)webView
runJavaScriptTextInputPanelWithPrompt:(NSString*)prompt
defaultText:(NSString*)defaultText
initiatedByFrame:(WKFrameInfo*)frame
completionHandler:(void (^)(NSString*))completionHandler
{
NSString* sender = [NSString stringWithFormat:@"%@", self.web_view.URL.host];
UIAlertController* alertController = [UIAlertController alertControllerWithTitle:prompt message:sender preferredStyle:UIAlertControllerStyleAlert];
[alertController addTextFieldWithConfigurationHandler:^(UITextField* textField) {
textField.placeholder = defaultText;
textField.text = defaultText;
}];
[alertController addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction* action) {
NSString* input = ((UITextField*)alertController.textFields.firstObject).text;
completionHandler(input);
}]];
[alertController addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction* action) { completionHandler(nil); }]];
[self presentViewController:alertController animated:YES completion:^ {}];
}
- (void)webView:(WKWebView*)webView decidePolicyForNavigationAction:(WKNavigationAction*)navigationAction decisionHandler:(void (^)(enum WKNavigationActionPolicy))decisionHandler
{
decisionHandler(navigationAction.shouldPerformDownload ? WKNavigationActionPolicyDownload : WKNavigationActionPolicyAllow);
}
- (void)webView:(WKWebView*)webView
decidePolicyForNavigationResponse:(WKNavigationResponse*)navigationResponse
decisionHandler:(void (^)(enum WKNavigationResponsePolicy))decisionHandler
{
decisionHandler(navigationResponse.canShowMIMEType ? WKNavigationResponsePolicyAllow : WKNavigationResponsePolicyDownload);
}
- (void)webView:(WKWebView*)webView navigationAction:(WKNavigationAction*)navigationAction didBecomeDownload:(WKDownload*)download
{
download.delegate = self;
}
- (void)webView:(WKWebView*)webView navigationResponse:(WKNavigationResponse*)navigationResponse didBecomeDownload:(WKDownload*)download
{
download.delegate = self;
}
- (void)download:(WKDownload*)download
decideDestinationUsingResponse:(NSURLResponse*)response
suggestedFilename:(NSString*)suggestedFilename
completionHandler:(void (^)(NSURL*))completionHandler
{
self.download_url = [[NSURL fileURLWithPath:NSTemporaryDirectory()] URLByAppendingPathComponent:suggestedFilename];
completionHandler(self.download_url);
}
- (void)downloadDidFinish:(WKDownload*)download
{
UIDocumentPickerViewController* picker = [[UIDocumentPickerViewController alloc] initForExportingURLs:@[ self.download_url ]];
picker.delegate = self;
[self presentViewController:picker animated:YES completion:nil];
}
- (void)download:(WKDownload*)download didFailWithError:(NSError*)error resumeData:(NSData*)resumeData
{
tf_printf("download didFailWithError:%s\n", [error.localizedDescription UTF8String]);
}
- (void)documentPicker:(UIDocumentPickerViewController*)controller didPickDocumentAtURLs:(NSArray<NSURL*>*)urls
{
[[NSFileManager defaultManager] removeItemAtURL:self.download_url error:nil];
}
- (void)documentPickerWasCancelled:(UIDocumentPickerViewController*)controller
{
[[NSFileManager defaultManager] removeItemAtURL:self.download_url error:nil];
}
@end
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow* window;
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
ViewController* view_controller = [[ViewController alloc] init];
self.window.rootViewController = view_controller;
[self.window makeKeyAndVisible];
return YES;
}
- (BOOL)application:(UIApplication*)application
continueUserActivity:(NSUserActivity*)activity
restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>>*))restorationHandler
{
if ([activity.activityType isEqual:CSSearchableItemActionType])
{
const char* identifier = [[activity.userInfo valueForKey:CSSearchableItemActivityIdentifier] UTF8String];
tf_printf("Jumping to search result: %s.\n", identifier);
char url[1024];
snprintf(url, sizeof(url), "http://localhost:12345/~core/ssb/#%s", identifier);
tf_printf("Navigating to %s.", url);
ViewController* view_controller = (ViewController*)self.window.rootViewController;
[view_controller.web_view loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithUTF8String:url]]]];
}
else
{
tf_printf("no search\n");
}
return NO;
}
@end
void tf_notify_message_added_ios(const char* identifier, const char* title, const char* content)
{
tf_printf("indexing: identifier=%s title=%s content=%s\n", identifier, title, content);
CSSearchableItemAttributeSet* attribute_set = [[CSSearchableItemAttributeSet alloc] initWithContentType:UTTypeText];
attribute_set.title = [NSString stringWithUTF8String:content];
attribute_set.contentDescription = [NSString stringWithUTF8String:title];
CSSearchableItem* item = [[CSSearchableItem alloc] initWithUniqueIdentifier:[NSString stringWithUTF8String:identifier] domainIdentifier:@"com.unprompted.tildefriends.messages"
attributeSet:attribute_set];
[[CSSearchableIndex defaultSearchableIndex] indexSearchableItems:@[ item ] completionHandler:^(NSError* _Nullable error) {
if (error)
{
tf_printf("indexing error: %s.\n", [error.localizedDescription UTF8String]);
}
else
{
tf_printf("indexed successfully.\n");
}
}];
}
int main(int argc, char* argv[])
{
NSFileManager* file_manager = [NSFileManager defaultManager];
NSString* library_directory = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0];
[file_manager changeCurrentDirectoryPath:library_directory];
size_t path_length = strlen(argv[0]) - strlen(basename(argv[0]));
size_t length = path_length + strlen("data.zip");
char* zip_path = alloca(length + 1);
snprintf(zip_path, length + 1, "%.*sdata.zip", (int)path_length, argv[0]);
tf_run_thread_start(zip_path);
return UIApplicationMain(argc, argv, nil, @"AppDelegate");
}