currency conversion

pull/15/head
Miguel Mota 6 years ago
parent 40f0b3a3b3
commit e0b3d59d0c

@ -43,6 +43,7 @@ In action
- Charts for coins and global market graphs
- Quick chart date range change
- Fuzzy searching for finding coins
- Currency conversion
- Save and view favorite coins
- Color support
- Help menu
@ -163,7 +164,7 @@ Key|Action
<kbd>2</kbd>|Sort table by *[2]4 hour change*
<kbd>7</kbd>|Sort table by *[7] day change*
<kbd>a</kbd>|Sort table by *[a]vailable supply*
<kbd>c</kbd>|Toggle [c]hart for highlighted coin
<kbd>c</kbd>|Show currency convert menu
<kbd>f</kbd>|Toggle show favorites
<kbd>F</kbd>|Toggle show favorites
<kbd>g</kbd>|Go to first line of page (vim inspired)
@ -231,7 +232,7 @@ You can then configure the actions you want for each key:
left = "previous_page"
right = "next_page"
up = "move_up"
c = "toggle_row_chart"
c = "show_currency_convert_menu"
"ctrl+c" = "quit"
"ctrl+d" = "page_down"
"ctrl+f" = "open_search"
@ -276,6 +277,7 @@ Action|Description
`first_chart_range`|Select first chart date range (e.g. 1H)
`first_page`|Go to first page
`help`|Show help
`hide_currency_convert_menu`|Hide currency convert menu
`last_chart_range`|Select last chart date range (e.g. All Time)
`last_page`|Go to last page
`move_to_page_first_row`|Move to first row on page
@ -296,6 +298,8 @@ Action|Description
`quit`|Quit application
`refresh`|Do a manual refresh on the data
`save`|Save config
`show_currency_convert_menu`|Show currency convert menu
`show_favorites`|Show favorites
`sort_column_1h_change`|Sort table by column *1 hour change*
`sort_column_24h_change`|Sort table by column *24 hour change*
`sort_column_24h_volume`|Sort table by column *24 hour volume*
@ -314,6 +318,7 @@ Action|Description
`sort_right_column`|Sort the column to the right of the highlighted column
`toggle_row_chart`|Toggle the chart for the highlighted row
`toggle_favorite`|Toggle coin as favorite
`toggle_show_currency_convert_menu`|Toggle show currency convert menu
`toggle_show_favorites`|Toggle show favorites
## FAQ
@ -422,6 +427,14 @@ Action|Description
<sup><sub>YTD = Year-to-date<sub></sup>
- Q: How do I change the fiat currency?
- A: Press <kbd>c</kbd> to show the currency convert menu, and press the corresponding key to select that as the fiat currency.
- Q: Which currencies can I convert to?
- A: The supported fiat currencies for conversion are `USD`, `EUR`, `GBP`, `CNY`, `HKD`, `JPY`, `KRW`, `NZD`, `CFH`, `MXN`, `AUD`, `IDR`, `RUB`, and `CAD`. The supported crypto currencies for conversion are `BTC` and `ETH`.
## Development
### Go

