amaurymartiny/shoot-i-smoke

View on GitHub
App/components/Cigarettes/Cigarette/Cigarette.tsx

Summary

Maintainability
A
1 hr
Test Coverage
// Shoot! I Smoke
// Copyright (C) 2018-2023  Marcelo S. Coelho, Amaury M.

// Shoot! I Smoke is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Shoot! I Smoke is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Shoot! I Smoke.  If not, see <http://www.gnu.org/licenses/>.

import React from 'react';
import {
    Image,
    ImageSourcePropType,
    StyleProp,
    StyleSheet,
    View,
    ViewStyle,
} from 'react-native';

import butt from '@shootismoke/ui/assets/images/butt.png';
import buttVertical from '@shootismoke/ui/assets/images/butt-vertical.png';
import head from '@shootismoke/ui/assets/images/head.png';
import headVertical from '@shootismoke/ui/assets/images/head-vertical.png';

export type CigaretteOrientation = 'diagonal' | 'horizontal' | 'vertical';

interface CigaretteProps {
    percentage: number;
    orientation: CigaretteOrientation;
    fullCigaretteLength: number;
    style?: StyleProp<ViewStyle>;
}

const styles = StyleSheet.create({
    butt: {
        bottom: 0,
        position: 'absolute',
        left: 0,
    },
    cigarette: {
        flexGrow: 1,
    },
    diagonal: {
        transform: [{ rotate: '45deg' }, { scale: 1 }],
    },
    head: {
        position: 'absolute',
        right: 0,
        top: 0,
        zIndex: 1,
    },
    inner: {
        bottom: 0,
        left: 0,
        overflow: 'hidden',
        position: 'absolute',
    },
});

/**
 * The percentage of cigarette length when `percentage=0`.
 */
const MIN_PERCENTAGE = 0.4;

/**
 * Given the full length of a cigarette, and the percentage of the cigarette
 * smoked, get the actual length of the cigarette.
 */
function getCigaretteActualLength(
    fullCigaretteLength: number,
    percentage: number
): number {
    return Math.ceil(
        ((1 - MIN_PERCENTAGE) * percentage + MIN_PERCENTAGE) *
            fullCigaretteLength
    );
}

/**
 * A cigarette's width:height aspect ratio.
 */
const CIGARETTE_ASPECT_RATIO = 21 / 280;
const CIGARETTE_HEAD_HW_RATIO = 27 / 20;

function getContainerStyle(
    orientation: CigaretteOrientation,
    fullCigaretteLength: number
): ViewStyle {
    // Assuming the cigarette is vertical:
    const height = fullCigaretteLength;
    const width = height * CIGARETTE_ASPECT_RATIO;

    switch (orientation) {
        case 'horizontal': {
            return {
                height: width,
                width: height,
            };
        }
        case 'vertical': {
            return {
                height: height,
                width: width,
            };
        }
        default:
            return {};
    }
}

/**
 * Render a horizontal or vertical cigarette.
 */
function renderCigarette(
    orientation: 'horizontal' | 'vertical',
    percentage: number,
    fullCigaretteLength: number,
    additionalStyle?: StyleProp<ViewStyle>
): React.ReactElement {
    // Assuming cigarette is vertical:
    const height = fullCigaretteLength;
    const width = height * CIGARETTE_ASPECT_RATIO;
    const actualHeight = getCigaretteActualLength(
        fullCigaretteLength,
        percentage
    );

    return (
        <View
            style={[
                getContainerStyle(orientation, fullCigaretteLength),
                additionalStyle,
            ]}
        >
            <View
                style={[
                    styles.inner,
                    orientation === 'horizontal'
                        ? {
                                height: '100%',
                                width: actualHeight,
                          }
                        : {
                                height: actualHeight,
                                width: '100%',
                          },
                ]}
            >
                <Image
                    source={
                        (orientation === 'vertical'
                            ? buttVertical
                            : butt) as ImageSourcePropType
                    }
                    style={[
                        styles.butt,
                        orientation === 'horizontal'
                            ? {
                                    height: '100%',
                                    width: fullCigaretteLength,
                              }
                            : {
                                    height: fullCigaretteLength,
                                    width: '100%',
                              },
                    ]}
                />
                <Image
                    source={
                        (orientation === 'vertical'
                            ? headVertical
                            : head) as ImageSourcePropType
                    }
                    style={[
                        styles.head,
                        orientation === 'horizontal'
                            ? {
                                    height: '100%',
                                    width: width * CIGARETTE_HEAD_HW_RATIO,
                              }
                            : {
                                    height: width * CIGARETTE_HEAD_HW_RATIO,
                                    width: '100%',
                              },
                    ]}
                />
            </View>
        </View>
    );
}

export function Cigarette(props: CigaretteProps): React.ReactElement {
    const { orientation, percentage, fullCigaretteLength, style } = props;

    // Only used for diagonal. Assuming cigarette is vertical:
    const height = getCigaretteActualLength(fullCigaretteLength, percentage);
    const width = fullCigaretteLength * CIGARETTE_ASPECT_RATIO;

    // For diagonal cigarettes, we render a horizontal cigarette, and rotate it
    // 45deg.
    return orientation === 'diagonal' ? (
        <View
            style={[
                {
                    height: (height + width) / Math.SQRT2,
                    width: (height + width) / Math.SQRT2,
                },
                style,
            ]}
        >
            <View style={styles.diagonal}>
                {renderCigarette('horizontal', percentage, fullCigaretteLength)}
            </View>
        </View>
    ) : (
        renderCigarette(orientation, percentage, fullCigaretteLength, style)
    );
}