Commit ca30df60 authored by Mark Cogan's avatar Mark Cogan Committed by Commit Bot

[iOS] Box/unroot language selector

This CL moves the UI implementation out of BeforeTranslateInfobar.

There's quite a lot going on here, but the basic interactions are straightforward:

- Since UI doesn't belong in browser/translate, chrome_ios_translate_client now
takes a language selection handler on initialization, and the before-translate
infobar uses that handler to request language selection from the user. That involves
passing a context object (LanguageSelectionContext) and a delegate for the UI
to signal when language selection is complete.

- The language selection handler passed into the translate client is an instance
of LanguageSelectionCoordinator, owned by the BVC. This is a long-lived coordinator,
repeatedly started and stopped by the LanguageSelectionHandler method call. It
creates a new view controller and mediator instance each time it's invoked.

- The view controller used for this isn't presented; it's a contained child of the
BVC, but it's "presented" and "dismissed" using a from-the-bottom animation. This
animation, along with the logic of adding and removing a child view controller, and
positioning the child inside the BVC, is handled by a generic 'presenter' interface.

- The presenter interface doesn't specify any particular positioning or animation;
this is defined by the VerticalAnimationContainer implementation. This is passed into
the coordinator by the BVC; effectively the BVC dictates how the child is presented,
and the coordinator doesn't know about it. There's a delegate protocol so the
coordinator knows when dismissal is completed.

- The view controller uses the mediator as a data source for language names, and
this relationship is set up over the consumer interface.

On one hand, this feels like a fairly straightforward refactor that gets UI code out
of browser/translate. On the other hand, it's creating five classes and six protocols
to move ~200 lines of code.

An argument could be made that more of the code in before_translate_infobar_controller
should move into the mediator; I suspect that will need to wait until we have better
patterns for refactoring infobars.

I do think the presenter pattern here is something we can use in general to better
encapsulate child view controller positioning and animation without needing to build
specific support for it in coordinators.

One issue here is that the coordinator is long-lived and acts as the language selection
handler. This is intentional, to keep code out of the BVC. An argument could be made that
the BVC should be the handler, and it should directly start/stop the coordinator.