@ -2,49 +2,52 @@ package cointop
func actionsMap() map[string]bool {
return map[string]bool{
"first_page": true,
"help": true,
"toggle_show_help": true,
"close_help": true,
"last_page": true,
"move_to_page_first_row": true,
"move_to_page_last_row": true,
"move_to_page_visible_first_row": true,
"move_to_page_visible_last_row": true,
"move_to_page_visible_middle_row": true,
"move_up": true,
"move_down": true,
"next_page": true,
"open_link": true,
"page_down": true,
"page_up": true,
"previous_page": true,
"quit": true,
"refresh": true,
"sort_column_1h_change": true,
"sort_column_24h_change": true,
"sort_column_24h_volume": true,
"sort_column_7d_change": true,
"sort_column_asc": true,
"sort_column_available_supply": true,
"sort_column_desc": true,
"sort_column_last_updated": true,
"sort_column_market_cap": true,
"sort_column_name": true,
"sort_column_price": true,
"sort_column_rank": true,
"sort_column_symbol": true,
"sort_column_total_supply": true,
"sort_left_column": true,
"sort_right_column": true,
"toggle_row_chart": true,
"open_search": true,
"toggle_favorite": true,
"toggle_show_favorites": true,
"previous_chart_range": true,
"next_chart_range": true,
"first_chart_range": true,
"last_chart_range": true,
"first_page": true,
"help": true,
"toggle_show_help": true,
"close_help": true,
"last_page": true,
"move_to_page_first_row": true,
"move_to_page_last_row": true,
"move_to_page_visible_first_row": true,
"move_to_page_visible_last_row": true,
"move_to_page_visible_middle_row": true,
"move_up": true,
"move_down": true,
"next_page": true,
"open_link": true,
"page_down": true,
"page_up": true,
"previous_page": true,
"quit": true,
"refresh": true,
"sort_column_1h_change": true,
"sort_column_24h_change": true,
"sort_column_24h_volume": true,
"sort_column_7d_change": true,
"sort_column_asc": true,
"sort_column_available_supply": true,
"sort_column_desc": true,
"sort_column_last_updated": true,
"sort_column_market_cap": true,
"sort_column_name": true,
"sort_column_price": true,
"sort_column_rank": true,
"sort_column_symbol": true,
"sort_column_total_supply": true,
"sort_left_column": true,
"sort_right_column": true,
"toggle_row_chart": true,
"open_search": true,
"toggle_favorite": true,
"toggle_show_favorites": true,
"previous_chart_range": true,
"next_chart_range": true,
"first_chart_range": true,
"last_chart_range": true,
"toggle_show_currency_convert_menu": true,
"show_currency_convert_menu": true,
"hide_currency_convert_menu": true,
}
}

