Compare commits

13 Commits

22 changed files with 463 additions and 135 deletions

23
Makefile Normal file
View File

@@ -0,0 +1,23 @@
.PHONY: all install uninstall build
PREFIX ?= /usr
all: bindings build
bindings:
shards install
./bin/gi-crystal
build:
shards build -Dpreview_mt --release --no-debug
install:
install -D -m 0755 bin/wince $(PREFIX)/bin/wince
install -D -m 0644 data/space.quietfeathers.wince.desktop $(PREFIX)/share/applications/space.quietfeathers.wince.desktop
install -D -m 0644 data/img/space.quietfeathers.wince.svg $(PREFIX)/share/icons/hicolor/scalable/apps/space.quietfeathers.wince.svg
gtk-update-icon-cache /usr/share/icons/hicolor
uninstall:
rm -f $(PREFIX)/bin/wince
rm -f $(PREFIX)/share/applications/space.quietfeathers.wince.desktop
rm -f $(PREFIX)/share/icons/hicolor/scalable/apps/space.quietfeathers.wince.svg
gtk-update-icon-cache /usr/share/icons/hicolor

View File

@@ -1,16 +1,25 @@
# Wince # Wince
A GTK 4 frontend for yelp Adaptive GTK4 app for searching local businesses and restaurants. Powered by [Yelp](https://yelp.com).
## Build ## Build
You gotta have blueprint installed ### Depenencies
* geoclue
* libshumate
* blueprint-compiler
* libadwaita
* gtk4
* crystal
If you're on arch you can run
``` ```
pacman -S blueprint-compiler crystal shards libadwaita 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 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
@@ -18,8 +27,10 @@ Then you can run the program with
crystal run src/gtktest.cr crystal run src/gtktest.cr
``` ```
You can build for production with You can build for production with `make` and install with `make install`
``` ## Credits
shards build -Dpreview_mt --release --no-debug
``` Map marker icon from [Paomedia on iconfinder](https://www.iconfinder.com/icons/299087/marker_map_icon)
The [Ultimate GTK4 crystal guide](https://ultimate-gtk4-crystal-guide.geopjr.dev/) without which I would not have been able to figure most of this out.

BIN
data/img/marker-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

View File

@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="27.495001mm"
height="27.494635mm"
viewBox="0 0 27.495001 27.494636"
version="1.1"
id="svg5"
xml:space="preserve"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs2"><linearGradient
id="linearGradient1299"><stop
style="stop-color:#ff2323;stop-opacity:1;"
offset="0"
id="stop1295" /><stop
style="stop-color:#ca5aaa;stop-opacity:1;"
offset="1"
id="stop1297" /></linearGradient><linearGradient
xlink:href="#linearGradient1299"
id="linearGradient1301"
x1="10.067801"
y1="22.259319"
x2="24.352367"
y2="22.259319"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(-1.5278011,-6.6120943)" /></defs><g
id="layer1"
transform="translate(-1.9349249,-1.8999069)"><circle
style="fill:#2f2d39;fill-opacity:1;stroke:#272730;stroke-width:1.4;stroke-dasharray:none;stroke-opacity:1"
id="path507"
cx="15.682425"
cy="15.647223"
r="11.547338" /><path
id="path3470"
style="fill:url(#linearGradient1301);fill-opacity:1;stroke-width:0.264583"
d="m 18.39274,9.0775417 a 4.431921,4.431921 0 0 0 -4.43228,4.4317693 4.431921,4.431921 0 0 0 0.31264,1.632976 l -5.4518611,4.655529 c -0.33702,0.287828 -0.37671,0.791079 -0.0889,1.128097 l 0.86248,1.009757 c 0.28782,0.337018 0.7910701,0.376712 1.1280901,0.08888 l 5.463231,-4.665865 c 0.002,-0.0014 0.003,-0.0028 0.005,-0.0041 a 4.431921,4.431921 0 0 0 2.20194,0.586527 4.431921,4.431921 0 0 0 4.43177,-4.431771 4.431921,4.431921 0 0 0 -4.43177,-4.4317693 z m -0.0109,1.6903363 a 2.8280926,2.8280926 0 0 1 2.82826,2.827733 2.8280926,2.8280926 0 0 1 -2.82826,2.828251 2.8280926,2.8280926 0 0 1 -2.82773,-2.828251 2.8280926,2.8280926 0 0 1 2.82773,-2.827733 z" /></g></svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1,9 @@
[Desktop Entry]
Type=Application
StartupWMClass=wince
Exec=wince %F
Name=wince
Comment=Search businesses and restaurants
Terminal=false
Icon=space.quietfeathers.wince
Categories=Utility;

View File

@@ -5,7 +5,7 @@ using Shumate 1.0;
Adw.ApplicationWindow mainWindow { Adw.ApplicationWindow mainWindow {
title: "Wince"; title: "Wince";
default-width: 700; default-width: 700;
default-height: 500; default-height: 700;
Gtk.Box { Gtk.Box {
orientation: vertical; orientation: vertical;
@@ -43,102 +43,124 @@ 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;
visible: false; visible: false;
Gtk.ScrolledWindow scrollWindow { Gtk.ScrolledWindow resultsScroll {
vexpand: true; vexpand: true;
width-request: 300; width-request: 300;
Gtk.ListBox searchResults {} Gtk.ListBox searchResults {}
} }
Adw.Clamp detailsClamp { Gtk.ScrolledWindow detailsScroll {
vexpand: true;
width-request: 300; width-request: 300;
maximum-size: 450;
tightening-threshold: 400;
Gtk.Box detailsBox { Adw.Clamp {
orientation: vertical; maximum-size: 450;
visible: false; margin-end: 12;
margin-start: 14; tightening-threshold: 400;
margin-end: 14;
Gtk.Button detailsBack { Gtk.Box detailsBox {
orientation: vertical;
visible: false; visible: false;
hexpand: false; margin-start: 14;
label: "Back to results";
margin-bottom: 8;
}
Gtk.Label detailsTitle { Gtk.Button detailsBack {
halign: start; visible: false;
styles ["title-1"] hexpand: false;
} label: "Back to results";
margin-bottom: 8;
Gtk.Box {
orientation: horizontal;
spacing: 12;
Gtk.Label detailsIsOpen {
halign: start;
} }
Gtk.Label detailsCurrentHours { Gtk.Label detailsTitle {
halign: start; halign: start;
wrap: true;
styles ["title-1"]
} }
Gtk.Label detailsPricing { Gtk.Box {
halign: start; orientation: horizontal;
} spacing: 12;
}
Gtk.ListBox { Gtk.Label detailsIsOpen {
styles ["boxed-list"]
selection-mode: none;
margin-top: 8;
hexpand: true;
Gtk.ListBoxRow {
Gtk.Label detailsAddress {
halign: start; halign: start;
margin-start: 12;
margin-top: 12;
margin-bottom: 12;
} }
}
Gtk.ListBoxRow { Gtk.Label detailsCurrentHours {
Gtk.Label detailsPhone {
halign: start; halign: start;
margin-start: 12;
margin-top: 12;
margin-bottom: 12;
} }
}
Gtk.ListBoxRow { Gtk.Label detailsPricing {
Gtk.LinkButton detailsUrl {
label: "View on yelp";
halign: start; halign: start;
} }
} }
}
Gtk.Label { Gtk.ListBox {
styles ["heading"] styles ["boxed-list"]
halign: start; selection-mode: none;
label: "Hours"; margin-top: 8;
margin-top: 16; hexpand: true;
}
Gtk.ListBox detailsHoursBox { Gtk.ListBoxRow {
styles ["boxed-list"] Gtk.Label detailsAddress {
selection-mode: none; halign: start;
margin-top: 8; margin-start: 12;
hexpand: true; margin-top: 12;
margin-bottom: 12;
}
}
Gtk.ListBoxRow {
Gtk.Label detailsPhone {
halign: start;
margin-start: 12;
margin-top: 12;
margin-bottom: 12;
}
}
Gtk.ListBoxRow {
Gtk.LinkButton detailsUrl {
label: "View on yelp";
halign: start;
}
}
}
Shumate.SimpleMap detailsMap {
margin-top: 8;
margin-start: 8;
margin-end: 8;
height-request: 220;
show-zoom-buttons: false;
}
Gtk.Label {
styles ["heading"]
halign: start;
label: "Hours";
margin-top: 16;
}
Gtk.ListBox detailsHoursBox {
styles ["boxed-list"]
selection-mode: none;
margin-top: 8;
margin-bottom: 8;
hexpand: true;
}
} }
} }
} }

View File

@@ -24,4 +24,4 @@ template Wince-BusinessRow : Gtk.ListBoxRow {
} }
} }
} }
} }

