fuse-open / fuselibs

Fuselibs is the Uno-libraries that provide the UI framework used in Fuse apps
https://npmjs.com/package/@fuse-open/fuselibs
MIT License
176 stars 72 forks source link

Fix iOS device orientation setup, honor the project settings #1375

Closed ichan-mb closed 3 years ago

ichan-mb commented 3 years ago

This is a regression fix for a feature introduced in #1354. The bugs reported here: #1374

This PR contains:

AndrewEQ commented 3 years ago

Cool, will do more testing and let ya know...

AndrewEQ commented 3 years ago

Ok here are my findings:

Uno 2.0.0-beta.5 + latest fuselibs (as of 28 Sep 2020)

Mobile.Orientations

iOS 12 - Xcode 10.1 - iPhone 6

iOS 13/14 - Xcode 12.0.1 - iPhone SE

Trigger Orientations

iOS 12 - Xcode 10.1 - iPhone 6

iOS 12 - Xcode 12.0.1 - iPhone 6

iOS 13/14 - Xcode 12.0.1 - iPhone SE

Test App:

<App Background="#222">

    <JavaScript File="MainView.js" />

    <Router ux:Name="router" />

    <Navigator DefaultPath="home" Active="{page}">
        <Page ux:Template="home">
            <StackPanel Alignment="Center" ItemSpacing="10">
                <Button Text="Portrait" Padding="12" Clicked="{gotoPortrait}">
                    <Rectangle Layer="Background" Color="#fff" CornerRadius="50" />
                </Button>
                <Button Text="PortraitUpsideDown" Padding="12" Clicked="{gotoPortraitUpsideDown}">
                    <Rectangle Layer="Background" Color="#fff" CornerRadius="50" />
                </Button>
                <Button Text="LandscapeLeft" Padding="12" Clicked="{gotoLandscapeLeft}">
                    <Rectangle Layer="Background" Color="#fff" CornerRadius="50" />
                </Button>
                <Button Text="LandscapeRight" Padding="12" Clicked="{gotoLandscapeRight}">
                    <Rectangle Layer="Background" Color="#fff" CornerRadius="50" />
                </Button>
            </StackPanel>
        </Page>
        <Page ux:Template="portrait">
            <Activated>
                <SetWindowOrientation To="Portrait" />
            </Activated>
            <StackPanel ItemSpacing="10" Alignment="Center">
                <StatusBarBackground />
                <Text TextColor="#fff" Alignment="Center">PORTRAIT</Text>

                <Button Text="Go Back" Padding="12" Clicked="{goBack}">
                    <Rectangle Layer="Background" Color="#fff" CornerRadius="50" />
                </Button>
            </StackPanel>
            <Rectangle Layer="Background" Color="#18f" />
        </Page>
        <Page ux:Template="portraitupsidedown">
            <Activated>
                <SetWindowOrientation To="PortraitUpsideDown" />
            </Activated>
            <StackPanel ItemSpacing="10" Alignment="Center">
                <StatusBarBackground />
                <Text TextColor="#fff" Alignment="Center">PORTRAIT UPSIDE DOWN</Text>

                <Button Text="Go Back" Padding="12" Clicked="{goBack}">
                    <Rectangle Layer="Background" Color="#fff" CornerRadius="50" />
                </Button>
            </StackPanel>
            <Rectangle Layer="Background" Color="#1f8" />
        </Page>
        <Page ux:Template="landscapeleftpage">
            <Activated>
                <SetWindowOrientation To="LandscapeLeft" />
            </Activated>
            <StackPanel ItemSpacing="10" Alignment="Center">
                <StatusBarBackground />
                <Text TextColor="#fff" Alignment="Center">LANDSCAPE LEFT</Text>

                <Button Text="Go Back" Padding="12" Clicked="{goBack}">
                    <Rectangle Layer="Background" Color="#fff" CornerRadius="50" />
                </Button>
            </StackPanel>
            <Rectangle Layer="Background" Color="#f18" />
        </Page>
        <Page ux:Template="landscaperight">
            <Activated>
                <SetWindowOrientation To="LandscapeRight" />
            </Activated>
            <StackPanel ItemSpacing="10" Alignment="Center">
                <StatusBarBackground />
                <Text TextColor="#fff" Alignment="Center">LANDSCAPE RIGHT</Text>

                <Button Text="Go Back" Padding="12" Clicked="{goBack}">
                    <Rectangle Layer="Background" Color="#fff" CornerRadius="50" />
                </Button>
            </StackPanel>
            <Rectangle Layer="Background" Color="#81f" />
        </Page>
    </Navigator>

    <Rectangle Layer="Background" Color="#18f" />
