<commit_before>import BottomSheet, { BottomSheetBackdrop } from "@gorhom/bottom-sheet"
import { CareerHighlightBottomSheet_query$key } from "__generated__/CareerHighlightBottomSheet_query.graphql"
import {
  MedianSalePriceAtAuctionQuery,
  MedianSalePriceAtAuctionQuery$data,
} from "__generated__/MedianSalePriceAtAuctionQuery.graphql"
import { LegacyBackButtonContext } from "app/navigation/NavStack"
import { isPad } from "app/utils/hardware"
import { compact } from "lodash"
import { Flex } from "palette"
import { useCallback, useContext, useEffect, useMemo, useRef } from "react"
import { StatusBar } from "react-native"
import { FlatList } from "react-native-gesture-handler"
import { graphql, useRefetchableFragment } from "react-relay"
import { useScreenDimensions } from "shared/hooks"
import { CareerHighlightBottomSheetItem } from "./Components/CareerHighlightBottomSheetItem"
import { useMedianSalePriceChartDataContext } from "./providers/MedianSalePriceChartDataContext"

export type CareerHighlightKindValueType =
  | "Solo Show"
  | "Group Show"
  | "Review"
  | "Biennial Inclusion"

const CareerHighlightKind: { [key: string]: CareerHighlightKindValueType } = {
  "Solo Show": "Solo Show",
  "Group Show": "Group Show",
  "Reviewed Solo Show": "Review",
  "Reviewed Group Show": "Review",
  "Biennial Inclusion": "Biennial Inclusion",
  "National Pavillion": "Biennial Inclusion",
}

interface CareerHighlightBottomSheetProps {
  artistId: string
  queryData: MedianSalePriceAtAuctionQuery$data
}

export const CareerHighlightBottomSheet: React.FC<CareerHighlightBottomSheetProps> = ({
  artistId,
  queryData,
}) => {
  const [data, refetch] = useRefetchableFragment<
    MedianSalePriceAtAuctionQuery,
    CareerHighlightBottomSheet_query$key
  >(careerHighlighsBottomSheetFragment, queryData)

  const backButtonContext = useContext(LegacyBackButtonContext)

  if (!data) {
    return null
  }

  const dataForFlatlist = (): Array<{
    year: number
    index: number
    highlights: Record<CareerHighlightKindValueType, string[]>
  }> => {
    const eventDigest = data.analyticsArtistSparklines?.edges?.find(
      (ev) => !!parseInt(ev?.node?.sparkles ?? "", 10) && ev?.node?.eventDigest
    )?.node?.eventDigest
    if (!eventDigest) {
      return []
    }
    const careerHighlightsMap = makeCareerHighlightMap(eventDigest)
    const years = Object.keys(careerHighlightsMap).sort((a, b) => parseInt(a, 10) - parseInt(b, 10))
    return years.map((year, index) => ({
      year: parseInt(year, 10),
      index,
      highlights: careerHighlightsMap[parseInt(year, 10)],
    }))
  }

  const dataContext = useMedianSalePriceChartDataContext()
  if (!dataContext) {
    return null
  }

  const reloadData = () => {
    refetch({ artistId })
  }

  useEffect(() => {
    reloadData()
    onXAxisHighlightPressed(null)
  }, [artistId])

  const flatlistRef = useRef<FlatList>(null)

  const { selectedXAxisHighlight, onXAxisHighlightPressed } = dataContext

  useEffect(() => {
    if (selectedXAxisHighlight) {
      const index = flatListData.find((d) => d.year === selectedXAxisHighlight)?.index
      if (index !== undefined) {
        flatlistRef.current?.scrollToIndex({ index, animated: false })
      }
    }
  }, [selectedXAxisHighlight])

  const bottomSheetRef = useRef<BottomSheet>(null)

  const { height } = useScreenDimensions()

  // the height here is divided by constants that seems good on device. Feel free to change them.
  const points = isPad()
    ? [height / 3.5, height / 2.5, height / 1.8]
    : [height / 2.8, height / 2, height / 1.35]

  const snapPoints = useMemo(() => points, [])

  const handleSheetChanges = useCallback((index: number) => {
    if (index === -1) {
      backButtonContext.updateShouldHideBackButton(false)
      StatusBar.setHidden(false, "slide")
      onXAxisHighlightPressed(null)
    } else {
      backButtonContext.updateShouldHideBackButton(true)
      StatusBar.setHidden(true, "slide")
    }
  }, [])

  const renderBackdrop = useCallback(
    (props) => <BottomSheetBackdrop {...props} disappearsOnIndex={-1} appearsOnIndex={0} />,
    []
  )

  // using our custom background to ensure backgroundColor is white
  const renderBackground = useCallback(
    (props) => (
      <Flex {...props} backgroundColor="white" borderTopRightRadius={10} borderTopLeftRadius={10} />
    ),
    []
  )

  const flatListData = useMemo(
    () => dataForFlatlist(),
    [JSON.stringify(data.analyticsArtistSparklines)]
  )
  if (!flatListData.length) {
    return null
  }

  return (
    <BottomSheet
      ref={bottomSheetRef}
      index={selectedXAxisHighlight ? 0 : -1}
      snapPoints={snapPoints}
      onChange={handleSheetChanges}
      enablePanDownToClose
      backdropComponent={renderBackdrop}
      backgroundComponent={renderBackground}
    >
      <FlatList // Note that this FlatList is from "react-native-gesture-handler" in order for scrolls to work seamlessly on android. BottomSheetFlatlist shipped with BottomSheet is janky on iOS.
        testID="BottomSheetFlatlist"
        ref={flatlistRef}
        data={flatListData}
        renderItem={({ item }) => (
          <CareerHighlightBottomSheetItem year={item.year} highlights={item.highlights} />
        )}
        keyExtractor={(item) => item.year.toString()}
        horizontal
        pagingEnabled
        showsHorizontalScrollIndicator={false}
      />
    </BottomSheet>
  )
}

