557 lines
21 KiB
Objective-C
557 lines
21 KiB
Objective-C
//
|
|
// SLComposeTableViewController.m
|
|
// PhotoShareExtension
|
|
//
|
|
// Created by Russell on 8/5/15.
|
|
//
|
|
//
|
|
|
|
#import "ReceiverTableViewController.h"
|
|
|
|
typedef NS_ENUM(NSUInteger, NixTableViewSection) {
|
|
kPlaylistHeader,
|
|
kPlaylist,
|
|
kFriendHeader,
|
|
kFriend
|
|
};
|
|
|
|
@interface ReceiverTableViewController ()
|
|
@property (nonatomic, strong) NSArray *playlistValues;
|
|
@property (nonatomic, strong) NSArray *friendValues;
|
|
@property (nonatomic, strong) NSMutableArray *selectedPlaylists;
|
|
@property (nonatomic, strong) NSMutableArray *selectedFriends;
|
|
- (IBAction)send:(id)sender;
|
|
- (void)preparePlaylistValues;
|
|
- (void)prepareFriendValues;
|
|
- (void)reloadData;
|
|
|
|
- (NSString *)headerTitleWithPlaylistCounts:(NSUInteger)playlists andFriendCounts:(NSUInteger)friends;
|
|
- (void)updateHeaderTitleWithCounts;
|
|
|
|
- (void)loadAllData:(void (^)(NSError *))completionHandler;
|
|
- (void)loadPlaylistsWithCompletionHandler:(void (^)(NSError *))completionHandler;
|
|
- (void)loadFriendsWithCompletionHandler:(void (^)(NSError *))completionHandler;
|
|
@end
|
|
|
|
@implementation ReceiverTableViewController
|
|
|
|
#pragma mark - IB
|
|
- (IBAction)send:(id)sender {
|
|
self.selectedPlaylists = [[NSMutableArray alloc]
|
|
initWithCapacity:self.containerViewController.playlistData.count];
|
|
self.selectedFriends = [[NSMutableArray alloc]
|
|
initWithCapacity:self.containerViewController.friendData.count];
|
|
|
|
NSArray *selectedIndexPaths = [self.tableView indexPathsForSelectedRows];
|
|
[selectedIndexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) {
|
|
@autoreleasepool {
|
|
switch (indexPath.section) {
|
|
case kPlaylist:
|
|
{
|
|
NSString *playlistId = self.containerViewController.playlistData[indexPath.row][@"id"];
|
|
[self.selectedPlaylists addObject:playlistId];
|
|
break;
|
|
}
|
|
case kFriend:
|
|
{
|
|
NSString *friendUsername = self.containerViewController.friendData[indexPath.row][@"username"];
|
|
[self.selectedFriends addObject:friendUsername];
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}];
|
|
|
|
self.containerViewController.playlistIds = [self.selectedPlaylists copy];
|
|
self.containerViewController.friendUsernames = [self.selectedFriends copy];
|
|
|
|
[self showActivityIndicator];
|
|
self.navigationItem.rightBarButtonItem.enabled = NO;
|
|
self.navigationItem.hidesBackButton = YES;
|
|
self.navigationController.interactivePopGestureRecognizer.enabled = NO;
|
|
|
|
[self.containerViewController send];
|
|
}
|
|
|
|
#pragma mark - Navigation
|
|
- (void)viewDidLoad {
|
|
[super viewDidLoad];
|
|
|
|
// Uncomment the following line to preserve selection between presentations.
|
|
// self.clearsSelectionOnViewWillAppear = NO;
|
|
|
|
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
|
|
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
|
|
|
|
self.tableView.editing = YES;
|
|
|
|
self.refreshControl = [[UIRefreshControl alloc] init];
|
|
[self.refreshControl addTarget:self action:@selector(reloadData) forControlEvents:UIControlEventValueChanged];
|
|
|
|
if (!self.containerViewController.playlistData || !self.containerViewController.friendData) {
|
|
[self showActivityIndicator];
|
|
[self loadAllData:^(NSError *error) {
|
|
[self hideActivityIndicator];
|
|
if (error) {
|
|
UIAlertController *alertController =
|
|
[UIAlertController alertControllerWithTitle:NSLocalizedString(@"signin_again_title", nil)
|
|
message:NSLocalizedString(@"signin_again_message", nil)
|
|
preferredStyle:UIAlertControllerStyleAlert];
|
|
[alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"close", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
|
|
[self.containerViewController.extensionContext cancelRequestWithError:error];
|
|
}]];
|
|
[self presentViewController:alertController animated:YES completion:nil];
|
|
} else {
|
|
if (self.playlistValues.count == 0 && self.friendValues.count == 0) {
|
|
NSError *err;
|
|
UIAlertController *alertController =
|
|
[UIAlertController alertControllerWithTitle:NSLocalizedString(@"no_receiver_title", nil)
|
|
message:NSLocalizedString(@"no_receiver_message", nil)
|
|
preferredStyle:UIAlertControllerStyleAlert];
|
|
[alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"close", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
|
|
[self.containerViewController.extensionContext cancelRequestWithError:err];
|
|
}]];
|
|
[self presentViewController:alertController animated:YES completion:nil];
|
|
} else {
|
|
[self.tableView reloadData];
|
|
}
|
|
}
|
|
}];
|
|
}
|
|
}
|
|
|
|
- (void)viewWillAppear:(BOOL)animated {
|
|
[super viewWillAppear:animated];
|
|
self.navigationItem.rightBarButtonItem.enabled = NO;
|
|
|
|
NSArray *selectedReceiverIndexPaths = self.containerViewController.selectedReceiverIndexPaths;
|
|
|
|
[self.containerViewController verifyServiceAvailableWithCompletionHandler:^(BOOL success) {
|
|
if ([selectedReceiverIndexPaths count]) {
|
|
self.navigationItem.rightBarButtonItem.enabled = YES;
|
|
}
|
|
}];
|
|
|
|
if (self.containerViewController.playlistData && self.containerViewController.friendData) {
|
|
[self preparePlaylistValues];
|
|
[self prepareFriendValues];
|
|
|
|
[self.tableView reloadData];
|
|
|
|
if ([selectedReceiverIndexPaths count]) {
|
|
[selectedReceiverIndexPaths
|
|
enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) {
|
|
[self.tableView selectRowAtIndexPath:indexPath
|
|
animated:NO
|
|
scrollPosition:UITableViewScrollPositionNone];
|
|
}];
|
|
[self updateHeaderTitleWithCounts];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)viewDidAppear:(BOOL)animated {
|
|
[super viewDidAppear:animated];
|
|
}
|
|
|
|
- (void)viewWillDisappear:(BOOL)animated {
|
|
[super viewWillDisappear:animated];
|
|
self.friendValues = nil;
|
|
self.playlistValues = nil;
|
|
self.containerViewController.friendData = nil;
|
|
self.containerViewController.playlistData = nil;
|
|
self.containerViewController.selectedReceiverIndexPaths = nil; //[self.tableView indexPathsForSelectedRows];
|
|
}
|
|
|
|
- (void)didReceiveMemoryWarning {
|
|
[super didReceiveMemoryWarning];
|
|
// Dispose of any resources that can be recreated.
|
|
}
|
|
|
|
#pragma mark - Private
|
|
- (void)showActivityIndicator {
|
|
UIActivityIndicatorView *activityIndicator = [[UIActivityIndicatorView alloc]
|
|
initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
|
|
self.navigationItem.titleView = activityIndicator;
|
|
[activityIndicator startAnimating];
|
|
}
|
|
|
|
- (void)hideActivityIndicator {
|
|
UIActivityIndicatorView *activityIndicator = (UIActivityIndicatorView *)self.navigationItem.titleView;
|
|
if ([activityIndicator isKindOfClass:[UIActivityIndicatorView class]]) {
|
|
[activityIndicator stopAnimating];
|
|
}
|
|
self.navigationItem.titleView = nil;
|
|
}
|
|
|
|
- (void)preparePlaylistValues {
|
|
NSMutableArray *playlistValues = [NSMutableArray arrayWithCapacity:self.containerViewController.playlistData.count];
|
|
for (NSDictionary *record in self.containerViewController.playlistData) {
|
|
@autoreleasepool {
|
|
NSString *name = record[@"name"];
|
|
[playlistValues addObject:name];
|
|
}
|
|
}
|
|
self.playlistValues = playlistValues;
|
|
}
|
|
|
|
- (void)prepareFriendValues {
|
|
NSMutableArray *friendValues = [NSMutableArray arrayWithCapacity:self.containerViewController.friendData.count];
|
|
for (NSDictionary *record in self.containerViewController.friendData) {
|
|
@autoreleasepool {
|
|
NSString *name = record[@"fullName"];
|
|
[friendValues addObject:name];
|
|
}
|
|
}
|
|
self.friendValues = friendValues;
|
|
}
|
|
|
|
- (void)reloadData {
|
|
NSArray *playlistData = [self.containerViewController.playlistData copy];
|
|
NSArray *friendData = [self.containerViewController.friendData copy];
|
|
NSMutableArray *selectedPlaylistIds = [[NSMutableArray alloc] initWithCapacity:playlistData.count];
|
|
NSMutableArray *selectedFriendUsernames = [[NSMutableArray alloc] initWithCapacity:friendData.count];
|
|
|
|
[[self.tableView indexPathsForSelectedRows]
|
|
enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) {
|
|
switch (indexPath.section) {
|
|
case kPlaylist:
|
|
[selectedPlaylistIds addObject:playlistData[indexPath.row][@"id"]];
|
|
break;
|
|
case kFriend:
|
|
[selectedFriendUsernames addObject:friendData[indexPath.row][@"username"]];
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}];
|
|
|
|
[self loadAllData:^(NSError *error) {
|
|
[self.refreshControl endRefreshing];
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
NSArray *freshPlaylistData = [self.containerViewController.playlistData copy];
|
|
NSArray *freshFriendData = [self.containerViewController.friendData copy];
|
|
|
|
[freshPlaylistData enumerateObjectsUsingBlock:^(NSDictionary *playlist, NSUInteger idx, BOOL *stop) {
|
|
@autoreleasepool {
|
|
if ([selectedPlaylistIds containsObject:playlist[@"id"]]) {
|
|
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:idx inSection:kPlaylist];
|
|
[self.tableView selectRowAtIndexPath:indexPath
|
|
animated:NO
|
|
scrollPosition:UITableViewScrollPositionNone];
|
|
}
|
|
}
|
|
}];
|
|
|
|
[freshFriendData enumerateObjectsUsingBlock:^(NSDictionary *friend, NSUInteger idx, BOOL *stop) {
|
|
@autoreleasepool {
|
|
if ([selectedFriendUsernames containsObject:friend[@"username"]]) {
|
|
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:idx inSection:kFriend];
|
|
[self.tableView selectRowAtIndexPath:indexPath
|
|
animated:NO
|
|
scrollPosition:UITableViewScrollPositionNone];
|
|
}
|
|
}
|
|
}];
|
|
});
|
|
}];
|
|
}
|
|
|
|
- (NSString *)headerTitleWithPlaylistCounts:(NSUInteger)playlists andFriendCounts:(NSUInteger)friends {
|
|
NSString *rawString;
|
|
if (playlists == 0) {
|
|
if (friends == 1) {
|
|
rawString = NSLocalizedString(@"navigation_header_n_selected_friend", nil);
|
|
} else {
|
|
rawString = NSLocalizedString(@"navigation_header_n_selected_friends", nil);
|
|
}
|
|
}
|
|
if (friends == 0) {
|
|
if (playlists == 1) {
|
|
rawString = NSLocalizedString(@"navigation_header_n_selected_playlist", nil);
|
|
} else {
|
|
rawString = NSLocalizedString(@"navigation_header_n_selected_playlists", nil);
|
|
}
|
|
}
|
|
if (0 < playlists && 0 < friends) {
|
|
rawString = NSLocalizedString(@"navigation_header_n_selected_receivers", nil);
|
|
}
|
|
if (playlists + friends == 0) {
|
|
return NSLocalizedString(@"navigation_header_select_receivers", nil);
|
|
}
|
|
return [NSString stringWithFormat:rawString, playlists + friends];
|
|
}
|
|
|
|
- (void)updateHeaderTitleWithCounts {
|
|
__block NSUInteger playlistCount = 0;
|
|
__block NSUInteger friendCount = 0;
|
|
[[self.tableView indexPathsForSelectedRows]
|
|
enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) {
|
|
switch (indexPath.section) {
|
|
case kPlaylist:
|
|
playlistCount++;
|
|
break;
|
|
|
|
case kFriend:
|
|
friendCount++;
|
|
break;
|
|
}
|
|
}];
|
|
NSString *title = [self headerTitleWithPlaylistCounts:playlistCount andFriendCounts:friendCount];
|
|
self.navigationItem.title = title;
|
|
}
|
|
|
|
#pragma mark Network
|
|
- (void)loadAllData:(void (^)(NSError *))completionHandler {
|
|
__block NSError *error = nil;
|
|
|
|
dispatch_group_t dispatchGroup = dispatch_group_create();
|
|
dispatch_group_enter(dispatchGroup);
|
|
|
|
[self loadPlaylistsWithCompletionHandler:^(NSError *playlistsError) {
|
|
if (playlistsError)
|
|
error = playlistsError;
|
|
dispatch_group_leave(dispatchGroup);
|
|
}];
|
|
|
|
dispatch_group_enter(dispatchGroup);
|
|
[self loadFriendsWithCompletionHandler:^(NSError *friendsError) {
|
|
if (friendsError)
|
|
error = friendsError;
|
|
dispatch_group_leave(dispatchGroup);
|
|
}];
|
|
|
|
dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(), ^{
|
|
completionHandler(error);
|
|
});
|
|
}
|
|
|
|
- (void)loadPlaylistsWithCompletionHandler:(void (^)(NSError *))completionHandler {
|
|
[self.containerViewController.api playlists:^(id data, NSURLResponse *response, NSError *error) {
|
|
if (error || !data || [data isKindOfClass:[NSDictionary class]]) {
|
|
completionHandler(error);
|
|
return;
|
|
}
|
|
|
|
if ([data isKindOfClass:[NSArray class]]) {
|
|
NSPredicate *predicateByTitle = [NSPredicate predicateWithFormat:@"SELF.type == %@", @"normal"];
|
|
NSArray *playlists = [(NSArray*)data filteredArrayUsingPredicate:predicateByTitle];
|
|
self.containerViewController.playlistData = playlists;
|
|
[self preparePlaylistValues];
|
|
[self.tableView reloadData];
|
|
}
|
|
|
|
completionHandler(nil);
|
|
}];
|
|
}
|
|
|
|
- (void)loadFriendsWithCompletionHandler:(void (^)(NSError *))completionHandler {
|
|
[self.containerViewController.api friends:^(NSDictionary *data, NSURLResponse *response, NSError *error) {
|
|
if (error || data[@"error"]) {
|
|
completionHandler(error);
|
|
return;
|
|
}
|
|
NSString *predString = [NSString stringWithFormat:@"(status='%@')", @"success"];
|
|
NSPredicate *pred = [NSPredicate predicateWithFormat:predString];
|
|
NSArray *friendsArr = [data[@"friends"] filteredArrayUsingPredicate:pred];
|
|
self.containerViewController.friendData = friendsArr;
|
|
[self prepareFriendValues];
|
|
[self.tableView reloadData];
|
|
completionHandler(nil);
|
|
}];
|
|
}
|
|
|
|
#pragma mark - Table view data source
|
|
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
|
|
// Return the number of sections.
|
|
return 4;
|
|
}
|
|
|
|
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
|
|
switch (section) {
|
|
case kPlaylistHeader:
|
|
return self.playlistValues.count ? NSLocalizedString(@"table_header_playlist", nil) : nil;
|
|
case kFriendHeader:
|
|
return self.friendValues.count ? NSLocalizedString(@"table_header_friend", nil) : nil;
|
|
default:
|
|
return nil;
|
|
}
|
|
}
|
|
|
|
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
|
|
// Return the number of rows in the section.
|
|
switch (section) {
|
|
case kPlaylist:
|
|
return self.playlistValues.count;
|
|
case kFriend:
|
|
return self.friendValues.count;
|
|
case kPlaylistHeader:
|
|
return self.playlistValues.count ? 1 : 0;
|
|
case kFriendHeader:
|
|
return self.friendValues.count ? 1 : 0;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
|
|
static NSString *cellId = @"SLComposeTableViewControllerCell";
|
|
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId];
|
|
if (cell == nil) {
|
|
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellId];
|
|
}
|
|
|
|
if ([[tableView indexPathsForSelectedRows] containsObject:indexPath]) {
|
|
[tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
|
|
}
|
|
|
|
switch (indexPath.section) {
|
|
case kPlaylist:
|
|
cell.textLabel.text = self.playlistValues[indexPath.row];
|
|
break;
|
|
case kFriend:
|
|
cell.textLabel.text = self.friendValues[indexPath.row];
|
|
break;
|
|
case kPlaylistHeader:
|
|
cell.textLabel.text = NSLocalizedString(@"table_select_all_playlists", nil);
|
|
break;
|
|
case kFriendHeader:
|
|
cell.textLabel.text = NSLocalizedString(@"table_select_all_friends", nil);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return cell;
|
|
}
|
|
|
|
/*
|
|
// Override to support conditional editing of the table view.
|
|
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
|
|
// Return NO if you do not want the specified item to be editable.
|
|
return YES;
|
|
}
|
|
*/
|
|
|
|
/*
|
|
// Override to support editing the table view.
|
|
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
|
|
if (editingStyle == UITableViewCellEditingStyleDelete) {
|
|
// Delete the row from the data source
|
|
[tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
|
|
} else if (editingStyle == UITableViewCellEditingStyleInsert) {
|
|
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
|
|
}
|
|
}
|
|
*/
|
|
|
|
/*
|
|
// Override to support rearranging the table view.
|
|
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath {
|
|
}
|
|
*/
|
|
|
|
#pragma mark - UITableViewDelegate
|
|
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
|
|
switch (indexPath.section) {
|
|
case kPlaylistHeader:
|
|
{
|
|
for (NSUInteger i = 0; i < [tableView numberOfRowsInSection:kPlaylist]; i++) {
|
|
NSIndexPath *playlistIndexPath = [NSIndexPath indexPathForRow:i inSection:kPlaylist];
|
|
[tableView selectRowAtIndexPath:playlistIndexPath
|
|
animated:NO
|
|
scrollPosition:UITableViewScrollPositionNone];
|
|
}
|
|
break;
|
|
}
|
|
|
|
case kFriendHeader:
|
|
{
|
|
for (NSUInteger i = 0; i < [tableView numberOfRowsInSection:kFriend]; i++) {
|
|
NSIndexPath *friendIndexPath = [NSIndexPath indexPathForRow:i inSection:kFriend];
|
|
[tableView selectRowAtIndexPath:friendIndexPath
|
|
animated:NO
|
|
scrollPosition:UITableViewScrollPositionNone];
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
self.navigationItem.rightBarButtonItem.enabled = YES;
|
|
[self updateHeaderTitleWithCounts];
|
|
}
|
|
|
|
- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath {
|
|
switch (indexPath.section) {
|
|
case kPlaylistHeader:
|
|
{
|
|
for (NSUInteger i = 0; i < [tableView numberOfRowsInSection:kPlaylist]; i++) {
|
|
NSIndexPath *playlistIndexPath = [NSIndexPath indexPathForRow:i inSection:kPlaylist];
|
|
[tableView deselectRowAtIndexPath:playlistIndexPath
|
|
animated:NO];
|
|
}
|
|
break;
|
|
}
|
|
|
|
case kFriendHeader:
|
|
{
|
|
for (NSUInteger i = 0; i < [tableView numberOfRowsInSection:kFriend]; i++) {
|
|
NSIndexPath *friendIndexPath = [NSIndexPath indexPathForRow:i inSection:kFriend];
|
|
[tableView deselectRowAtIndexPath:friendIndexPath
|
|
animated:NO];
|
|
}
|
|
break;
|
|
}
|
|
|
|
case kPlaylist:
|
|
{
|
|
NSIndexPath *headerIndexPath = [NSIndexPath indexPathForRow:0 inSection:kPlaylistHeader];
|
|
[tableView deselectRowAtIndexPath:headerIndexPath
|
|
animated:NO];
|
|
break;
|
|
}
|
|
|
|
case kFriend:
|
|
{
|
|
NSIndexPath *headerIndexPath = [NSIndexPath indexPathForRow:0 inSection:kFriendHeader];
|
|
[tableView deselectRowAtIndexPath:headerIndexPath
|
|
animated:NO];
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (![tableView indexPathsForSelectedRows]) {
|
|
self.navigationItem.rightBarButtonItem.enabled = NO;
|
|
}
|
|
[self updateHeaderTitleWithCounts];
|
|
}
|
|
|
|
/*
|
|
// Override to support conditional rearranging of the table view.
|
|
- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath {
|
|
// Return NO if you do not want the item to be re-orderable.
|
|
return YES;
|
|
}
|
|
*/
|
|
|
|
/*
|
|
#pragma mark - Navigation
|
|
|
|
// In a storyboard-based application, you will often want to do a little preparation before navigation
|
|
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
|
|
// Get the new view controller using [segue destinationViewController].
|
|
// Pass the selected object to the new view controller.
|
|
}
|
|
*/
|
|
|
|
@end
|