module Views exposing (view) import Browser import Colors import Consts import Core import Element exposing (Element) import Element.Background as Background import Element.Border as Border import Element.Font as Font import Element.Input as Input import Element.Keyed as Keyed import Html import Html.Attributes import Json.Encode as Encode import Time import TimeUtils import Twitch view : Core.FullModel -> Browser.Document Core.Msg view model = let element = case model of Core.Unloaded _ _ _ -> Element.el [ Element.padding 10, Element.centerX ] spinner Core.Loaded m -> viewContent m in { title = title model , body = [ Element.layout [ Font.color Colors.blackFont , Font.size Consts.normalFontSize , Font.family [ Font.typeface "Cantarell" ] ] (Element.column [ Element.width Element.fill ] [ topBar, element ] ) ] } title : Core.FullModel -> String title model = case model of Core.Unloaded _ _ _ -> Consts.url Core.Loaded m -> case m.page of Core.Home -> Consts.url Core.Playlist p -> Consts.url ++ " - " ++ p.name Core.Video p v -> Consts.url ++ " - " ++ p.name ++ " - " ++ v.name viewContent : Core.Model -> Element Core.Msg viewContent model = case model.page of Core.Home -> playlistsView model.device model.playlists Core.Playlist playlist -> videoMiniaturesView model.device model.zone playlist Core.Video playlist video -> videoView model.device model.zone playlist video topBar : Element Core.Msg topBar = Element.row [ Element.width Element.fill , Background.color Colors.primary , Font.color Colors.white , Font.size Consts.homeFontSize ] [ homeButton ] homeButton : Element Core.Msg homeButton = Input.button [ Element.padding Consts.homePadding , Element.height Element.fill , Element.mouseOver [ Background.color Colors.primaryOver ] , Font.bold ] { label = Element.text Consts.name , onPress = Just Core.HomeClicked } playlistsView : Element.Device -> List Twitch.Playlist -> Element Core.Msg playlistsView device playlists = let empty = Element.el [ Element.width Element.fill ] Element.none views = List.map playlistView playlists grouped = group (numberOfVideosPerRow device) views rows = grouped |> List.map (\x -> List.map (Maybe.withDefault empty) x) |> List.map (Element.row [ Element.spacing 10, Element.width Element.fill ]) final = Element.column [ Element.padding 10, Element.spacing 10, Element.width Element.fill ] rows in final playlistView : Twitch.Playlist -> Element Core.Msg playlistView playlist = let image = Element.image [ Element.width Element.fill , Element.height Element.fill , Element.inFront inFront ] { description = "", src = Twitch.playlistMiniatureUrl playlist } length = List.length playlist.videos label = String.fromInt length ++ " video" ++ (if length > 1 then "s" else "" ) inFront = Element.text label |> Element.el [ Background.color Colors.greyBackground , Border.rounded 5 , Element.padding 5 , Font.color Colors.white ] |> Element.el [ Element.alignBottom , Element.alignRight , Element.padding 5 ] display = Element.column [ Element.width Element.fill, Element.spacing 10 ] [ image , Element.paragraph [ Font.bold, Font.size Consts.titleFontSize ] [ Element.text playlist.name ] ] button = Input.button [ Element.width Element.fill, Element.alignTop ] { label = display , onPress = Just (Core.PlaylistClicked playlist.url) } in button videoMiniaturesView : Element.Device -> Time.Zone -> Twitch.PlaylistWithVideos -> Element Core.Msg videoMiniaturesView device zone playlist = let empty = Element.el [ Element.width Element.fill ] Element.none views = List.map (videoMiniatureView zone playlist) playlist.videos grouped = group (numberOfVideosPerRow device) views rows = grouped |> List.map (\x -> List.map (Maybe.withDefault empty) x) |> List.map (Element.row [ Element.spacing 10, Element.width Element.fill ]) final = Element.column [ Element.padding 10, Element.spacing 10, Element.width Element.fill ] rows in final videoMiniature : Twitch.Video -> Element Core.Msg videoMiniature video = let inFront = Element.text label |> Element.el [ Background.color Colors.greyBackground , Border.rounded 5 , Element.padding 5 , Font.color Colors.white ] |> Element.el [ Element.alignBottom , Element.alignRight , Element.padding 5 ] image = Element.image [ Element.width Element.fill , Element.height Element.fill , Element.inFront inFront ] { description = "", src = Twitch.videoMiniatureUrl video } label = formatTime video.duration in image videoMiniatureView : Time.Zone -> Twitch.PlaylistWithVideos -> Twitch.Video -> Element Core.Msg videoMiniatureView zone playlist video = let display = Element.column [ Element.width Element.fill, Element.spacing 10 ] [ videoMiniature video , videoDescription zone video ] button = Input.button [ Element.width Element.fill, Element.alignTop ] { label = display , onPress = Just (Core.VideoClicked playlist video) } in button videoInList : Time.Zone -> Twitch.PlaylistWithVideos -> Twitch.Video -> Twitch.Video -> Element Core.Msg videoInList zone playlist activeVideo video = let ( msg, attr ) = if video == activeVideo then ( Nothing , [ Element.width Element.fill , Background.color Colors.selected , Border.color Colors.primary , Border.width 2 ] ) else ( Just (Core.VideoClicked playlist video), [ Element.width Element.fill ] ) label = Element.row [ Element.width Element.fill, Element.spacing 10 ] [ Element.el [ Element.width (Element.fillPortion 2) ] (videoMiniature video) , Element.el [ Element.width (Element.fillPortion 3), Element.paddingXY 0 10, Element.alignTop ] (videoDescription zone video) ] in Input.button attr { label = label, onPress = msg } videoView : Element.Device -> Time.Zone -> Twitch.PlaylistWithVideos -> Twitch.Video -> Element Core.Msg videoView device zone playlist video = let ( builder, contentPadding ) = case device.class of Element.Phone -> ( Element.column [ Element.width Element.fill, Element.spacing 10 ] , Element.paddingXY 10 0 ) _ -> ( Element.row [ Element.padding 10, Element.width Element.fill, Element.spacing 10 ] , Element.padding 0 ) in builder [ Element.column [ Element.width (Element.fillPortion 2) , Element.spacing 10 , Element.alignTop ] [ Keyed.el [ Element.width Element.fill , Element.height (Element.px 0) , Element.htmlAttribute (Html.Attributes.style "padding-top" "56.25%") , Element.htmlAttribute (Html.Attributes.style "position" "relative") ] ( video.url , Element.html (Html.video [ Html.Attributes.id (Twitch.videoId video) , Html.Attributes.class "video-js" , Html.Attributes.class "vjs-default-skin" , Html.Attributes.class "wf" , Html.Attributes.property "data-setup" (Encode.string "{\"fluid\": true}") , Html.Attributes.style "position" "absolute" , Html.Attributes.style "top" "0" , Html.Attributes.style "height" "100%" , Html.Attributes.controls True , Html.Attributes.autoplay True ] [] ) ) , Element.paragraph [ Font.size Consts.homeFontSize , Font.bold , contentPadding ] [ Element.text video.name ] , case video.date of Just date -> Element.paragraph [ contentPadding, Font.size Consts.titleFontSize ] [ Element.text ("Diffusé le " ++ formatDate zone date) ] _ -> Element.none ] , Element.column [ contentPadding , Element.alignTop , Element.spacing 10 , Element.width (Element.fillPortion 1) ] (List.map (videoInList zone playlist video) playlist.videos) ] videoDescription : Time.Zone -> Twitch.Video -> Element Core.Msg videoDescription zone video = Element.column [ Element.spacing 10 ] [ Element.paragraph [ Font.bold , Font.size Consts.titleFontSize ] [ Element.text video.name ] , case video.date of Just date -> Element.paragraph [ Font.color Colors.greyFont ] [ Element.text ("Diffusé le " ++ formatDate zone date) ] _ -> Element.none ] formatDate : Time.Zone -> Time.Posix -> String formatDate zone time = let day = Time.toDay zone time |> String.fromInt |> TimeUtils.pad2 month = Time.toMonth zone time |> TimeUtils.monthToString year = Time.toYear zone time |> String.fromInt |> TimeUtils.pad2 hours = Time.toHour zone time |> String.fromInt minutes = Time.toMinute zone time |> String.fromInt |> TimeUtils.pad2 in day ++ "/" ++ month ++ "/" ++ year ++ " à " ++ hours ++ "h" ++ minutes formatTime : Int -> String formatTime time = let hours = toHours time minutes = toMinutes time seconds = toSeconds time hoursString = String.fromInt hours minutesString = if minutes < 10 then "0" ++ String.fromInt minutes else String.fromInt minutes secondsString = if seconds < 10 then "0" ++ String.fromInt seconds else String.fromInt seconds in hoursString ++ ":" ++ minutesString ++ ":" ++ secondsString toHours : Int -> Int toHours i = i // 3600 toMinutes : Int -> Int toMinutes i = modBy 3600 i // 60 toSeconds : Int -> Int toSeconds i = modBy 60 i numberOfVideosPerRow : Element.Device -> Int numberOfVideosPerRow device = case device.class of Element.Phone -> 1 Element.Tablet -> 3 _ -> 4 group : Int -> List a -> List (List (Maybe a)) group size list = let grouped = List.map (List.map Just) (groupAux size list []) groupedRev = List.reverse grouped ( firstFixed, tail ) = case groupedRev of h :: t -> ( List.concat [ h, List.repeat (size - List.length h) Nothing ], t ) [] -> ( [], [] ) fixed = (firstFixed :: tail) |> List.reverse in fixed groupAux : Int -> List a -> List (List a) -> List (List a) groupAux size list acc = if List.isEmpty list then List.reverse acc else let groupHead = List.take size list groupTail = List.drop size list in groupAux size groupTail (groupHead :: acc) spinner : Element Core.Msg spinner = Element.html (Html.div [ Html.Attributes.class "lds-spinner" ] [ Html.div [] [] , Html.div [] [] , Html.div [] [] , Html.div [] [] , Html.div [] [] , Html.div [] [] , Html.div [] [] , Html.div [] [] , Html.div [] [] , Html.div [] [] , Html.div [] [] , Html.div [] [] ] )