const careerHighlighsBottomSheetFragment = graphql`
  fragment CareerHighlightBottomSheet_query on Query
  @refetchable(queryName: "CareerHighlighsBottomSheetRefetchQuery")
  @argumentDefinitions(artistID: { type: "String!" }) {
    analyticsArtistSparklines(artistId: $artistID, last: 9) {
      edges {
        node {
          eventDigest
          sparkles
          year
        }
      }
    }
  }
`

/**
 * Prepares the eventDigest and creates a map of each year to the highlight kind
 * @param eventDigest
 * @returns Record<number, Record<CareerHighlightKindValueType, string[]>>
 * @example
 * {
 *   2014: {
 *     "Solo Show": ["ArtMuseum", "AnotherMuseum"],
 *     "Review": ["The Guardian", "The Sun"]
 *   },
 *   2015: {
 *     "Group Show": ["ArtMuseum", "AnotherMuseum"],
 *     "Biennial": ["Documenta"]
 *   },
 * }
 */
export const makeCareerHighlightMap = (
  eventDigest: string
): Record<number, Record<CareerHighlightKindValueType, string[]>> => {
  const minimumYear = new Date().getFullYear() - 8
  const result: Record<number, Record<CareerHighlightKindValueType, string[]>> = {}
  const arr = eventDigest ? eventDigest.split(";") : []
  if (!arr.length) {
    return result
  }
  for (const digest of arr) {
    // eventDigest may begin with the year, this regex extracts the first occurence
    const yearStr = digest.match(/\b(19|20)\d{2}\b/)?.[0]
    if (yearStr && parseInt(yearStr, 10) >= minimumYear) {
      const year = parseInt(yearStr, 10)
      const titleAndBody = digest.replace(`${year}`, "").trim()
      const [title, body] = titleAndBody?.split("@") ?? []
      const kind = CareerHighlightKind[title.trim()]
      const currentYear = result[year] ?? {}
      const updatedYear = { ...currentYear, [kind]: compact([...currentYear[kind], body.trim()]) }
      result[year] = updatedYear
    }
  }
  return result
}
<commit_msg>fix(CX-2968): Do Not Hide StatusBar on Median Sale Price Chart Screen (#7449)<commit_after>import BottomSheet, { BottomSheetBackdrop } from "@gorhom/bottom-sheet"
import { CareerHighlightBottomSheet_query$key } from "__generated__/CareerHighlightBottomSheet_query.graphql"
import {
  MedianSalePriceAtAuctionQuery,
  MedianSalePriceAtAuctionQuery$data,
} from "__generated__/MedianSalePriceAtAuctionQuery.graphql"
import { LegacyBackButtonContext } from "app/navigation/NavStack"
import { isPad } from "app/utils/hardware"
import { compact } from "lodash"
import { Flex } from "palette"
import { useCallback, useContext, useEffect, useMemo, useRef } from "react"
import { FlatList } from "react-native-gesture-handler"
import { graphql, useRefetchableFragment } from "react-relay"
import { useScreenDimensions } from "shared/hooks"
import { CareerHighlightBottomSheetItem } from "./Components/CareerHighlightBottomSheetItem"
import { useMedianSalePriceChartDataContext } from "./providers/MedianSalePriceChartDataContext"

export type CareerHighlightKindValueType =
  | "Solo Show"
  | "Group Show"
  | "Review"
  | "Biennial Inclusion"

const CareerHighlightKind: { [key: string]: CareerHighlightKindValueType } = {
  "Solo Show": "Solo Show",
  "Group Show": "Group Show",
  "Reviewed Solo Show": "Review",
  "Reviewed Group Show": "Review",
  "Biennial Inclusion": "Biennial Inclusion",
  "National Pavillion": "Biennial Inclusion",
}

interface CareerHighlightBottomSheetProps {
  artistId: string
  queryData: MedianSalePriceAtAuctionQuery$data
}

export const CareerHighlightBottomSheet: React.FC<CareerHighlightBottomSheetProps> = ({
  artistId,
  queryData,
}) => {
  const [data, refetch] = useRefetchableFragment<
    MedianSalePriceAtAuctionQuery,
    CareerHighlightBottomSheet_query$key
  >(careerHighlighsBottomSheetFragment, queryData)

  const backButtonContext = useContext(LegacyBackButtonContext)

  if (!data) {
    return null
  }

  const dataForFlatlist = (): Array<{
    year: number
    index: number
    highlights: Record<CareerHighlightKindValueType, string[]>
  }> => {
    const eventDigest = data.analyticsArtistSparklines?.edges?.find(
      (ev) => !!parseInt(ev?.node?.sparkles ?? "", 10) && ev?.node?.eventDigest
    )?.node?.eventDigest
    if (!eventDigest) {
      return []
    }
    const careerHighlightsMap = makeCareerHighlightMap(eventDigest)
    const years = Object.keys(careerHighlightsMap).sort((a, b) => parseInt(a, 10) - parseInt(b, 10))
    return years.map((year, index) => ({
      year: parseInt(year, 10),
      index,
      highlights: careerHighlightsMap[parseInt(year, 10)],
    }))
  }

  const dataContext = useMedianSalePriceChartDataContext()
  if (!dataContext) {
    return null
  }

  const reloadData = () => {
    refetch({ artistId })
  }

  useEffect(() => {
    reloadData()
    onXAxisHighlightPressed(null)
  }, [artistId])

  const flatlistRef = useRef<FlatList>(null)

  const { selectedXAxisHighlight, onXAxisHighlightPressed } = dataContext

  useEffect(() => {
    if (selectedXAxisHighlight) {
      const index = flatListData.find((d) => d.year === selectedXAxisHighlight)?.index
      if (index !== undefined) {
        flatlistRef.current?.scrollToIndex({ index, animated: false })
      }
    }
  }, [selectedXAxisHighlight])

  const bottomSheetRef = useRef<BottomSheet>(null)

  const { height } = useScreenDimensions()

  // the height here is divided by constants that seems good on device. Feel free to change them.
  const points = isPad()
    ? [height / 3.5, height / 2.5, height / 1.8]
    : [height / 2.8, height / 2, height / 1.35]

  const snapPoints = useMemo(() => points, [])

  const handleSheetChanges = useCallback((index: number) => {
    if (index === -1) {
      backButtonContext.updateShouldHideBackButton(false)
      onXAxisHighlightPressed(null)
    } else {
      backButtonContext.updateShouldHideBackButton(true)
    }
  }, [])

  const renderBackdrop = useCallback(
    (props) => <BottomSheetBackdrop {...props} disappearsOnIndex={-1} appearsOnIndex={0} />,
    []
  )

  // using our custom background to ensure backgroundColor is white
  const renderBackground = useCallback(
    (props) => (
      <Flex {...props} backgroundColor="white" borderTopRightRadius={10} borderTopLeftRadius={10} />
    ),
    []
  )

  const flatListData = useMemo(
    () => dataForFlatlist(),
    [JSON.stringify(data.analyticsArtistSparklines)]
  )
  if (!flatListData.length) {
    return null
  }

  return (
    <BottomSheet
      ref={bottomSheetRef}
      index={selectedXAxisHighlight ? 0 : -1}
      snapPoints={snapPoints}
      onChange={handleSheetChanges}
      enablePanDownToClose
      backdropComponent={renderBackdrop}
      backgroundComponent={renderBackground}
    >
      <FlatList // Note that this FlatList is from "react-native-gesture-handler" in order for scrolls to work seamlessly on android. BottomSheetFlatlist shipped with BottomSheet is janky on iOS.
        testID="BottomSheetFlatlist"
        ref={flatlistRef}
        data={flatListData}
        renderItem={({ item }) => (
          <CareerHighlightBottomSheetItem year={item.year} highlights={item.highlights} />
        )}
        keyExtractor={(item) => item.year.toString()}
        horizontal
        pagingEnabled
        showsHorizontalScrollIndicator={false}
      />
    </BottomSheet>
  )
}

const careerHighlighsBottomSheetFragment = graphql`
  fragment CareerHighlightBottomSheet_query on Query
  @refetchable(queryName: "CareerHighlighsBottomSheetRefetchQuery")
  @argumentDefinitions(artistID: { type: "String!" }) {
    analyticsArtistSparklines(artistId: $artistID, last: 9) {
      edges {
        node {
          eventDigest
          sparkles
          year
        }
      }
    }
  }
`

/**
 * Prepares the eventDigest and creates a map of each year to the highlight kind
 * @param eventDigest
 * @returns Record<number, Record<CareerHighlightKindValueType, string[]>>
 * @example
 * {
 *   2014: {
 *     "Solo Show": ["ArtMuseum", "AnotherMuseum"],
 *     "Review": ["The Guardian", "The Sun"]
 *   },
 *   2015: {
 *     "Group Show": ["ArtMuseum", "AnotherMuseum"],
 *     "Biennial": ["Documenta"]
 *   },
 * }
 */
export const makeCareerHighlightMap = (
  eventDigest: string
): Record<number, Record<CareerHighlightKindValueType, string[]>> => {
  const minimumYear = new Date().getFullYear() - 8
  const result: Record<number, Record<CareerHighlightKindValueType, string[]>> = {}
  const arr = eventDigest ? eventDigest.split(";") : []
  if (!arr.length) {
    return result
  }
  for (const digest of arr) {
    // eventDigest may begin with the year, this regex extracts the first occurence
    const yearStr = digest.match(/\b(19|20)\d{2}\b/)?.[0]
    if (yearStr && parseInt(yearStr, 10) >= minimumYear) {
      const year = parseInt(yearStr, 10)
      const titleAndBody = digest.replace(`${year}`, "").trim()
      const [title, body] = titleAndBody?.split("@") ?? []
      const kind = CareerHighlightKind[title.trim()]
      const currentYear = result[year] ?? {}
      const updatedYear = { ...currentYear, [kind]: compact([...currentYear[kind], body.trim()]) }
      result[year] = updatedYear
    }
  }
  return result
}

{'fraction_non_alphanumeric': 0.12508591065292096, 'fraction_numerical': 0.007560137457044674, 'mean_word_length': 3.7770847012475377, 'pattern_counts': {'":': 20, '<': 39, '<?xml version=': 0, '>': 75, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6205092', 'n_tokens_mistral': 4630, 'n_tokens_neox': 4386, 'n_words': 981}