Skip to content

Commit

Permalink
Add stock price countdown
Browse files Browse the repository at this point in the history
  • Loading branch information
sgobotta committed Mar 11, 2024
1 parent df70d90 commit ef0ce48
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 21 deletions.
99 changes: 94 additions & 5 deletions lib/ex_finance_web/live/public/cedears/cedears_live/show.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ defmodule ExFinanceWeb.Public.CedearsLive.Show do

@fetch_interval :timer.seconds(30)

# ----------------------------------------------------------------------------
# Lifecycle
#

@impl true
def mount(_params, _session, socket) do
:ok = Currencies.subscribe_currencies()
Expand All @@ -32,14 +36,21 @@ defmodule ExFinanceWeb.Public.CedearsLive.Show do
|> assign_cedear_price_calc(cedear_price_calc)
|> assign_stock_price(nil)
|> assign_average_stock_price(Decimal.new(0))
|> assign_stock_price_changes()}
|> assign_stock_price_changes()
|> assign_timeleft_to_next_update(nil)
|> assign_countdown_ref(nil)}
end

# ----------------------------------------------------------------------------
# Handlers
#

@impl true
def handle_params(%{"id" => id}, _uri, socket) do
cedear = Instruments.get_cedear!(id)

{:ok, stock_price_worker_pid, maybe_stock_price} =
{:ok, stock_price_worker_pid,
{maybe_stock_price, maybe_millis_to_next_update}} =
StockPrices.subscribe_stock_price(cedear.symbol)

socket =
Expand All @@ -59,13 +70,15 @@ defmodule ExFinanceWeb.Public.CedearsLive.Show do
end

schedule_heartbeat(stock_price_worker_pid)
maybe_schedule_countdown(maybe_millis_to_next_update)

{:noreply,
socket
|> assign_stock_price(maybe_stock_price)
|> assign(:page_title, cedear.name)
|> assign(:section_title, gettext("%{cedear} price", cedear: cedear.name))
|> assign_cedear(cedear)}
|> assign_cedear(cedear)
|> assign_timeleft_to_next_update(maybe_millis_to_next_update)}
end

@impl true
Expand All @@ -81,7 +94,8 @@ defmodule ExFinanceWeb.Public.CedearsLive.Show do

@impl true
def handle_info(
{:new_stock_price, %ExFinnhub.StockPrice{} = stock_price},
{:new_stock_price,
{%ExFinnhub.StockPrice{} = stock_price, maybe_millis_to_next_update}},
socket
) do
stock_price_changes =
Expand All @@ -91,10 +105,13 @@ defmodule ExFinanceWeb.Public.CedearsLive.Show do
stock_price
)

maybe_schedule_countdown(maybe_millis_to_next_update)

{:noreply,
socket
|> assign_stock_price(stock_price)
|> assign_stock_price_changes(stock_price_changes)}
|> assign_stock_price_changes(stock_price_changes)
|> assign_timeleft_to_next_update(maybe_millis_to_next_update)}
end

@impl true
Expand All @@ -104,6 +121,37 @@ defmodule ExFinanceWeb.Public.CedearsLive.Show do
{:noreply, socket}
end

@impl true
def handle_info(
{:timeleft_to_next_update, 0},
%{assigns: %{countdown_ref: countdown_ref}} = socket
) do
if countdown_ref != nil do
Process.cancel_timer(countdown_ref)
end

{:noreply, socket}
end

def handle_info({:timeleft_to_next_update, nil}, socket),
do: {:noreply, socket}

def handle_info(
{:timeleft_to_next_update, timeleft},
%{assigns: %{countdown_ref: countdown_ref}} = socket
) do
if countdown_ref != nil do
Process.cancel_timer(countdown_ref)
end

countdown_ref = maybe_schedule_countdown(timeleft)

{:noreply,
socket
|> assign_timeleft_to_next_update(timeleft)
|> assign_countdown_ref(countdown_ref)}
end

@impl true
def handle_event(
"change_cedear_price",
Expand Down Expand Up @@ -134,6 +182,10 @@ defmodule ExFinanceWeb.Public.CedearsLive.Show do
|> assign_form(changeset)}
end

# ----------------------------------------------------------------------------
# Assignment functions
#

defp assign_cedear(socket, cedear),
do: assign(socket, :cedear, cedear)