</App>

MainView.js:

var Observable = require('FuseJS/Observable');

var page = Observable('home');

function gotoPortrait() {
  router.push('portrait');
  console.log('gotoPortrait')
}

function gotoPortraitUpsideDown() {
  router.push('portraitupsidedown');
  console.log('gotoPortraitUpsideDown')
}

function gotoLandscape() {
  router.push('landscape');
  console.log('gotoLandscape')
}

function gotoLandscapeLeft() {
  router.push('landscapeleftpage');
  console.log('gotoLandscapeLeft')
}

function gotoLandscapeRight() {
  router.push('landscaperight');
  console.log('gotoLandscapeRight')
}

function goBack() {
  router.goBack()
  console.log('goBack')
}

module.exports = {
  page,
  gotoPortrait,
  gotoPortraitUpsideDown,
  gotoLandscape,
  gotoLandscapeLeft,
  gotoLandscapeRight,
  goBack
};
AndrewEQ commented 3 years ago

Here's a good article on iOS orientation: https://useyourloaf.com/blog/upside-down-and-rotating-iphones

ichan-mb commented 3 years ago

Thank you for the detailed testing. I don't have iOS devices with iOS 12 so I can't take the test to see it by myself 🙏.

Trigger Orientations

I'm curious about trigger orientation is not working on the ios 14 using Xcode 12. Have you tried opening / launching the testing app from the home screen, not from the XCode? because in my testing using iPhone XR iOS 14, launching from XCode indeed the trigger is not working, but when it launched directly from the home screen, the trigger works as intended. Do you have the same experience?

Here is the screen recording, launching the app from the home screen, and everything works perfectly: https://drive.google.com/file/d/1xuwGK1OvTquWVQ6AENAWnmTHP-edBSAF/view?usp=sharing

I don't know if is because I'm using the iPhone X series. I'll try to take a test again tomorrow using iPhone 7 in my office.

And the tests that you have been done on iOS 12 - Xcode 12.0.1 - iPhone 6 that crashed miserably because it doesn't know about the windowScene property and that because windowScene is only available on the iOS 13 up. What is strange is that on the source code, I did the conditional compiling and only call windowScene if it's on the iOS 13 up as you can see it here: https://github.com/fuse-open/fuselibs/pull/1375/files#diff-f0519ddc88708b91b1a8a05a96f94bcbR418. Am I do it wrong? I need suggestion from you and @mortend about this

Mobile.Orientations

In my test when Mobile.Orientations project settings are set, on iOS 14 using iPhone XR with Xcode 12, it is working as intended, launching the app from the Xcode or from the home screen it will set the orientation according to the Mobile.Orientations setting. I've to try again with a different device model tomorrow to make sure.

I also notice that when you set Mobile.Orientations to PortraitUpsideDown or LandscapeLeft or LandscapeRight. The generated of the Xcode project doesn't set to the correct setting of the Device Orientation according to the Mobile.Orientations but leave all the device orientation unchecked all as seen in the picture below:

image

The issue I think is in the template of info.plist: https://github.com/fuse-open/uno/blob/c3a98bb5138419348cc9170f7c5352e2b9accdad/lib/UnoCore/Targets/iOS/%40(Project.Name)/%40(Project.Name)-Info.plist#L54 We don't take care of all the possibilities of the Mobile.Orientations

My guess that because of this, the test that have you been done on the iPhone 6 ios 12 using Xcode 10 got crashed when setting up to PortraitUpsideDown

mortend commented 3 years ago

And the tests that you have been done on iOS 12 - Xcode 12.0.1 - iPhone 6 that crashed miserably because it doesn't know about the windowScene property and that because windowScene is only available on the iOS 13 up. What is strange is that on the source code, I did the conditional compiling and only call windowScene if it's on the iOS 13 up as you can see it here: https://github.com/fuse-open/fuselibs/pull/1375/files#diff-f0519ddc88708b91b1a8a05a96f94bcbR418. Am I do it wrong? I need suggestion from you and @mortend about this

@ichan-mb Regarding conditional compiling, I think we'll also need a run-time test to see if we're running on iOS 13 (apps compiled using iOS 13 SDK can still be run on older iOS versions). Something like this might work.

UIInterfaceOrientation mask;

#if defined(__IPHONE_13_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0
if (@available(iOS 13.0, *)) {
    mask = [[UIApplication sharedApplication].windows firstObject].windowScene.interfaceOrientation;
    // ...
}
#endif

mask = [[UIApplication sharedApplication] statusBarOrientation];
// ...

Note that the #else was removed because we need to fall-through in case the run-time test fail when run on iOS 12 or older.

AndrewEQ commented 3 years ago

Round 2

Trigger Orientations

Mobile.Orientations

Portrait value discussion

So there's a "Landscape" value thats supposed to represent both LandscapeLeft and LandscapeRight. In the same vein, "Portrait" is trying to represent both "Portrait" and "Upside Down". I think this is done in error as when you just want to restrict to only Portrait, you won't be able to.

What do you guys think of the value "PortraitUpsideDown" for the combo of "Portrait" and "Upside Down", then Portrait represents just Portrait and UpsideDown represents Upside Down?

AndrewEQ commented 3 years ago

So to confirm, the difference between "Xcode testing" and "home screen testing" feels like compile-time vs run-time checking?

Compile time checking: #if defined(__IPHONE_13_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0 Runtime checking: if (@available(iOS 13.0, *)) {

AndrewEQ commented 3 years ago

Android case-insensitive version for: https://github.com/fuse-open/uno/blob/6aa9bf8c65c7bce7a23bdebe5ed1d7b216929d7d/lib/UnoCore/Targets/Android/app/src/main/AndroidManifest.xml#L103

#if @(Project.Mobile.Orientations:ToLower:Equals('portrait'))
                  android:screenOrientation="portrait"
#elif @(Project.Mobile.Orientations:ToLower:Equals('upsidedown'))
                  android:screenOrientation="reversePortrait"
#elif @(Project.Mobile.Orientations:ToLower:Equals('landscapeleft'))
                  android:screenOrientation="landscape"
#elif @(Project.Mobile.Orientations:ToLower:Equals('landscaperight'))
                  android:screenOrientation="reverseLandscape"
#elif @(Project.Mobile.Orientations:ToLower:Equals('portraitupsidedown'))
                  android:screenOrientation="sensorPortrait"
#elif @(Project.Mobile.Orientations:ToLower:Equals('landscape'))
                  android:screenOrientation="sensorLandscape"
#else
                  android:screenOrientation="user"
#endif
AndrewEQ commented 3 years ago

iOS case-insensitive version for: https://github.com/fuse-open/uno/blob/c3a98bb5138419348cc9170f7c5352e2b9accdad/lib/UnoCore/Targets/iOS/%40(Project.Name)/%40(Project.Name)-Info.plist#L54


    <key>UISupportedInterfaceOrientations</key>
    <array>
#if @(Project.Mobile.Orientations:ToLower:Equals('auto')) || @(Project.Mobile.Orientations:ToLower:Equals('portraitupsidedown'))
        <string>UIInterfaceOrientationPortrait</string>
        <string>UIInterfaceOrientationPortraitUpsideDown</string>
