chromium/remoting/host/it2me/it2me_confirmation_dialog_mac.mm

// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <memory>

#include "remoting/host/it2me/it2me_confirmation_dialog.h"

#import <Cocoa/Cocoa.h>

#include <utility>

#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/i18n/message_formatter.h"
#include "base/location.h"
#include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "remoting/base/string_resources.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/l10n/l10n_util_mac.h"

@interface It2MeConfirmationDialogMacController : NSObject {
 @private
  NSAlert* __strong _confirmation_alert;
  std::u16string _username;
  remoting::It2MeConfirmationDialog::ResultCallback _dialog_action_callback;
}

- (instancetype)initWithCallback:
                    (remoting::It2MeConfirmationDialog::ResultCallback)callback
                        username:(const std::string&)username;
- (void)show;
- (void)hide;
- (void)onCancel:(id)sender;
- (void)onAccept:(id)sender;
@end

namespace remoting {

namespace {
// Time to wait before closing the dialog and cancelling the connection.
constexpr base::TimeDelta kDialogTimeout = base::Minutes(1);
}  // namespace

// Bridge between C++ and ObjC implementations of It2MeConfirmationDialog.
class It2MeConfirmationDialogMac : public It2MeConfirmationDialog {
 public:
  It2MeConfirmationDialogMac();

  It2MeConfirmationDialogMac(const It2MeConfirmationDialogMac&) = delete;
  It2MeConfirmationDialogMac& operator=(const It2MeConfirmationDialogMac&) =
      delete;

  ~It2MeConfirmationDialogMac() override;

  // It2MeConfirmationDialog implementation.
  void Show(const std::string& remote_user_email,
            ResultCallback callback) override;

 private:
  void OnDialogAction(Result result);

  It2MeConfirmationDialogMacController* __strong controller_;

  ResultCallback result_callback_;

  base::OneShotTimer dialog_timer_;
};

It2MeConfirmationDialogMac::It2MeConfirmationDialogMac() = default;

It2MeConfirmationDialogMac::~It2MeConfirmationDialogMac() {
  dialog_timer_.Stop();

  if (controller_) {
    @autoreleasepool {
      [controller_ hide];
    }
  }
}

void It2MeConfirmationDialogMac::Show(const std::string& remote_user_email,
                                      ResultCallback callback) {
  result_callback_ = std::move(callback);

  dialog_timer_.Start(
      FROM_HERE, kDialogTimeout,
      base::BindOnce(&It2MeConfirmationDialogMac::OnDialogAction,
                     base::Unretained(this), Result::CANCEL));

  ResultCallback dialog_action_callback = base::BindOnce(
      &It2MeConfirmationDialogMac::OnDialogAction, base::Unretained(this));

  @autoreleasepool {
    controller_ = [[It2MeConfirmationDialogMacController alloc]
        initWithCallback:std::move(dialog_action_callback)
                username:remote_user_email];
    [controller_ show];
  }
}

void It2MeConfirmationDialogMac::OnDialogAction(Result result) {
  dialog_timer_.Stop();

  if (controller_) {
    @autoreleasepool {
      [controller_ hide];
      controller_ = nil;
    }
  }

  if (result_callback_) {
    std::move(result_callback_).Run(result);
  }
}

std::unique_ptr<It2MeConfirmationDialog>
It2MeConfirmationDialogFactory::Create() {
  return std::make_unique<It2MeConfirmationDialogMac>();
}

}  // namespace remoting

@implementation It2MeConfirmationDialogMacController

- (instancetype)initWithCallback:
                    (remoting::It2MeConfirmationDialog::ResultCallback)callback
                        username:(const std::string&)username {
  if ((self = [super init])) {
    _username = base::UTF8ToUTF16(username);
    _dialog_action_callback = std::move(callback);
  }
  return self;
}

- (void)show {
  _confirmation_alert = [[NSAlert alloc] init];

  std::u16string dialog_text =
      base::i18n::MessageFormatter::FormatWithNumberedArgs(
          l10n_util::GetStringUTF16(
              IDS_SHARE_CONFIRM_DIALOG_MESSAGE_WITH_USERNAME),
          _username);
  _confirmation_alert.messageText = base::SysUTF16ToNSString(dialog_text);

  NSButton* cancel_button = [_confirmation_alert
      addButtonWithTitle:l10n_util::GetNSString(
                             IDS_SHARE_CONFIRM_DIALOG_DECLINE)];
  cancel_button.action = @selector(onCancel:);
  cancel_button.target = self;

  NSButton* confirm_button = [_confirmation_alert
      addButtonWithTitle:l10n_util::GetNSString(
                             IDS_SHARE_CONFIRM_DIALOG_CONFIRM)];
  confirm_button.action = @selector(onAccept:);
  confirm_button.target = self;

  NSBundle* bundle = [NSBundle bundleForClass:[self class]];
  NSString* imagePath = [bundle pathForResource:@"chromoting128" ofType:@"png"];
  NSImage* image = [[NSImage alloc] initByReferencingFile:imagePath];
  _confirmation_alert.icon = image;
  [_confirmation_alert layout];

  // Force alert to be at the proper level and location.
  NSWindow* confirmation_window = _confirmation_alert.window;
  [confirmation_window center];
  confirmation_window.title = l10n_util::GetNSString(IDS_PRODUCT_NAME);
  confirmation_window.level = NSNormalWindowLevel;
  [confirmation_window orderFrontRegardless];
  [confirmation_window makeKeyWindow];
}

- (void)hide {
  if (_confirmation_alert) {
    [_confirmation_alert.window close];
    _confirmation_alert = nil;
  }
}

- (void)onCancel:(id)sender {
  [self hide];
  if (_dialog_action_callback) {
    std::move(_dialog_action_callback)
        .Run(remoting::It2MeConfirmationDialog::Result::CANCEL);
  }
}

- (void)onAccept:(id)sender {
  [self hide];
  if (_dialog_action_callback) {
    std::move(_dialog_action_callback)
        .Run(remoting::It2MeConfirmationDialog::Result::OK);
  }
}

@end