Skip to content

Commit

Permalink
Implement image viewer (#45)
Browse files Browse the repository at this point in the history
* Implement image viewer
  • Loading branch information
zMoooooritz committed Dec 23, 2023
1 parent 12e5bf9 commit a7a0b88
Show file tree
Hide file tree
Showing 7 changed files with 354 additions and 38 deletions.
32 changes: 30 additions & 2 deletions pkg/http/http.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package http

import (
"image"
"io"
"net/http"
"time"
Expand All @@ -11,11 +12,13 @@ const (
agentName string = "nachrichten-agent"
)

func FetchURL(url string) ([]byte, error) {
client := http.Client{
var (
client http.Client = http.Client{
Timeout: time.Second * httpTimeout,
}
)

func FetchURL(url string) ([]byte, error) {
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return nil, err
Expand All @@ -39,3 +42,28 @@ func FetchURL(url string) ([]byte, error) {

return body, nil
}

func LoadImage(url string) (image.Image, error) {
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return nil, err
}

req.Header.Set("User-Agent", agentName)

res, err := client.Do(req)
if err != nil {
return nil, err
}

if res.Body != nil {
defer res.Body.Close()
}

img, _, err := image.Decode(res.Body)
if err != nil {
return nil, err
}

return img, nil
}
145 changes: 145 additions & 0 deletions pkg/tui/image.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package tui

import (
"fmt"
"image"
"strings"
"time"

"github.com/charmbracelet/bubbles/viewport"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/zMoooooritz/nachrichten/pkg/config"
"github.com/zMoooooritz/nachrichten/pkg/util"
)

type ImageViewer struct {
style config.Style
isActive bool
isFocused bool
isFullScreen bool
toplineText string
dateText string
viewport viewport.Model
image image.Image
}

func NewImageViewer(s config.Style) ImageViewer {
return ImageViewer{
style: s,
isActive: false,
viewport: viewport.New(0, 0),
image: image.Rect(0, 0, 1, 1),
}
}

func (i *ImageViewer) SetActive(isActive bool) {
i.isActive = isActive
}

func (i *ImageViewer) IsActive() bool {
return i.isActive
}

func (i *ImageViewer) SetFocused(isFocused bool) {
i.isFocused = isFocused
}

func (i *ImageViewer) IsFocused() bool {
return i.isFocused
}

func (i *ImageViewer) SetFullScreen(isFullScreen bool) {
i.isFullScreen = isFullScreen
}

func (i *ImageViewer) IsFullScreen() bool {
return i.isFullScreen
}

func (i *ImageViewer) SetDims(w, h int) {
i.viewport.Width = w
i.viewport.Height = h - lipgloss.Height(i.headerView()) - lipgloss.Height(i.footerView())
i.viewport.YPosition = lipgloss.Height(i.headerView())
i.PushImageToViewer()
}

func (i *ImageViewer) SetImage(img image.Image) {
i.image = img
i.PushImageToViewer()
}

func (i *ImageViewer) PushImageToViewer() {
w := i.viewport.Width - 4
h := i.viewport.Height - 2
image := util.ImageToAscii(i.image, uint(w), uint(h), true)

strRepr := ""
for _, row := range image {
rowRepr := ""
for _, char := range row {
rowRepr += char
}
strRepr += lipgloss.PlaceHorizontal(i.viewport.Width, lipgloss.Center, rowRepr) + "\n"
}

strRepr = lipgloss.PlaceVertical(h, lipgloss.Center, strRepr)
i.viewport.SetContent(strRepr)
}

func (i *ImageViewer) SetHeaderContent(topline string, date time.Time) {
i.toplineText = topline
i.dateText = date.Format(germanDateFormat)
}

func (i ImageViewer) Init() tea.Cmd {
return nil
}

func (i ImageViewer) Update(msg tea.Msg) (ImageViewer, tea.Cmd) {
var cmd tea.Cmd
i.viewport, cmd = i.viewport.Update(msg)
return i, tea.Batch(cmd)
}

func (i ImageViewer) View() string {
if !i.isActive {
return ""
}
return fmt.Sprintf("%s\n%s\n%s", i.headerView(), i.viewport.View(), i.footerView())
}

func (i ImageViewer) headerView() string {
titleStyle := i.style.ReaderTitleInactiveStyle
lineStyle := i.style.InactiveStyle
dateStyle := i.style.ReaderInfoInactiveStyle
fillCharacter := config.SingleFillCharacter
if i.isFocused || i.isFullScreen {
titleStyle = i.style.ReaderTitleActiveStyle
lineStyle = i.style.ActiveStyle
dateStyle = i.style.ReaderInfoActiveStyle
fillCharacter = config.DoubleFillCharacter
}

title := titleStyle.Render(i.toplineText)
date := dateStyle.Render(i.dateText)
line := lineStyle.Render(strings.Repeat(fillCharacter, util.Max(0, i.viewport.Width-lipgloss.Width(title)-lipgloss.Width(date))))

return lipgloss.JoinHorizontal(lipgloss.Center, title, line, date)
}

func (i ImageViewer) footerView() string {
infoStyle := i.style.ReaderInfoInactiveStyle
lineStyle := i.style.InactiveStyle
fillCharacter := config.SingleFillCharacter
if i.isFocused || i.isFullScreen {
infoStyle = i.style.ReaderInfoActiveStyle
lineStyle = i.style.ActiveStyle
fillCharacter = config.DoubleFillCharacter
}

info := infoStyle.Render(fmt.Sprintf("%3.f%%", i.viewport.ScrollPercent()*100))
line := lineStyle.Render(strings.Repeat(fillCharacter, util.Max(0, i.viewport.Width-lipgloss.Width(info))))

return lipgloss.JoinHorizontal(lipgloss.Center, line, info)
}
6 changes: 6 additions & 0 deletions pkg/tui/keybinds.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ type KeyMap struct {
full key.Binding
start key.Binding
end key.Binding
image key.Binding
open key.Binding
video key.Binding
shortNews key.Binding
Expand Down Expand Up @@ -61,6 +62,10 @@ func GetKeyMap() KeyMap {
key.WithKeys("G", "end"),
key.WithHelp("G/end", "end"),
),
image: key.NewBinding(
key.WithKeys("i"),
key.WithHelp("i", "image"),
),
open: key.NewBinding(
key.WithKeys("o"),
key.WithHelp("o", "open"),
Expand Down Expand Up @@ -95,6 +100,7 @@ func (k KeyMap) FullHelp() [][]key.Binding {
{k.full},
{k.start},
{k.end},
{k.image},
{k.open},
{k.video},
{k.shortNews},
Expand Down
17 changes: 13 additions & 4 deletions pkg/tui/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,9 @@ import (
"github.com/zMoooooritz/nachrichten/pkg/util"
)

const (
germanDateFormat string = "15:04 02.01.06"
)

type Reader struct {
style config.Style
isActive bool
isFocused bool
isFullScreen bool
toplineText string
Expand All @@ -28,6 +25,7 @@ type Reader struct {
func NewReader(s config.Style) Reader {
return Reader{
style: s,
isActive: true,
viewport: viewport.New(0, 0),
}
}
Expand All @@ -40,6 +38,14 @@ func (r *Reader) GotoBottom() {
r.viewport.GotoBottom()
}

func (r *Reader) SetActive(isActive bool) {
r.isActive = isActive
}

func (r *Reader) IsActive() bool {
return r.isActive
}

func (r *Reader) SetFocused(isFocused bool) {
r.isFocused = isFocused
}
Expand Down Expand Up @@ -83,6 +89,9 @@ func (r Reader) Update(msg tea.Msg) (Reader, tea.Cmd) {
}

func (r Reader) View() string {
if !r.isActive {
return ""
}
return fmt.Sprintf("%s\n%s\n%s", r.headerView(), r.viewport.View(), r.footerView())
}

Expand Down
4 changes: 4 additions & 0 deletions pkg/tui/selector.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ func (s *Selector) PrevList() {
s.activeListIndex = (len(s.lists) + s.activeListIndex - 1) % len(s.lists)
}

func (s *Selector) IsFocused() bool {
return s.isFocused
}

func (s *Selector) SetFocused(isFocused bool) {
s.isFocused = isFocused
}
Expand Down

0 comments on commit a7a0b88

Please sign in to comment.