Expand Down Expand Up @@ -168,6 +220,18 @@ defmodule ExFinanceWeb.Public.CedearsLive.Show do
defp assign_stock_price_changes(socket, stock_price_changes),
do: assign(socket, :stock_price_changes, stock_price_changes)

defp assign_timeleft_to_next_update(socket, timeleft_to_next_update),
do: assign(socket, :timeleft_to_next_update, timeleft_to_next_update)

@spec assign_countdown_ref(Phoenix.LiveView.Socket.t(), reference() | nil) ::
Phoenix.LiveView.Socket.t()
defp assign_countdown_ref(socket, countdown_ref),
do: assign(socket, :countdown_ref, countdown_ref)

# ----------------------------------------------------------------------------
# Render functions
#

defp render_country(%Cedear{country: country}), do: String.upcase(country)

defp render_ratio(%Cedear{ratio: ratio}), do: "1:#{Decimal.round(ratio)}"
Expand Down Expand Up @@ -215,6 +279,13 @@ defmodule ExFinanceWeb.Public.CedearsLive.Show do
"#{fair_cedear_price}"
end

@spec render_timeleft(number()) :: integer()
def render_timeleft(milliseconds), do: round(milliseconds / 1000)

# ----------------------------------------------------------------------------
# Colors functions
#

defp get_color_by_percentage_change(%{change_percentage: %Decimal{coef: 0}}),
do: "gray"

Expand All @@ -231,7 +302,25 @@ defmodule ExFinanceWeb.Public.CedearsLive.Show do

defp get_color_by_price_change(%{change_price: %Decimal{sign: -1}}), do: "red"

# ----------------------------------------------------------------------------
# Timers functions
#

@spec schedule_heartbeat(pid()) :: reference()
defp schedule_heartbeat(pid),
do: Process.send_after(self(), {:heartbeat, pid}, @fetch_interval)

@spec maybe_schedule_countdown(non_neg_integer() | nil) :: reference() | nil
defp maybe_schedule_countdown(nil), do: nil

defp maybe_schedule_countdown(millis) do
next_tick = :timer.seconds(1)

_timeleft_ref =
Process.send_after(
self(),
{:timeleft_to_next_update, millis - next_tick},
next_tick
)
end
end
52 changes: 36 additions & 16 deletions lib/ex_finance_web/live/public/cedears/cedears_live/show.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -51,22 +51,42 @@
</ul>
<div class="flex flex-row w-full py-2">
<div>
<h3 class="mt-2 text-md font-bold text-indigo-500 text-left">
<%= gettext("Stock price") %>
</h3>
<p class="text-base text-green-400 text-left cursor-text">
<span class="text-green-400">USD</span> <%= render_stock_price(
@stock_price_changes
) %>
</p>
<h3 class="mt-2 text-md font-bold text-indigo-500 text-left">
<%= gettext("Fair cedear price") %>
</h3>
<p class="text-base text-sky-400 text-left cursor-text">
<span class="text-sky-400">ARS</span> <%= render_fair_cedear_price(
@stock_price_changes
) %>
</p>
<div>
<h3 class="mt-2 text-md font-bold text-indigo-500 text-left">
<%= gettext("Stock price") %>
</h3>
<div class="grid grid-cols-3">
<div class="col-span-2">
<p class="text-base text-left">
<span class="text-green-400 cursor-text">
USD <%= render_stock_price(@stock_price_changes) %>
</span>
</p>
</div>
<div class="
col-span-1 justify-self-center self-center
block h-6 min-w-8 w-8 text-center
cursor-default
bg-indigo-400/75 text-white rounded-sm
">
<span class="
text-[0.65rem] font-medium leading-6
">
<%= render_timeleft(@timeleft_to_next_update) %>
</span>
</div>
</div>
</div>
<div>
<h3 class="mt-2 text-md font-bold text-indigo-500 text-left">
<%= gettext("Fair cedear price") %>
</h3>
<p class="text-base text-sky-400 text-left">
<span class="text-sky-400 cursor-text">
ARS <%= render_fair_cedear_price(@stock_price_changes) %>
</span>
</p>
</div>
<button class="
cursor-default
text-sm mt-6 px-4 py-2
Expand Down

0 comments on commit ef0ce48

Please sign in to comment.