Skip to content

Commit e161691

Browse files
author
andriyha
committed
[ADD] Added Split Mode support + finalizing refactoring with keyboard and orientation handling
1 parent b311f20 commit e161691

File tree

9 files changed

+98
-37
lines changed

9 files changed

+98
-37
lines changed

features/system-tests/step_definitions/steps.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ defineSupportCode(function(context) {
1717
width: 0,
1818
height: 0
1919
},
20+
orientation: 90,
2021
navigator: {
2122
userAgent: '',
2223
standalone: false
@@ -59,6 +60,7 @@ defineSupportCode(function(context) {
5960
Given('a user agent equals to {string}', function(userAgent) {
6061
this.win.navigator.userAgent = userAgent
6162
this.browserUiState = new BrowserUiState(this.win, Orientation.LANDSCAPE)
63+
this.win.orientation = 90
6264
})
6365

6466
Given('screen dimensions is {int} x {int}', function(width, height) {
@@ -76,6 +78,7 @@ defineSupportCode(function(context) {
7678
When('browser is rotated to portrait', function() {
7779
let stateProvider = this.stateProvider ? this.stateProvider : this.browserUiState._provider
7880
stateProvider._deviceOrientationDetector._toggleCurrentOrientation()
81+
this.win.orientation = 0
7982
})
8083

8184
When('screen dimensions changes to {int} x {int}', function(width, height) {

features/unit-tests/state-provider.feature

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ Feature: State Provider
1414
And stateProvider.collapsedThreshold should be equal <th-l-col>
1515
And stateProvider.keyboardThreshold should be equal <th-l-key>
1616
And stateProvider.state should be equal "COLLAPSED"
17-
And stateProvider.viewportWidthAdjustedIfNeeded should be correct
18-
And stateProvider.isIphoneX should correspond to "<device>"
17+
And stateProvider._viewportWidthAdjustedIfNeeded should be correct
18+
And stateProvider._isIphoneX should correspond to "<device>"
1919

2020
When after swipe up window dimensions changes to <win-w-swiped-l> x <win-h-swiped-l>
2121

@@ -39,8 +39,8 @@ Feature: State Provider
3939
And stateProvider.collapsedThreshold should be equal <th-p-col>
4040
And stateProvider.keyboardThreshold should be equal <th-p-key>
4141
And stateProvider.state should be equal "COLLAPSED"
42-
And stateProvider.viewportWidthAdjustedIfNeeded should be correct
43-
And stateProvider.isIphoneX should correspond to "<device>"
42+
And stateProvider._viewportWidthAdjustedIfNeeded should be correct
43+
And stateProvider._isIphoneX should correspond to "<device>"
4444

4545
When after swipe up window dimensions changes to <win-w-swiped-p> x <win-h-swiped-p>
4646

@@ -55,4 +55,4 @@ Feature: State Provider
5555
Examples:
5656
| device | os-ver | ua-ver | ua-mode | screen-w-l | screen-h-l | win-w-init-l | win-h-init-l | win-w-swiped-l | win-h-swiped-l | screen-w-p | screen-h-p | win-w-init-p | win-h-init-p | win-w-swiped-p | win-h-swiped-p | th-l-col | th-l-key | th-p-col | th-p-key | user-agent-string |
5757
| iPhone 7 | 10.2 | Safari 10 | | 375 | 667 | 667 | 331 | 667 | 375 | 375 | 667 | 375 | 559 | 375 | 627 | 6.75 | 48.0 | 12.0 | 32.7 | "Mozilla/5.0 (iPhone; CPU iPhone OS 10_2_1 like Mac OS X) AppleWebKit/602.4.6 (KHTML, like Gecko) Version/10.0 Mobile/14D27 Safari/602.1" |
58-
| iPhone X | 11.0 | Safari 11 | | 375 | 812 | 724 | 325 | 667 | 375 | 375 | 812 | 375 | 635 | 375 | 748 | 6.75 | 48.0 | 12.0 | 32.7 | "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1" |
58+
| iPhone X | 11.0 | Safari 11 | | 375 | 812 | 724 | 325 | 724 | 375 | 375 | 812 | 375 | 635 | 375 | 748 | 6.75 | 48.0 | 12.0 | 32.7 | "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1" |

features/unit-tests/step_definitions/state-provider.steps.js

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ defineSupportCode(function(context) {
1111
Given('testing state provider with a user agent equals to {string}', function(userAgent) {
1212
this.win.navigator.userAgent = userAgent
1313
this.stateProvider = new StateProvider(this.win, this.thresholds, Orientation.LANDSCAPE)
14+
this.win.orientation = 90
1415
})
1516

1617
Then('stateProvider.screenAspectRatio should be equal {int}/{int}', function (width, height) {
@@ -19,7 +20,7 @@ defineSupportCode(function(context) {
1920

2021
Then('stateProvider.viewportAspectRatio should be equal {int}/{int}', function (width, height) {
2122
let widthAdjusted = this.stateProvider.orientation === Orientation.LANDSCAPE &&
22-
this.stateProvider.isIphoneX() ? this.win.screen.height : width
23+
this.stateProvider._isIphoneX() ? this.win.screen.height : width
2324

2425
this.stateProvider.viewportAspectRatio.should.be.equal(widthAdjusted/height)
2526
})
@@ -52,14 +53,14 @@ defineSupportCode(function(context) {
5253
this.stateProvider.state.should.be.equal(state)
5354
})
5455

55-
Then('stateProvider.viewportWidthAdjustedIfNeeded should be correct', function () {
56-
let width = this.stateProvider.isIphoneX() ? this.win.screen.height : this.win.innerWidth
57-
this.stateProvider.viewportWidthAdjustedIfNeeded.should.be.equal(width)
56+
Then('stateProvider._viewportWidthAdjustedIfNeeded should be correct', function () {
57+
let width = this.stateProvider._isIphoneX() ? this.win.screen.height : this.win.innerWidth
58+
this.stateProvider._viewportWidthAdjustedIfNeeded.should.be.equal(width)
5859
})
5960

60-
Then('stateProvider.isIphoneX should correspond to {string}', function (device) {
61+
Then('stateProvider._isIphoneX should correspond to {string}', function (device) {
6162
if (device === 'iPhone X') {
62-
this.stateProvider.isIphoneX().should.be.true
63+
this.stateProvider._isIphoneX().should.be.true
6364
}
6465
})
6566
})

src/browser-ui-state/device-detectors/device-orientation-detector.js

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ export const Orientation = {
55
PORTRAIT: 'PORTRAIT'
66
}
77

8+
const splitModeThreshold = 200
9+
810
export default class DeviceOrientationDetector {
911
constructor(win, initialOrientation) {
1012
this._win = win
@@ -41,10 +43,30 @@ export default class DeviceOrientationDetector {
4143
}
4244

4345
_getOrientationLegacy(width, height) {
44-
if (this._initialOrientation) {
46+
if (/\W(?:iPhone|iPod|iPad)\W/i.test(this._win.navigator.userAgent)) {
47+
return Math.abs(this._win.orientation) === 90 ? Orientation.LANDSCAPE : Orientation.PORTRAIT
48+
} else if (this._initialOrientation) {
4549
return this._currentOrientation
4650
} else {
4751
return width > height ? Orientation.LANDSCAPE : Orientation.PORTRAIT
4852
}
4953
}
54+
55+
_isSplitMode() {
56+
if (/\WiPad\W/i.test(this._win.navigator.userAgent)) {
57+
if (this.orientation === Orientation.LANDSCAPE) {
58+
return Math.max(this._win.screen.width, this._win.screen.height) - this._win.innerWidth > splitModeThreshold
59+
} else {
60+
return Math.min(this._win.screen.width, this._win.screen.height) - this._win.innerWidth > splitModeThreshold
61+
}
62+
} else {
63+
if (this.orientation === Orientation.LANDSCAPE) {
64+
return Math.max(this._win.screen.width, this._win.screen.height) - this._win.innerWidth > splitModeThreshold
65+
} else if (!this._keyboardNoResizeDetector.keyboardShown) {
66+
return Math.max(this._win.screen.width, this._win.screen.height) - this._win.innerHeight > splitModeThreshold
67+
} else {
68+
return false
69+
}
70+
}
71+
}
5072
}

src/browser-ui-state/device-detectors/user-agent-detector.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ const UserAgentsRegExp = {
2222
CHROME_IOS: /\W(?:iPhone|iPod|iPad)\W.*\W(?:Chrome|CriOS|CrMo)\W/i,
2323
SAFARI_IPHONE: /\W(?:iPhone|iPod)\W.*\WVersion\/(?!.*\WMQQBrowser\W)/i,
2424
SAFARI_IPAD: /\WiPad\W.*\WVersion\//i,
25-
UC_BROWSER_EN_ANDROID: /\WAndroid\W.*(?!\Wzh-CN\W).*\WUCBrowser\W/i,
26-
UC_BROWSER_CN_ANDROID: /\WAndroid\W.*\Wzh-CN\W.*\WUCBrowser\W/i, //TODO check why it doesn't detects as CN but EN on Pixel
25+
UC_BROWSER_EN_ANDROID: /\WAndroid\W(?!.*\Wzh-CN\W).*\WUCBrowser\W/i,
26+
UC_BROWSER_CN_ANDROID: /\WAndroid\W.*\Wzh-CN\W.*\WUCBrowser\W/i,
2727
UC_BROWSER_IOS: /\W(?:iPhone|iPod|iPad)\W.*\WUCBrowser\/[0-9]{2}/i,
2828
UC_BROWSER_EN_IOS_STATIC: /\W(?:iPhone|iPod|iPad)\W.*\Wen-.*\WUCBrowser\/[0-9]\./i, //on iPhone 4S, UC EN 9.3 is static (well with manual refreshes it is not)
2929
DU_BROWSER: /\Wbdbrowser\W/i,

src/browser-ui-state/state-providers/keyboard-no-resize-state-provider.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ import StateProvider from './state-provider'
22
import States from './states'
33

44
export default class KeyboardNoResizeStateProvider extends StateProvider {
5-
constructor(win, thresholds) {
6-
super(win, thresholds)
5+
constructor(win, thresholds, initialOrientation) {
6+
super(win, thresholds, initialOrientation)
77
}
88

99
get state() {
1010
if (this._deviceOrientationDetector._keyboardNoResizeDetector.keyboardShown) {
11-
return States.KEYBOARD_NO_RESIZE //TODO maybe add handling to resize happens when keyboardShown to catch it disappearing?
11+
return States.KEYBOARD_NO_RESIZE
1212
} else {
1313
return super.state
1414
}

src/browser-ui-state/state-providers/state-provider.js

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ export default class StateProvider {
2424
get viewportAspectRatio() {
2525
const currentOrientation = this._deviceOrientationDetector.orientation
2626
let {innerWidth : viewportWidth, innerHeight : viewportHeight} = this._win
27-
const viewportWiderSize = currentOrientation === Orientation.LANDSCAPE ? this.viewportWidthAdjustedIfNeeded : viewportHeight
27+
const viewportWiderSize = currentOrientation === Orientation.LANDSCAPE ?
28+
this._viewportWidthAdjustedIfNeeded : viewportHeight
2829
const viewportNarrowerSize = currentOrientation === Orientation.PORTRAIT ? viewportWidth : viewportHeight
2930

3031
return viewportWiderSize / viewportNarrowerSize
@@ -50,11 +51,16 @@ export default class StateProvider {
5051

5152
get state() {
5253
const deviation = this.deviation
54+
const isSplitMode = this._deviceOrientationDetector._isSplitMode()
5355

5456
let state = States.EXPANDED
5557

56-
if (fscreen.fullscreenElement) {
57-
state = States.HTML5_FULLSCREEN //Case with keyboard in HTML5 fullscreen mode can't be traced correctly
58+
if (fscreen.fullscreenElement && isSplitMode) {
59+
state = States.HTML5_FULLSCREEN_IN_SPLIT_MODE //Case with keyboard in HTML5 fullscreen mode can't be traced correctly
60+
} else if (isSplitMode) {
61+
state = States.SPLIT_MODE
62+
} else if (fscreen.fullscreenElement) {
63+
state = States.HTML5_FULLSCREEN
5864
} else if (deviation > this.keyboardThreshold) {
5965
state = States.KEYBOARD
6066
} else if (deviation > this.collapsedThreshold) {
@@ -71,11 +77,11 @@ export default class StateProvider {
7177
* always the largest side of the screen).
7278
* Relevant only for landscape
7379
*/
74-
get viewportWidthAdjustedIfNeeded() {
75-
return this.isIphoneX() ? this._win.screen.height : this._win.innerWidth
80+
get _viewportWidthAdjustedIfNeeded() {
81+
return this._isIphoneX() ? this._win.screen.height : this._win.innerWidth
7682
}
7783

78-
isIphoneX() {
84+
_isIphoneX() {
7985
return /\WiPhone\W/i.test(this._win.navigator.userAgent) && this._win.screen.height === 812
8086
}
8187
}
Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,43 @@
11
export default {
2-
COLLAPSED: 'COLLAPSED', //e.g. initial browser state with address bar visible on the screen
3-
EXPANDED: 'EXPANDED', //e.g. state usually received after swiping up so that address bar gets hidden
4-
KEYBOARD: 'KEYBOARD', //e.g. after tapping on input element when on-screen keyboard appears and resizes viewport
5-
KEYBOARD_NO_RESIZE: 'KEYBOARD_NO_RESIZE', //e.g. after tapping on input element when on-screen keyboard appears but doesn't resize viewport
6-
HTML5_FULLSCREEN: 'HTML5_FULLSCREEN', //e.g. after switching to HTML5 Full Screen mode (not supported by all browsers)
7-
DESKTOP: 'DESKTOP', //e.g. regular desktop browser
8-
DESKTOP_HTML5_FULLSCREEN: 'DESKTOP_HTML5_FULLSCREEN', //e.g. desktop browser in HTML5 full screen mode
9-
STATIC: 'STATIC', //e.g. browser doesn't resize it's window content when it's bars get displayed
10-
// or hidden, or the bars are static and doesn't disappear at all; used only for
11-
// browsers which are known to behave like this, no programmatic way to detect this in other way
12-
SAFARI_HOMESCREEN: 'SAFARI_HOMESCREEN', //e.g. no browser's UI, page added as app to homescreen
13-
UNKNOWN: 'UNKNOWN' //e.g. browser is unknown to the library, so impossible to find-out it's state
14-
//TODO Detect split screen (landscape easy - diff between screen.width and win.innerWidth; portrait hard - diff + keyboardShown
2+
COLLAPSED: 'COLLAPSED',
3+
//Initial browser state with address bar visible on the screen
4+
5+
EXPANDED: 'EXPANDED',
6+
//State which usually received after swiping up so that address bar gets hidden
7+
8+
KEYBOARD: 'KEYBOARD',
9+
//After tapping on input element when on-screen keyboard appears and resizes viewport
10+
11+
KEYBOARD_NO_RESIZE: 'KEYBOARD_NO_RESIZE',
12+
//After tapping on input element when on-screen keyboard appears but doesn't resize viewport (mostly iOS)
13+
14+
HTML5_FULLSCREEN: 'HTML5_FULLSCREEN',
15+
//After switching to HTML5 Full Screen mode (not supported by all browsers, completely absent on iOS)
16+
17+
SPLIT_MODE: 'SPLIT_MODE',
18+
//Aka 'multi-tasking', 'split-view', 'split screen', 'side-by-side', e.g.:
19+
// - iOS 10+ iPad Safari 'Split View': https://support.apple.com/en-us/HT207522
20+
// - iOS 9+ iPad 'Split View': https://support.apple.com/en-us/HT207582
21+
// - Android 7+ 'Multi-Window': https://developer.android.com/guide/topics/ui/multi-window.html
22+
23+
HTML5_FULLSCREEN_IN_SPLIT_MODE: 'HTML5_FULLSCREEN_IN_SPLIT_MODE',
24+
//When HTML5 Full Screen mode is launched in Split Mode - it still covers only the area of the browser initiated
25+
//Full Screen, so it might be needed to differentiate this mode
26+
27+
DESKTOP: 'DESKTOP',
28+
//Regular desktop browser
29+
30+
DESKTOP_HTML5_FULLSCREEN: 'DESKTOP_HTML5_FULLSCREEN',
31+
//Desktop browser in HTML5 full screen mode
32+
33+
STATIC: 'STATIC',
34+
//Browser doesn't resize it's window content when it's bars get displayed or hidden,
35+
// or the bars are static and doesn't disappear at all; used only for browsers which are known to behave like this,
36+
// no programmatic way to detect this in other way
37+
38+
SAFARI_HOMESCREEN: 'SAFARI_HOMESCREEN',
39+
//No browser's UI, page added as app to homescreen, just a shortcut for window.navigator.standalone property
40+
41+
UNKNOWN: 'UNKNOWN'
42+
//Browser is unknown to the library, so impossible to find-out it's state
1543
}

src/demo/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import {Orientation} from "../browser-ui-state/device-detectors/device-orientati
55

66
class BrowserUiStateDemo {
77
constructor() {
8-
let initialOrientation = window.innerWidth > window.innerHeight ? Orientation.LANDSCAPE : Orientation.PORTRAIT
8+
let initialOrientation = window.innerWidth > window.innerHeight ?
9+
Orientation.LANDSCAPE : Orientation.PORTRAIT
910

1011
this.browserUiState = new BrowserUiState(window, initialOrientation)
1112

0 commit comments

Comments
 (0)