View File

@@ -0,0 +1,22 @@
using Gtk 4.0;
using Adw 1;
template Wince-HourRow : Gtk.ListBoxRow {
Gtk.Box {
orientation: horizontal;
margin-start: 12;
margin-end: 12;
margin-top: 12;
margin-bottom: 12;
hexpand: true;
Gtk.Label dayOfWeek {
halign: start;
}
Gtk.Label hours {
halign: end;
hexpand: true;
}
}
}

View File

@@ -2,6 +2,8 @@
<gresources> <gresources>
<gresource prefix="/wince"> <gresource prefix="/wince">
<file compressed="true" preprocess="xml-stripblanks">ui/compiled/main.ui</file> <file compressed="true" preprocess="xml-stripblanks">ui/compiled/main.ui</file>
<file compressed="true" preprocess="xml-stripblanks">ui/compiled/templates.ui</file> <file compressed="true" preprocess="xml-stripblanks">ui/compiled/templates/businessrow.ui</file>
<file compressed="true" preprocess="xml-stripblanks">ui/compiled/templates/hourrow.ui</file>
<file compressed="true">img/marker-icon.png</file>
</gresource> </gresource>
</gresources> </gresources>

View File

@@ -1,13 +1,13 @@
# NOTICE: This lockfile contains some overrides from shard.override.yml # NOTICE: This lockfile contains some overrides from shard.override.yml
version: 2.0 version: 2.0
shards: shards:
gi-crystal: gi-crystal: # Overridden
git: https://github.com/hugopl/gi-crystal.git git: https://github.com/hugopl/gi-crystal.git
version: 0.14.0+git.commit.569f8d4b77effe312c642fc60929000e3e9514e2 version: 0.15.0+git.commit.bcf127b37c309a52f4b17566e9663029c16dcbb2
gtk4: # Overridden gtk4: # Overridden
git: https://github.com/hugopl/gtk4.cr.git git: https://github.com/hugopl/gtk4.cr.git
version: 0.12.0+git.commit.ef2eb03b38b25ea2780e83cf50af3a6807ac6293 version: 0.12.0+git.commit.a46b9295a7cffcc4ed2af596276c17196b2948fb
libadwaita: libadwaita:
git: https://github.com/geopjr/libadwaita.cr.git git: https://github.com/geopjr/libadwaita.cr.git

View File

@@ -2,3 +2,6 @@ dependencies:
gtk4: gtk4:
github: hugopl/gtk4.cr github: hugopl/gtk4.cr
branch: master branch: master
gi-crystal:
github: hugopl/gi-crystal
branch: master

View File

@@ -9,10 +9,9 @@ targets:
main: src/wince.cr main: src/wince.cr
dependencies: dependencies:
gtk4:
github: hugopl/gtk4.cr
libadwaita: libadwaita:
github: GeopJr/libadwaita.cr github: GeopJr/libadwaita.cr
branch: main
crystal: 1.6.2 crystal: 1.6.2

View File

@@ -0,0 +1,2 @@
namespace: Geoclue
version: "2.0"

View File

@@ -0,0 +1,2 @@
namespace: Shumate
version: "1.0"

View File

@@ -1,10 +1,31 @@
module Wince::Location module Wince::Location
extend self extend self
client = Geoclue::Simple.new() @@client : Geoclue::Simple? = nil
def find_location() def setup_client
location = client.location callback = ->(object : GObject::Object | Nil, result : Gio::AsyncResult) {
[location.latitude, location.longitude] @@client = Geoclue::Simple.new_finish(result)
puts "location callback"
}
Geoclue::Simple.new(
"space.quietfeathers.Wince",
Geoclue::AccuracyLevel::Street,
nil,
callback,
nil
)
end
# returns latlon if available, otherwise empty array
def find_location
if @@client.nil?
return [] of Float64
end
location = @@client.try do |c|
[c.location.latitude, c.location.longitude]
end || [] of Float64
end end
end end

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,7 +3,7 @@ module Wince
VERSION = {{read_file("./shard.yml").split("version: ")[1].split("\n")[0]}} VERSION = {{read_file("./shard.yml").split("version: ")[1].split("\n")[0]}}
{% {%
`blueprint-compiler batch-compile ./data/ui/compiled ./data/ui/ ./data/ui/*.blp` `blueprint-compiler batch-compile ./data/ui/compiled ./data/ui/ ./data/ui/templates/*.blp ./data/ui/*.blp`
%} %}
Gio.register_resource("data/wince.gresource.xml", "data") Gio.register_resource("data/wince.gresource.xml", "data")
end end

View File

@@ -1,5 +1,6 @@
module Wince module Wince
@[Gtk::UiTemplate(resource: "/wince/ui/compiled/templates.ui", children: %w(businessName businessRating businessDistance))] @[Gtk::UiTemplate(resource: "/wince/ui/compiled/templates/businessrow.ui",
children: %w(businessName businessRating businessDistance))]
class BusinessRow < Gtk::ListBoxRow class BusinessRow < Gtk::ListBoxRow
include Gtk::WidgetTemplate include Gtk::WidgetTemplate

View File

@@ -0,0 +1,24 @@
module Wince
@[Gtk::UiTemplate(resource: "/wince/ui/compiled/templates/hourrow.ui",
children: %w(dayOfWeek hours))]
class HourRow < Gtk::ListBoxRow
include Gtk::WidgetTemplate
@dayOfWeekLabel : Gtk::Label
@hoursLabel : Gtk::Label
def initialize(dayOfWeek : Time::DayOfWeek, hours : String)
super()
@dayOfWeekLabel = Gtk::Label.cast(template_child("dayOfWeek"))
@hoursLabel = Gtk::Label.cast(template_child("hours"))
@dayOfWeekLabel.text = dayOfWeek.to_s
if hours.empty?
@hoursLabel.text = "closed"
else
@hoursLabel.markup = hours
end
end
end
end

View File

@@ -3,16 +3,18 @@ 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) @@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(", ") }.join(seperator)
end end
def day_of_week_to_int(day : Time::DayOfWeek) def day_of_week_to_int(day : Time::DayOfWeek)
@@ -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,14 +1,17 @@
require "json"
require "time" require "time"
require "../templates/businessrow.cr" require "../templates/businessrow.cr"
require "../templates/hourrow.cr"
require "../api/location.cr"
require "../api/yelp.cr" require "../api/yelp.cr"
require "../utils/utils.cr" require "../utils/utils.cr"
module Wince module Wince
@@main_window_id = 0_u32 @@main_window_id = 0_u32
@@business_rows = [] of BusinessRow @@business_rows = [] of BusinessRow
@@hour_rows = [] of HourRow
@@business_ids = [] of String @@business_ids = [] of String
@@marker = nil
def activate(app : Adw::Application) def activate(app : Adw::Application)
main_window = APP.window_by_id(@@main_window_id) main_window = APP.window_by_id(@@main_window_id)
@@ -21,7 +24,7 @@ module Wince
DETAILS_BACK.clicked_signal.connect do DETAILS_BACK.clicked_signal.connect do
BUSINESS_LIST.unselect_all BUSINESS_LIST.unselect_all
LEAFLET.visible_child = SCROLL_VIEW LEAFLET.visible_child = RESULTS_SCROLL
end end
SEARCH_BUTTON.clicked_signal.connect do SEARCH_BUTTON.clicked_signal.connect do
@@ -36,24 +39,50 @@ 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
Location.setup_client
window.present window.present
end end
def setup_map
DETAILS_MAP.map_source = Shumate::MapSourceRegistry
.new_with_defaults().by_id(Shumate::MAP_SOURCE_OSM_MAPNIK)
pixbuf = GdkPixbuf::Pixbuf.new_from_resource("/wince/img/marker-icon.png")
icon = Gtk::Image.new_from_pixbuf(pixbuf)
@@marker = Shumate::Marker.new
@@marker.try do|m|
m.height_request = 64
m.width_request = 64
m.child = icon
marker_layer = Shumate::MarkerLayer.new(DETAILS_MAP.viewport)
marker_layer.add_marker(m)
DETAILS_MAP.add_overlay_layer(marker_layer)
end
end
def handle_geolocate def handle_geolocate
puts "no, not yet" latlon = Location.find_location()
if !latlon.empty?
LOCATION_ENTRY.text = "#{latlon[0]}, #{latlon[1]}"
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
@@ -61,7 +90,15 @@ module Wince
@@business_rows.each { |row| BUSINESS_LIST.remove(row) } @@business_rows.each { |row| BUSINESS_LIST.remove(row) }
end end
def clear_hour_rows
@@hour_rows.each { |row| DETAILS_HOURS_BOX.remove(row) }
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
@@ -74,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
@@ -96,31 +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()
@@hour_rows = format_hours(response.hours[0])
@@hour_rows.each { |hour_row| DETAILS_HOURS_BOX.append(hour_row) }
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
@@ -130,9 +176,24 @@ module Wince
end end
DETAILS_BOX.visible = true DETAILS_BOX.visible = true
LEAFLET.visible_child = DETAILS_CLAMP LEAFLET.visible_child = DETAILS_SCROLL
end
def set_map_location(coordinates : Yelp::Coordinates)
viewport = DETAILS_MAP.viewport
viewport.set_location(coordinates.latitude, coordinates.longitude)
viewport.zoom_level = 16
@@marker.try {|m| m.set_location(coordinates.latitude, coordinates.longitude) }
end
def format_hours(hours : Yelp::Hours)
Time::DayOfWeek.values.map do |day|
hours_for_day = Utils.hours_for_day(hours, day, "\n")
HourRow.new(day, hours_for_day)
end
end end
APP.activate_signal.connect(->activate(Adw::Application)) APP.activate_signal.connect(->activate(Adw::Application))
exit(APP.run(ARGV)) exit(APP.run(ARGV))
end end

