Compare commits

...

5 Commits

6 changed files with 152 additions and 56 deletions

View File

@@ -19,7 +19,7 @@ If you're on arch you can run
pacman -S blueprint-compiler crystal shards libadwaita libshumate georclue pacman -S blueprint-compiler crystal shards libadwaita libshumate georclue
``` ```
Then you gotta get an API key from Yelp and put it in the file `api_key` at the root of the project. Note: the `api_key1` file cannot contain a trailing newline. Then you gotta get an API key from Yelp and put it in a file at `~/.config/wince/api_key`
Then you can run the program with Then you can run the program with

View File

@@ -43,6 +43,14 @@ Adw.ApplicationWindow mainWindow {
label: "Wince is powered by Yelp"; label: "Wince is powered by Yelp";
} }
Gtk.Label configNotFoundText {
halign: center;
valign: center;
vexpand: true;
visible: false;
label: "API key not found. You must place your yelp api key in ~/.config/wince/api_key";
}
Adw.Leaflet leaflet { Adw.Leaflet leaflet {
can-navigate-forward: false; can-navigate-forward: false;
can-navigate-back: true; can-navigate-back: true;

View File

@@ -1,10 +1,74 @@
require "http/client" require "http/client"
require "io" require "io"
require "json"
require "../utils/utils.cr"
module Wince::Yelp module Wince::Yelp
extend self extend self
@@token : String = {{ read_file("./api_key") }} class SearchResponse
include JSON::Serializable
property businesses : Array(Business)?
property error : Error?
end
class Error
include JSON::Serializable
property code : String
property description : String
end
class Business
include JSON::Serializable
property id : String
property name : String
property rating : Float32
property distance : Float32
end
class DetailsResponse
include JSON::Serializable
property error : Error?
property name : String
property price : String?
property display_phone : String?
property location : Location
property coordinates : Coordinates
property url : String
property hours : Array(Hours)
def is_open
hours[0].is_open_now
end
end
class Coordinates
include JSON::Serializable
property latitude : Float32
property longitude : Float32
end
class Location
include JSON::Serializable
property display_address : Array(String)
end
class Hours
include JSON::Serializable
property open : Array(Open)
property is_open_now : Bool
end
class Open
include JSON::Serializable
property is_overnight : Bool
@[JSON::Field(key: "start")]
property open : String
@[JSON::Field(key: "end")]
property close : String
property day : Int32
end
def search_businesses(search : String, location : String) def search_businesses(search : String, location : String)
@@ -19,8 +83,10 @@ module Wince::Yelp
path: "/v3/businesses/search", path: "/v3/businesses/search",
query: params query: params
) )
headers = HTTP::Headers{ "Authorization" => "Bearer " + @@token } headers = HTTP::Headers{ "Authorization" => "Bearer " + Utils.api_key }
response = HTTP::Client.get(uri, headers) response = HTTP::Client.get(uri, headers)
{response.status_code, SearchResponse.from_json(response.body)}
end end
def get_business_info(id : String) def get_business_info(id : String)
@@ -30,8 +96,10 @@ module Wince::Yelp
host: "api.yelp.com", host: "api.yelp.com",
path: "/v3/businesses/#{id}", path: "/v3/businesses/#{id}",
) )
headers = HTTP::Headers{ "Authorization" => "Bearer " + @@token } headers = HTTP::Headers{ "Authorization" => "Bearer " + Utils.api_key }
response = HTTP::Client.get(uri, headers) response = HTTP::Client.get(uri, headers)
{response.status_code, DetailsResponse.from_json(response.body)}
end end
end end

View File

@@ -3,13 +3,15 @@ require "time" # yeah me too
module Wince::Utils module Wince::Utils
extend self extend self
def hours_for_day(hours_json : JSON::Any, day : Time::DayOfWeek, seperator : String) @@config_path = Path.home.join("/.config/wince/api_key")
open = hours_json[0]["open"].as_a @@api_key = ""
def hours_for_day(hours : Yelp::Hours, day : Time::DayOfWeek, seperator : String)
day_number = day_of_week_to_int(day) day_number = day_of_week_to_int(day)
formatted_hours = open.select { |hour| hour["day"].as_i == day_number }.map { |hour| formatted_hours = hours.open.select { |hour| hour.day == day_number }.map { |hour|
start_hour = hour["start"].as_s start_hour = hour.open
end_hour = hour["end"].as_s end_hour = hour.close
"#{start_hour.insert(2, ":")}-#{end_hour.insert(2, ":")}" "#{start_hour.insert(2, ":")}-#{end_hour.insert(2, ":")}"
}.join(seperator) }.join(seperator)
@@ -34,11 +36,23 @@ module Wince::Utils
end end
end end
def format_address(display_address_json : JSON::Any) def format_address(display_address : Array(String))
display_address_json.as_a.map { |line| line.as_s? || "" }.join("\n") display_address.join("\n")
end end
def load_url_to_image(url : String, image : Gtk::Image) def api_key_exists?
File.exists? @@config_path
end end
end
def read_api_key
@@api_key = File.read(@@config_path).strip
end
def api_key
if @@api_key.blank?
read_api_key
end
@@api_key
end
end

View File

@@ -1,4 +1,3 @@
require "json"
require "time" require "time"
require "../templates/businessrow.cr" require "../templates/businessrow.cr"
@@ -40,7 +39,13 @@ module Wince
handle_business_select handle_business_select
end end
unless Utils.api_key_exists?
POWERD_BY_TEXT.visible = false
CONFIG_NOT_FOUND_TEXT.visible = true
end
setup_map setup_map
Location.setup_client
window.present window.present
end end
@@ -71,17 +76,13 @@ module Wince
end end
end end
def yelp_response_to_business_ids(response : JSON::Any) def yelp_response_to_business_ids(businesses : Array(Yelp::Business))
response["businesses"].as_a.map { |b| b["id"].as_s } businesses.map { |b| b.id }
end end
def yelp_response_to_business_rows(response : JSON::Any) def yelp_response_to_business_rows(businesses : Array(Yelp::Business))
response["businesses"].as_a.map do |business| businesses.map do |business|
name = business["name"].as_s? || "" BusinessRow.new(business.name, business.rating, business.distance)
rating = business["rating"].as_f32
distance = business["distance"].as_f32
BusinessRow.new(name, rating, distance)
end end
end end
@@ -94,6 +95,10 @@ module Wince
end end
def handle_search def handle_search
unless Utils.api_key_exists?
return
end
search = SEARCH_ENTRY.buffer.text search = SEARCH_ENTRY.buffer.text
location = LOCATION_ENTRY.buffer.text location = LOCATION_ENTRY.buffer.text
@@ -106,19 +111,23 @@ module Wince
LEAFLET.visible = true LEAFLET.visible = true
POWERD_BY_TEXT.visible = false POWERD_BY_TEXT.visible = false
response = Yelp.search_businesses(search, location) status_code, response = Yelp.search_businesses(search, location)
if response.status_code != 200 if status_code != 200
puts "api call error" puts "api call error"
puts response.body if response.error.is_a? Yelp::Error
puts response.error.as(Yelp::Error).description
end
return #TODO: show a toast here return #TODO: show a toast here
end end
response_json = JSON.parse(response.body) # this can technically fail if we get a weird case of a 200 response
# but a malformed response, but it's probably fine
businesses = response.businesses.as(Array(Yelp::Business))
clear_business_rows() clear_business_rows()
@@business_ids = yelp_response_to_business_ids(response_json) @@business_ids = yelp_response_to_business_ids(businesses)
@@business_rows = yelp_response_to_business_rows(response_json) @@business_rows = yelp_response_to_business_rows(businesses)
@@business_rows.each do |row| @@business_rows.each do |row|
BUSINESS_LIST.append(row) BUSINESS_LIST.append(row)
end end
@@ -128,37 +137,36 @@ module Wince
index = @@business_rows.index(BUSINESS_LIST.selected_row) || 0 index = @@business_rows.index(BUSINESS_LIST.selected_row) || 0
id = @@business_ids[index] id = @@business_ids[index]
response = Yelp.get_business_info(id) status_code, response = Yelp.get_business_info(id)
if response.status_code != 200 if status_code != 200
puts "api call error" puts "api call error"
puts response.body if response.error.is_a? Yelp::Error
puts response.error.as(Yelp::Error).description
end
return #TODO: show a toast here return #TODO: show a toast here
end end
response_json = JSON.parse(response.body)
DETAILS_TITLE.text = response_json["name"].as_s? || "" DETAILS_TITLE.text = response.name || ""
is_open = response_json["hours"][0]["is_open_now"].as_bool? || false if response.is_open
if is_open
DETAILS_IS_OPEN.markup = "<span foreground=\"green\">open</span>" DETAILS_IS_OPEN.markup = "<span foreground=\"green\">open</span>"
else else
DETAILS_IS_OPEN.markup = "<span foreground=\"red\">closed</span>" DETAILS_IS_OPEN.markup = "<span foreground=\"red\">closed</span>"
end end
DETAILS_CURRENT_HOURS.text = Utils.hours_for_day(response_json["hours"], Time.local.day_of_week, ", ") DETAILS_CURRENT_HOURS.text = Utils.hours_for_day(response.hours[0], Time.local.day_of_week, ", ")
DETAILS_PRICING.text = response_json["price"].as_s? || "" DETAILS_PRICING.text = response.price || ""
DETAILS_ADDRESS.markup = Utils.format_address(response_json["location"]["display_address"]) DETAILS_ADDRESS.markup = Utils.format_address(response.location.display_address)
DETAILS_PHONE.text = response_json["display_phone"].as_s? || "no phone number" DETAILS_PHONE.text = response.display_phone || "no phone number"
DETAILS_URL.uri = response_json["url"].as_s? || "" DETAILS_URL.uri = response.url || ""
clear_hour_rows() clear_hour_rows()
@@hour_rows = format_hours(response_json["hours"]) @@hour_rows = format_hours(response.hours[0])
@@hour_rows.each { |hour_row| DETAILS_HOURS_BOX.append(hour_row) } @@hour_rows.each { |hour_row| DETAILS_HOURS_BOX.append(hour_row) }
set_map_location(response_json["coordinates"]) set_map_location(response.coordinates)
# If we're in the small layout we want to show the back button # If we're in the small layout we want to show the back button
if LEAFLET.folded if LEAFLET.folded
@@ -171,21 +179,18 @@ module Wince
LEAFLET.visible_child = DETAILS_SCROLL LEAFLET.visible_child = DETAILS_SCROLL
end end
def set_map_location(coordinates : JSON::Any) def set_map_location(coordinates : Yelp::Coordinates)
latitude = coordinates["latitude"].as_f
longitude = coordinates["longitude"].as_f
viewport = DETAILS_MAP.viewport viewport = DETAILS_MAP.viewport
viewport.set_location(latitude, longitude) viewport.set_location(coordinates.latitude, coordinates.longitude)
viewport.zoom_level = 16 viewport.zoom_level = 16
@@marker.try {|m| m.set_location(latitude, longitude) } @@marker.try {|m| m.set_location(coordinates.latitude, coordinates.longitude) }
end end
def format_hours(hours_json : JSON::Any) def format_hours(hours : Yelp::Hours)
Time::DayOfWeek.values.map do |day| Time::DayOfWeek.values.map do |day|
hours = Utils.hours_for_day(hours_json, day, "\n") hours_for_day = Utils.hours_for_day(hours, day, "\n")
HourRow.new(day, hours) HourRow.new(day, hours_for_day)
end end
end end

View File

@@ -1,6 +1,6 @@
require "libadwaita" require "libadwaita"
require "../lib/libadwaita/lib/gi-crystal/src/auto/geoclue-2.0/*" require "../lib/gi-crystal/src/auto/geoclue-2.0/*"
require "../lib/libadwaita/lib/gi-crystal/src/auto/shumate-1.0/*" require "../lib/gi-crystal/src/auto/shumate-1.0/*"
require "./modules/prerequisites.cr" require "./modules/prerequisites.cr"
require "./modules/views/main.cr" require "./modules/views/main.cr"
@@ -28,6 +28,7 @@ module Wince
DETAILS_BACK = Gtk::Button.cast(B_UI["detailsBack"]) DETAILS_BACK = Gtk::Button.cast(B_UI["detailsBack"])
DETAILS_HOURS_BOX = Gtk::ListBox.cast(B_UI["detailsHoursBox"]) DETAILS_HOURS_BOX = Gtk::ListBox.cast(B_UI["detailsHoursBox"])
DETAILS_MAP = Shumate::SimpleMap.cast(B_UI["detailsMap"]) DETAILS_MAP = Shumate::SimpleMap.cast(B_UI["detailsMap"])
CONFIG_NOT_FOUND_TEXT = Gtk::Label.cast(B_UI["configNotFoundText"])
APP = Adw::Application.new("space.quietfeathers.wince", Gio::ApplicationFlags::None) APP = Adw::Application.new("space.quietfeathers.wince", Gio::ApplicationFlags::None)
end end