package tui import ( "context" "github.com/charmbracelet/bubbles/table" "github.com/charmbracelet/bubbles/viewport" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/glamour" "olexsmir.xyz/smutok/internal/store" ) type Syncer interface { Sync(ctx context.Context) error } type AppState int const ( ArticlesView AppState = iota FeedsView ReadingView ) type Model struct { ctx context.Context state AppState isQutting bool showErr bool err error syncer Syncer store *store.Sqlite articles []store.Article glamur *glamour.TermRenderer table table.Model // feeds, articles feed viewport viewport.Model // article content reader } func NewModel( ctx context.Context, syncer Syncer, store *store.Sqlite, ) *Model { tbl := table.New(table.WithFocused(true)) vp := viewport.New(0, 0) return &Model{ ctx: ctx, syncer: syncer, store: store, glamur: &glamour.TermRenderer{}, table: tbl, viewport: vp, state: ArticlesView, isQutting: false, showErr: false, err: nil, articles: nil, } } func (m *Model) Init() tea.Cmd { return tea.Batch( tea.SetWindowTitle("smutok"), m.fetchArticles(store.ArticleAll), ) } func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var cmd tea.Cmd switch msg := msg.(type) { case errMsg: m.err = msg m.showErr = true return m, nil case tea.WindowSizeMsg: m.table.SetHeight(msg.Height) m.table.SetWidth(msg.Width - 2) m.viewport.Height = msg.Height m.viewport.Width = msg.Width return m, nil case fetchedArticles: m.articles = msg m.setupTableWithArticles() return m, nil case tea.KeyMsg: // page specific keys switch m.state { case ArticlesView, FeedsView: m.table, cmd = m.table.Update(msg) case ReadingView: m.viewport, cmd = m.viewport.Update(msg) } // global keys switch msg.String() { case "q": m.isQutting = true return m, tea.Quit } } return m, cmd } func (m *Model) View() string { if m.isQutting { return "" } var out string switch m.state { case ArticlesView, FeedsView: out = m.table.View() case ReadingView: out = m.viewport.View() } return out }