Bug: 785388
Cq-Include-Trybots: master.tryserver.chromium.mac:ios-simulator-cronet;master.tryserver.chromium.mac:ios-simulator-full-configs
Change-Id: Ied9da19bb88cabd1e14b67689052597186e190e8
Reviewed-on: https://chromium-review.googlesource.com/764327Reviewed-by: default avatarLouis Romero <lpromero@chromium.org>
Commit-Queue: Mark Cogan <marq@chromium.org>
Cr-Commit-Position: refs/heads/master@{#517818}
parent 9aca76ca
...@@ -23,7 +23,6 @@ ...@@ -23,7 +23,6 @@
#include "ios/chrome/browser/history/history_tab_helper.h" #include "ios/chrome/browser/history/history_tab_helper.h"
#include "ios/chrome/browser/history/top_sites_factory.h" #include "ios/chrome/browser/history/top_sites_factory.h"
#import "ios/chrome/browser/infobars/infobar_manager_impl.h" #import "ios/chrome/browser/infobars/infobar_manager_impl.h"
#import "ios/chrome/browser/language/url_language_histogram_factory.h"
#import "ios/chrome/browser/passwords/password_tab_helper.h" #import "ios/chrome/browser/passwords/password_tab_helper.h"
#include "ios/chrome/browser/reading_list/reading_list_model_factory.h" #include "ios/chrome/browser/reading_list/reading_list_model_factory.h"
#import "ios/chrome/browser/reading_list/reading_list_web_state_observer.h" #import "ios/chrome/browser/reading_list/reading_list_web_state_observer.h"
...@@ -82,16 +81,6 @@ void AttachTabHelpers(web::WebState* web_state) { ...@@ -82,16 +81,6 @@ void AttachTabHelpers(web::WebState* web_state) {
ReadingListModelFactory::GetForBrowserState(browser_state); ReadingListModelFactory::GetForBrowserState(browser_state);
ReadingListWebStateObserver::CreateForWebState(web_state, model); ReadingListWebStateObserver::CreateForWebState(web_state, model);
// The language detection helper accepts a callback from the translate
// client, so must be created after it.
ChromeIOSTranslateClient::CreateForWebState(web_state);
language::IOSLanguageDetectionTabHelper::CreateForWebState(
web_state,
ChromeIOSTranslateClient::FromWebState(web_state)
->GetTranslateDriver()
->CreateLanguageDetectionCallback(),
UrlLanguageHistogramFactory::GetForBrowserState(browser_state));
ios::ChromeBrowserState* original_browser_state = ios::ChromeBrowserState* original_browser_state =
browser_state->GetOriginalChromeBrowserState(); browser_state->GetOriginalChromeBrowserState();
favicon::WebFaviconDriver::CreateForWebState( favicon::WebFaviconDriver::CreateForWebState(
......
...@@ -11,6 +11,10 @@ source_set("translate") { ...@@ -11,6 +11,10 @@ source_set("translate") {
"before_translate_infobar_controller.mm", "before_translate_infobar_controller.mm",
"chrome_ios_translate_client.h", "chrome_ios_translate_client.h",
"chrome_ios_translate_client.mm", "chrome_ios_translate_client.mm",
"language_selection_context.h",
"language_selection_context.mm",
"language_selection_delegate.h",
"language_selection_handler.h",
"never_translate_infobar_controller.h", "never_translate_infobar_controller.h",
"never_translate_infobar_controller.mm", "never_translate_infobar_controller.mm",
"translate_accept_languages_factory.cc", "translate_accept_languages_factory.cc",
...@@ -102,6 +106,7 @@ source_set("external_url_eg_tests") { ...@@ -102,6 +106,7 @@ source_set("external_url_eg_tests") {
"//components/translate/core/common", "//components/translate/core/common",
"//components/translate/ios/browser", "//components/translate/ios/browser",
"//ios/chrome/browser/browser_state", "//ios/chrome/browser/browser_state",
"//ios/chrome/browser/ui/translate:translate_ui",
"//ios/chrome/test/app:test_support", "//ios/chrome/test/app:test_support",
"//ios/chrome/test/earl_grey:test_support", "//ios/chrome/test/earl_grey:test_support",
"//ios/testing:ios_test_support", "//ios/testing:ios_test_support",
......
...@@ -7,16 +7,13 @@ ...@@ -7,16 +7,13 @@
#include "ios/chrome/browser/infobars/infobar_controller.h" #include "ios/chrome/browser/infobars/infobar_controller.h"
// The accessibility identifier of the cancel button on language picker view. @protocol LanguageSelectionHandler;
// NOTE: this should not be used on iOS 9 for testing.
extern NSString* const kLanguagePickerCancelButtonId;
// The accessibility identifier of the done button on language picker view.
// NOTE: this should not be used on iOS 9 for testing.
extern NSString* const kLanguagePickerDoneButtonId;
@interface BeforeTranslateInfoBarController : InfoBarController @interface BeforeTranslateInfoBarController : InfoBarController
@property(nonatomic, weak) id<LanguageSelectionHandler>
languageSelectionHandler;
@end @end
#endif // IOS_CHROME_BROWSER_TRANSLATE_BEFORE_TRANSLATE_INFOBAR_CONTROLLER_H_ #endif // IOS_CHROME_BROWSER_TRANSLATE_BEFORE_TRANSLATE_INFOBAR_CONTROLLER_H_
...@@ -5,6 +5,8 @@ ...@@ -5,6 +5,8 @@
#ifndef IOS_CHROME_BROWSER_TRANSLATE_CHROME_IOS_TRANSLATE_CLIENT_H_ #ifndef IOS_CHROME_BROWSER_TRANSLATE_CHROME_IOS_TRANSLATE_CLIENT_H_
#define IOS_CHROME_BROWSER_TRANSLATE_CHROME_IOS_TRANSLATE_CLIENT_H_ #define IOS_CHROME_BROWSER_TRANSLATE_CHROME_IOS_TRANSLATE_CLIENT_H_
#import <Foundation/Foundation.h>
#include <memory> #include <memory>
#include <string> #include <string>
...@@ -13,6 +15,7 @@ ...@@ -13,6 +15,7 @@
#include "components/translate/core/browser/translate_step.h" #include "components/translate/core/browser/translate_step.h"
#include "components/translate/core/common/translate_errors.h" #include "components/translate/core/common/translate_errors.h"
#include "components/translate/ios/browser/ios_translate_driver.h" #include "components/translate/ios/browser/ios_translate_driver.h"
#include "ios/chrome/browser/translate/language_selection_handler.h"
#include "ios/web/public/web_state/web_state_observer.h" #include "ios/web/public/web_state/web_state_observer.h"
#include "ios/web/public/web_state/web_state_user_data.h" #include "ios/web/public/web_state/web_state_user_data.h"
...@@ -41,6 +44,13 @@ class ChromeIOSTranslateClient ...@@ -41,6 +44,13 @@ class ChromeIOSTranslateClient
public: public:
~ChromeIOSTranslateClient() override; ~ChromeIOSTranslateClient() override;
// Creates a translation client tab helper and attaches it to |web_state|. The
// |language_selection_handler| may not be nil, and is not retained by the
// ChromeIOSTranslateClient.
static void CreateForWebState(
web::WebState* web_state,
id<LanguageSelectionHandler> language_selection_handler);
// Helper method to return a new TranslatePrefs instance. // Helper method to return a new TranslatePrefs instance.
static std::unique_ptr<translate::TranslatePrefs> CreateTranslatePrefs( static std::unique_ptr<translate::TranslatePrefs> CreateTranslatePrefs(
PrefService* prefs); PrefService* prefs);
...@@ -70,7 +80,9 @@ class ChromeIOSTranslateClient ...@@ -70,7 +80,9 @@ class ChromeIOSTranslateClient
void ShowReportLanguageDetectionErrorUI(const GURL& report_url) override; void ShowReportLanguageDetectionErrorUI(const GURL& report_url) override;
private: private:
explicit ChromeIOSTranslateClient(web::WebState* web_state); ChromeIOSTranslateClient(
web::WebState* web_state,
id<LanguageSelectionHandler> language_selection_handler);
friend class web::WebStateUserData<ChromeIOSTranslateClient>; friend class web::WebStateUserData<ChromeIOSTranslateClient>;
// web::WebStateObserver implementation. // web::WebStateObserver implementation.
...@@ -82,6 +94,7 @@ class ChromeIOSTranslateClient ...@@ -82,6 +94,7 @@ class ChromeIOSTranslateClient
std::unique_ptr<translate::TranslateManager> translate_manager_; std::unique_ptr<translate::TranslateManager> translate_manager_;
translate::IOSTranslateDriver translate_driver_; translate::IOSTranslateDriver translate_driver_;
__weak id<LanguageSelectionHandler> language_selection_handler_;
DISALLOW_COPY_AND_ASSIGN(ChromeIOSTranslateClient); DISALLOW_COPY_AND_ASSIGN(ChromeIOSTranslateClient);
}; };
......
...@@ -52,7 +52,21 @@ ...@@ -52,7 +52,21 @@
DEFINE_WEB_STATE_USER_DATA_KEY(ChromeIOSTranslateClient); DEFINE_WEB_STATE_USER_DATA_KEY(ChromeIOSTranslateClient);
ChromeIOSTranslateClient::ChromeIOSTranslateClient(web::WebState* web_state) // static
void ChromeIOSTranslateClient::CreateForWebState(
web::WebState* web_state,
id<LanguageSelectionHandler> language_selection_handler) {
DCHECK(web_state);
if (!FromWebState(web_state)) {
web_state->SetUserData(UserDataKey(),
base::WrapUnique(new ChromeIOSTranslateClient(
web_state, language_selection_handler)));
}
}
ChromeIOSTranslateClient::ChromeIOSTranslateClient(
web::WebState* web_state,
id<LanguageSelectionHandler> language_selection_handler)
: web_state_(web_state), : web_state_(web_state),
translate_manager_(base::MakeUnique<translate::TranslateManager>( translate_manager_(base::MakeUnique<translate::TranslateManager>(
this, this,
...@@ -64,7 +78,9 @@ ChromeIOSTranslateClient::ChromeIOSTranslateClient(web::WebState* web_state) ...@@ -64,7 +78,9 @@ ChromeIOSTranslateClient::ChromeIOSTranslateClient(web::WebState* web_state)
web_state->GetBrowserState())))), web_state->GetBrowserState())))),
translate_driver_(web_state, translate_driver_(web_state,
web_state->GetNavigationManager(), web_state->GetNavigationManager(),
translate_manager_.get()) { translate_manager_.get()),
language_selection_handler_(language_selection_handler) {
DCHECK(language_selection_handler);
web_state_->AddObserver(this); web_state_->AddObserver(this);
} }
...@@ -96,10 +112,14 @@ std::unique_ptr<infobars::InfoBar> ChromeIOSTranslateClient::CreateInfoBar( ...@@ -96,10 +112,14 @@ std::unique_ptr<infobars::InfoBar> ChromeIOSTranslateClient::CreateInfoBar(
controller = [[AfterTranslateInfoBarController alloc] controller = [[AfterTranslateInfoBarController alloc]
initWithDelegate:infobar.get()]; initWithDelegate:infobar.get()];
break; break;
case translate::TRANSLATE_STEP_BEFORE_TRANSLATE: case translate::TRANSLATE_STEP_BEFORE_TRANSLATE: {
controller = [[BeforeTranslateInfoBarController alloc] BeforeTranslateInfoBarController* beforeController =
initWithDelegate:infobar.get()]; [[BeforeTranslateInfoBarController alloc]
initWithDelegate:infobar.get()];
beforeController.languageSelectionHandler = language_selection_handler_;
controller = beforeController;
break; break;
}
case translate::TRANSLATE_STEP_NEVER_TRANSLATE: case translate::TRANSLATE_STEP_NEVER_TRANSLATE:
controller = [[NeverTranslateInfoBarController alloc] controller = [[NeverTranslateInfoBarController alloc]
initWithDelegate:infobar.get()]; initWithDelegate:infobar.get()];
......
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef IOS_CHROME_BROWSER_TRANSLATE_LANGUAGE_SELECTION_CONTEXT_H_
#define IOS_CHROME_BROWSER_TRANSLATE_LANGUAGE_SELECTION_CONTEXT_H_
#import <Foundation/Foundation.h>
namespace translate {
class TranslateInfoBarDelegate;
}
// Context information for a language selection event.
@interface LanguageSelectionContext : NSObject
// Convenience initializer that populates all of the properties of the returned
// object with the passed parameters.
+ (instancetype)contextWithLanguageData:
(translate::TranslateInfoBarDelegate*)languageData
initialIndex:(size_t)initialLanguageIndex
unavailableIndex:(size_t)unavailableLanguageIndex;
// The object that provides language data for the selection.
@property(nonatomic, readonly)
const translate::TranslateInfoBarDelegate* languageData;
// The index of the language that's initially selected.
@property(nonatomic, readonly) size_t initialLanguageIndex;
// The index of the language that can't be selected (because this context is
// for source selection and this is the index of the target language, or
// vice-versa).
@property(nonatomic, readonly) size_t unavailableLanguageIndex;
@end
#endif // IOS_CHROME_BROWSER_TRANSLATE_LANGUAGE_SELECTION_CONTEXT_H_
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ios/chrome/browser/translate/language_selection_context.h"
@interface LanguageSelectionContext ()
// Redeclare public properties readwrite.
@property(nonatomic, readwrite)
const translate::TranslateInfoBarDelegate* languageData;
@property(nonatomic, readwrite) size_t initialLanguageIndex;
@property(nonatomic, readwrite) size_t unavailableLanguageIndex;
@end
@implementation LanguageSelectionContext
@synthesize languageData = _languageData;
@synthesize initialLanguageIndex = _initialLanguageIndex;
@synthesize unavailableLanguageIndex = _unavailableLanguageIndex;
+ (instancetype)contextWithLanguageData:
(translate::TranslateInfoBarDelegate*)languageData
initialIndex:(size_t)initialLanguageIndex
unavailableIndex:(size_t)unavailableLanguageIndex {
LanguageSelectionContext* context = [[LanguageSelectionContext alloc] init];
context.languageData = languageData;
context.initialLanguageIndex = initialLanguageIndex;
context.unavailableLanguageIndex = unavailableLanguageIndex;
return context;
}
@end
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef IOS_CHROME_BROWSER_TRANSLATE_LANGUAGE_SELECTION_DELEGATE_H_
#define IOS_CHROME_BROWSER_TRANSLATE_LANGUAGE_SELECTION_DELEGATE_H_
#import <Foundation/Foundation.h>
#include <string>
// Protocol adopted by an object that can receive language selection information
// from the UI layer.
@protocol LanguageSelectionDelegate
// Tells the delegate that the language identified by |languageCode| was
// selected and the language selector was closed.
- (void)languageSelectorSelectedLanguage:(std::string)languageCode;
// Tells the delegate that the language selector was closed without a selection
// being made.
- (void)languageSelectorClosedWithoutSelection;
@end
#endif // IOS_CHROME_BROWSER_TRANSLATE_LANGUAGE_SELECTION_DELEGATE_H_
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef IOS_CHROME_BROWSER_TRANSLATE_LANGUAGE_SELECTION_HANDLER_H_
#define IOS_CHROME_BROWSER_TRANSLATE_LANGUAGE_SELECTION_HANDLER_H_
#import <Foundation/Foundation.h>
@class LanguageSelectionContext;
@protocol LanguageSelectionDelegate;
// Protocol adopted by an object that can provide an interface for a user to
// select a language from the languages exposed by a translate_infobar_delegate.
@protocol LanguageSelectionHandler
// Tells the handler to display a language selector using the language
// information in |languages| and telling |delegate| the results of the
// selection.
- (void)showLanguageSelectorWithContext:(LanguageSelectionContext*)context
delegate:(id<LanguageSelectionDelegate>)delegate;
@end
#endif // IOS_CHROME_BROWSER_TRANSLATE_LANGUAGE_SELECTION_HANDLER_H_
...@@ -21,8 +21,8 @@ ...@@ -21,8 +21,8 @@
#include "components/translate/ios/browser/ios_translate_driver.h" #include "components/translate/ios/browser/ios_translate_driver.h"
#import "components/translate/ios/browser/js_translate_manager.h" #import "components/translate/ios/browser/js_translate_manager.h"
#include "ios/chrome/browser/browser_state/chrome_browser_state.h" #include "ios/chrome/browser/browser_state/chrome_browser_state.h"
#include "ios/chrome/browser/translate/before_translate_infobar_controller.h"
#include "ios/chrome/browser/translate/chrome_ios_translate_client.h" #include "ios/chrome/browser/translate/chrome_ios_translate_client.h"
#include "ios/chrome/browser/ui/translate/language_selection_view_controller.h"
#import "ios/chrome/test/app/chrome_test_util.h" #import "ios/chrome/test/app/chrome_test_util.h"
#import "ios/chrome/test/app/tab_test_util.h" #import "ios/chrome/test/app/tab_test_util.h"
#include "ios/chrome/test/app/web_view_interaction_test_util.h" #include "ios/chrome/test/app/web_view_interaction_test_util.h"
......
...@@ -258,6 +258,7 @@ source_set("ui_internal") { ...@@ -258,6 +258,7 @@ source_set("ui_internal") {
"//components/image_fetcher/ios", "//components/image_fetcher/ios",
"//components/infobars/core", "//components/infobars/core",
"//components/keyed_service/ios", "//components/keyed_service/ios",
"//components/language/ios/browser",
"//components/payments/core", "//components/payments/core",
"//components/prefs", "//components/prefs",
"//components/reading_list/core", "//components/reading_list/core",
...@@ -281,6 +282,7 @@ source_set("ui_internal") { ...@@ -281,6 +282,7 @@ source_set("ui_internal") {
"//ios/chrome/browser/first_run", "//ios/chrome/browser/first_run",
"//ios/chrome/browser/geolocation:geolocation_internal", "//ios/chrome/browser/geolocation:geolocation_internal",
"//ios/chrome/browser/infobars", "//ios/chrome/browser/infobars",
"//ios/chrome/browser/language",
"//ios/chrome/browser/metrics:metrics_internal", "//ios/chrome/browser/metrics:metrics_internal",
"//ios/chrome/browser/net", "//ios/chrome/browser/net",
"//ios/chrome/browser/passwords", "//ios/chrome/browser/passwords",
...@@ -296,6 +298,7 @@ source_set("ui_internal") { ...@@ -296,6 +298,7 @@ source_set("ui_internal") {
"//ios/chrome/browser/ssl", "//ios/chrome/browser/ssl",
"//ios/chrome/browser/store_kit", "//ios/chrome/browser/store_kit",
"//ios/chrome/browser/tabs", "//ios/chrome/browser/tabs",
"//ios/chrome/browser/translate",
"//ios/chrome/browser/ui/activity_services:coordinator", "//ios/chrome/browser/ui/activity_services:coordinator",
"//ios/chrome/browser/ui/activity_services/requirements", "//ios/chrome/browser/ui/activity_services/requirements",
"//ios/chrome/browser/ui/alert_coordinator", "//ios/chrome/browser/ui/alert_coordinator",
...@@ -328,6 +331,7 @@ source_set("ui_internal") { ...@@ -328,6 +331,7 @@ source_set("ui_internal") {
"//ios/chrome/browser/ui/page_info:coordinator", "//ios/chrome/browser/ui/page_info:coordinator",
"//ios/chrome/browser/ui/page_info/requirements", "//ios/chrome/browser/ui/page_info/requirements",
"//ios/chrome/browser/ui/payments", "//ios/chrome/browser/ui/payments",
"//ios/chrome/browser/ui/presenters",
"//ios/chrome/browser/ui/print", "//ios/chrome/browser/ui/print",
"//ios/chrome/browser/ui/qr_scanner:coordinator", "//ios/chrome/browser/ui/qr_scanner:coordinator",
"//ios/chrome/browser/ui/qr_scanner/requirements", "//ios/chrome/browser/ui/qr_scanner/requirements",
...@@ -344,6 +348,7 @@ source_set("ui_internal") { ...@@ -344,6 +348,7 @@ source_set("ui_internal") {
"//ios/chrome/browser/ui/toolbar/public:toolbar_base_feature", "//ios/chrome/browser/ui/toolbar/public:toolbar_base_feature",
"//ios/chrome/browser/ui/tools_menu", "//ios/chrome/browser/ui/tools_menu",
"//ios/chrome/browser/ui/tools_menu:configuration", "//ios/chrome/browser/ui/tools_menu:configuration",
"//ios/chrome/browser/ui/translate",
"//ios/chrome/browser/ui/util", "//ios/chrome/browser/ui/util",
"//ios/chrome/browser/ui/voice", "//ios/chrome/browser/ui/voice",
"//ios/chrome/browser/upgrade", "//ios/chrome/browser/upgrade",
......
...@@ -44,6 +44,7 @@ ...@@ -44,6 +44,7 @@
#include "components/feature_engagement/public/tracker.h" #include "components/feature_engagement/public/tracker.h"
#include "components/image_fetcher/ios/ios_image_data_fetcher_wrapper.h" #include "components/image_fetcher/ios/ios_image_data_fetcher_wrapper.h"
#include "components/infobars/core/infobar_manager.h" #include "components/infobars/core/infobar_manager.h"
#import "components/language/ios/browser/ios_language_detection_tab_helper.h"
#include "components/payments/core/features.h" #include "components/payments/core/features.h"
#include "components/prefs/pref_service.h" #include "components/prefs/pref_service.h"
#include "components/reading_list/core/reading_list_model.h" #include "components/reading_list/core/reading_list_model.h"
...@@ -75,6 +76,7 @@ ...@@ -75,6 +76,7 @@
#include "ios/chrome/browser/infobars/infobar_container_ios.h" #include "ios/chrome/browser/infobars/infobar_container_ios.h"
#include "ios/chrome/browser/infobars/infobar_container_view.h" #include "ios/chrome/browser/infobars/infobar_container_view.h"
#include "ios/chrome/browser/infobars/infobar_manager_impl.h" #include "ios/chrome/browser/infobars/infobar_manager_impl.h"
#import "ios/chrome/browser/language/url_language_histogram_factory.h"
#import "ios/chrome/browser/metrics/new_tab_page_uma.h" #import "ios/chrome/browser/metrics/new_tab_page_uma.h"
#include "ios/chrome/browser/metrics/tab_usage_recorder.h" #include "ios/chrome/browser/metrics/tab_usage_recorder.h"
#import "ios/chrome/browser/open_url_util.h" #import "ios/chrome/browser/open_url_util.h"
...@@ -107,6 +109,8 @@ ...@@ -107,6 +109,8 @@
#import "ios/chrome/browser/tabs/tab_model_observer.h" #import "ios/chrome/browser/tabs/tab_model_observer.h"
#import "ios/chrome/browser/tabs/tab_private.h" #import "ios/chrome/browser/tabs/tab_private.h"
#import "ios/chrome/browser/tabs/tab_snapshotting_delegate.h" #import "ios/chrome/browser/tabs/tab_snapshotting_delegate.h"
#import "ios/chrome/browser/translate/chrome_ios_translate_client.h"
#import "ios/chrome/browser/translate/language_selection_handler.h"
#import "ios/chrome/browser/ui/activity_services/activity_service_legacy_coordinator.h" #import "ios/chrome/browser/ui/activity_services/activity_service_legacy_coordinator.h"
#import "ios/chrome/browser/ui/activity_services/requirements/activity_service_presentation.h" #import "ios/chrome/browser/ui/activity_services/requirements/activity_service_presentation.h"
#import "ios/chrome/browser/ui/alert_coordinator/alert_coordinator.h" #import "ios/chrome/browser/ui/alert_coordinator/alert_coordinator.h"
...@@ -151,6 +155,7 @@ ...@@ -151,6 +155,7 @@
#import "ios/chrome/browser/ui/page_info/requirements/page_info_presentation.h" #import "ios/chrome/browser/ui/page_info/requirements/page_info_presentation.h"
#import "ios/chrome/browser/ui/page_not_available_controller.h" #import "ios/chrome/browser/ui/page_not_available_controller.h"
#import "ios/chrome/browser/ui/payments/payment_request_manager.h" #import "ios/chrome/browser/ui/payments/payment_request_manager.h"
#import "ios/chrome/browser/ui/presenters/vertical_animation_container.h"
#import "ios/chrome/browser/ui/print/print_controller.h" #import "ios/chrome/browser/ui/print/print_controller.h"
#import "ios/chrome/browser/ui/qr_scanner/qr_scanner_legacy_coordinator.h" #import "ios/chrome/browser/ui/qr_scanner/qr_scanner_legacy_coordinator.h"
#import "ios/chrome/browser/ui/qr_scanner/requirements/qr_scanner_presenting.h" #import "ios/chrome/browser/ui/qr_scanner/requirements/qr_scanner_presenting.h"
...@@ -179,6 +184,7 @@ ...@@ -179,6 +184,7 @@
#import "ios/chrome/browser/ui/tools_menu/tools_menu_configuration.h" #import "ios/chrome/browser/ui/tools_menu/tools_menu_configuration.h"
#import "ios/chrome/browser/ui/tools_menu/tools_menu_view_item.h" #import "ios/chrome/browser/ui/tools_menu/tools_menu_view_item.h"
#import "ios/chrome/browser/ui/tools_menu/tools_popup_controller.h" #import "ios/chrome/browser/ui/tools_menu/tools_popup_controller.h"
#import "ios/chrome/browser/ui/translate/language_selection_coordinator.h"
#include "ios/chrome/browser/ui/ui_util.h" #include "ios/chrome/browser/ui/ui_util.h"
#import "ios/chrome/browser/ui/uikit_ui_util.h" #import "ios/chrome/browser/ui/uikit_ui_util.h"
#import "ios/chrome/browser/ui/util/pasteboard_util.h" #import "ios/chrome/browser/ui/util/pasteboard_util.h"
...@@ -561,6 +567,9 @@ NSString* const kBrowserViewControllerSnackbarCategory = ...@@ -561,6 +567,9 @@ NSString* const kBrowserViewControllerSnackbarCategory =
// Coordinator for the External Search UI. // Coordinator for the External Search UI.
ExternalSearchCoordinator* _externalSearchCoordinator; ExternalSearchCoordinator* _externalSearchCoordinator;
// Coordinator for the language selection UI.
LanguageSelectionCoordinator* _languageSelectionCoordinator;
// Fake status bar view used to blend the toolbar into the status bar. // Fake status bar view used to blend the toolbar into the status bar.
UIView* _fakeStatusBarView; UIView* _fakeStatusBarView;
...@@ -1038,6 +1047,11 @@ applicationCommandEndpoint:(id<ApplicationCommands>)applicationCommandEndpoint { ...@@ -1038,6 +1047,11 @@ applicationCommandEndpoint:(id<ApplicationCommands>)applicationCommandEndpoint {
_snackbarCoordinator.dispatcher = _dispatcher; _snackbarCoordinator.dispatcher = _dispatcher;
[_snackbarCoordinator start]; [_snackbarCoordinator start];
_languageSelectionCoordinator =
[[LanguageSelectionCoordinator alloc] initWithBaseViewController:self];
_languageSelectionCoordinator.presenter =
[[VerticalAnimationContainer alloc] init];
_javaScriptDialogPresenter.reset( _javaScriptDialogPresenter.reset(
new JavaScriptDialogPresenterImpl(_dialogPresenter)); new JavaScriptDialogPresenterImpl(_dialogPresenter));
_webStateDelegate.reset(new web::WebStateDelegateBridge(self)); _webStateDelegate.reset(new web::WebStateDelegateBridge(self));
...@@ -2519,6 +2533,23 @@ bubblePresenterForFeature:(const base::Feature&)feature ...@@ -2519,6 +2533,23 @@ bubblePresenterForFeature:(const base::Feature&)feature
NetExportTabHelper::CreateForWebState(tab.webState, self); NetExportTabHelper::CreateForWebState(tab.webState, self);
CaptivePortalDetectorTabHelper::CreateForWebState(tab.webState, self); CaptivePortalDetectorTabHelper::CreateForWebState(tab.webState, self);
// The language detection helper accepts a callback from the translate
// client, so must be created after it.
// This will explode if the webState doesn't have a JS injection manager
// (this only comes up in unit tests), so check for that and bypass the
// init of the translation helpers if needed.
// TODO(crbug.com/785238): Remove the need for this check.
if (tab.webState->GetJSInjectionReceiver()) {
ChromeIOSTranslateClient::CreateForWebState(tab.webState,
_languageSelectionCoordinator);
language::IOSLanguageDetectionTabHelper::CreateForWebState(
tab.webState,
ChromeIOSTranslateClient::FromWebState(tab.webState)
->GetTranslateDriver()
->CreateLanguageDetectionCallback(),
UrlLanguageHistogramFactory::GetForBrowserState(self.browserState));
}
if (AccountConsistencyService* accountConsistencyService = if (AccountConsistencyService* accountConsistencyService =
ios::AccountConsistencyServiceFactory::GetForBrowserState( ios::AccountConsistencyServiceFactory::GetForBrowserState(
self.browserState)) { self.browserState)) {
......
# Copyright 2017 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
source_set("presenters") {
configs += [ "//build/config/compiler:enable_arc" ]
sources = [
"contained_presenter.h",
"contained_presenter_delegate.h",
"vertical_animation_container.h",
"vertical_animation_container.mm",
]
deps = [
"//base",
"//ios/chrome/browser/ui/util:constraints_ui",
]
}
source_set("unit_tests") {
testonly = true
configs += [ "//build/config/compiler:enable_arc" ]
sources = [
"vertical_animation_container_unittest.mm",
]
deps = [
":presenters",
"//base",
"//base/test:test_support",
"//ios/chrome/test/base",
"//testing/gtest",
]
}
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef IOS_CHROME_BROWSER_UI_PRESENTERS_CONTAINED_PRESENTER_H_
#define IOS_CHROME_BROWSER_UI_PRESENTERS_CONTAINED_PRESENTER_H_
#import <UIKit/UIKit.h>
@protocol ContainedPresenterDelegate;
// Helper that manages the positioning and presentation of a contained (child)
// view controller. Implementors determine the specifics of the positioning
// and presentation.
@protocol ContainedPresenter
// The view controller which will contain the presented (child) view controller.
@property(nonatomic, weak) UIViewController* baseViewController;
// The view controller to be presented.
@property(nonatomic, weak) UIViewController* presentedViewController;
// The delegate object which will be told about presentation events.
@property(nonatomic, weak) id<ContainedPresenterDelegate> delegate;
// Prepares the view controllers for presentation. The presented view controller
// should become a child of the base view controller.
- (void)prepareForPresentation;
// Presents the presented view controller, animating the presentation if
// |animated| is YES. If |animated| is NO, any layout changes should execute
// synchronously. This is a no-op if the presented view controller is already
// being presented. It's an error to call this without calling
// -prepareForPresentation first.
- (void)presentAnimated:(BOOL)animated;
// Dismisses the presented view controller, animating the dismissal if
// |animated| is YES. If |animated| is NO, any layout changes should execute
// synchronously. This is a no-op if the presented view controller is already
// dismissed, or hasn't been presented. Once the presentation completes (or
// synchronously if |animated| is NO), |delegate| should have its
// -containedPresenterDidDismiss: method called, and the presented view
// controller should stop being a child of the base view controller.
- (void)dismissAnimated:(BOOL)animated;
@end
#endif // IOS_CHROME_BROWSER_UI_PRESENTERS_CONTAINED_PRESENTER_H_
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef IOS_CHROME_BROWSER_UI_PRESENTERS_CONTAINED_PRESENTER_DELEGATE_H_
#define IOS_CHROME_BROWSER_UI_PRESENTERS_CONTAINED_PRESENTER_DELEGATE_H_
#import <Foundation/Foundation.h>
@protocol ContainedPresenter;
// Protocol for an object which acts as a delegate for a contained presenter,
// and which is informed about dismissal events.
@protocol ContainedPresenterDelegate
// Tells the delegate that |presenter| has finished dismissing.
- (void)containedPresenterDidDismiss:(id<ContainedPresenter>)presenter;
@end
#endif // IOS_CHROME_BROWSER_UI_PRESENTERS_CONTAINED_PRESENTER_DELEGATE_H_
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef IOS_CHROME_BROWSER_UI_PRESENTERS_VERTICAL_ANIMATION_CONTAINER_H_
#define IOS_CHROME_BROWSER_UI_PRESENTERS_VERTICAL_ANIMATION_CONTAINER_H_
#import <UIKit/UIKit.h>
#import "ios/chrome/browser/ui/presenters/contained_presenter.h"
// Helper that manages the positioning and presentation of a view controller,
// anchoring at the bottom of its container, and animating it up from below.
@interface VerticalAnimationContainer : NSObject<ContainedPresenter>
@end
#endif // IOS_CHROME_BROWSER_UI_PRESENTERS_VERTICAL_ANIMATION_CONTAINER_H_
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/browser/ui/presenters/vertical_animation_container.h"
#include "base/logging.h"
#import "ios/chrome/browser/ui/presenters/contained_presenter_delegate.h"
#include "ios/chrome/browser/ui/util/constraints_ui_util.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
namespace {
NSTimeInterval kAnimationDuration = 0.2;
}
@interface VerticalAnimationContainer ()
@property(nonatomic) NSArray<NSLayoutConstraint*>* dismissedConstraints;
@property(nonatomic) NSArray<NSLayoutConstraint*>* presentedConstraints;
@end
@implementation VerticalAnimationContainer
// Synthesize ContainedPresenter properties.
@synthesize baseViewController = _baseViewController;
@synthesize presentedViewController = _presentedViewController;
@synthesize delegate = _delegate;
// Synthesize private properties.
@synthesize dismissedConstraints = _dismissedConstraints;
@synthesize presentedConstraints = _presentedConstraints;
- (void)prepareForPresentation {
[self.baseViewController addChildViewController:self.presentedViewController];
[self.baseViewController.view addSubview:self.presentedViewController.view];
// Shorter names for more readable constraint code.
UIView* container = self.baseViewController.view;
UIView* contents = self.presentedViewController.view;
// The contents view will be sized and positioned by constraints.
contents.translatesAutoresizingMaskIntoConstraints = NO;
// Sizing constraints never change, so they don't need to be stored.
// Height is sized by the contents of |contents|.
[contents.widthAnchor constraintEqualToAnchor:container.widthAnchor].active =
YES;
// The horizontal position of the contents in the container also doesn't
// change.
[contents.centerXAnchor constraintEqualToAnchor:container.centerXAnchor]
.active = YES;
// When dismissed, the top of the contents is just below the bottom of the
// container.
self.dismissedConstraints = @[
[contents.topAnchor constraintEqualToAnchor:container.bottomAnchor],
];
// When presented, the bottom of the contents matches the bottom of the
// container.
self.presentedConstraints = @[
[contents.bottomAnchor constraintEqualToAnchor:container.bottomAnchor],
];
// The contents start off dismissed.
[NSLayoutConstraint activateConstraints:self.dismissedConstraints];
// Ensure the contents are actually positioned in the dismissed positions.
[self.baseViewController.view layoutIfNeeded];
[self.presentedViewController
didMoveToParentViewController:self.baseViewController];
}
- (void)presentAnimated:(BOOL)animated {
// An error if -prepareForPresentation hasn't been called yet.
// Simple coherence test: the presented view controller's view has a
// superview.
DCHECK(self.presentedViewController.view.superview);
// No-op if already presented.
if (self.presentedConstraints[0].active)
return;
[UIView animateWithDuration:animated ? kAnimationDuration : 0.0
animations:^{
[NSLayoutConstraint
deactivateConstraints:self.dismissedConstraints];
[NSLayoutConstraint
activateConstraints:self.presentedConstraints];
[self.baseViewController.view layoutIfNeeded];
}];
}
- (void)dismissAnimated:(BOOL)animated {
// No-op if already dismissed.
if (self.dismissedConstraints[0].active)
return;
void (^animations)() = ^{
[NSLayoutConstraint deactivateConstraints:self.presentedConstraints];
[NSLayoutConstraint activateConstraints:self.dismissedConstraints];
[self.baseViewController.view layoutIfNeeded];
};
void (^completion)(BOOL) = ^(BOOL finished) {
[self cleanUpAfterDismissal];
[self.delegate containedPresenterDidDismiss:self];
};
if (animated) {
// Trigger a layout pass before the animation, so that any pending updates
// aren't animated along with the dismissal.
[self.baseViewController.view layoutIfNeeded];
[UIView animateWithDuration:kAnimationDuration
animations:animations
completion:completion];
} else {
// Just execute everything synchronously if the dismissal isn't animated.
// Note that just using an animation duration of 0 in -animateWithDuration:
// will still call the completion block asynchronously.
animations();
completion(YES);
}
}
#pragma mark - private
- (void)cleanUpAfterDismissal {
if (self.presentedViewController.parentViewController !=
self.baseViewController) {
return;
}
[self.presentedViewController willMoveToParentViewController:nil];
[self.presentedViewController.view removeFromSuperview];
[self.presentedViewController removeFromParentViewController];
}
@end
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/browser/ui/presenters/vertical_animation_container.h"
#import "ios/chrome/browser/ui/presenters/contained_presenter.h"
#import "ios/chrome/browser/ui/presenters/contained_presenter_delegate.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/platform_test.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
// Test delegate helper; the delegate callback sets the |dismissed| property.
@interface TestContainedPresenterDelegate : NSObject<ContainedPresenterDelegate>
@property(nonatomic) BOOL dismissed;
@end
@implementation TestContainedPresenterDelegate
@synthesize dismissed = _dismissed;
- (void)containedPresenterDidDismiss:(id<ContainedPresenter>)presenter {
self.dismissed = YES;
}
@end
namespace {
class VerticalAnimationContainerTest : public PlatformTest {
public:
VerticalAnimationContainerTest()
: delegate_([[TestContainedPresenterDelegate alloc] init]),
base_([[UIViewController alloc] init]),
presented_([[UIViewController alloc] init]),
presenter_([[VerticalAnimationContainer alloc] init]) {
presenter_.baseViewController = base_;
presenter_.presentedViewController = presented_;
presenter_.delegate = delegate_;
}
protected:
__strong TestContainedPresenterDelegate* delegate_;
__strong UIViewController* base_;
__strong UIViewController* presented_;
__strong id<ContainedPresenter> presenter_;
};
TEST_F(VerticalAnimationContainerTest, TestPreparation) {
[presenter_ prepareForPresentation];
// General expectations for presentation prep.
EXPECT_TRUE([base_.childViewControllers containsObject:presented_]);
EXPECT_TRUE(presented_.view.superview == base_.view);
// For vertical animation, the presented view should start below the
// base view controller's view, and be the same width.
EXPECT_EQ(base_.view.frame.size.width, presented_.view.bounds.size.width);
EXPECT_EQ(presented_.view.frame.origin.x, 0);
EXPECT_GE(presented_.view.frame.origin.y, base_.view.bounds.size.height);
}
TEST_F(VerticalAnimationContainerTest, TestPresentation) {
[presenter_ prepareForPresentation];
[presenter_ presentAnimated:NO];
// For vertical animation, the presented view should be entirely contained
// by the base view controller's view, and be aligned with its bottom.
EXPECT_TRUE(CGRectContainsRect(base_.view.bounds, presented_.view.frame));
EXPECT_EQ(CGRectGetMaxY(base_.view.bounds),
CGRectGetMaxY(presented_.view.frame));
// The delegate method should not be called here.
EXPECT_FALSE(delegate_.dismissed);
}
TEST_F(VerticalAnimationContainerTest, TestDismissal) {
[presenter_ prepareForPresentation];
[presenter_ presentAnimated:NO];
[presenter_ dismissAnimated:NO];
EXPECT_FALSE([base_.childViewControllers containsObject:presented_]);
EXPECT_FALSE(presented_.view.superview == base_.view);
EXPECT_TRUE(delegate_.dismissed);
}
} // namespace
# Copyright 2017 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
source_set("translate") {
configs += [ "//build/config/compiler:enable_arc" ]
sources = [
"language_selection_coordinator.h",
"language_selection_coordinator.mm",
"language_selection_mediator.h",
"language_selection_mediator.mm",
]
deps = [
":translate_ui",
"//base",
"//components/translate/core/browser",
"//ios/chrome/browser",
"//ios/chrome/browser/translate",
"//ios/chrome/browser/ui/presenters",
]
}
source_set("translate_ui") {
configs += [ "//build/config/compiler:enable_arc" ]
sources = [
"language_selection_consumer.h",
"language_selection_provider.h",
"language_selection_view_controller.h",
"language_selection_view_controller.mm",
]
deps = [
"//base",
]
}
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef IOS_CHROME_BROWSER_UI_TRANSLATE_LANGUAGE_SELECTION_CONSUMER_H_
#define IOS_CHROME_BROWSER_UI_TRANSLATE_LANGUAGE_SELECTION_CONSUMER_H_
#import <Foundation/Foundation.h>
@protocol LanguageSelectionProvider;
// Consumer protocol for a view controller providing a language selection
// interface.
@protocol LanguageSelectionConsumer
// The language provider that the consumer should use to fetch language
// information for display.
@property(nonatomic, weak) id<LanguageSelectionProvider> provider;
// The number of languages available for display in the interface.
@property(nonatomic) int languageCount;
// The index of the initially selected language.
@property(nonatomic) int initialLanguageIndex;
// The index of a language unavailable for selection (because it has already
// been selected, for example).
@property(nonatomic) int disabledLanguageIndex;
@end
#endif // IOS_CHROME_BROWSER_UI_TRANSLATE_LANGUAGE_SELECTION_CONSUMER_H_
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef IOS_CHROME_BROWSER_UI_TRANSLATE_LANGUAGE_SELECTION_COORDINATOR_H_
#define IOS_CHROME_BROWSER_UI_TRANSLATE_LANGUAGE_SELECTION_COORDINATOR_H_
#import <UIKit/UIKit.h>
#import "ios/chrome/browser/translate/language_selection_handler.h"
@protocol ContainedPresenter;
// A coordinator for a language selection UI. This is intended for display as
// a contained, not presented, view controller.
// The methods defined in the LanguageSelectionHandler protocol will be called
// to start the coordinator.
@interface LanguageSelectionCoordinator : NSObject<LanguageSelectionHandler>
// Creates a coordinator that will use |viewController| as the base view
// controller for presenting its UI.
- (instancetype)initWithBaseViewController:(UIViewController*)viewController
NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
// The base view controller the receiver was initialized with.
@property(weak, nonatomic, readonly) UIViewController* baseViewController;
// Presenter to use to for presenting the receiver's view controller.
@property(nonatomic) id<ContainedPresenter> presenter;
@end
#endif // IOS_CHROME_BROWSER_UI_TRANSLATE_LANGUAGE_SELECTION_COORDINATOR_H_
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/browser/ui/translate/language_selection_coordinator.h"
#import "base/logging.h"
#import "ios/chrome/browser/translate/language_selection_delegate.h"
#import "ios/chrome/browser/ui/presenters/contained_presenter.h"
#import "ios/chrome/browser/ui/presenters/contained_presenter_delegate.h"
#import "ios/chrome/browser/ui/translate/language_selection_mediator.h"
#import "ios/chrome/browser/ui/translate/language_selection_view_controller.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
@interface LanguageSelectionCoordinator ()<
LanguageSelectionViewControllerDelegate,
ContainedPresenterDelegate>
// The view controller this coordinator manages.
@property(nonatomic) LanguageSelectionViewController* selectionViewController;
// A mediator to interoperate with the translation model.
@property(nonatomic) LanguageSelectionMediator* selectionMediator;
// A delegate, provided by showLanguageSelectorWithContext:delegate:.
@property(nonatomic, weak) id<LanguageSelectionDelegate> selectionDelegate;
// YES if the coordinator has started displaying its UI.
@property(nonatomic, readonly) BOOL started;
// Starts displaying the UI for this coordinator, using self.presenter to handle
// the positioning and display of self.selectionViewController.
// It is an error to call this method when self.presenter is nil.
- (void)start;
// Cleans up state after the UI for this coordinator has stopped being
// displayed.
- (void)stop;
@end
@implementation LanguageSelectionCoordinator
// Public properties.
@synthesize baseViewController = _baseViewController;
@synthesize presenter = _presenter;
// Private properties.
@synthesize selectionViewController = _selectionViewController;
@synthesize selectionMediator = _selectionMediator;
@synthesize selectionDelegate = _selectionDelegate;
- (nullable instancetype)initWithBaseViewController:
(UIViewController*)viewController {
if ((self = [super init])) {
_baseViewController = viewController;
}
return self;
}
#pragma mark - private property implementation
- (BOOL)started {
return self.presenter.presentedViewController != nil;
}
#pragma mark - private methods
- (void)start {
DCHECK(self.presenter);
self.presenter.baseViewController = self.baseViewController;
self.presenter.presentedViewController = self.selectionViewController;
self.presenter.delegate = self;
[self.presenter prepareForPresentation];
[self.presenter presentAnimated:YES];
}
- (void)stop {
self.selectionViewController = nil;
self.selectionMediator = nil;
}
#pragma mark - LanguageSelectionHandler
- (void)showLanguageSelectorWithContext:(LanguageSelectionContext*)context
delegate:
(id<LanguageSelectionDelegate>)delegate {
if (self.started)
return;
self.selectionDelegate = delegate;
self.selectionMediator =
[[LanguageSelectionMediator alloc] initWithContext:context];
self.selectionViewController = [[LanguageSelectionViewController alloc] init];
self.selectionViewController.delegate = self;
self.selectionMediator.consumer = self.selectionViewController;
[self start];
}
#pragma mark - LanguageSelectionViewControllerDelegate
- (void)languageSelectedAtIndex:(int)index {
[self.selectionDelegate
languageSelectorSelectedLanguage:
[self.selectionMediator languageCodeForLanguageAtIndex:index]];
[self.presenter dismissAnimated:YES];
}
- (void)languageSelectionCanceled {
[self.selectionDelegate languageSelectorClosedWithoutSelection];
[self.presenter dismissAnimated:YES];
}
#pragma mark - ContainedPresenterDelegate
- (void)containedPresenterDidDismiss:(id<ContainedPresenter>)presenter {
DCHECK(presenter == self.presenter);
[self stop];
}
@end
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef IOS_CHROME_BROWSER_UI_TRANSLATE_LANGUAGE_SELECTION_MEDIATOR_H_
#define IOS_CHROME_BROWSER_UI_TRANSLATE_LANGUAGE_SELECTION_MEDIATOR_H_
#import <Foundation/Foundation.h>
#include <string>
@protocol LanguageSelectionConsumer;
@class LanguageSelectionContext;
// Mediator object to configure and provide data for language selection.
@interface LanguageSelectionMediator : NSObject
// Designated initializer. |context| is the context object provided for language
// selection.
- (instancetype)initWithContext:(LanguageSelectionContext*)context
NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
// Consumer for this mediator.
@property(nonatomic, weak) id<LanguageSelectionConsumer> consumer;
// Utility method for the coordinator to map a selected language index to a
// language name.
- (std::string)languageCodeForLanguageAtIndex:(int)index;
@end
#endif // IOS_CHROME_BROWSER_UI_TRANSLATE_LANGUAGE_SELECTION_MEDIATOR_H_
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/browser/ui/translate/language_selection_mediator.h"
#include "base/logging.h"
#include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "components/translate/core/browser/translate_infobar_delegate.h"
#import "ios/chrome/browser/translate/language_selection_context.h"
#import "ios/chrome/browser/ui/translate/language_selection_consumer.h"
#import "ios/chrome/browser/ui/translate/language_selection_provider.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
@interface LanguageSelectionMediator ()<LanguageSelectionProvider>
@property(nonatomic) LanguageSelectionContext* context;
@end
@implementation LanguageSelectionMediator
@synthesize consumer = _consumer;
@synthesize context = _context;
- (instancetype)initWithContext:(LanguageSelectionContext*)context {
if ((self = [super init])) {
_context = context;
}
return self;
}
- (void)setConsumer:(id<LanguageSelectionConsumer>)consumer {
_consumer = consumer;
self.consumer.languageCount = self.context.languageData->num_languages();
self.consumer.initialLanguageIndex = self.context.initialLanguageIndex;
self.consumer.disabledLanguageIndex = self.context.unavailableLanguageIndex;
self.consumer.provider = self;
}
#pragma mark - Public methods
- (std::string)languageCodeForLanguageAtIndex:(int)index {
return self.context.languageData->language_code_at(index);
}
#pragma mark - LanguageSelectionProvider
- (NSString*)languageNameAtIndex:(int)languageIndex {
if (languageIndex < 0 ||
languageIndex >=
static_cast<int>(self.context.languageData->num_languages())) {
NOTREACHED() << "Language index " << languageIndex
<< " out of expected range.";
return nil;
}
return base::SysUTF16ToNSString(
self.context.languageData->language_name_at(languageIndex));
}
@end
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef IOS_CHROME_BROWSER_UI_TRANSLATE_LANGUAGE_SELECTION_PROVIDER_H_
#define IOS_CHROME_BROWSER_UI_TRANSLATE_LANGUAGE_SELECTION_PROVIDER_H_
#import <Foundation/Foundation.h>
// Protocol for a provider that can map indexes to language names. An
// implementer of this protocol should be consistent over its lifetime, always
// returning the same language information for a given index.
@protocol LanguageSelectionProvider
// The name of the language (in the application's locale) of the language at
// index |languageIndex|, or nil if |languageIndex| is outside of the range
// of indices handled by the implementer.
- (NSString*)languageNameAtIndex:(int)languageIndex;
@end
#endif // IOS_CHROME_BROWSER_UI_TRANSLATE_LANGUAGE_SELECTION_PROVIDER_H_
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef IOS_CHROME_BROWSER_UI_TRANSLATE_LANGUAGE_SELECTION_VIEW_CONTROLLER_H_
#define IOS_CHROME_BROWSER_UI_TRANSLATE_LANGUAGE_SELECTION_VIEW_CONTROLLER_H_
#import <UIKit/UIKit.h>
#import "ios/chrome/browser/ui/translate/language_selection_consumer.h"
// The accessibility identifier of the cancel button on language picker view.
// NOTE: this should not be used on iOS 9 for testing.
extern NSString* const kLanguagePickerCancelButtonId;
// The accessibility identifier of the done button on language picker view.
// NOTE: this should not be used on iOS 9 for testing.
extern NSString* const kLanguagePickerDoneButtonId;
// A delegate for a LanguageSelectionViewController instance, which the view
// controller tells about selection events.
@protocol LanguageSelectionViewControllerDelegate
// Tells the delegate that a language was selected. |index| will be an index
// in the range provided to the view controller over via the consumer protocol.
- (void)languageSelectedAtIndex:(int)index;
// Tells the delegate that language selection was cancelled.
- (void)languageSelectionCanceled;
@end
// A view controller that displays a picker view for selecting a language from
// a list of provided languages.
@interface LanguageSelectionViewController
: UIViewController<LanguageSelectionConsumer>
// The delegate for this view controller.
@property(nonatomic, weak) id<LanguageSelectionViewControllerDelegate> delegate;
@end
#endif // IOS_CHROME_BROWSER_UI_TRANSLATE_LANGUAGE_SELECTION_VIEW_CONTROLLER_H_
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/browser/ui/translate/language_selection_view_controller.h"
#import "base/logging.h"
#import "base/mac/foundation_util.h"
#import "ios/chrome/browser/ui/translate/language_selection_provider.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
NSString* const kLanguagePickerCancelButtonId = @"LanguagePickerCancelButton";
NSString* const kLanguagePickerDoneButtonId = @"LanguagePickerDoneButton";
namespace {
CGFloat kUIPickerFontSize = 26;
}
@interface LanguageSelectionViewController ()<UIPickerViewDataSource,
UIPickerViewDelegate>
@property(nonatomic, weak) UIPickerView* picker;
// Action methods for navigation bar buttons.
- (void)languageSelectionDone;
- (void)languageSelectionCancelled;
@end
@implementation LanguageSelectionViewController
// Synthesize properties defined by LanguageSelectionConsumer
@synthesize languageCount = _languageCount;
@synthesize initialLanguageIndex = _initialLanguageIndex;
@synthesize disabledLanguageIndex = _disabledLanguageIndex;
@synthesize provider = _provider;
// Synthesize public properties
@synthesize delegate = _delegate;
// Synthesize private properties
@synthesize picker = _picker;
#pragma mark - UIViewController
- (void)viewDidLoad {
DCHECK(_languageCount && _provider);
UIPickerView* picker = [[UIPickerView alloc] initWithFrame:CGRectZero];
picker.backgroundColor = UIColor.whiteColor;
picker.translatesAutoresizingMaskIntoConstraints = NO;
picker.showsSelectionIndicator = YES;
picker.dataSource = self;
picker.delegate = self;
[picker selectRow:self.initialLanguageIndex inComponent:0 animated:NO];
[self.view addSubview:picker];
self.picker = picker;
UINavigationBar* bar = [[UINavigationBar alloc] initWithFrame:CGRectZero];
bar.translatesAutoresizingMaskIntoConstraints = NO;
UIBarButtonItem* doneButton = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemDone
target:self
action:@selector(languageSelectionDone)];
[doneButton setAccessibilityIdentifier:kLanguagePickerDoneButtonId];
UIBarButtonItem* cancelButton = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemCancel
target:self
action:@selector(languageSelectionCancelled)];
[cancelButton setAccessibilityIdentifier:kLanguagePickerCancelButtonId];
UINavigationItem* item = [[UINavigationItem alloc] initWithTitle:@""];
[item setRightBarButtonItem:doneButton];
[item setLeftBarButtonItem:cancelButton];
[item setHidesBackButton:YES];
[bar pushNavigationItem:item animated:NO];
[self.view addSubview:bar];
// Bar sits on top of the picker, both are full width. Constraints don't
// change. Height is entirely determined by the preferred content sizes of
// the bar and picker.
[NSLayoutConstraint activateConstraints:@[
[bar.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor],
[picker.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor],
[bar.widthAnchor constraintEqualToAnchor:self.view.widthAnchor],
[picker.widthAnchor constraintEqualToAnchor:self.view.widthAnchor],
[bar.topAnchor constraintEqualToAnchor:self.view.topAnchor],
[picker.topAnchor constraintEqualToAnchor:bar.bottomAnchor],
[picker.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor],
]];
}
#pragma mark - UIPickerViewDataSource
- (NSInteger)pickerView:(UIPickerView*)pickerView
numberOfRowsInComponent:(NSInteger)component {
return _languageCount;
}
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView*)pickerView {
return 1;
}
#pragma mark - UIPickerViewDelegate
- (UIView*)pickerView:(UIPickerView*)pickerView
viewForRow:(NSInteger)row
forComponent:(NSInteger)component
reusingView:(UIView*)view {
DCHECK_EQ(0, component);
UILabel* label = base::mac::ObjCCast<UILabel>(view) ?: [[UILabel alloc] init];
label.text = [_provider languageNameAtIndex:row];
label.textAlignment = NSTextAlignmentCenter;
UIFont* font = [UIFont systemFontOfSize:kUIPickerFontSize];
if (row == _initialLanguageIndex) {
font = [UIFont boldSystemFontOfSize:kUIPickerFontSize];
} else if (row == _disabledLanguageIndex) {
label.enabled = NO;
}
label.font = font;
return label;
}
#pragma mark - Navigation buttons
- (void)languageSelectionDone {
[self.delegate
languageSelectedAtIndex:[self.picker selectedRowInComponent:0]];
}
- (void)languageSelectionCancelled {
[self.delegate languageSelectionCanceled];
}
@end
...@@ -203,6 +203,7 @@ test("ios_chrome_unittests") { ...@@ -203,6 +203,7 @@ test("ios_chrome_unittests") {
"//ios/chrome/browser/ui/omnibox:unit_tests", "//ios/chrome/browser/ui/omnibox:unit_tests",
"//ios/chrome/browser/ui/payments:unit_tests", "//ios/chrome/browser/ui/payments:unit_tests",
"//ios/chrome/browser/ui/payments/cells:unit_tests", "//ios/chrome/browser/ui/payments/cells:unit_tests",
"//ios/chrome/browser/ui/presenters:unit_tests",
"//ios/chrome/browser/ui/promos:unit_tests", "//ios/chrome/browser/ui/promos:unit_tests",
"//ios/chrome/browser/ui/reading_list:unit_tests", "//ios/chrome/browser/ui/reading_list:unit_tests",
"//ios/chrome/browser/ui/safe_mode:unit_tests", "//ios/chrome/browser/ui/safe_mode:unit_tests",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment