Skip to content

Commit

Permalink
fix: [#175419820] Restore screen brightness (#2728)
Browse files Browse the repository at this point in the history
* [#175419820] Refactored the components in a more functional style. Added Async useEffects to save and restore brightness on navigation focus

* [#175419820] Handled the hardware backpress case. Refactored to improve performances on event DidFocus

* [#175419820] Tested UseLayoutEffect for responsivenes

* [#175419820] Refactored FiscalCodeLandscapeOverlay

* [#175419820] Added test

* [#175419820] Switched to async effect. Removed component state variable inititalBrightness

* [#175419820] Removed useless alert

* [#175419820] Solution with a brightness varible locally scoped to effect

* [#175419820] Removed debug printings

* [#175419820] Relaxed test

Co-authored-by: Giovanni Mancini <g.mancini.0gmail.com>
Co-authored-by: Matteo Boschi <[email protected]>
  • Loading branch information
GiovanniMancini and Undermaken authored Feb 8, 2021
1 parent f4b64ca commit 385a3b5
Show file tree
Hide file tree
Showing 3 changed files with 303 additions and 212 deletions.
195 changes: 120 additions & 75 deletions ts/components/FiscalCodeLandscapeOverlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@ import { Municipality } from "../../definitions/content/Municipality";
import IconFont from "../components/ui/IconFont";
import I18n from "../i18n";
import customVariables from "../theme/variables";
import { getBrightness, setBrightness } from "../utils/brightness";
import FiscalCodeComponent from "./FiscalCodeComponent";
import AppHeader from "./ui/AppHeader";

type Props = Readonly<{
export type Props = Readonly<{
onCancel: () => void;
profile: InitializedProfile;
municipality: pot.Pot<Municipality, Error>;
Expand Down Expand Up @@ -52,93 +53,137 @@ const styles = StyleSheet.create({
}
});

export default class FiscalCodeLandscapeOverlay extends React.PureComponent<
Props
> {
private scrollTimeout?: number;
private ScrollVewRef = React.createRef<ScrollView>();
const HIGH_BRIGHTNESS = 1.0; // Target screen brightness for a very bright screen

private handleBackPress = () => {
this.props.onCancel();
return true;
};
const FiscalCodeLandscapeOverlay: React.FunctionComponent<Props> = (
props: Props
) => {
// eslint-disable-next-line functional/no-let
let scrollTimeout: number | undefined;

public componentDidMount() {
BackHandler.addEventListener("hardwareBackPress", this.handleBackPress);
}
const ScrollViewRef = React.createRef<ScrollView>();

public componentWillUnmount() {
BackHandler.removeEventListener("hardwareBackPress", this.handleBackPress);
// if there is an active timeout, clear it!
if (this.scrollTimeout !== undefined) {
clearTimeout(this.scrollTimeout);
// eslint-disable-next-line
this.scrollTimeout = undefined;
}
}
const handleBackPress = () => {
// On backpress the component gets unmounted, so the brightness is restored by the
// cleanup function
props.onCancel();
return true;
};

private scrollToEnd = () => {
if (this.props.showBackSide && this.ScrollVewRef.current) {
const scrollToEnd = () => {
if (props.showBackSide && ScrollViewRef.current) {
// dalay the scroll to end command to wait until the ingress animation is completed
// eslint-disable-next-line
this.scrollTimeout = setTimeout(() => {
if (this.ScrollVewRef.current) {
this.ScrollVewRef.current.scrollToEnd({ animated: true });
scrollTimeout = setTimeout(() => {
if (ScrollViewRef.current) {
ScrollViewRef.current.scrollToEnd({ animated: true });
}
}, 300);
}
};

public render() {
return (
<Container style={{ backgroundColor: customVariables.brandDarkGray }}>
<AppHeader noLeft={true} dark={true}>
<Body />
</AppHeader>
<StatusBar
backgroundColor={customVariables.brandDarkGray}
barStyle={"light-content"}
/>
<ScrollView
style={styles.content}
ref={this.ScrollVewRef}
onLayout={this.scrollToEnd}
>
<View style={styles.headerSpacer} />
<View spacer={true} />
<View>
<FiscalCodeComponent
type={"Landscape"}
profile={this.props.profile}
getBackSide={false}
municipality={this.props.municipality}
/>
</View>

<View spacer={true} />
React.useEffect(() => {
BackHandler.addEventListener("hardwareBackPress", handleBackPress);
return () => {
BackHandler.removeEventListener("hardwareBackPress", handleBackPress);
// if there is an active timeout, clear it!
if (scrollTimeout !== undefined) {
clearTimeout(scrollTimeout);
// eslint-disable-next-line
scrollTimeout = undefined;
}
};
}, []);

// Brightness effect manager
React.useEffect(() => {
// eslint-disable-next-line functional/no-let
let myBrightness: number | undefined;

const myBrightF = async () => {
myBrightness = await getBrightness()
.fold(
() => undefined,
_ => _
)
.run();
};

const mySetBrightF = async () => {
await myBrightF();
if (myBrightness) {
await setBrightness(HIGH_BRIGHTNESS).run();
}
};

const finishedSet = mySetBrightF();

return () => {
const restoreDeviceBrightnessF = async () => {
await finishedSet;
if (myBrightness) {
await setBrightness(myBrightness)
.fold(
() => undefined,
_ => _
)
.run();
}
};
void restoreDeviceBrightnessF();
};
}, []);

return (
<Container style={{ backgroundColor: customVariables.brandDarkGray }}>
<AppHeader noLeft={true} dark={true}>
<Body />
</AppHeader>
<StatusBar
backgroundColor={customVariables.brandDarkGray}
barStyle={"light-content"}
/>
<ScrollView
style={styles.content}
ref={ScrollViewRef}
onLayout={scrollToEnd}
>
<View style={styles.headerSpacer} />
<View spacer={true} />
<View>
<FiscalCodeComponent
type={"Landscape"}
profile={this.props.profile}
getBackSide={true}
municipality={this.props.municipality}
profile={props.profile}
getBackSide={false}
municipality={props.municipality}
/>

<View spacer={true} large={true} />
<View spacer={true} large={true} />
</ScrollView>
<View style={styles.closeButton}>
<Button
transparent={true}
onPress={this.props.onCancel}
accessible={true}
accessibilityRole={"button"}
accessibilityLabel={I18n.t("global.buttons.close")}
>
<IconFont name="io-close" color={customVariables.colorWhite} />
</Button>
</View>
</Container>
);
}
}

<View spacer={true} />

<FiscalCodeComponent
type={"Landscape"}
profile={props.profile}
getBackSide={true}
municipality={props.municipality}
/>

<View spacer={true} large={true} />
<View spacer={true} large={true} />
</ScrollView>
<View style={styles.closeButton}>
<Button
transparent={true}
onPress={props.onCancel}
accessible={true}
accessibilityRole={"button"}
accessibilityLabel={I18n.t("global.buttons.close")}
>
<IconFont name="io-close" color={customVariables.colorWhite} />
</Button>
</View>
</Container>
);
};

export default FiscalCodeLandscapeOverlay;
88 changes: 88 additions & 0 deletions ts/components/__tests__/FiscalCodeLandscapeOverlay.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import configureMockStore from "redux-mock-store";
import { render, waitFor } from "@testing-library/react-native";
import { Provider } from "react-redux";
import * as React from "react";
import { tryCatch } from "fp-ts/lib/TaskEither";

import FiscalCodeLandscapeOverlay, {
Props as FiscalCodeProps
} from "../FiscalCodeLandscapeOverlay";
import { FiscalCode } from "../../../definitions/backend/FiscalCode";
import { EmailAddress } from "../../../definitions/backend/EmailAddress";
import { GlobalState } from "../../store/reducers/types";
import { applicationChangeState } from "../../store/actions/application";
import { appReducer } from "../../store/reducers";
import * as myBrightness from "../../utils/brightness";
import { PreferredLanguageEnum } from ".../../../definitions/backend/PreferredLanguage";

jest.mock("react-native-share", () => jest.fn());

describe("Test How Fiscal Code Overlay gets rendered on lifetime methods", () => {
afterAll(() => jest.resetAllMocks());

const myProps: FiscalCodeProps = {
onCancel: jest.fn(),
profile: {
accepted_tos_version: 3,
blocked_inbox_or_channels: {},
email: "[email protected]" as EmailAddress,
family_name: "Rossi",
fiscal_code: "ZXCVBN66Z66K666Q" as FiscalCode,
has_profile: true,
is_email_enabled: false,
is_email_validated: true,
is_inbox_enabled: true,
is_webhook_enabled: true,
name: "Mario",
preferred_languages: ["it_IT"] as Array<PreferredLanguageEnum>,
version: 7
},
municipality: {
error: {
name: "myError",
message: "myMessage"
},
kind: "PotNoneError"
},
showBackSide: false
};

it("Should call getBrightness and setBrightness", async () => {
const getSpy = jest.spyOn(myBrightness, "getBrightness").mockReturnValue(
tryCatch(
() => new Promise(() => 0),
reason => new Error(String(reason))
)
);

const mockStoreFactory = configureMockStore<GlobalState>();

const globalState: GlobalState = appReducer(
undefined,
applicationChangeState("active")
);

const myStore = mockStoreFactory({
...globalState,
// While the component under test is disconnected from the store, some
// inner components import ConnectionBar which is connected
network: { isConnected: true, actionQueue: [] }
} as GlobalState);

const component = render(
<Provider store={myStore}>
<FiscalCodeLandscapeOverlay {...myProps} />
</Provider>
);

const myButton = component.queryByA11yLabel("Chiudi");

component.unmount();
await waitFor(() => expect(myButton).toBeNull(), {
timeout: 10000
});

// Read the brightness
expect(getSpy).toHaveBeenCalled();
});
});
Loading

0 comments on commit 385a3b5

Please sign in to comment.