View File

@@ -1,4 +1,6 @@
require "libadwaita" require "libadwaita"
GICrystal.require("Geoclue", "2.0")
GICrystal.require("Shumate", "1.0")
require "./modules/prerequisites.cr" require "./modules/prerequisites.cr"
require "./modules/views/main.cr" require "./modules/views/main.cr"
@@ -12,7 +14,8 @@ module Wince
SEARCH_BUTTON = Gtk::Button.cast(B_UI["searchButton"]) SEARCH_BUTTON = Gtk::Button.cast(B_UI["searchButton"])
BUSINESS_LIST = Gtk::ListBox.cast(B_UI["searchResults"]) BUSINESS_LIST = Gtk::ListBox.cast(B_UI["searchResults"])
POWERD_BY_TEXT = Gtk::Label.cast(B_UI["poweredByText"]) POWERD_BY_TEXT = Gtk::Label.cast(B_UI["poweredByText"])
SCROLL_VIEW = Gtk::ScrolledWindow.cast(B_UI["scrollWindow"]) RESULTS_SCROLL = Gtk::ScrolledWindow.cast(B_UI["resultsScroll"])
DETAILS_SCROLL = Gtk::ScrolledWindow.cast(B_UI["detailsScroll"])
LEAFLET = Adw::Leaflet.cast(B_UI["leaflet"]) LEAFLET = Adw::Leaflet.cast(B_UI["leaflet"])
DETAILS_BOX = Gtk::Box.cast(B_UI["detailsBox"]) DETAILS_BOX = Gtk::Box.cast(B_UI["detailsBox"])
DETAILS_TITLE = Gtk::Label.cast(B_UI["detailsTitle"]) DETAILS_TITLE = Gtk::Label.cast(B_UI["detailsTitle"])
@@ -22,8 +25,10 @@ module Wince
DETAILS_ADDRESS = Gtk::Label.cast(B_UI["detailsAddress"]) DETAILS_ADDRESS = Gtk::Label.cast(B_UI["detailsAddress"])
DETAILS_PHONE = Gtk::Label.cast(B_UI["detailsPhone"]) DETAILS_PHONE = Gtk::Label.cast(B_UI["detailsPhone"])
DETAILS_URL = Gtk::LinkButton.cast(B_UI["detailsUrl"]) DETAILS_URL = Gtk::LinkButton.cast(B_UI["detailsUrl"])
DETAILS_CLAMP = Adw::Clamp.cast(B_UI["detailsClamp"])
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_MAP = Shumate::SimpleMap.cast(B_UI["detailsMap"])
CONFIG_NOT_FOUND_TEXT = Gtk::Label.cast(B_UI["configNotFoundText"])
APP = Adw::Application.new("dev.wince", Gio::ApplicationFlags::None) APP = Adw::Application.new("space.quietfeathers.wince", Gio::ApplicationFlags::None)
end end