2015-12-31 09:12:45 +00:00
|
|
|
|
/*
|
2016-10-05 07:36:52 +00:00
|
|
|
|
Copyright 2016 OpenMarket Ltd
|
|
|
|
|
|
2015-12-31 09:12:45 +00:00
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
|
You may obtain a copy of the License at
|
2016-10-05 07:36:52 +00:00
|
|
|
|
|
2015-12-31 09:12:45 +00:00
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
2016-10-05 07:36:52 +00:00
|
|
|
|
|
2015-12-31 09:12:45 +00:00
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
|
limitations under the License.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#import "RoomSearchViewController.h"
|
|
|
|
|
|
2016-10-05 07:36:52 +00:00
|
|
|
|
#import "RoomMessagesSearchViewController.h"
|
|
|
|
|
#import "RoomSearchDataSource.h"
|
2015-12-31 15:12:32 +00:00
|
|
|
|
|
2016-10-05 07:36:52 +00:00
|
|
|
|
#import "RoomFilesSearchViewController.h"
|
|
|
|
|
#import "FilesSearchCellData.h"
|
2015-12-31 15:12:32 +00:00
|
|
|
|
|
2016-05-19 15:22:29 +00:00
|
|
|
|
#import "AppDelegate.h"
|
2019-01-10 16:36:36 +00:00
|
|
|
|
#import "Riot-Swift.h"
|
2016-05-19 15:22:29 +00:00
|
|
|
|
|
2016-03-02 10:52:35 +00:00
|
|
|
|
@interface RoomSearchViewController ()
|
|
|
|
|
{
|
2016-10-05 07:36:52 +00:00
|
|
|
|
RoomMessagesSearchViewController *messagesSearchViewController;
|
|
|
|
|
RoomSearchDataSource *messagesSearchDataSource;
|
2016-05-19 15:22:29 +00:00
|
|
|
|
|
2016-10-05 07:36:52 +00:00
|
|
|
|
RoomFilesSearchViewController *filesSearchViewController;
|
|
|
|
|
MXKSearchDataSource *filesSearchDataSource;
|
2016-03-02 10:52:35 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
2015-12-31 09:12:45 +00:00
|
|
|
|
@implementation RoomSearchViewController
|
|
|
|
|
|
2017-01-03 13:40:23 +00:00
|
|
|
|
- (void)finalizeInit
|
|
|
|
|
{
|
|
|
|
|
[super finalizeInit];
|
|
|
|
|
|
|
|
|
|
// The navigation bar tint color and the rageShake Manager are handled by super (see SegmentedViewController).
|
|
|
|
|
}
|
|
|
|
|
|
2015-12-31 15:12:32 +00:00
|
|
|
|
- (void)viewDidLoad
|
|
|
|
|
{
|
2016-10-05 07:36:52 +00:00
|
|
|
|
// Set up the SegmentedVC tabs before calling [super viewDidLoad]
|
|
|
|
|
NSMutableArray* viewControllers = [[NSMutableArray alloc] init];
|
|
|
|
|
NSMutableArray* titles = [[NSMutableArray alloc] init];
|
|
|
|
|
|
|
|
|
|
[titles addObject: NSLocalizedStringFromTable(@"search_messages", @"Vector", nil)];
|
|
|
|
|
messagesSearchViewController = [RoomMessagesSearchViewController searchViewController];
|
|
|
|
|
[viewControllers addObject:messagesSearchViewController];
|
|
|
|
|
|
|
|
|
|
// add Files tab
|
|
|
|
|
[titles addObject: NSLocalizedStringFromTable(@"search_files", @"Vector", nil)];
|
|
|
|
|
filesSearchViewController = [RoomFilesSearchViewController searchViewController];
|
|
|
|
|
[viewControllers addObject:filesSearchViewController];
|
|
|
|
|
|
|
|
|
|
[self initWithTitles:titles viewControllers:viewControllers defaultSelected:0];
|
|
|
|
|
|
2015-12-31 15:12:32 +00:00
|
|
|
|
[super viewDidLoad];
|
2016-02-10 21:12:17 +00:00
|
|
|
|
|
2017-09-11 20:37:00 +00:00
|
|
|
|
// Add the Riot background image when search bar is empty
|
2016-01-05 15:03:58 +00:00
|
|
|
|
[self addBackgroundImageViewToView:self.view];
|
2016-10-05 07:36:52 +00:00
|
|
|
|
|
|
|
|
|
// Initialize here the data sources if a matrix session has been already set.
|
|
|
|
|
[self initializeDataSources];
|
|
|
|
|
|
|
|
|
|
self.searchBar.autocapitalizationType = UITextAutocapitalizationTypeNone;
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-11 20:37:00 +00:00
|
|
|
|
- (void)userInterfaceThemeDidChange
|
|
|
|
|
{
|
|
|
|
|
[super userInterfaceThemeDidChange];
|
|
|
|
|
|
|
|
|
|
UIImageView *backgroundImageView = self.backgroundImageView;
|
|
|
|
|
if (backgroundImageView)
|
|
|
|
|
{
|
2019-01-11 10:45:27 +00:00
|
|
|
|
UIImage *image = [MXKTools paintImage:backgroundImageView.image withColor:ThemeService.shared.theme.matrixSearchBackgroundImageTintColor];
|
2017-09-11 20:37:00 +00:00
|
|
|
|
backgroundImageView.image = image;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-05 07:36:52 +00:00
|
|
|
|
- (void)destroy
|
|
|
|
|
{
|
|
|
|
|
[super destroy];
|
2015-12-31 15:12:32 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-12-31 09:29:36 +00:00
|
|
|
|
- (void)viewWillAppear:(BOOL)animated
|
|
|
|
|
{
|
|
|
|
|
[super viewWillAppear:animated];
|
2016-06-03 15:29:34 +00:00
|
|
|
|
|
2016-10-05 07:36:52 +00:00
|
|
|
|
// Let's child display the loading not this view controller
|
|
|
|
|
if (self.activityIndicator)
|
|
|
|
|
{
|
|
|
|
|
[self.activityIndicator stopAnimating];
|
|
|
|
|
self.activityIndicator = nil;
|
|
|
|
|
}
|
2017-12-22 12:19:40 +00:00
|
|
|
|
|
|
|
|
|
// Screen tracking
|
2018-06-27 07:55:06 +00:00
|
|
|
|
[[Analytics sharedInstance] trackScreen:@"RoomsSearch"];
|
2016-10-05 07:36:52 +00:00
|
|
|
|
|
|
|
|
|
// Enable the search field by default at the screen opening
|
|
|
|
|
if (self.searchBarHidden)
|
|
|
|
|
{
|
|
|
|
|
[self showSearch:NO];
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-12-31 09:29:36 +00:00
|
|
|
|
|
2016-10-05 07:36:52 +00:00
|
|
|
|
- (void)viewDidAppear:(BOOL)animated
|
|
|
|
|
{
|
|
|
|
|
[super viewDidAppear:animated];
|
|
|
|
|
|
|
|
|
|
// Refresh the search results.
|
|
|
|
|
// Note: We wait for 'viewDidAppear' call to consider the actual view size during this update.
|
|
|
|
|
[self updateSearch];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#pragma mark -
|
|
|
|
|
|
|
|
|
|
- (void)setRoomDataSource:(MXKRoomDataSource *)roomDataSource
|
|
|
|
|
{
|
|
|
|
|
// Remove existing matrix session if any
|
|
|
|
|
while (self.mainSession)
|
2016-03-02 15:23:56 +00:00
|
|
|
|
{
|
2016-10-05 07:36:52 +00:00
|
|
|
|
[self removeMatrixSession:self.mainSession];
|
2016-03-02 15:23:56 +00:00
|
|
|
|
}
|
2016-05-19 15:22:29 +00:00
|
|
|
|
|
2016-10-05 07:36:52 +00:00
|
|
|
|
_roomDataSource = roomDataSource;
|
|
|
|
|
|
|
|
|
|
[self addMatrixSession:_roomDataSource.mxSession];
|
|
|
|
|
|
|
|
|
|
// Check whether the controller's view is already loaded into memory.
|
|
|
|
|
if (messagesSearchViewController)
|
|
|
|
|
{
|
|
|
|
|
// Prepare data sources
|
|
|
|
|
[self initializeDataSources];
|
|
|
|
|
}
|
2016-05-19 15:22:29 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-10-05 07:36:52 +00:00
|
|
|
|
- (void)initializeDataSources
|
2016-05-19 15:22:29 +00:00
|
|
|
|
{
|
2016-10-05 07:36:52 +00:00
|
|
|
|
MXSession *mainSession = self.mainSession;
|
2016-05-19 15:22:29 +00:00
|
|
|
|
|
2016-10-05 07:36:52 +00:00
|
|
|
|
if (mainSession && _roomDataSource)
|
2016-05-19 15:22:29 +00:00
|
|
|
|
{
|
2016-10-05 07:36:52 +00:00
|
|
|
|
// Init the search for messages
|
|
|
|
|
messagesSearchDataSource = [[RoomSearchDataSource alloc] initWithRoomDataSource:_roomDataSource];
|
|
|
|
|
[messagesSearchViewController displaySearch:messagesSearchDataSource];
|
|
|
|
|
|
2016-10-06 08:56:52 +00:00
|
|
|
|
// Init the search for attachments
|
|
|
|
|
filesSearchDataSource = [[MXKSearchDataSource alloc] initWithMatrixSession:mainSession];
|
|
|
|
|
filesSearchDataSource.roomEventFilter.rooms = @[_roomDataSource.roomId];
|
|
|
|
|
filesSearchDataSource.roomEventFilter.containsURL = YES;
|
2016-10-05 07:36:52 +00:00
|
|
|
|
filesSearchDataSource.shouldShowRoomDisplayName = NO;
|
|
|
|
|
[filesSearchDataSource registerCellDataClass:FilesSearchCellData.class forCellIdentifier:kMXKSearchCellDataIdentifier];
|
|
|
|
|
[filesSearchViewController displaySearch:filesSearchDataSource];
|
2016-05-19 15:22:29 +00:00
|
|
|
|
}
|
2015-12-31 09:29:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-01-05 10:07:36 +00:00
|
|
|
|
#pragma mark - Override MXKViewController
|
|
|
|
|
|
|
|
|
|
- (void)setKeyboardHeight:(CGFloat)keyboardHeight
|
|
|
|
|
{
|
2016-01-05 15:27:50 +00:00
|
|
|
|
[self setKeyboardHeightForBackgroundImage:keyboardHeight];
|
2016-10-05 07:36:52 +00:00
|
|
|
|
|
2016-01-05 10:07:36 +00:00
|
|
|
|
[super setKeyboardHeight:keyboardHeight];
|
2016-10-05 07:36:52 +00:00
|
|
|
|
|
|
|
|
|
[self checkAndShowBackgroundImage];
|
2016-01-05 10:07:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-10-05 07:36:52 +00:00
|
|
|
|
- (void)startActivityIndicator
|
2016-03-02 15:23:56 +00:00
|
|
|
|
{
|
2016-10-05 07:36:52 +00:00
|
|
|
|
// Redirect the operation to the currently displayed VC
|
|
|
|
|
// It is a MXKViewController or a MXKTableViewController. So it supports startActivityIndicator
|
|
|
|
|
[self.selectedViewController performSelector:@selector(startActivityIndicator)];
|
|
|
|
|
}
|
2016-03-02 15:23:56 +00:00
|
|
|
|
|
2016-10-05 07:36:52 +00:00
|
|
|
|
- (void)stopActivityIndicator
|
|
|
|
|
{
|
|
|
|
|
// The selected view controller mwy have changed since the call of [self startActivityIndicator]
|
|
|
|
|
// So, stop the activity indicator for all children
|
|
|
|
|
for (UIViewController *viewController in self.viewControllers)
|
2016-03-02 15:23:56 +00:00
|
|
|
|
{
|
2016-10-05 07:36:52 +00:00
|
|
|
|
[viewController performSelector:@selector(stopActivityIndicator)];
|
2016-03-02 15:23:56 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-05 07:36:52 +00:00
|
|
|
|
#pragma mark - Override UIViewController+VectorSearch
|
|
|
|
|
|
|
|
|
|
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
|
2015-12-31 09:29:36 +00:00
|
|
|
|
{
|
2016-10-05 07:36:52 +00:00
|
|
|
|
if (!self.searchBar.text.length)
|
|
|
|
|
{
|
|
|
|
|
// Reset current search if any
|
|
|
|
|
[self updateSearch];
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-12-31 09:29:36 +00:00
|
|
|
|
|
2016-10-05 07:36:52 +00:00
|
|
|
|
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar
|
|
|
|
|
{
|
|
|
|
|
[searchBar resignFirstResponder];
|
|
|
|
|
|
|
|
|
|
if (self.selectedViewController == messagesSearchViewController || self.selectedViewController == filesSearchViewController)
|
2016-01-05 10:07:36 +00:00
|
|
|
|
{
|
2016-10-05 07:36:52 +00:00
|
|
|
|
// As the messages/files search is done homeserver-side, launch it only on the "Search" button
|
|
|
|
|
[self updateSearch];
|
2016-01-05 10:07:36 +00:00
|
|
|
|
}
|
2015-12-31 09:29:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-10-05 07:36:52 +00:00
|
|
|
|
- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar
|
2015-12-31 09:29:36 +00:00
|
|
|
|
{
|
2016-10-05 07:36:52 +00:00
|
|
|
|
[searchBar resignFirstResponder];
|
|
|
|
|
|
2015-12-31 09:29:36 +00:00
|
|
|
|
// Leave the screen
|
|
|
|
|
[self.navigationController popViewControllerAnimated:YES];
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-05 07:36:52 +00:00
|
|
|
|
- (void)setKeyboardHeightForBackgroundImage:(CGFloat)keyboardHeight
|
2015-12-31 15:12:32 +00:00
|
|
|
|
{
|
2016-10-05 07:36:52 +00:00
|
|
|
|
[super setKeyboardHeightForBackgroundImage:keyboardHeight];
|
|
|
|
|
|
|
|
|
|
if (keyboardHeight > 0)
|
2015-12-31 15:12:32 +00:00
|
|
|
|
{
|
2016-10-05 07:36:52 +00:00
|
|
|
|
[self checkAndShowBackgroundImage];
|
2015-12-31 15:12:32 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-05 07:36:52 +00:00
|
|
|
|
// Check conditions before displaying the background
|
|
|
|
|
- (void)checkAndShowBackgroundImage
|
2015-12-31 15:12:32 +00:00
|
|
|
|
{
|
2016-10-05 07:36:52 +00:00
|
|
|
|
// Note: This background is hidden when keyboard is dismissed.
|
|
|
|
|
// The other conditions depend on the current selected view controller.
|
|
|
|
|
if (self.selectedViewController == messagesSearchViewController)
|
2015-12-31 15:12:32 +00:00
|
|
|
|
{
|
2016-10-05 07:36:52 +00:00
|
|
|
|
self.backgroundImageView.hidden = ((messagesSearchDataSource.serverCount != 0) || !messagesSearchViewController.noResultsLabel.isHidden || (self.keyboardHeight == 0));
|
|
|
|
|
}
|
|
|
|
|
else if (self.selectedViewController == filesSearchViewController)
|
|
|
|
|
{
|
|
|
|
|
self.backgroundImageView.hidden = ((filesSearchDataSource.serverCount != 0) || !filesSearchViewController.noResultsLabel.isHidden || (self.keyboardHeight == 0));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
self.backgroundImageView.hidden = (self.keyboardHeight == 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!self.backgroundImageView.hidden)
|
|
|
|
|
{
|
|
|
|
|
[self.backgroundImageView layoutIfNeeded];
|
|
|
|
|
[self.selectedViewController.view layoutIfNeeded];
|
|
|
|
|
|
|
|
|
|
// Check whether there is enough space to display this background
|
|
|
|
|
// For example, in landscape with the iPhone 5 & 6 screen size, the backgroundImageView must be hidden.
|
|
|
|
|
if (self.backgroundImageView.frame.origin.y < 0 || (self.selectedViewController.view.frame.size.height - self.backgroundImageViewBottomConstraint.constant) < self.backgroundImageView.frame.size.height)
|
|
|
|
|
{
|
|
|
|
|
self.backgroundImageView.hidden = YES;
|
|
|
|
|
}
|
2015-12-31 15:12:32 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-05 07:36:52 +00:00
|
|
|
|
#pragma mark - Override SegmentedViewController
|
2016-01-05 10:07:36 +00:00
|
|
|
|
|
2016-10-05 07:36:52 +00:00
|
|
|
|
- (void)setSelectedIndex:(NSUInteger)selectedIndex
|
2015-12-31 15:12:32 +00:00
|
|
|
|
{
|
2016-10-05 07:36:52 +00:00
|
|
|
|
[super setSelectedIndex:selectedIndex];
|
|
|
|
|
|
|
|
|
|
[self updateSearch];
|
2016-03-02 10:52:35 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-10-05 07:36:52 +00:00
|
|
|
|
#pragma mark - Navigation
|
2016-03-02 10:52:35 +00:00
|
|
|
|
|
|
|
|
|
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
|
|
|
|
|
{
|
|
|
|
|
[super prepareForSegue:segue sender:sender];
|
2016-10-05 07:36:52 +00:00
|
|
|
|
|
2016-03-02 10:52:35 +00:00
|
|
|
|
if ([[segue identifier] isEqualToString:@"showTimeline"])
|
|
|
|
|
{
|
2016-10-05 07:36:52 +00:00
|
|
|
|
// Check whether an event has been selected from messages or files search tab
|
|
|
|
|
MXEvent *selectedSearchEvent = messagesSearchViewController.selectedEvent;
|
|
|
|
|
MXSession *selectedSearchEventSession = messagesSearchDataSource.mxSession;
|
|
|
|
|
if (!selectedSearchEvent)
|
|
|
|
|
{
|
|
|
|
|
selectedSearchEvent = filesSearchViewController.selectedEvent;
|
|
|
|
|
selectedSearchEventSession = filesSearchDataSource.mxSession;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (selectedSearchEvent)
|
|
|
|
|
{
|
|
|
|
|
RoomViewController *roomViewController = segue.destinationViewController;
|
2018-07-20 08:43:04 +00:00
|
|
|
|
|
|
|
|
|
[RoomDataSource loadRoomDataSourceWithRoomId:selectedSearchEvent.roomId
|
|
|
|
|
initialEventId:selectedSearchEvent.eventId
|
|
|
|
|
andMatrixSession:selectedSearchEventSession onComplete:^(RoomDataSource *roomDataSource) {
|
|
|
|
|
|
|
|
|
|
[roomDataSource finalizeInitialization];
|
|
|
|
|
roomDataSource.markTimelineInitialEvent = YES;
|
|
|
|
|
|
|
|
|
|
[roomViewController displayRoom:roomDataSource];
|
|
|
|
|
roomViewController.hasRoomDataSourceOwnership = YES;
|
|
|
|
|
|
|
|
|
|
roomViewController.navigationItem.leftItemsSupplementBackButton = YES;
|
|
|
|
|
}];
|
2016-10-05 07:36:52 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Hide back button title
|
|
|
|
|
self.navigationItem.backBarButtonItem =[[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil];
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-03-02 10:52:35 +00:00
|
|
|
|
|
2016-10-05 07:36:52 +00:00
|
|
|
|
#pragma mark - Search
|
2016-03-02 10:52:35 +00:00
|
|
|
|
|
2016-10-05 07:36:52 +00:00
|
|
|
|
// Update search results under the currently selected tab
|
|
|
|
|
- (void)updateSearch
|
|
|
|
|
{
|
|
|
|
|
if (self.searchBar.text.length)
|
|
|
|
|
{
|
|
|
|
|
self.backgroundImageView.hidden = YES;
|
|
|
|
|
|
|
|
|
|
// Forward the search request to the data source
|
|
|
|
|
if (self.selectedViewController == messagesSearchViewController)
|
|
|
|
|
{
|
|
|
|
|
// Launch the search only if the keyboard is no more visible
|
|
|
|
|
if (!self.searchBar.isFirstResponder)
|
|
|
|
|
{
|
|
|
|
|
// Do it asynchronously to give time to messagesSearchViewController to be set up
|
|
|
|
|
// so that it can display its loading wheel
|
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
|
|
|
[messagesSearchDataSource searchMessages:self.searchBar.text force:NO];
|
|
|
|
|
messagesSearchViewController.shouldScrollToBottomOnRefresh = YES;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (self.selectedViewController == filesSearchViewController)
|
|
|
|
|
{
|
|
|
|
|
// Launch the search only if the keyboard is no more visible
|
|
|
|
|
if (!self.searchBar.isFirstResponder)
|
|
|
|
|
{
|
|
|
|
|
// Do it asynchronously to give time to filesSearchViewController to be set up
|
|
|
|
|
// so that it can display its loading wheel
|
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
|
|
|
[filesSearchDataSource searchMessages:self.searchBar.text force:NO];
|
|
|
|
|
filesSearchViewController.shouldScrollToBottomOnRefresh = YES;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-03-02 10:52:35 +00:00
|
|
|
|
}
|
2016-10-05 07:36:52 +00:00
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Nothing to search - Reset search result (if any)
|
|
|
|
|
if (messagesSearchDataSource.searchText.length)
|
|
|
|
|
{
|
|
|
|
|
[messagesSearchDataSource searchMessages:nil force:NO];
|
|
|
|
|
}
|
|
|
|
|
if (filesSearchDataSource.searchText.length)
|
|
|
|
|
{
|
|
|
|
|
[filesSearchDataSource searchMessages:nil force:NO];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[self checkAndShowBackgroundImage];
|
2016-03-02 10:52:35 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-12-31 09:12:45 +00:00
|
|
|
|
@end
|