@ -64,6 +64,9 @@ type Cointop struct {
helpviewname string
helpvisible bool
currencyconversion string
convertmenuview *gocui.View
convertmenuviewname string
convertmenuvisible bool
}
// Instance running cointop instance
@ -136,6 +139,7 @@ func Run() {
statusbarviewname: "statusbar",
searchfieldviewname: "searchfield",
helpviewname: "help",
convertmenuviewname: "convertmenu",
currencyconversion: "USD",
}
Instance = &ct
@ -180,7 +184,7 @@ func Run() {
}
func (ct *Cointop) quit() error {
if ct.helpvisible || ct.searchfieldvisible {
if ct.helpvisible || ct.convertmenuvisible || ct.searchfieldvisible {
return nil
}

@ -0,0 +1,141 @@
package cointop
import (
"fmt"
"sort"
"github.com/miguelmota/cointop/pkg/color"
"github.com/miguelmota/cointop/pkg/pad"
)
//var supportedfiatconversions = []string{"AUD", "BRL", "CAD", "CHF", "CLP", "CNY", "CZK", "DKK", "EUR", "GBP", "HKD", "HUF", "IDR", "ILS", "INR", "JPY", "KRW", "MXN", "MYR", "NOK", "NZD", "PHP", "PKR", "PLN", "RUB", "SEK", "SGD", "THB", "TRY", "TWD", "ZAR"}
var supportedfiatconversions = map[string]string{
"USD": "US Dollar",
"EUR": "Euro",
"GBP": "British Pound",
"CNY": "Chinese Yuan",
"HKD": "Hong Kong Dollar",
"JPY": "Japanese Yen",
"KRW": "South Korean Won",
"NZD": "New Zealand Dollar",
"CFH": "Swiss Franc",
"MXN": "Mexican Peso",
"AUD": "Australian Dollar",
"IDR": "Indonesian Rupiah",
"RUB": "Russian Ruble",
"CAD": "Canadian dollar",
}
var supportedcryptoconversion = map[string]string{
"BTC": "Bitcoin",
"ETH": "Ethereum",
}
var alphanumericcharacters = []rune{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'}
func (ct *Cointop) supportedCurrencyConversions() map[string]string {
all := map[string]string{}
for k, v := range supportedfiatconversions {
all[k] = v
}
for k, v := range supportedcryptoconversion {
all[k] = v
}
return all
}
func (ct *Cointop) supportedFiatCurrencyConversions() map[string]string {
return supportedfiatconversions
}
func (ct *Cointop) supportedCryptoCurrencyConversions() map[string]string {
return supportedfiatconversions
}
func (ct *Cointop) sortedSupportedCurrencyConversions() []string {
currencies := ct.supportedCurrencyConversions()
keys := make([]string, 0, len(currencies))
for k := range currencies {
keys = append(keys, k)
}
sort.Strings(keys)
return keys
}
func (ct *Cointop) toggleConvertMenu() error {
ct.convertmenuvisible = !ct.convertmenuvisible
if ct.convertmenuvisible {
return ct.showConvertMenu()
}
return ct.hideConvertMenu()
}
func (ct *Cointop) updateConvertMenu() {
header := color.GreenBg(fmt.Sprintf(" Currency Conversion %s\n\n", pad.Left("[q] close menu ", ct.maxtablewidth-20, " ")))
helpline := " Press the corresponding key to select currency for conversion\n\n"
cnt := 0
h := ct.viewHeight(ct.convertmenuviewname)
percol := h - 5
cols := make([][]string, percol)
for i := range cols {
cols[i] = make([]string, 20)
}
keys := ct.sortedSupportedCurrencyConversions()
currencies := ct.supportedCurrencyConversions()
for i, key := range keys {
currency := currencies[key]
if cnt%percol == 0 {
cnt = 0
}
item := fmt.Sprintf(" [ %1s ] %4s %-34s", string(alphanumericcharacters[i]), key, color.Yellow(currency))
cols[cnt] = append(cols[cnt], item)
cnt = cnt + 1
}
var body string
for i := 0; i < percol; i++ {
var row string
for j := 0; j < len(cols[i]); j++ {
item := cols[i][j]
row = fmt.Sprintf("%s%s", row, item)
}
body = fmt.Sprintf("%s%s\n", body, row)
}
content := fmt.Sprintf("%s%s%s", header, helpline, body)
ct.update(func() {
ct.convertmenuview.Clear()
ct.convertmenuview.Frame = true
fmt.Fprintln(ct.convertmenuview, content)
})
}
func (ct *Cointop) showConvertMenu() error {
ct.convertmenuvisible = true
ct.updateConvertMenu()
ct.setActiveView(ct.convertmenuviewname)
return nil
}
func (ct *Cointop) hideConvertMenu() error {
ct.convertmenuvisible = false
ct.setViewOnBottom(ct.convertmenuviewname)
ct.setActiveView(ct.tableviewname)
ct.update(func() {
ct.convertmenuview.Clear()
ct.convertmenuview.Frame = false
fmt.Fprintln(ct.convertmenuview, "")
})
return nil
}
func (ct *Cointop) setCurrencyConverstion(convert string) func() error {
return func() error {
ct.currencyconversion = convert
ct.hideConvertMenu()
go ct.refreshAll()
return nil
}
}

@ -25,7 +25,7 @@ func (ct *Cointop) updateHelp() {
header := color.GreenBg(fmt.Sprintf(" Help %s\n\n", pad.Left("[q] close help ", ct.maxtablewidth-10, " ")))
cnt := 0
h := ct.viewHeight("help")
h := ct.viewHeight(ct.helpviewname)
percol := h - 3
cols := make([][]string, percol)
for i := range cols {
@ -63,7 +63,7 @@ func (ct *Cointop) updateHelp() {
func (ct *Cointop) showHelp() error {
ct.helpvisible = true
ct.updateHelp()
ct.setActiveView("help")
ct.setActiveView(ct.helpviewname)
return nil
}

@ -241,6 +241,9 @@ func (ct *Cointop) keybindings(g *gocui.Gui) error {
case "toggle_show_help":
fn = ct.keyfn(ct.toggleHelp)
view = ""
case "show_help":
fn = ct.keyfn(ct.showHelp)
view = ""
case "hide_help":
fn = ct.keyfn(ct.hideHelp)
view = "help"
@ -298,6 +301,15 @@ func (ct *Cointop) keybindings(g *gocui.Gui) error {
fn = ct.keyfn(ct.firstChartRange)
case "last_chart_range":
fn = ct.keyfn(ct.lastChartRange)
case "toggle_show_currency_convert_menu":
fn = ct.keyfn(ct.toggleConvertMenu)
view = ""
case "show_currency_convert_menu":
fn = ct.keyfn(ct.showConvertMenu)
view = ""
case "hide_currency_convert_menu":
fn = ct.keyfn(ct.hideConvertMenu)
view = "convertmenu"
default:
fn = ct.keyfn(ct.noop)
}
@ -310,14 +322,27 @@ func (ct *Cointop) keybindings(g *gocui.Gui) error {
ct.setKeybindingMod(gocui.KeyCtrlZ, gocui.ModNone, ct.keyfn(ct.forceQuit), "")
// searchfield keys
ct.setKeybindingMod(gocui.KeyEnter, gocui.ModNone, ct.keyfn(ct.doSearch), "searchfield")
ct.setKeybindingMod(gocui.KeyEsc, gocui.ModNone, ct.keyfn(ct.cancelSearch), "searchfield")
ct.setKeybindingMod(gocui.KeyEnter, gocui.ModNone, ct.keyfn(ct.doSearch), ct.searchfieldviewname)
ct.setKeybindingMod(gocui.KeyEsc, gocui.ModNone, ct.keyfn(ct.cancelSearch), ct.searchfieldviewname)
// keys to quit help when open
ct.setKeybindingMod(gocui.KeyEsc, gocui.ModNone, ct.keyfn(ct.hideHelp), "help")
ct.setKeybindingMod('q', gocui.ModNone, ct.keyfn(ct.hideHelp), "help")
ct.setKeybindingMod('x', gocui.ModNone, ct.keyfn(ct.hideHelp), "help")
ct.setKeybindingMod('c', gocui.ModNone, ct.keyfn(ct.hideHelp), "help")
ct.setKeybindingMod(gocui.KeyEsc, gocui.ModNone, ct.keyfn(ct.hideHelp), ct.helpviewname)
ct.setKeybindingMod('q', gocui.ModNone, ct.keyfn(ct.hideHelp), ct.helpviewname)
ct.setKeybindingMod('x', gocui.ModNone, ct.keyfn(ct.hideHelp), ct.helpviewname)
ct.setKeybindingMod('c', gocui.ModNone, ct.keyfn(ct.hideHelp), ct.helpviewname)
// keys to quit convert menu when open
ct.setKeybindingMod(gocui.KeyEsc, gocui.ModNone, ct.keyfn(ct.hideConvertMenu), ct.convertmenuviewname)
ct.setKeybindingMod('q', gocui.ModNone, ct.keyfn(ct.hideConvertMenu), ct.convertmenuviewname)
ct.setKeybindingMod('x', gocui.ModNone, ct.keyfn(ct.hideConvertMenu), ct.convertmenuviewname)
// character key press to select option
// TODO: use scrolling table
keys := ct.sortedSupportedCurrencyConversions()
for i, k := range keys {
ct.setKeybindingMod(rune(alphanumericcharacters[i]), gocui.ModNone, ct.keyfn(ct.setCurrencyConverstion(k)), ct.convertmenuviewname)
}
return nil
}

@ -113,12 +113,23 @@ func (ct *Cointop) layout(g *gocui.Gui) error {
ct.helpview.Frame = false
ct.helpview.BgColor = gocui.ColorBlack
ct.helpview.FgColor = gocui.ColorWhite
}
if v, err := g.SetView(ct.convertmenuviewname, 1, 1, ct.maxtablewidth-2, maxY-1); err != nil {
if err != gocui.ErrUnknownView {
return err
}
ct.convertmenuview = v
ct.convertmenuview.Frame = false
ct.convertmenuview.BgColor = gocui.ColorBlack
ct.convertmenuview.FgColor = gocui.ColorWhite
// run only once on init.
// this bit of code should be at the bottom
ct.g = g
g.SetViewOnBottom(ct.searchfieldviewname) // hide
g.SetViewOnBottom(ct.helpviewname) // hide
g.SetViewOnBottom(ct.convertmenuviewname) // hide
ct.setActiveView(ct.tableviewname)
ct.intervalFetchData()
}

@ -32,7 +32,8 @@ func defaultShortcuts() map[string]string {
"2": "sort_column_24h_change",
"7": "sort_column_7d_change",
"a": "sort_column_available_supply",
"c": "toggle_row_chart",
"c": "show_currency_convert_menu",
"C": "show_currency_convert_menu",
"f": "toggle_show_favorites",
"F": "toggle_show_favorites",
"g": "move_to_page_first_row",

@ -11,7 +11,7 @@ func (ct *Cointop) updateStatusbar(s string) {
ct.statusbarview.Clear()
currpage := ct.currentDisplayPage()
totalpages := ct.totalPages()
base := fmt.Sprintf("%sQuit %sHelp %sChart %sRange %sSearch", "[Q]", "[?]", "[Enter]", "[[ ]]", "[/]")
base := fmt.Sprintf("%sQuit %sHelp %sChart %sRange %sSearch %sConvert", "[Q]", "[?]", "[Enter]", "[[ ]]", "[/]", "[C]")
fmt.Fprintln(ct.statusbarview, pad.Right(fmt.Sprintf("%v %sPage %v/%v %s", base, "[← →]", currpage, totalpages, s), ct.maxtablewidth, " "))
})
}

@ -142,6 +142,9 @@ func (ct *Cointop) updateTable() error {
if end < 0 {
end = 0
}
if start >= end {
return nil
}
if end > 0 {
sliced = allcoins[start:end]
}

@ -2,6 +2,7 @@ package api
import (
"strconv"
"sync"
apitypes "github.com/miguelmota/cointop/pkg/api/types"
cmc "github.com/miguelmota/cointop/pkg/cmc"
@ -16,11 +17,15 @@ func New() *Service {
return &Service{}
}
// GetAllCoinData gets all coin data
func (s *Service) GetAllCoinData(convert string) (map[string]apitypes.Coin, error) {
// https://api.coinmarketcap.com/v1/ticker/?start=0&limit=0
func getLimitedCoinData(convert string, offset int) (map[string]apitypes.Coin, error) {
ret := make(map[string]apitypes.Coin)
max := 100
coins, err := cmc.Tickers(&cmc.TickersOptions{
Convert: convert,
Start: max * offset,
Limit: max,
})
if err != nil {
return ret, err
@ -46,6 +51,27 @@ func (s *Service) GetAllCoinData(convert string) (map[string]apitypes.Coin, erro
return ret, nil
}
// GetAllCoinData gets all coin data
func (s *Service) GetAllCoinData(convert string) (map[string]apitypes.Coin, error) {
var wg sync.WaitGroup
ret := make(map[string]apitypes.Coin)
for i := 0; i < 10; i++ {
wg.Add(1)
go func(j int) {
coins, err := getLimitedCoinData(convert, j)
if err != nil {
return
}
for k, v := range coins {
ret[k] = v
}
defer wg.Done()
}(i)
}
wg.Wait()
return ret, nil
}
// GetCoinGraphData gets coin graph data
func (s *Service) GetCoinGraphData(coin string, start int64, end int64) (apitypes.CoinGraph, error) {
ret := apitypes.CoinGraph{}

Loading…
Cancel
Save