From ebb20b739da37614cd7257366c9cfbc9a96972b6 Mon Sep 17 00:00:00 2001 From: Oleg Montak Date: Thu, 9 May 2024 00:59:42 +0300 Subject: [PATCH] [ios] CarPlay/phone switch prototype Signed-off-by: Oleg Montak --- .../car/screens/MapPlaceholderScreen.java | 5 +- .../res/layout/activity_map_placeholder.xml | 6 +- data/strings/strings.txt | 20 ++--- .../Maps/Classes/CarPlay/CarPlayService.swift | 53 +++++++++++- .../CarPlay/CarPlayWindowScaleAdjuster.swift | 51 +++++++++++ .../CarPlay/CarplayPlaceholderView.swift | 85 +++++++++++++++++++ iphone/Maps/Classes/MapViewController.mm | 7 +- iphone/Maps/Classes/MapsAppDelegate.mm | 40 --------- iphone/Maps/Maps.xcodeproj/project.pbxproj | 8 ++ iphone/Maps/UI/Storyboard/Main.storyboard | 44 +++++----- 10 files changed, 237 insertions(+), 82 deletions(-) create mode 100644 iphone/Maps/Classes/CarPlay/CarPlayWindowScaleAdjuster.swift create mode 100644 iphone/Maps/Classes/CarPlay/CarplayPlaceholderView.swift diff --git a/android/app/src/main/java/app/organicmaps/car/screens/MapPlaceholderScreen.java b/android/app/src/main/java/app/organicmaps/car/screens/MapPlaceholderScreen.java index 0ed472acb3..a58e1cd5ba 100644 --- a/android/app/src/main/java/app/organicmaps/car/screens/MapPlaceholderScreen.java +++ b/android/app/src/main/java/app/organicmaps/car/screens/MapPlaceholderScreen.java @@ -25,15 +25,14 @@ public class MapPlaceholderScreen extends BaseScreen @Override public Template onGetTemplate() { - final MessageTemplate.Builder builder = new MessageTemplate.Builder(getCarContext().getString(R.string.aa_used_on_the_phone_screen)); + final MessageTemplate.Builder builder = new MessageTemplate.Builder(getCarContext().getString(R.string.car_used_on_the_phone_screen)); final Header.Builder headerBuilder = new Header.Builder(); headerBuilder.setStartHeaderAction(Action.APP_ICON); headerBuilder.setTitle(getCarContext().getString(R.string.app_name)); builder.setHeader(headerBuilder.build()); - builder.setIcon(new CarIcon.Builder(IconCompat.createWithResource(getCarContext(), R.drawable.ic_phone_android)).build()); - builder.addAction(new Action.Builder().setTitle(getCarContext().getString(R.string.aa_continue_in_the_car)) + builder.addAction(new Action.Builder().setTitle(getCarContext().getString(R.string.car_continue_in_the_car)) .setOnClickListener(() -> DisplayManager.from(getCarContext()).changeDisplay(DisplayType.Car)).build()); return builder.build(); diff --git a/android/app/src/main/res/layout/activity_map_placeholder.xml b/android/app/src/main/res/layout/activity_map_placeholder.xml index 44be48f2ed..45707d7fed 100644 --- a/android/app/src/main/res/layout/activity_map_placeholder.xml +++ b/android/app/src/main/res/layout/activity_map_placeholder.xml @@ -34,7 +34,7 @@ @@ -47,5 +47,5 @@ android:layout_marginEnd="32dp" android:layout_marginStart="32dp" android:layout_marginTop="24dp" - android:text="@string/aa_continue_on_the_phone" /> - \ No newline at end of file + android:text="@string/car_continue_on_the_phone" /> + diff --git a/data/strings/strings.txt b/data/strings/strings.txt index cf92f3f674..2d0f187a1d 100644 --- a/data/strings/strings.txt +++ b/data/strings/strings.txt @@ -29348,9 +29348,9 @@ zh-Hans = 我们的主要目标是构建您会喜欢的快速、注重隐私、易于使用的地图。 zh-Hant = 我們的主要目標是構建您會喜歡的快速、注重隱私、易於使用的地圖。 - [aa_used_on_the_phone_screen] - comment = Text on the Android Auto placeholder screen that maps are displayed on the phone screen - tags = android + [car_used_on_the_phone_screen] + comment = Text on the Android Auto or CarPlay placeholder screen that maps are displayed on the phone screen + tags = android,ios en = You are now using Organic Maps on the phone screen ar = أنت الآن تستخدم الخرائط العضوية على شاشة الهاتف az = İndi telefon ekranında Organic Maps istifadə edirsiniz @@ -29393,9 +29393,9 @@ zh-Hans = 您现在正在手机屏幕上使用有机地图 zh-Hant = 您現在正在手機螢幕上使用有機地圖 - [aa_used_on_the_car_screen] + [car_used_on_the_car_screen] comment = Text on the phone placeholder screen that maps are displayed on the car screen - tags = android + tags = android,ios en = You are now using Organic Maps on the car screen ar = أنت الآن تستخدم الخرائط العضوية على شاشة السيارة az = Hazırda avtomobil ekranında Organic Maps istifadə edirsiniz @@ -29483,9 +29483,9 @@ zh-Hans = 您已连接到 Android Auto zh-Hant = 您已連接到 Android Auto - [aa_continue_on_the_phone] + [car_continue_on_the_phone] comment = Displayed on the phone screen. Button to display maps on the phone screen instead of a car - tags = android + tags = android,ios en = Continue on the phone ar = تواصل على الهاتف az = Telefonda davam edin @@ -29528,9 +29528,9 @@ zh-Hans = 在手机上继续 zh-Hant = 繼續打電話 - [aa_continue_in_the_car] - comment = Displayed on the Android Auto screen. Button to display maps on the car screen instead of a phone. Must be no more than 18 symbols! - tags = android + [car_continue_in_the_car] + comment = Displayed on the Android Auto or CarPlay screen. Button to display maps on the car screen instead of a phone. Must be no more than 18 symbols! + tags = android,ios en = To the car screen ar = إلى شاشة السيارة az = Avtomobil ekranına diff --git a/iphone/Maps/Classes/CarPlay/CarPlayService.swift b/iphone/Maps/Classes/CarPlay/CarPlayService.swift index 7749b24afe..f709d98dd5 100644 --- a/iphone/Maps/Classes/CarPlay/CarPlayService.swift +++ b/iphone/Maps/Classes/CarPlay/CarPlayService.swift @@ -58,9 +58,45 @@ final class CarPlayService: NSObject { updateContentStyle(configuration.contentStyle) } FrameworkHelper.updatePositionArrowOffset(false, offset: 5) + + CarPlayWindowScaleAdjuster.updateAppearance( + fromWindow: MapsAppDelegate.theApp().window, + toWindow: window, + isCarplayActivated: true + ) } - @objc func destroy() { + private var savedInterfaceController: CPInterfaceController? + @objc func showOnPhone() { + savedInterfaceController = interfaceController + switchScreenToPhone() + showPhoneModeAlert() + } + + @objc func showOnCarplay() { + if let window, let savedInterfaceController { + setup(window: window, interfaceController: savedInterfaceController) + } + } + + private func showPhoneModeAlert() { + let switchToCarAction = CPAlertAction( + title: L("car_continue_in_the_car"), + style: .default, + handler: { [weak self] _ in + self?.savedInterfaceController?.dismissTemplate(animated: false) + self?.showOnCarplay() + } + ) + let alert = CPAlertTemplate( + titleVariants: [L("car_used_on_the_phone_screen")], + actions: [switchToCarAction] + ) + savedInterfaceController?.dismissTemplate(animated: false) + savedInterfaceController?.presentTemplate(alert, animated: false) + } + + private func switchScreenToPhone() { if let carplayVC = carplayVC { carplayVC.removeMapView() } @@ -84,6 +120,21 @@ final class CarPlayService: NSObject { interfaceController = nil ThemeManager.invalidate() FrameworkHelper.updatePositionArrowOffset(true, offset: 0) + + if let window { + CarPlayWindowScaleAdjuster.updateAppearance( + fromWindow: window, + toWindow: MapsAppDelegate.theApp().window, + isCarplayActivated: false + ) + } + } + + @objc func destroy() { + if isCarplayActivated { + switchScreenToPhone() + } + savedInterfaceController = nil } @objc func interfaceStyle() -> UIUserInterfaceStyle { diff --git a/iphone/Maps/Classes/CarPlay/CarPlayWindowScaleAdjuster.swift b/iphone/Maps/Classes/CarPlay/CarPlayWindowScaleAdjuster.swift new file mode 100644 index 0000000000..ee8a735361 --- /dev/null +++ b/iphone/Maps/Classes/CarPlay/CarPlayWindowScaleAdjuster.swift @@ -0,0 +1,51 @@ +import Foundation +import UIKit + +enum CarPlayWindowScaleAdjuster { + + static func updateAppearance( + fromWindow sourceWindow: UIWindow, + toWindow destinationWindow: UIWindow, + isCarplayActivated: Bool + ) { + + let sourceContentScale = sourceWindow.screen.scale; + let destinationContentScale = destinationWindow.screen.scale; + + if abs(sourceContentScale - destinationContentScale) > 0.1 { + if isCarplayActivated { + updateVisualScale(to: destinationContentScale) + } else { + updateVisualScaleToMain() + } + } + } + + private static func updateVisualScale(to scale: CGFloat) { + if isGraphicContextInitialized { + mapViewController?.mapView.updateVisualScale(to: scale) + } else { + DispatchQueue.main.async { + updateVisualScale(to: scale) + } + } + } + + private static func updateVisualScaleToMain() { + if isGraphicContextInitialized { + mapViewController?.mapView.updateVisualScaleToMain() + } else { + DispatchQueue.main.async { + updateVisualScaleToMain() + } + } + } + + private static var isGraphicContextInitialized: Bool { + return mapViewController?.mapView.graphicContextInitialized ?? false + } + + private static var mapViewController: MapViewController? { + return MapViewController.shared() + } +} diff --git a/iphone/Maps/Classes/CarPlay/CarplayPlaceholderView.swift b/iphone/Maps/Classes/CarPlay/CarplayPlaceholderView.swift new file mode 100644 index 0000000000..c4076dabea --- /dev/null +++ b/iphone/Maps/Classes/CarPlay/CarplayPlaceholderView.swift @@ -0,0 +1,85 @@ +import Foundation + +class CarplayPlaceholderView: UIView { + private let containerView = UIView() + private let imageView = UIImageView() + private let descriptionLabel = UILabel() + private let switchButton = UIButton(type: .system); + + override init(frame: CGRect) { + super.init(frame: frame) + commonInit() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + commonInit() + } + + private func commonInit() { + backgroundColor = .clear + + addSubview(containerView) + + imageView.image = UIImage(named: "ic_carplay_activated") + imageView.contentMode = .scaleAspectFit + containerView.addSubview(imageView) + + descriptionLabel.text = L("car_used_on_the_car_screen") + descriptionLabel.font = UIFont.bold24() + descriptionLabel.textColor = UIColor.blackSecondaryText() + descriptionLabel.textAlignment = .center + descriptionLabel.numberOfLines = 0 + containerView.addSubview(descriptionLabel) + + switchButton.setTitle(L("car_continue_on_the_phone"), for: .normal) + switchButton.addTarget(self, action: #selector(onSwitchButtonTap), for: .touchUpInside) + switchButton.setTitleColor(UIColor.whitePrimaryText(), for: .normal) + switchButton.titleLabel?.font = UIFont.medium16() + switchButton.titleLabel?.lineBreakMode = .byWordWrapping + switchButton.titleLabel?.textAlignment = .center + switchButton.backgroundColor = UIColor.linkBlue() + switchButton.layer.cornerRadius = 8 + containerView.addSubview(switchButton) + + setupConstraints() + } + + @objc private func onSwitchButtonTap(_ sender: UIButton) { + CarPlayService.shared.showOnPhone() + } + + private func setupConstraints() { + translatesAutoresizingMaskIntoConstraints = false + + containerView.translatesAutoresizingMaskIntoConstraints = false + imageView.translatesAutoresizingMaskIntoConstraints = false + descriptionLabel.translatesAutoresizingMaskIntoConstraints = false + switchButton.translatesAutoresizingMaskIntoConstraints = false + + let horizontalPadding: CGFloat = 24 + + NSLayoutConstraint.activate([ + containerView.centerYAnchor.constraint(equalTo: centerYAnchor), + containerView.topAnchor.constraint(greaterThanOrEqualTo: topAnchor), + containerView.leadingAnchor.constraint(equalTo: leadingAnchor), + containerView.trailingAnchor.constraint(equalTo: trailingAnchor), + containerView.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor), + + imageView.topAnchor.constraint(equalTo: containerView.topAnchor), + imageView.centerXAnchor.constraint(equalTo: centerXAnchor), + imageView.widthAnchor.constraint(equalToConstant: 160), + imageView.heightAnchor.constraint(equalToConstant: 160), + + descriptionLabel.topAnchor.constraint(equalTo: imageView.bottomAnchor, constant: 32), + descriptionLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: horizontalPadding), + descriptionLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -horizontalPadding), + + switchButton.topAnchor.constraint(equalTo: descriptionLabel.bottomAnchor, constant: 24), + switchButton.leadingAnchor.constraint(equalTo: leadingAnchor, constant: horizontalPadding), + switchButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -horizontalPadding), + switchButton.heightAnchor.constraint(equalToConstant: 48), + switchButton.bottomAnchor.constraint(equalTo: containerView.bottomAnchor) + ]) + } +} diff --git a/iphone/Maps/Classes/MapViewController.mm b/iphone/Maps/Classes/MapViewController.mm index 2610de2207..112c702046 100644 --- a/iphone/Maps/Classes/MapViewController.mm +++ b/iphone/Maps/Classes/MapViewController.mm @@ -84,7 +84,7 @@ NSString *const kPP2BookmarkEditingSegue = @"PP2BookmarkEditing"; @property(strong, nonatomic) IBOutlet NSLayoutConstraint *placePageAreaKeyboard; @property(strong, nonatomic) IBOutlet NSLayoutConstraint *sideButtonsAreaBottom; @property(strong, nonatomic) IBOutlet NSLayoutConstraint *sideButtonsAreaKeyboard; -@property(strong, nonatomic) IBOutlet UIImageView *carplayPlaceholderLogo; +@property(strong, nonatomic) IBOutlet UIView *carplayPlaceholderView; @property(strong, nonatomic) BookmarksCoordinator * bookmarksCoordinator; @property(strong, nonatomic) NSHashTable> *listeners; @@ -343,7 +343,6 @@ NSString *const kPP2BookmarkEditingSegue = @"PP2BookmarkEditing"; // After all users migrate to OAuth2 we can remove next code [self migrateOAuthCredentials]; - /// @todo: Uncomment update dialog when will be ready to handle big traffic bursts. /* if (!DeepLinkHandler.shared.isLaunchedByDeeplink) @@ -664,7 +663,7 @@ NSString *const kPP2BookmarkEditingSegue = @"PP2BookmarkEditing"; #pragma mark - CarPlay map append/remove - (void)disableCarPlayRepresentation { - self.carplayPlaceholderLogo.hidden = YES; + self.carplayPlaceholderView.hidden = YES; self.mapView.frame = self.view.bounds; [self.view insertSubview:self.mapView atIndex:0]; [[self.mapView.topAnchor constraintEqualToAnchor:self.view.topAnchor] setActive:YES]; @@ -686,7 +685,7 @@ NSString *const kPP2BookmarkEditingSegue = @"PP2BookmarkEditing"; if (!self.controlsView.isHidden) { self.controlsView.hidden = YES; } - self.carplayPlaceholderLogo.hidden = NO; + self.carplayPlaceholderView.hidden = NO; } #pragma mark - MWMBookmarksObserver diff --git a/iphone/Maps/Classes/MapsAppDelegate.mm b/iphone/Maps/Classes/MapsAppDelegate.mm index 13272815aa..e3e24ba733 100644 --- a/iphone/Maps/Classes/MapsAppDelegate.mm +++ b/iphone/Maps/Classes/MapsAppDelegate.mm @@ -79,10 +79,6 @@ using namespace osm_auth_ios; return self.mapViewController.mapView.drapeEngineCreated; } -- (BOOL)isGraphicContextInitialized { - return self.mapViewController.mapView.graphicContextInitialized; -} - - (void)searchText:(NSString *)searchString { if (!self.isDrapeEngineCreated) { dispatch_async(dispatch_get_main_queue(), ^{ @@ -417,48 +413,12 @@ using namespace osm_auth_ios; didConnectCarInterfaceController:(CPInterfaceController *)interfaceController toWindow:(CPWindow *)window API_AVAILABLE(ios(12.0)) { [self.carplayService setupWithWindow:window interfaceController:interfaceController]; - [self updateAppearanceFromWindow:self.window toWindow:window isCarplayActivated:YES]; } - (void)application:(UIApplication *)application didDisconnectCarInterfaceController:(CPInterfaceController *)interfaceController fromWindow:(CPWindow *)window API_AVAILABLE(ios(12.0)) { [self.carplayService destroy]; - [self updateAppearanceFromWindow:window toWindow:self.window isCarplayActivated:NO]; -} - -- (void)updateAppearanceFromWindow:(UIWindow *)sourceWindow - toWindow:(UIWindow *)destinationWindow - isCarplayActivated:(BOOL)isCarplayActivated { - CGFloat sourceContentScale = sourceWindow.screen.scale; - CGFloat destinationContentScale = destinationWindow.screen.scale; - if (ABS(sourceContentScale - destinationContentScale) > 0.1) { - if (isCarplayActivated) { - [self updateVisualScale:destinationContentScale]; - } else { - [self updateVisualScaleToMain]; - } - } -} - -- (void)updateVisualScale:(CGFloat)scale { - if ([self isGraphicContextInitialized]) { - [self.mapViewController.mapView updateVisualScaleTo:scale]; - } else { - dispatch_async(dispatch_get_main_queue(), ^{ - [self updateVisualScale:scale]; - }); - } -} - -- (void)updateVisualScaleToMain { - if ([self isGraphicContextInitialized]) { - [self.mapViewController.mapView updateVisualScaleToMain]; - } else { - dispatch_async(dispatch_get_main_queue(), ^{ - [self updateVisualScaleToMain]; - }); - } } @end diff --git a/iphone/Maps/Maps.xcodeproj/project.pbxproj b/iphone/Maps/Maps.xcodeproj/project.pbxproj index 9caf76600b..696fa524d1 100644 --- a/iphone/Maps/Maps.xcodeproj/project.pbxproj +++ b/iphone/Maps/Maps.xcodeproj/project.pbxproj @@ -325,6 +325,8 @@ 6B15907226623AE500944BBA /* 00_NotoSansThai-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 6B15907026623AE500944BBA /* 00_NotoSansThai-Regular.ttf */; }; 6B679E89266BFD0A0074AE2A /* 00_NotoNaskhArabic-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 6B679E88266BFD090074AE2A /* 00_NotoNaskhArabic-Regular.ttf */; }; 6B9978361C89A316003B8AA0 /* editor.config in Resources */ = {isa = PBXBuildFile; fileRef = 6B9978341C89A316003B8AA0 /* editor.config */; }; + 8C4FB9C72BEFEFF400D44877 /* CarPlayWindowScaleAdjuster.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C4FB9C62BEFEFF400D44877 /* CarPlayWindowScaleAdjuster.swift */; }; + 8CB13C3B2BF1276A004288F2 /* CarplayPlaceholderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CB13C3A2BF1276A004288F2 /* CarplayPlaceholderView.swift */; }; 99012847243F0D6900C72B10 /* UIViewController+alternative.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99012846243F0D6900C72B10 /* UIViewController+alternative.swift */; }; 9901284F244732DB00C72B10 /* BottomTabBarPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99012849244732DB00C72B10 /* BottomTabBarPresenter.swift */; }; 99012851244732DB00C72B10 /* BottomTabBarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9901284B244732DB00C72B10 /* BottomTabBarViewController.swift */; }; @@ -1168,6 +1170,8 @@ 6B15907026623AE500944BBA /* 00_NotoSansThai-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "00_NotoSansThai-Regular.ttf"; path = "../../data/00_NotoSansThai-Regular.ttf"; sourceTree = ""; }; 6B679E88266BFD090074AE2A /* 00_NotoNaskhArabic-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "00_NotoNaskhArabic-Regular.ttf"; path = "../../data/00_NotoNaskhArabic-Regular.ttf"; sourceTree = ""; }; 6B9978341C89A316003B8AA0 /* editor.config */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = editor.config; path = ../../data/editor.config; sourceTree = ""; }; + 8C4FB9C62BEFEFF400D44877 /* CarPlayWindowScaleAdjuster.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarPlayWindowScaleAdjuster.swift; sourceTree = ""; }; + 8CB13C3A2BF1276A004288F2 /* CarplayPlaceholderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarplayPlaceholderView.swift; sourceTree = ""; }; 8D1107310486CEB800E47090 /* OMaps.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = OMaps.plist; plistStructureDefinitionIdentifier = "com.apple.xcode.plist.structure-definition.iphone.info-plist"; sourceTree = ""; }; 978D4A30199A11E600D72CA7 /* faq.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; name = faq.html; path = ../../data/faq.html; sourceTree = ""; }; 97A5967E19B9CD47007A963F /* copyright.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; name = copyright.html; path = ../../data/copyright.html; sourceTree = ""; }; @@ -2908,6 +2912,8 @@ CDCA273E2238087700167D87 /* MWMCarPlaySearchService.mm */, CDCA2746223FD24600167D87 /* MWMCarPlaySearchResultObject.h */, CDCA2747223FD24600167D87 /* MWMCarPlaySearchResultObject.mm */, + 8C4FB9C62BEFEFF400D44877 /* CarPlayWindowScaleAdjuster.swift */, + 8CB13C3A2BF1276A004288F2 /* CarplayPlaceholderView.swift */, ); path = CarPlay; sourceTree = ""; @@ -4213,7 +4219,9 @@ F6E2FE4F1E097BA00083EBEC /* MWMActionBarButton.m in Sources */, 47F86CFF20C936FC00FEE291 /* TabView.swift in Sources */, 34AB66741FC5AA330078E451 /* BaseRoutePreviewStatus.swift in Sources */, + 8C4FB9C72BEFEFF400D44877 /* CarPlayWindowScaleAdjuster.swift in Sources */, 349D1CE41E3F836900A878FD /* UIViewController+Hierarchy.swift in Sources */, + 8CB13C3B2BF1276A004288F2 /* CarplayPlaceholderView.swift in Sources */, CDB4D4E1222D70DF00104869 /* CarPlayMapViewController.swift in Sources */, 471AB98923AA8A3500F56D49 /* IDownloaderDataSource.swift in Sources */, EDE243E72B6D55610057369B /* InfoView.swift in Sources */, diff --git a/iphone/Maps/UI/Storyboard/Main.storyboard b/iphone/Maps/UI/Storyboard/Main.storyboard index 39f2b7e6cf..c5060dde26 100644 --- a/iphone/Maps/UI/Storyboard/Main.storyboard +++ b/iphone/Maps/UI/Storyboard/Main.storyboard @@ -1,10 +1,11 @@ - + - + + @@ -16,13 +17,10 @@ - + + + + @@ -168,18 +166,18 @@ - - + + @@ -187,6 +185,7 @@ + @@ -213,6 +212,7 @@ + @@ -284,7 +284,7 @@ - + @@ -876,7 +876,7 @@ - + @@ -888,13 +888,13 @@ - + @@ -937,17 +937,17 @@ - + - + - + - +