Commit a3e0119f authored by Gordon Seto's avatar Gordon Seto Committed by Commit Bot

[CrOS Settings] Add invalid activation code UI.

Add UI for informing when an activation code is invalid in the
activation code page.

Screenshot:
https://screenshot.googleplex.com/3gLCppUSwZbqovc.png

Bug: 1093185
Change-Id: Ieb9959cdbd0dd754454eaa69d5c5f4ac5453c8fe
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2555917
Commit-Queue: Gordon Seto <gordonseto@google.com>
Reviewed-by: default avatarAzeem Arshad <azeemarshad@chromium.org>
Reviewed-by: default avatarKyle Horimoto <khorimoto@chromium.org>
Cr-Commit-Position: refs/heads/master@{#830714}
parent f23603e5
...@@ -336,6 +336,9 @@ ...@@ -336,6 +336,9 @@
<message name="IDS_CELLULAR_SETUP_ESIM_PAGE_SCAN_QR_CODE_RETRY" desc="Label for button that uses the camera to rescan for QR codes when clicked."> <message name="IDS_CELLULAR_SETUP_ESIM_PAGE_SCAN_QR_CODE_RETRY" desc="Label for button that uses the camera to rescan for QR codes when clicked.">
use camera again use camera again
</message> </message>
<message name="IDS_CELLULAR_SETUP_ESIM_PAGE_SCAN_QR_CODE_INVALID" desc="Label informing the user that the activation code is invalid.">
Invalid code, please try again.
</message>
<message name="IDS_CELLULAR_SETUP_ESTABLISH_NETWORK_CONNECTION" desc="Message, informing user that a network connection is being established during cellular setup"> <message name="IDS_CELLULAR_SETUP_ESTABLISH_NETWORK_CONNECTION" desc="Message, informing user that a network connection is being established during cellular setup">
Establishing network connection ... Establishing network connection ...
</message> </message>
......
03f243f8e9d5b4a127aa449581efe8fcde8e583b
\ No newline at end of file
...@@ -56,6 +56,7 @@ constexpr webui::LocalizedString kLocalizedStringsWithoutPlaceholders[] = { ...@@ -56,6 +56,7 @@ constexpr webui::LocalizedString kLocalizedStringsWithoutPlaceholders[] = {
{"useCamera", IDS_CELLULAR_SETUP_ESIM_PAGE_USE_CAMERA}, {"useCamera", IDS_CELLULAR_SETUP_ESIM_PAGE_USE_CAMERA},
{"scanQRCodeSuccess", IDS_CELLULAR_SETUP_ESIM_PAGE_SCAN_QR_CODE_SUCCESS}, {"scanQRCodeSuccess", IDS_CELLULAR_SETUP_ESIM_PAGE_SCAN_QR_CODE_SUCCESS},
{"qrCodeRetry", IDS_CELLULAR_SETUP_ESIM_PAGE_SCAN_QR_CODE_RETRY}, {"qrCodeRetry", IDS_CELLULAR_SETUP_ESIM_PAGE_SCAN_QR_CODE_RETRY},
{"scanQrCodeInvalid", IDS_CELLULAR_SETUP_ESIM_PAGE_SCAN_QR_CODE_INVALID},
{"profileListPageMessage", IDS_CELLULAR_SETUP_PROFILE_LIST_PAGE_MESSAGE}}; {"profileListPageMessage", IDS_CELLULAR_SETUP_PROFILE_LIST_PAGE_MESSAGE}};
} // namespace } // namespace
......
...@@ -38,19 +38,23 @@ suite('CrComponentsActivationCodePageTest', function() { ...@@ -38,19 +38,23 @@ suite('CrComponentsActivationCodePageTest', function() {
const startScanningContainer = const startScanningContainer =
activationCodePage.$$('#startScanningContainer'); activationCodePage.$$('#startScanningContainer');
const startScanningButton = activationCodePage.$$('#startScanningButton'); const startScanningButton = activationCodePage.$$('#startScanningButton');
const scanSuccessContainer = activationCodePage.$$('#scanSuccessContainer'); const scanFinishContainer = activationCodePage.$$('#scanFinishContainer');
const switchCameraButton = activationCodePage.$$('#switchCameraButton'); const switchCameraButton = activationCodePage.$$('#switchCameraButton');
const scanSuccessContainer = activationCodePage.$$('#scanSuccessContainer');
const scanFailureContainer = activationCodePage.$$('#scanFailureContainer');
assertTrue(!!video); assertTrue(!!video);
assertTrue(!!startScanningContainer); assertTrue(!!startScanningContainer);
assertTrue(!!startScanningButton); assertTrue(!!startScanningButton);
assertTrue(!!scanSuccessContainer); assertTrue(!!scanFinishContainer);
assertTrue(!!switchCameraButton); assertTrue(!!switchCameraButton);
assertTrue(!!scanSuccessContainer);
assertTrue(!!scanFailureContainer);
// Initial state should only be showing the start scanning UI. // Initial state should only be showing the start scanning UI.
assertFalse(startScanningContainer.hidden); assertFalse(startScanningContainer.hidden);
assertTrue(video.hidden); assertTrue(video.hidden);
assertTrue(scanSuccessContainer.hidden); assertTrue(scanFinishContainer.hidden);
assertTrue(switchCameraButton.hidden); assertTrue(switchCameraButton.hidden);
// Click the start scanning button. // Click the start scanning button.
...@@ -60,18 +64,55 @@ suite('CrComponentsActivationCodePageTest', function() { ...@@ -60,18 +64,55 @@ suite('CrComponentsActivationCodePageTest', function() {
// The video should be visible and start scanning UI hidden. // The video should be visible and start scanning UI hidden.
assertFalse(video.hidden); assertFalse(video.hidden);
assertTrue(startScanningContainer.hidden); assertTrue(startScanningContainer.hidden);
assertTrue(scanSuccessContainer.hidden); assertTrue(scanFinishContainer.hidden);
assertTrue(switchCameraButton.hidden); assertTrue(switchCameraButton.hidden);
// Mock detecting an activation code. // Mock detecting an activation code.
activationCodePage.$$('#activationCode').value = 'ACTIVATION_CODE'; activationCodePage.$$('#activationCode').value = 'ACTIVATION_CODE';
await flushAsync(); await flushAsync();
// The scanSuccessContainer should now be visible, video and start scanning // The scanFinishContainer and scanSuccessContainer should now be visible,
// UI hidden. // video, start scanning UI and scanFailureContainer hidden.
assertFalse(scanFinishContainer.hidden);
assertTrue(startScanningContainer.hidden);
assertTrue(video.hidden);
assertFalse(scanSuccessContainer.hidden);
assertTrue(scanFailureContainer.hidden);
// Mock an invalid activation code.
activationCodePage.showError = true;
// The scanFinishContainer and scanFailureContainer should now be visible,
// video, start scanning UI and scanSuccessContainer hidden.
assertFalse(scanFinishContainer.hidden);
assertTrue(startScanningContainer.hidden);
assertTrue(video.hidden);
assertTrue(scanSuccessContainer.hidden);
assertFalse(scanFailureContainer.hidden);
// Enter a new activation code
activationCodePage.$$('#activationCode').value = 'ACTIVATION_CODE 2';
await flushAsync();
// The scanFinishContainer and scanSuccessContainer should now be visible,
// video, start scanning UI and scanFailureContainer hidden.
assertFalse(scanFinishContainer.hidden);
assertTrue(startScanningContainer.hidden);
assertTrue(video.hidden);
assertFalse(scanSuccessContainer.hidden); assertFalse(scanSuccessContainer.hidden);
assertTrue(scanFailureContainer.hidden);
assertFalse(activationCodePage.showError);
// Mock another invalid activation code.
activationCodePage.showError = true;
// The scanFinishContainer and scanFailureContainer should now be visible,
// video, start scanning UI and scanSuccessContainer hidden.
assertFalse(scanFinishContainer.hidden);
assertTrue(startScanningContainer.hidden); assertTrue(startScanningContainer.hidden);
assertTrue(video.hidden); assertTrue(video.hidden);
assertTrue(scanSuccessContainer.hidden);
assertFalse(scanFailureContainer.hidden);
}); });
test('Switch camera button states', async function() { test('Switch camera button states', async function() {
......
...@@ -81,9 +81,8 @@ suite('CrComponentsEsimFlowUiTest', function() { ...@@ -81,9 +81,8 @@ suite('CrComponentsEsimFlowUiTest', function() {
eSimPage.selectedESimPageName_ === eSimPage.selectedESimPageName_ ===
cellular_setup.ESimPageName.ACTIVATION_CODE && cellular_setup.ESimPageName.ACTIVATION_CODE &&
eSimPage.selectedESimPageName_ === activationCodePage.id); eSimPage.selectedESimPageName_ === activationCodePage.id);
// TODO(crbug.com/1093185) We don't have a way to show the error on the DOM assertTrue(activationCodePage.$$('#scanSuccessContainer').hidden);
// right now. Check internal property for now. assertFalse(activationCodePage.$$('#scanFailureContainer').hidden);
assertTrue(eSimPage.showError_);
}); });
test('No eSIM profile flow valid activation code', async function() { test('No eSIM profile flow valid activation code', async function() {
......
...@@ -35,15 +35,6 @@ ...@@ -35,15 +35,6 @@
background-color: transparent; background-color: transparent;
} }
#scanSuccessMessage {
color: green;
font-size: medium;
}
#scanSuccessMessage:hover {
cursor: default;
}
.center { .center {
left: 50%; left: 50%;
position: absolute; position: absolute;
...@@ -61,24 +52,44 @@ ...@@ -61,24 +52,44 @@
width: 20px; width: 20px;
} }
#useCameraAgainButton { .scan-finish-image {
font-weight: 500;
left: 50%;
margin-top: 16px;
transform: translateX(-50%);
}
#scanSuccessImage {
height: 20px; height: 20px;
position: absolute; position: absolute;
width: 20px; width: 20px;
} }
#scanSuccessMessage { .scan-finish-message {
padding-inline-end: 0; padding-inline-end: 0;
padding-inline-start: 30px; padding-inline-start: 30px;
} }
.scan-finish-message:hover {
cursor: default;
}
#scanSuccessContainer {
margin-bottom: 8px;
}
#scanSuccessMessage {
color: green;
font-size: medium;
}
#scanFailureContainer {
margin-bottom: 4px;
}
#scanFailureMessage {
color: var(--google-red-600);
}
#useCameraAgainButton {
display: block;
font-weight: 500;
text-align: center;
}
#switchCameraButton { #switchCameraButton {
background-color: rgba(0, 0, 0, 0.04); background-color: rgba(0, 0, 0, 0.04);
border-radius: 4px; border-radius: 4px;
...@@ -124,16 +135,26 @@ ...@@ -124,16 +135,26 @@
[[i18n('useCamera')]] [[i18n('useCamera')]]
</cr-button> </cr-button>
</div> </div>
<div class="center" id="scanSuccessContainer" <div class="center" id="scanFinishContainer"
hidden$="[[isUiElementHidden_(UiElement.SCAN_SUCCESS, state_)]]"> hidden$="[[isUiElementHidden_(UiElement.SCAN_FINISH, state_)]]">
<div> <div>
<img id="scanSuccessImage" <div id="scanSuccessContainer"
src="activation_code_page_checked.svg" hidden$="[[isUiElementHidden_(UiElement.SCAN_SUCCESS, state_)]]">
aria-hidden="true"> <img class="scan-finish-image"
<span class="label" id="scanSuccessMessage"> src="activation_code_page_checked.svg">
<span class="label scan-finish-message" id="scanSuccessMessage">
[[i18n('scanQRCodeSuccess')]] [[i18n('scanQRCodeSuccess')]]
</span> </span>
</div> </div>
<div id="scanFailureContainer"
hidden$="[[isUiElementHidden_(UiElement.SCAN_FAILURE, state_)]]">
<img class="scan-finish-image"
src="activation_code_page_error.svg">
<span class="label scan-finish-message" id="scanFailureMessage">
[[i18n('scanQrCodeInvalid')]]
</span>
</div>
</div>
<cr-button id="useCameraAgainButton" <cr-button id="useCameraAgainButton"
on-click="startScanning_"> on-click="startScanning_">
[[i18n('qrCodeRetry')]] [[i18n('qrCodeRetry')]]
......
...@@ -16,14 +16,17 @@ const PageState = { ...@@ -16,14 +16,17 @@ const PageState = {
SWITCHING_CAM_USER_TO_ENVIRONMENT: 4, SWITCHING_CAM_USER_TO_ENVIRONMENT: 4,
SWITCHING_CAM_ENVIRONMENT_TO_USER: 5, SWITCHING_CAM_ENVIRONMENT_TO_USER: 5,
SUCCESS: 6, SUCCESS: 6,
FAILURE: 7,
}; };
/** @enum {number} */ /** @enum {number} */
const UiElement = { const UiElement = {
START_SCANNING: 1, START_SCANNING: 1,
VIDEO: 2, VIDEO: 2,
SCAN_SUCCESS: 3, SWITCH_CAMERA: 3,
SWITCH_CAMERA: 4, SCAN_FINISH: 4,
SCAN_SUCCESS: 5,
SCAN_FAILURE: 6,
}; };
/** /**
...@@ -42,6 +45,12 @@ Polymer({ ...@@ -42,6 +45,12 @@ Polymer({
observer: 'onActivationCodeChanged_', observer: 'onActivationCodeChanged_',
}, },
showError: {
type: Boolean,
notify: true,
observer: 'onShowErrorChanged_',
},
/** /**
* @type {!PageState} * @type {!PageState}
* @private * @private
...@@ -49,6 +58,7 @@ Polymer({ ...@@ -49,6 +58,7 @@ Polymer({
state_: { state_: {
type: Object, type: Object,
value: PageState, value: PageState,
observer: 'onStateChanged_',
}, },
/** @private */ /** @private */
...@@ -225,12 +235,13 @@ Polymer({ ...@@ -225,12 +235,13 @@ Polymer({
onActivationCodeChanged_() { onActivationCodeChanged_() {
const activationCode = this.validateActivationCode_(this.activationCode); const activationCode = this.validateActivationCode_(this.activationCode);
this.fire('activation-code-updated', {activationCode: activationCode}); this.fire('activation-code-updated', {activationCode: activationCode});
// TODO(crbug.com/1093185): Handle if activation code is invalid.
if (activationCode) { if (activationCode) {
if (this.stream_) { if (this.stream_) {
this.stream_.getTracks()[0].stop(); this.stream_.getTracks()[0].stop();
} }
this.state_ = PageState.SUCCESS; this.state_ = PageState.SUCCESS;
} else {
this.state_ = PageState.FAILURE;
} }
}, },
...@@ -260,6 +271,20 @@ Polymer({ ...@@ -260,6 +271,20 @@ Polymer({
this.startScanning_(); this.startScanning_();
}, },
/** @private */
onShowErrorChanged_() {
if (this.showError) {
this.state_ = PageState.FAILURE;
}
},
/** @private */
onStateChanged_() {
if (this.state_ !== PageState.FAILURE) {
this.showError = false;
}
},
/** /**
* @param {UiElement} uiElement * @param {UiElement} uiElement
* @param {PageState} state * @param {PageState} state
...@@ -273,12 +298,16 @@ Polymer({ ...@@ -273,12 +298,16 @@ Polymer({
case UiElement.VIDEO: case UiElement.VIDEO:
return state !== PageState.SCANNING_USER_FACING && return state !== PageState.SCANNING_USER_FACING &&
state !== PageState.SCANNING_ENVIRONMENT_FACING; state !== PageState.SCANNING_ENVIRONMENT_FACING;
case UiElement.SCAN_SUCCESS:
return state !== PageState.SUCCESS;
case UiElement.SWITCH_CAMERA: case UiElement.SWITCH_CAMERA:
const isScanning = state === PageState.SCANNING_USER_FACING || const isScanning = state === PageState.SCANNING_USER_FACING ||
state === PageState.SCANNING_ENVIRONMENT_FACING; state === PageState.SCANNING_ENVIRONMENT_FACING;
return !(isScanning && hasMultipleCameras); return !(isScanning && hasMultipleCameras);
case UiElement.SCAN_FINISH:
return state !== PageState.SUCCESS && state !== PageState.FAILURE;
case UiElement.SCAN_SUCCESS:
return state !== PageState.SUCCESS;
case UiElement.SCAN_FAILURE:
return state !== PageState.FAILURE;
} }
}, },
......
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M9 10h2V6H9v4zm1-8c-4.416 0-8 3.584-8 8s3.584 8 8 8 8-3.584 8-8-3.584-8-8-8zm0 14c-3.308 0-6-2.693-6-6 0-3.308 2.692-6 6-6 3.307 0 6 2.692 6 6 0 3.307-2.693 6-6 6zm-1-2h2v-2H9v2z" fill="#D93025"/></svg>
\ No newline at end of file
...@@ -31,7 +31,8 @@ ...@@ -31,7 +31,8 @@
selected-profiles="{{selectedProfiles_}}"> selected-profiles="{{selectedProfiles_}}">
</profile-discovery-list-page> </profile-discovery-list-page>
<activation-code-page id="activationCodePage" <activation-code-page id="activationCodePage"
activation-code="{{activationCode_}}"> activation-code="{{activationCode_}}"
show-error="{{showError_}}">
</activation-code-page> </activation-code-page>
<final-page <final-page
id="finalPage" id="finalPage"
......
...@@ -175,8 +175,6 @@ cr.define('cellular_setup', function() { ...@@ -175,8 +175,6 @@ cr.define('cellular_setup', function() {
handleProfileInstallResponse_(response) { handleProfileInstallResponse_(response) {
// TODO(crbug.com/1093185) Handle // TODO(crbug.com/1093185) Handle
// confirmation code if needed. // confirmation code if needed.
// TODO(crbug.com/1093185) If response.result ===
// kErrorInvalidActivationCode, show error in activation code page.
this.showError_ = response.result !== this.showError_ = response.result !==
chromeos.cellularSetup.mojom.ProfileInstallResult.kSuccess; chromeos.cellularSetup.mojom.ProfileInstallResult.kSuccess;
if (response.result === if (response.result ===
......
...@@ -13,12 +13,14 @@ ...@@ -13,12 +13,14 @@
url(final_page_success_2x.png) 2x); url(final_page_success_2x.png) 2x);
background-position: center center; background-position: center center;
background-repeat: no-repeat; background-repeat: no-repeat;
background-size: contain;
} }
.error[slot='page-body'] { .error[slot='page-body'] {
background-image: -webkit-image-set( background-image: -webkit-image-set(
url(error_1x.png) 1x, url(error_1x.png) 1x,
url(error_2x.png) 2x); url(error_2x.png) 2x);
background-size: contain;
} }
</style> </style>
<base-page title="[[getTitle_(showError)]]" <base-page title="[[getTitle_(showError)]]"
......
...@@ -39,6 +39,10 @@ ...@@ -39,6 +39,10 @@
file="cr_components/chromeos/cellular_setup/activation_code_page_switch_camera.svg" file="cr_components/chromeos/cellular_setup/activation_code_page_switch_camera.svg"
type="BINDATA" type="BINDATA"
compress="gzip" /> compress="gzip" />
<include name="IDR_WEBUI_CHROMEOS_CELLULAR_SETUP_ACTIVATION_CODE_PAGE_ERROR_SVG"
file="cr_components/chromeos/cellular_setup/activation_code_page_error.svg"
type="BINDATA"
compress="gzip" />
<include name="IDR_WEBUI_CHROMEOS_CELLULAR_SETUP_ERROR_1X_PNG" <include name="IDR_WEBUI_CHROMEOS_CELLULAR_SETUP_ERROR_1X_PNG"
file="cr_components/chromeos/cellular_setup/error_1x.png" file="cr_components/chromeos/cellular_setup/error_1x.png"
type="BINDATA" type="BINDATA"
......
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