Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/PojavLauncher_iOS
Path: blob/main/Natives/LauncherMenuViewController.m
589 views
1
#import "authenticator/BaseAuthenticator.h"
2
#import "AccountListViewController.h"
3
#import "AFNetworking.h"
4
#import "ALTServerConnection.h"
5
#import "LauncherNavigationController.h"
6
#import "LauncherMenuViewController.h"
7
#import "LauncherNewsViewController.h"
8
#import "LauncherPreferences.h"
9
#import "LauncherPreferencesViewController.h"
10
#import "LauncherProfilesViewController.h"
11
#import "PLProfiles.h"
12
#import "UIButton+AFNetworking.h"
13
#import "UIImageView+AFNetworking.h"
14
#import "UIKit+hook.h"
15
#import "ios_uikit_bridge.h"
16
#import "utils.h"
17
18
#include <dlfcn.h>
19
20
@implementation LauncherMenuCustomItem
21
22
+ (LauncherMenuCustomItem *)title:(NSString *)title imageName:(NSString *)imageName action:(id)action {
23
LauncherMenuCustomItem *item = [[LauncherMenuCustomItem alloc] init];
24
item.title = title;
25
item.imageName = imageName;
26
item.action = action;
27
return item;
28
}
29
30
+ (LauncherMenuCustomItem *)vcClass:(Class)class {
31
id vc = [class new];
32
LauncherMenuCustomItem *item = [[LauncherMenuCustomItem alloc] init];
33
item.title = [vc title];
34
item.imageName = [vc imageName];
35
// View controllers are put into an array to keep its state
36
item.vcArray = @[vc];
37
return item;
38
}
39
40
@end
41
42
@interface LauncherMenuViewController()
43
@property(nonatomic) NSMutableArray<LauncherMenuCustomItem*> *options;
44
@property(nonatomic) UILabel *statusLabel;
45
@property(nonatomic) int lastSelectedIndex;
46
@end
47
48
@implementation LauncherMenuViewController
49
50
#define contentNavigationController ((LauncherNavigationController *)self.splitViewController.viewControllers[1])
51
52
- (void)viewDidLoad {
53
[super viewDidLoad];
54
55
self.isInitialVc = YES;
56
57
UIImageView *titleView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"AppLogo"]];
58
[titleView setContentMode:UIViewContentModeScaleAspectFit];
59
self.navigationItem.titleView = titleView;
60
[titleView sizeToFit];
61
62
self.options = @[
63
[LauncherMenuCustomItem vcClass:LauncherNewsViewController.class],
64
[LauncherMenuCustomItem vcClass:LauncherProfilesViewController.class],
65
[LauncherMenuCustomItem vcClass:LauncherPreferencesViewController.class],
66
].mutableCopy;
67
if (realUIIdiom != UIUserInterfaceIdiomTV) {
68
[self.options addObject:(id)[LauncherMenuCustomItem
69
title:localize(@"launcher.menu.custom_controls", nil)
70
imageName:@"MenuCustomControls" action:^{
71
[contentNavigationController performSelector:@selector(enterCustomControls)];
72
}]];
73
}
74
[self.options addObject:
75
(id)[LauncherMenuCustomItem
76
title:localize(@"launcher.menu.execute_jar", nil)
77
imageName:@"MenuInstallJar" action:^{
78
[contentNavigationController performSelector:@selector(enterModInstaller)];
79
}]];
80
81
// TODO: Finish log-uploading service integration
82
[self.options addObject:
83
(id)[LauncherMenuCustomItem
84
title:localize(@"login.menu.sendlogs", nil)
85
imageName:@"square.and.arrow.up" action:^{
86
NSString *latestlogPath = [NSString stringWithFormat:@"file://%s/latestlog.old.txt", getenv("POJAV_HOME")];
87
NSLog(@"Path is %@", latestlogPath);
88
UIActivityViewController *activityVC;
89
if (realUIIdiom != UIUserInterfaceIdiomTV) {
90
activityVC = [[UIActivityViewController alloc]
91
initWithActivityItems:@[[NSURL URLWithString:latestlogPath]]
92
applicationActivities:nil];
93
} else {
94
dlopen("/System/Library/PrivateFrameworks/SharingUI.framework/SharingUI", RTLD_GLOBAL);
95
activityVC =
96
[[NSClassFromString(@"SFAirDropSharingViewControllerTV") alloc]
97
performSelector:@selector(initWithSharingItems:)
98
withObject:@[[NSURL URLWithString:latestlogPath]]];
99
}
100
activityVC.popoverPresentationController.sourceView = titleView;
101
activityVC.popoverPresentationController.sourceRect = titleView.bounds;
102
[self presentViewController:activityVC animated:YES completion:nil];
103
}]];
104
105
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
106
dateFormatter.dateFormat = @"MM-dd";
107
NSString* date = [dateFormatter stringFromDate:NSDate.date];
108
if([date isEqualToString:@"06-29"] || [date isEqualToString:@"06-30"] || [date isEqualToString:@"07-01"]) {
109
[self.options addObject:(id)[LauncherMenuCustomItem
110
title:@"Technoblade never dies!"
111
imageName:@"" action:^{
112
openLink(self, [NSURL URLWithString:@"https://youtu.be/DPMluEVUqS0"]);
113
}]];
114
}
115
116
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
117
118
self.navigationController.toolbarHidden = NO;
119
UIActivityIndicatorViewStyle indicatorStyle = UIActivityIndicatorViewStyleMedium;
120
UIActivityIndicatorView *toolbarIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:indicatorStyle];
121
[toolbarIndicator startAnimating];
122
self.toolbarItems = @[
123
[[UIBarButtonItem alloc] initWithCustomView:toolbarIndicator],
124
[[UIBarButtonItem alloc] init]
125
];
126
self.toolbarItems[1].tintColor = UIColor.labelColor;
127
128
// Setup the account button
129
self.accountBtnItem = [self drawAccountButton];
130
131
[self updateAccountInfo];
132
133
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
134
[self.tableView selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionNone];
135
[self tableView:self.tableView didSelectRowAtIndexPath:indexPath];
136
137
if (getEntitlementValue(@"get-task-allow")) {
138
[self displayProgress:localize(@"login.jit.checking", nil)];
139
if (isJITEnabled(false)) {
140
[self displayProgress:localize(@"login.jit.enabled", nil)];
141
[self displayProgress:nil];
142
} else {
143
[self enableJITWithAltKit];
144
}
145
} else if (!NSProcessInfo.processInfo.macCatalystApp && !getenv("SIMULATOR_DEVICE_NAME")) {
146
[self displayProgress:localize(@"login.jit.fail", nil)];
147
[self displayProgress:nil];
148
UIAlertController* alert = [UIAlertController alertControllerWithTitle:localize(@"login.jit.fail.title", nil)
149
message:localize(@"login.jit.fail.description_unsupported", nil)
150
preferredStyle:UIAlertControllerStyleAlert];
151
UIAlertAction* okAction = [UIAlertAction actionWithTitle:localize(@"OK", nil) style:UIAlertActionStyleDefault handler:^(id action){
152
exit(-1);
153
}];
154
[alert addAction:okAction];
155
[self presentViewController:alert animated:YES completion:nil];
156
}
157
}
158
159
- (void)viewWillAppear:(BOOL)animated {
160
[super viewWillAppear:animated];
161
[self restoreHighlightedSelection];
162
}
163
164
- (UIBarButtonItem *)drawAccountButton {
165
if (!self.accountBtnItem) {
166
self.accountButton = [UIButton buttonWithType:UIButtonTypeCustom];
167
[self.accountButton addTarget:self action:@selector(selectAccount:) forControlEvents:UIControlEventPrimaryActionTriggered];
168
self.accountButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;
169
170
self.accountButton.titleEdgeInsets = UIEdgeInsetsMake(0, 4, 0, -4);
171
self.accountButton.imageView.contentMode = UIViewContentModeScaleAspectFit;
172
self.accountButton.titleLabel.lineBreakMode = NSLineBreakByWordWrapping;
173
self.accountBtnItem = [[UIBarButtonItem alloc] initWithCustomView:self.accountButton];
174
}
175
176
[self updateAccountInfo];
177
178
return self.accountBtnItem;
179
}
180
181
- (void)restoreHighlightedSelection {
182
// Restore the selected row when the view appears again
183
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:self.lastSelectedIndex inSection:0];
184
[self.tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
185
}
186
187
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
188
{
189
return self.options.count;
190
}
191
192
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
193
{
194
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
195
if (cell == nil) {
196
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
197
}
198
199
cell.textLabel.text = [self.options[indexPath.row] title];
200
201
UIImage *origImage = [UIImage systemImageNamed:[self.options[indexPath.row]
202
performSelector:@selector(imageName)]];
203
if (origImage) {
204
UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:CGSizeMake(40, 40)];
205
UIImage *image = [renderer imageWithActions:^(UIGraphicsImageRendererContext*_Nonnull myContext) {
206
CGFloat scaleFactor = 40/origImage.size.height;
207
[origImage drawInRect:CGRectMake(20 - origImage.size.width*scaleFactor/2, 0, origImage.size.width*scaleFactor, 40)];
208
}];
209
cell.imageView.image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
210
}
211
212
if (cell.imageView.image == nil) {
213
cell.imageView.layer.magnificationFilter = kCAFilterNearest;
214
cell.imageView.layer.minificationFilter = kCAFilterNearest;
215
cell.imageView.image = [UIImage imageNamed:[self.options[indexPath.row]
216
performSelector:@selector(imageName)]];
217
cell.imageView.image = [cell.imageView.image _imageWithSize:CGSizeMake(40, 40)];
218
}
219
return cell;
220
}
221
222
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
223
{
224
LauncherMenuCustomItem *selected = self.options[indexPath.row];
225
226
if (selected.action != nil) {
227
[self restoreHighlightedSelection];
228
((LauncherMenuCustomItem *)selected).action();
229
} else {
230
if(self.isInitialVc) {
231
self.isInitialVc = NO;
232
} else {
233
self.options[self.lastSelectedIndex].vcArray = contentNavigationController.viewControllers;
234
[contentNavigationController setViewControllers:selected.vcArray animated:NO];
235
self.lastSelectedIndex = indexPath.row;
236
}
237
selected.vcArray[0].navigationItem.rightBarButtonItem = self.accountBtnItem;
238
selected.vcArray[0].navigationItem.leftBarButtonItem = self.splitViewController.displayModeButtonItem;
239
selected.vcArray[0].navigationItem.leftItemsSupplementBackButton = true;
240
}
241
}
242
243
- (void)selectAccount:(UIButton *)sender {
244
AccountListViewController *vc = [[AccountListViewController alloc] init];
245
vc.whenDelete = ^void(NSString* name) {
246
if ([name isEqualToString:getPrefObject(@"internal.selected_account")]) {
247
BaseAuthenticator.current = nil;
248
setPrefObject(@"internal.selected_account", @"");
249
[self updateAccountInfo];
250
}
251
};
252
vc.whenItemSelected = ^void() {
253
setPrefObject(@"internal.selected_account", BaseAuthenticator.current.authData[@"username"]);
254
[self updateAccountInfo];
255
if (sender != self.accountButton) {
256
// Called from the play button, so call back to continue
257
[sender sendActionsForControlEvents:UIControlEventPrimaryActionTriggered];
258
}
259
};
260
vc.modalPresentationStyle = UIModalPresentationPopover;
261
vc.preferredContentSize = CGSizeMake(350, 250);
262
263
UIPopoverPresentationController *popoverController = vc.popoverPresentationController;
264
popoverController.sourceView = sender;
265
popoverController.sourceRect = sender.bounds;
266
popoverController.permittedArrowDirections = UIPopoverArrowDirectionAny;
267
popoverController.delegate = vc;
268
[self presentViewController:vc animated:YES completion:nil];
269
}
270
271
- (void)updateAccountInfo {
272
NSDictionary *selected = BaseAuthenticator.current.authData;
273
CGSize size = CGSizeMake(contentNavigationController.view.frame.size.width, contentNavigationController.view.frame.size.height);
274
275
if (selected == nil) {
276
if((size.width / 3) > 200) {
277
[self.accountButton setAttributedTitle:[[NSAttributedString alloc] initWithString:localize(@"login.option.select", nil)] forState:UIControlStateNormal];
278
} else {
279
[self.accountButton setAttributedTitle:(NSAttributedString *)@"" forState:UIControlStateNormal];
280
}
281
[self.accountButton setImage:[UIImage imageNamed:@"DefaultAccount"] forState:UIControlStateNormal];
282
[self.accountButton sizeToFit];
283
return;
284
}
285
286
// Remove the prefix "Demo." if there is
287
BOOL isDemo = [selected[@"username"] hasPrefix:@"Demo."];
288
NSMutableAttributedString *title = [[NSMutableAttributedString alloc] initWithString:[selected[@"username"] substringFromIndex:(isDemo?5:0)]];
289
290
// Check if we're switching between demo and full mode
291
BOOL shouldUpdateProfiles = (getenv("DEMO_LOCK")!=NULL) != isDemo;
292
293
// Reset states
294
unsetenv("DEMO_LOCK");
295
setenv("POJAV_GAME_DIR", [NSString stringWithFormat:@"%s/Library/Application Support/minecraft", getenv("POJAV_HOME")].UTF8String, 1);
296
297
id subtitle;
298
if (isDemo) {
299
subtitle = localize(@"login.option.demo", nil);
300
setenv("DEMO_LOCK", "1", 1);
301
setenv("POJAV_GAME_DIR", [NSString stringWithFormat:@"%s/.demo", getenv("POJAV_HOME")].UTF8String, 1);
302
} else if (selected[@"xboxGamertag"] == nil) {
303
subtitle = localize(@"login.option.local", nil);
304
} else {
305
// Display the Xbox gamertag for online accounts
306
subtitle = selected[@"xboxGamertag"];
307
}
308
309
subtitle = [[NSAttributedString alloc] initWithString:subtitle attributes:@{NSFontAttributeName: [UIFont systemFontOfSize:12]}];
310
[title appendAttributedString:[[NSAttributedString alloc] initWithString:@"\n" attributes:nil]];
311
[title appendAttributedString:subtitle];
312
313
if((size.width / 3) > 200) {
314
[self.accountButton setAttributedTitle:title forState:UIControlStateNormal];
315
} else {
316
[self.accountButton setAttributedTitle:(NSAttributedString *)@"" forState:UIControlStateNormal];
317
}
318
319
// TODO: Add caching mechanism for profile pictures
320
NSURL *url = [NSURL URLWithString:[selected[@"profilePicURL"] stringByReplacingOccurrencesOfString:@"\\/" withString:@"/"]];
321
UIImage *placeholder = [UIImage imageNamed:@"DefaultAccount"];
322
[self.accountButton setImageForState:UIControlStateNormal withURL:url placeholderImage:placeholder];
323
[self.accountButton.imageView setImageWithURL:url placeholderImage:placeholder];
324
[self.accountButton sizeToFit];
325
326
// Update profiles and local version list if needed
327
if (shouldUpdateProfiles) {
328
[contentNavigationController fetchLocalVersionList];
329
[contentNavigationController performSelector:@selector(reloadProfileList)];
330
}
331
332
// Update tableView whenever we have
333
UITableViewController *tableVC = contentNavigationController.viewControllers.lastObject;
334
if ([tableVC isKindOfClass:UITableViewController.class]) {
335
[tableVC.tableView reloadData];
336
}
337
}
338
339
- (void)displayProgress:(NSString *)status {
340
if (status == nil) {
341
[(UIActivityIndicatorView *)self.toolbarItems[0].customView stopAnimating];
342
} else {
343
self.toolbarItems[1].title = status;
344
}
345
}
346
347
- (void)enableJITWithAltKit {
348
[ALTServerManager.sharedManager startDiscovering];
349
[ALTServerManager.sharedManager autoconnectWithCompletionHandler:^(ALTServerConnection *connection, NSError *error) {
350
if (error) {
351
NSLog(@"[AltKit] Could not auto-connect to server. %@", error.localizedRecoverySuggestion);
352
[self displayProgress:localize(@"login.jit.fail", nil)];
353
[self displayProgress:nil];
354
}
355
[connection enableUnsignedCodeExecutionWithCompletionHandler:^(BOOL success, NSError *error) {
356
if (success) {
357
NSLog(@"[AltKit] Successfully enabled JIT compilation!");
358
[ALTServerManager.sharedManager stopDiscovering];
359
[self displayProgress:localize(@"login.jit.enabled", nil)];
360
[self displayProgress:nil];
361
} else {
362
NSLog(@"[AltKit] Error enabling JIT: %@", error.localizedRecoverySuggestion);
363
[self displayProgress:localize(@"login.jit.fail", nil)];
364
[self displayProgress:nil];
365
}
366
[connection disconnect];
367
}];
368
}];
369
}
370
371
@end
372
373