Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/PojavLauncher_iOS
Path: blob/main/Natives/AccountListViewController.m
589 views
1
#import <AuthenticationServices/AuthenticationServices.h>
2
3
#import "authenticator/BaseAuthenticator.h"
4
#import "AccountListViewController.h"
5
#import "AFNetworking.h"
6
#import "LauncherPreferences.h"
7
#import "UIImageView+AFNetworking.h"
8
#import "ios_uikit_bridge.h"
9
#import "utils.h"
10
11
@interface AccountListViewController()<ASWebAuthenticationPresentationContextProviding>
12
13
@property(nonatomic, strong) NSMutableArray *accountList;
14
@property(nonatomic) ASWebAuthenticationSession *authVC;
15
16
@end
17
18
@implementation AccountListViewController
19
20
- (void)viewDidLoad {
21
[super viewDidLoad];
22
23
if (self.accountList == nil) {
24
self.accountList = [NSMutableArray array];
25
} else {
26
[self.accountList removeAllObjects];
27
}
28
29
// List accounts
30
NSString *listPath = [NSString stringWithFormat:@"%s/accounts", getenv("POJAV_HOME")];
31
NSFileManager *fm = [NSFileManager defaultManager];
32
NSArray *files = [fm contentsOfDirectoryAtPath:listPath error:nil];
33
for(NSString *file in files) {
34
NSString *path = [listPath stringByAppendingPathComponent:file];
35
BOOL isDir = NO;
36
[fm fileExistsAtPath:path isDirectory:(&isDir)];
37
if(!isDir && [file hasSuffix:@".json"]) {
38
[self.accountList addObject:parseJSONFromFile(path)];
39
}
40
}
41
42
[self.tableView setSeparatorStyle:UITableViewCellSeparatorStyleSingleLine];
43
}
44
45
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
46
{
47
return self.accountList.count + 1;
48
}
49
50
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
51
{
52
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
53
54
if (cell == nil) {
55
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"cell"];
56
}
57
58
if (indexPath.row == self.accountList.count) {
59
cell.imageView.image = [UIImage imageNamed:@"IconAdd"];
60
cell.textLabel.text = localize(@"login.option.add", nil);
61
return cell;
62
}
63
64
NSDictionary *selected = self.accountList[indexPath.row];
65
// By default, display the saved username
66
cell.textLabel.text = selected[@"username"];
67
if ([selected[@"username"] hasPrefix:@"Demo."]) {
68
// Remove the prefix "Demo."
69
cell.textLabel.text = [selected[@"username"] substringFromIndex:5];
70
cell.detailTextLabel.text = localize(@"login.option.demo", nil);
71
} else if (selected[@"xboxGamertag"] == nil) {
72
cell.detailTextLabel.text = localize(@"login.option.local", nil);
73
} else {
74
// Display the Xbox gamertag for online accounts
75
cell.detailTextLabel.text = selected[@"xboxGamertag"];
76
}
77
78
cell.imageView.contentMode = UIViewContentModeCenter;
79
[cell.imageView setImageWithURL:[NSURL URLWithString:[selected[@"profilePicURL"] stringByReplacingOccurrencesOfString:@"\\/" withString:@"/"]] placeholderImage:[UIImage imageNamed:@"DefaultAccount"]];
80
81
return cell;
82
}
83
84
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
85
[tableView deselectRowAtIndexPath:indexPath animated:NO];
86
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
87
88
if (indexPath.row == self.accountList.count) {
89
[self actionAddAccount:cell];
90
return;
91
}
92
93
self.modalInPresentation = YES;
94
self.tableView.userInteractionEnabled = NO;
95
[self addActivityIndicatorTo:cell];
96
97
id callback = ^(id status, BOOL success) {
98
[self callbackMicrosoftAuth:status success:success forCell:cell];
99
};
100
[[BaseAuthenticator loadSavedName:self.accountList[indexPath.row][@"username"]] refreshTokenWithCallback:callback];
101
}
102
103
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
104
if (editingStyle == UITableViewCellEditingStyleDelete) {
105
// TODO: invalidate token
106
107
NSString *str = self.accountList[indexPath.row][@"username"];
108
NSFileManager *fm = [NSFileManager defaultManager];
109
NSString *path = [NSString stringWithFormat:@"%s/accounts/%@.json", getenv("POJAV_HOME"), str];
110
if (self.whenDelete != nil) {
111
self.whenDelete(str);
112
}
113
NSString *xuid = self.accountList[indexPath.row][@"xuid"];
114
if (xuid) {
115
[MicrosoftAuthenticator clearTokenDataOfProfile:xuid];
116
}
117
[fm removeItemAtPath:path error:nil];
118
[self.accountList removeObjectAtIndex:indexPath.row];
119
[tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
120
}
121
}
122
123
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
124
{
125
if (indexPath.row == self.accountList.count) {
126
return UITableViewCellEditingStyleNone;
127
} else {
128
return UITableViewCellEditingStyleDelete;
129
}
130
}
131
132
- (NSDictionary *)parseQueryItems:(NSString *)url {
133
NSMutableDictionary *result = [NSMutableDictionary new];
134
NSArray<NSURLQueryItem *> *queryItems = [NSURLComponents componentsWithString:url].queryItems;
135
for (NSURLQueryItem *item in queryItems) {
136
result[item.name] = item.value;
137
}
138
return result;
139
}
140
141
- (void)actionAddAccount:(UITableViewCell *)sender {
142
UIAlertController *picker = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet];
143
UIAlertAction *actionMicrosoft = [UIAlertAction actionWithTitle:localize(@"login.option.microsoft", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
144
[self actionLoginMicrosoft:sender];
145
}];
146
[picker addAction:actionMicrosoft];
147
UIAlertAction *actionLocal = [UIAlertAction actionWithTitle:localize(@"login.option.local", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
148
[self actionLoginLocal:sender];
149
}];
150
[picker addAction:actionLocal];
151
UIAlertAction *cancel = [UIAlertAction actionWithTitle:localize(@"Cancel", nil) style:UIAlertActionStyleCancel handler:nil];
152
[picker addAction:cancel];
153
154
picker.popoverPresentationController.sourceView = sender;
155
picker.popoverPresentationController.sourceRect = sender.bounds;
156
157
[self presentViewController:picker animated:YES completion:nil];
158
}
159
160
- (void)actionLoginLocal:(UIView *)sender {
161
if (getPrefBool(@"warnings.local_warn")) {
162
setPrefBool(@"warnings.local_warn", NO);
163
UIAlertController *alert = [UIAlertController alertControllerWithTitle:localize(@"login.warn.title.localmode", nil) message:localize(@"login.warn.message.localmode", nil) preferredStyle:UIAlertControllerStyleActionSheet];
164
alert.popoverPresentationController.sourceView = sender;
165
alert.popoverPresentationController.sourceRect = sender.bounds;
166
UIAlertAction *ok = [UIAlertAction actionWithTitle:localize(@"OK", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {[self actionLoginLocal:sender];}];
167
[alert addAction:ok];
168
[self presentViewController:alert animated:YES completion:nil];
169
return;
170
}
171
UIAlertController *controller = [UIAlertController alertControllerWithTitle:localize(@"Sign in", nil) message:localize(@"login.option.local", nil) preferredStyle:UIAlertControllerStyleAlert];
172
[controller addTextFieldWithConfigurationHandler:^(UITextField *textField) {
173
textField.placeholder = localize(@"login.alert.field.username", nil);
174
textField.clearButtonMode = UITextFieldViewModeWhileEditing;
175
textField.borderStyle = UITextBorderStyleRoundedRect;
176
}];
177
[controller addAction:[UIAlertAction actionWithTitle:localize(@"OK", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
178
NSArray *textFields = controller.textFields;
179
UITextField *usernameField = textFields[0];
180
if (usernameField.text.length < 3 || usernameField.text.length > 16) {
181
controller.message = localize(@"login.error.username.outOfRange", nil);
182
[self presentViewController:controller animated:YES completion:nil];
183
} else {
184
id callback = ^(id status, BOOL success) {
185
self.whenItemSelected();
186
[self dismissViewControllerAnimated:YES completion:nil];
187
};
188
[[[LocalAuthenticator alloc] initWithInput:usernameField.text] loginWithCallback:callback];
189
}
190
}]];
191
[controller addAction:[UIAlertAction actionWithTitle:localize(@"Cancel", nil) style:UIAlertActionStyleCancel handler:nil]];
192
[self presentViewController:controller animated:YES completion:nil];
193
}
194
195
- (void)actionLoginMicrosoft:(UITableViewCell *)sender {
196
NSURL *url = [NSURL URLWithString:@"https://login.live.com/oauth20_authorize.srf?client_id=00000000402b5328&response_type=code&scope=service%3A%3Auser.auth.xboxlive.com%3A%3AMBI_SSL&redirect_url=https%3A%2F%2Flogin.live.com%2Foauth20_desktop.srf"];
197
198
self.authVC =
199
[[ASWebAuthenticationSession alloc] initWithURL:url
200
callbackURLScheme:@"ms-xal-00000000402b5328"
201
completionHandler:^(NSURL * _Nullable callbackURL, NSError * _Nullable error)
202
{
203
if (callbackURL == nil) {
204
if (error.code != ASWebAuthenticationSessionErrorCodeCanceledLogin) {
205
showDialog(localize(@"Error", nil), error.localizedDescription);
206
}
207
return;
208
}
209
// NSLog(@"URL returned = %@", [callbackURL absoluteString]);
210
211
NSDictionary *queryItems = [self parseQueryItems:callbackURL.absoluteString];
212
if (queryItems[@"code"]) {
213
self.modalInPresentation = YES;
214
self.tableView.userInteractionEnabled = NO;
215
[self addActivityIndicatorTo:sender];
216
id callback = ^(id status, BOOL success) {
217
if ([status isKindOfClass:NSString.class] && [status isEqualToString:@"DEMO"] && success) {
218
showDialog(localize(@"login.warn.title.demomode", nil), localize(@"login.warn.message.demomode", nil));
219
}
220
[self callbackMicrosoftAuth:status success:success forCell:sender];
221
};
222
[[[MicrosoftAuthenticator alloc] initWithInput:queryItems[@"code"]] loginWithCallback:callback];
223
} else {
224
if ([queryItems[@"error"] hasPrefix:@"access_denied"]) {
225
// Ignore access denial responses
226
return;
227
}
228
showDialog(localize(@"Error", nil), queryItems[@"error_description"]);
229
}
230
}];
231
232
self.authVC.prefersEphemeralWebBrowserSession = YES;
233
self.authVC.presentationContextProvider = self;
234
235
if ([self.authVC start] == NO) {
236
showDialog(localize(@"Error", nil), @"Unable to open Safari");
237
}
238
}
239
240
- (void)addActivityIndicatorTo:(UITableViewCell *)cell {
241
UIActivityIndicatorViewStyle indicatorStyle = UIActivityIndicatorViewStyleMedium;
242
UIActivityIndicatorView *indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:indicatorStyle];
243
cell.accessoryView = indicator;
244
[indicator sizeToFit];
245
[indicator startAnimating];
246
}
247
248
- (void)removeActivityIndicatorFrom:(UITableViewCell *)cell {
249
UIActivityIndicatorView *indicator = (id)cell.accessoryView;
250
[indicator stopAnimating];
251
cell.accessoryView = nil;
252
}
253
254
- (void)callbackMicrosoftAuth:(id)status success:(BOOL)success forCell:(UITableViewCell *)cell {
255
if (status != nil) {
256
if (success) {
257
cell.detailTextLabel.text = status;
258
} else {
259
self.modalInPresentation = NO;
260
self.tableView.userInteractionEnabled = YES;
261
[self removeActivityIndicatorFrom:cell];
262
cell.detailTextLabel.text = [status localizedDescription];
263
NSData *errorData = ((NSError *)status).userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey];
264
NSString *errorStr = [[NSString alloc] initWithData:errorData encoding:NSUTF8StringEncoding];
265
NSLog(@"[MSA] Error: %@", errorStr);
266
showDialog(localize(@"Error", nil), errorStr);
267
}
268
} else if (success) {
269
self.whenItemSelected();
270
[self removeActivityIndicatorFrom:cell];
271
[self dismissViewControllerAnimated:YES completion:nil];
272
}
273
}
274
275
#pragma mark - UIPopoverPresentationControllerDelegate
276
- (UIModalPresentationStyle)adaptivePresentationStyleForPresentationController:(UIPresentationController *)controller traitCollection:(UITraitCollection *)traitCollection {
277
return UIModalPresentationNone;
278
}
279
280
#pragma mark - ASWebAuthenticationPresentationContextProviding
281
- (ASPresentationAnchor)presentationAnchorForWebAuthenticationSession:(ASWebAuthenticationSession *)session {
282
return UIApplication.sharedApplication.windows.firstObject;
283
}
284
285
@end
286
287