#endif
#if @(Project.Mobile.Orientations:ToLower:Equals('auto')) || @(Project.Mobile.Orientations:ToLower:Equals('landscape'))
        <string>UIInterfaceOrientationLandscapeLeft</string>
        <string>UIInterfaceOrientationLandscapeRight</string>
#endif
#if @(Project.Mobile.Orientations:ToLower:Equals('portrait'))
        <string>UIInterfaceOrientationPortrait</string>
#endif
#if @(Project.Mobile.Orientations:ToLower:Equals('upsidedown'))
        <string>UIInterfaceOrientationPortraitUpsideDown</string>
#endif
#if @(Project.Mobile.Orientations:ToLower:Equals('landscapeleft'))
        <string>UIInterfaceOrientationLandscapeLeft</string>
#endif
#if @(Project.Mobile.Orientations:ToLower:Equals('landscaperight'))
        <string>UIInterfaceOrientationLandscapeRight</string>
#endif
    </array>
    <key>UISupportedInterfaceOrientations~ipad</key>
    <array>
#if @(Project.Mobile.Orientations:ToLower:Equals('auto')) || @(Project.Mobile.Orientations:ToLower:Equals('portraitupsidedown'))
        <string>UIInterfaceOrientationPortrait</string>
        <string>UIInterfaceOrientationPortraitUpsideDown</string>
#endif
#if @(Project.Mobile.Orientations:ToLower:Equals('auto')) || @(Project.Mobile.Orientations:ToLower:Equals('landscape'))
        <string>UIInterfaceOrientationLandscapeLeft</string>
        <string>UIInterfaceOrientationLandscapeRight</string>
#endif
#if @(Project.Mobile.Orientations:ToLower:Equals('portrait'))
        <string>UIInterfaceOrientationPortrait</string>
#endif
#if @(Project.Mobile.Orientations:ToLower:Equals('upsidedown'))
        <string>UIInterfaceOrientationPortraitUpsideDown</string>
#endif
#if @(Project.Mobile.Orientations:ToLower:Equals('landscapeleft'))
        <string>UIInterfaceOrientationLandscapeLeft</string>
#endif
#if @(Project.Mobile.Orientations:ToLower:Equals('landscaperight'))
        <string>UIInterfaceOrientationLandscapeRight</string>
#endif
    </array>
AndrewEQ commented 3 years ago

Updated my round 2 findings as I realised I was testing with the wrong Uno.

AndrewEQ commented 3 years ago

Code for SystemUI.uno:

        private static int GetProjectSettingsOrientation()
        {
            if (@(Project.Mobile.Orientations:ToLower) == "portraitupsidedown")
                return  extern<int>"UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown";
            if (@(Project.Mobile.Orientations:ToLower) == "portrait")
                return  extern<int>"UIInterfaceOrientationMaskPortrait";
            if (@(Project.Mobile.Orientations:ToLower) == "upsidedown")
                return  extern<int>"UIInterfaceOrientationMaskPortraitUpsideDown";
            if (@(Project.Mobile.Orientations:ToLower) == "landscape")
                return  extern<int>"UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight";
            if (@(Project.Mobile.Orientations:ToLower) == "landscapeleft")
                return  extern<int>"UIInterfaceOrientationMaskLandscapeLeft";
            if (@(Project.Mobile.Orientations:ToLower) == "landscaperight")
                return  extern<int>"UIInterfaceOrientationMaskLandscapeRight";
            return  extern<int>"UIInterfaceOrientationMaskAll";
        }
AndrewEQ commented 3 years ago

Trigger Orientations

Ok tested Xcode 12

Mobile.Orientations

I guess we can fix them in another PR, seeing how the naming logic is broken for when we just want Portrait, we could add another value called "PortraitOnly".

mortend commented 3 years ago

Is this PR ready to be merged now?

AndrewEQ commented 3 years ago

Its just upside down orientation not working on iOS12 but if y'all ok with that for now, then we're good to go.