Compare commits
14 Commits
29719f9eb4
...
broken-gi-
| Author | SHA1 | Date | |
|---|---|---|---|
| bbc2cc5733 | |||
| bd80d44ba6 | |||
| cedd8fb851 | |||
| ada6881249 | |||
| 992a8e9e8d | |||
| c5091351e2 | |||
| 349a6cf756 | |||
| 7833d6fa57 | |||
| 52ccf6d080 | |||
| cae4e512f5 | |||
| 23e48e7d4d | |||
| 0c0f7d881e | |||
| a768f4732e | |||
| bf6dfd1919 |
23
Makefile
Normal file
23
Makefile
Normal 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
|
||||
27
README.md
27
README.md
@@ -1,16 +1,25 @@
|
||||
# Wince
|
||||
|
||||
A GTK 4 frontend for yelp
|
||||
Adaptive GTK4 app for searching local businesses and restaurants. Powered by [Yelp](https://yelp.com).
|
||||
|
||||
## 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
|
||||
|
||||
@@ -18,8 +27,10 @@ Then you can run the program with
|
||||
crystal run src/gtktest.cr
|
||||
```
|
||||
|
||||
You can build for production with
|
||||
You can build for production with `make` and install with `make install`
|
||||
|
||||
```
|
||||
shards build -Dpreview_mt --release --no-debug
|
||||
```
|
||||
## Credits
|
||||
|
||||
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
BIN
data/img/marker-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.0 KiB |
39
data/img/space.quietfeathers.wince.svg
Normal file
39
data/img/space.quietfeathers.wince.svg
Normal 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 |
9
data/space.quietfeathers.wince.desktop
Normal file
9
data/space.quietfeathers.wince.desktop
Normal 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;
|
||||
151
data/ui/main.blp
151
data/ui/main.blp
@@ -1,10 +1,11 @@
|
||||
using Gtk 4.0;
|
||||
using Adw 1;
|
||||
using Shumate 1.0;
|
||||
|
||||
Adw.ApplicationWindow mainWindow {
|
||||
title: "Wince";
|
||||
default-width: 700;
|
||||
default-height: 500;
|
||||
default-height: 700;
|
||||
|
||||
Gtk.Box {
|
||||
orientation: vertical;
|
||||
@@ -27,6 +28,9 @@ Adw.ApplicationWindow mainWindow {
|
||||
Gtk.Entry locationEntry {
|
||||
placeholder-text: "Location";
|
||||
}
|
||||
Gtk.Button geolocateButton {
|
||||
icon-name: "find-location";
|
||||
}
|
||||
Gtk.Button searchButton {
|
||||
icon-name: "edit-find";
|
||||
}
|
||||
@@ -39,74 +43,123 @@ Adw.ApplicationWindow mainWindow {
|
||||
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 {
|
||||
can-navigate-forward: false;
|
||||
can-navigate-back: true;
|
||||
visible: false;
|
||||
|
||||
Gtk.ScrolledWindow scrollWindow {
|
||||
Gtk.ScrolledWindow resultsScroll {
|
||||
vexpand: true;
|
||||
width-request: 300;
|
||||
|
||||
Gtk.ListBox searchResults {}
|
||||
}
|
||||
|
||||
Gtk.Box detailsBox {
|
||||
orientation: vertical;
|
||||
visible: false;
|
||||
margin-start: 14;
|
||||
margin-end: 14;
|
||||
Gtk.ScrolledWindow detailsScroll {
|
||||
vexpand: true;
|
||||
width-request: 300;
|
||||
|
||||
Gtk.Label detailsTitle {
|
||||
halign: start;
|
||||
styles ["title-1"]
|
||||
}
|
||||
Adw.Clamp {
|
||||
maximum-size: 450;
|
||||
margin-end: 12;
|
||||
tightening-threshold: 400;
|
||||
|
||||
Gtk.Box {
|
||||
orientation: horizontal;
|
||||
spacing: 12;
|
||||
Gtk.Box detailsBox {
|
||||
orientation: vertical;
|
||||
visible: false;
|
||||
margin-start: 14;
|
||||
|
||||
Gtk.Label detailsIsOpen {
|
||||
halign: start;
|
||||
}
|
||||
|
||||
Gtk.Label detailsCurrentHours {
|
||||
halign: start;
|
||||
}
|
||||
|
||||
Gtk.Label detailsPricing {
|
||||
halign: start;
|
||||
}
|
||||
}
|
||||
|
||||
Gtk.ListBox {
|
||||
styles ["boxed-list"]
|
||||
selection-mode: none;
|
||||
margin-top: 8;
|
||||
hexpand: true;
|
||||
|
||||
Gtk.ListBoxRow {
|
||||
Gtk.Label detailsAddress {
|
||||
halign: start;
|
||||
margin-start: 12;
|
||||
margin-top: 12;
|
||||
margin-bottom: 12;
|
||||
Gtk.Button detailsBack {
|
||||
visible: false;
|
||||
hexpand: false;
|
||||
label: "Back to results";
|
||||
margin-bottom: 8;
|
||||
}
|
||||
}
|
||||
|
||||
Gtk.ListBoxRow {
|
||||
Gtk.Label detailsPhone {
|
||||
Gtk.Label detailsTitle {
|
||||
halign: start;
|
||||
margin-start: 12;
|
||||
margin-top: 12;
|
||||
margin-bottom: 12;
|
||||
wrap: true;
|
||||
styles ["title-1"]
|
||||
}
|
||||
}
|
||||
|
||||
Gtk.ListBoxRow {
|
||||
Gtk.LinkButton detailsUrl {
|
||||
label: "View on yelp";
|
||||
Gtk.Box {
|
||||
orientation: horizontal;
|
||||
spacing: 12;
|
||||
|
||||
Gtk.Label detailsIsOpen {
|
||||
halign: start;
|
||||
}
|
||||
|
||||
Gtk.Label detailsCurrentHours {
|
||||
halign: start;
|
||||
}
|
||||
|
||||
Gtk.Label detailsPricing {
|
||||
halign: start;
|
||||
}
|
||||
}
|
||||
|
||||
Gtk.ListBox {
|
||||
styles ["boxed-list"]
|
||||
selection-mode: none;
|
||||
margin-top: 8;
|
||||
hexpand: true;
|
||||
|
||||
Gtk.ListBoxRow {
|
||||
Gtk.Label detailsAddress {
|
||||
halign: start;
|
||||
margin-start: 12;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,4 +24,4 @@ template Wince-BusinessRow : Gtk.ListBoxRow {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
22
data/ui/templates/hourrow.blp
Normal file
22
data/ui/templates/hourrow.blp
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,8 @@
|
||||
<gresources>
|
||||
<gresource prefix="/wince">
|
||||
<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>
|
||||
</gresources>
|
||||
|
||||
@@ -9,10 +9,9 @@ targets:
|
||||
main: src/wince.cr
|
||||
|
||||
dependencies:
|
||||
gtk4:
|
||||
github: hugopl/gtk4.cr
|
||||
libadwaita:
|
||||
github: GeopJr/libadwaita.cr
|
||||
branch: main
|
||||
|
||||
crystal: 1.6.2
|
||||
|
||||
|
||||
2
src/bindings/geoclue/binding.yml
Normal file
2
src/bindings/geoclue/binding.yml
Normal file
@@ -0,0 +1,2 @@
|
||||
namespace: Geoclue
|
||||
version: "2.0"
|
||||
2
src/bindings/libshumate/binding.yml
Normal file
2
src/bindings/libshumate/binding.yml
Normal file
@@ -0,0 +1,2 @@
|
||||
namespace: Shumate
|
||||
version: "1.0"
|
||||
27
src/modules/api/location.cr
Normal file
27
src/modules/api/location.cr
Normal file
@@ -0,0 +1,27 @@
|
||||
module Wince::Location
|
||||
extend self
|
||||
|
||||
@@channel = Channel(Geoclue::Simple).new(1)
|
||||
@@client : Geoclue::Simple? = nil
|
||||
@@fiber : Fiber = spawn setup_client
|
||||
|
||||
def setup_client
|
||||
@@channel.send Geoclue::Simple.new_sync("space.quietfeathers.Wince", Geoclue::AccuracyLevel::Street, nil)
|
||||
Fiber.yield
|
||||
end
|
||||
|
||||
# returns latlon if available, otherwise empty array
|
||||
def find_location
|
||||
if !@@fiber.dead?
|
||||
return [] of Float64
|
||||
end
|
||||
|
||||
if @@client.nil?
|
||||
@@client = @@channel.receive
|
||||
end
|
||||
|
||||
location = @@client.try do |c|
|
||||
[c.location.latitude, c.location.longitude]
|
||||
end || [] of Float64
|
||||
end
|
||||
end
|
||||
@@ -1,10 +1,74 @@
|
||||
require "http/client"
|
||||
require "io"
|
||||
require "json"
|
||||
|
||||
require "../utils/utils.cr"
|
||||
|
||||
module Wince::Yelp
|
||||
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)
|
||||
|
||||
@@ -19,8 +83,10 @@ module Wince::Yelp
|
||||
path: "/v3/businesses/search",
|
||||
query: params
|
||||
)
|
||||
headers = HTTP::Headers{ "Authorization" => "Bearer " + @@token }
|
||||
headers = HTTP::Headers{ "Authorization" => "Bearer " + Utils.api_key }
|
||||
response = HTTP::Client.get(uri, headers)
|
||||
|
||||
{response.status_code, SearchResponse.from_json(response.body)}
|
||||
end
|
||||
|
||||
def get_business_info(id : String)
|
||||
@@ -30,8 +96,10 @@ module Wince::Yelp
|
||||
host: "api.yelp.com",
|
||||
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.status_code, DetailsResponse.from_json(response.body)}
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -3,7 +3,7 @@ module Wince
|
||||
|
||||
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")
|
||||
end
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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
|
||||
include Gtk::WidgetTemplate
|
||||
|
||||
|
||||
24
src/modules/templates/hourrow.cr
Normal file
24
src/modules/templates/hourrow.cr
Normal 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
|
||||
@@ -3,16 +3,18 @@ require "time" # yeah me too
|
||||
module Wince::Utils
|
||||
extend self
|
||||
|
||||
def hours_for_day(hours_json : JSON::Any, day : Time::DayOfWeek)
|
||||
open = hours_json[0]["open"].as_a
|
||||
@@config_path = Path.home.join("/.config/wince/api_key")
|
||||
@@api_key = ""
|
||||
|
||||
def hours_for_day(hours : Yelp::Hours, day : Time::DayOfWeek, seperator : String)
|
||||
day_number = day_of_week_to_int(day)
|
||||
|
||||
formatted_hours = open.select { |hour| hour["day"].as_i == day_number }.map { |hour|
|
||||
start_hour = hour["start"].as_s
|
||||
end_hour = hour["end"].as_s
|
||||
formatted_hours = hours.open.select { |hour| hour.day == day_number }.map { |hour|
|
||||
start_hour = hour.open
|
||||
end_hour = hour.close
|
||||
|
||||
"#{start_hour.insert(2, ":")}-#{end_hour.insert(2, ":")}"
|
||||
}.join(", ")
|
||||
}.join(seperator)
|
||||
end
|
||||
|
||||
def day_of_week_to_int(day : Time::DayOfWeek)
|
||||
@@ -34,11 +36,23 @@ module Wince::Utils
|
||||
end
|
||||
end
|
||||
|
||||
def format_address(display_address_json : JSON::Any)
|
||||
display_address_json.as_a.map { |line| line.as_s? || "" }.join("\n")
|
||||
def format_address(display_address : Array(String))
|
||||
display_address.join("\n")
|
||||
end
|
||||
|
||||
def load_url_to_image(url : String, image : Gtk::Image)
|
||||
|
||||
def api_key_exists?
|
||||
File.exists? @@config_path
|
||||
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
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
require "json"
|
||||
require "time"
|
||||
|
||||
require "../templates/businessrow.cr"
|
||||
require "../templates/hourrow.cr"
|
||||
require "../api/location.cr"
|
||||
require "../api/yelp.cr"
|
||||
require "../utils/utils.cr"
|
||||
|
||||
module Wince
|
||||
@@main_window_id = 0_u32
|
||||
@@business_rows = [] of BusinessRow
|
||||
@@hour_rows = [] of HourRow
|
||||
@@business_ids = [] of String
|
||||
@@marker = nil
|
||||
|
||||
def activate(app : Adw::Application)
|
||||
main_window = APP.window_by_id(@@main_window_id)
|
||||
@@ -19,28 +22,67 @@ module Wince
|
||||
|
||||
@@main_window_id = window.id
|
||||
|
||||
DETAILS_BACK.clicked_signal.connect do
|
||||
BUSINESS_LIST.unselect_all
|
||||
LEAFLET.visible_child = RESULTS_SCROLL
|
||||
end
|
||||
|
||||
SEARCH_BUTTON.clicked_signal.connect do
|
||||
handle_search
|
||||
end
|
||||
|
||||
GEOLOCATE_BUTTON.clicked_signal.connect do
|
||||
handle_geolocate
|
||||
end
|
||||
|
||||
BUSINESS_LIST.row_selected_signal.connect do
|
||||
handle_business_select
|
||||
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
|
||||
end
|
||||
|
||||
def yelp_response_to_business_ids(response : JSON::Any)
|
||||
response["businesses"].as_a.map { |b| b["id"].as_s }
|
||||
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 yelp_response_to_business_rows(response : JSON::Any)
|
||||
response["businesses"].as_a.map do |business|
|
||||
name = business["name"].as_s? || ""
|
||||
rating = business["rating"].as_f32
|
||||
distance = business["distance"].as_f32
|
||||
def handle_geolocate
|
||||
latlon = Location.find_location()
|
||||
|
||||
BusinessRow.new(name, rating, distance)
|
||||
if !latlon.empty?
|
||||
LOCATION_ENTRY.text = "#{latlon[0]}, #{latlon[1]}"
|
||||
end
|
||||
end
|
||||
|
||||
def yelp_response_to_business_ids(businesses : Array(Yelp::Business))
|
||||
businesses.map { |b| b.id }
|
||||
end
|
||||
|
||||
def yelp_response_to_business_rows(businesses : Array(Yelp::Business))
|
||||
businesses.map do |business|
|
||||
BusinessRow.new(business.name, business.rating, business.distance)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -48,7 +90,15 @@ module Wince
|
||||
@@business_rows.each { |row| BUSINESS_LIST.remove(row) }
|
||||
end
|
||||
|
||||
def clear_hour_rows
|
||||
@@hour_rows.each { |row| DETAILS_HOURS_BOX.remove(row) }
|
||||
end
|
||||
|
||||
def handle_search
|
||||
unless Utils.api_key_exists?
|
||||
return
|
||||
end
|
||||
|
||||
search = SEARCH_ENTRY.buffer.text
|
||||
location = LOCATION_ENTRY.buffer.text
|
||||
|
||||
@@ -61,19 +111,23 @@ module Wince
|
||||
LEAFLET.visible = true
|
||||
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 response.body
|
||||
if response.error.is_a? Yelp::Error
|
||||
puts response.error.as(Yelp::Error).description
|
||||
end
|
||||
return #TODO: show a toast here
|
||||
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()
|
||||
@@business_ids = yelp_response_to_business_ids(response_json)
|
||||
@@business_rows = yelp_response_to_business_rows(response_json)
|
||||
@@business_ids = yelp_response_to_business_ids(businesses)
|
||||
@@business_rows = yelp_response_to_business_rows(businesses)
|
||||
@@business_rows.each do |row|
|
||||
BUSINESS_LIST.append(row)
|
||||
end
|
||||
@@ -83,36 +137,63 @@ module Wince
|
||||
index = @@business_rows.index(BUSINESS_LIST.selected_row) || 0
|
||||
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 response.body
|
||||
if response.error.is_a? Yelp::Error
|
||||
puts response.error.as(Yelp::Error).description
|
||||
end
|
||||
return #TODO: show a toast here
|
||||
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 is_open
|
||||
if response.is_open
|
||||
DETAILS_IS_OPEN.markup = "<span foreground=\"green\">open</span>"
|
||||
else
|
||||
DETAILS_IS_OPEN.markup = "<span foreground=\"red\">closed</span>"
|
||||
end
|
||||
|
||||
DETAILS_CURRENT_HOURS.text = Utils.hours_for_day(response_json["hours"], Time.local.day_of_week)
|
||||
DETAILS_PRICING.text = response_json["price"].as_s? || ""
|
||||
DETAILS_ADDRESS.markup = Utils.format_address(response_json["location"]["display_address"])
|
||||
DETAILS_PHONE.text = response_json["display_phone"].as_s? || ""
|
||||
DETAILS_URL.uri = response_json["url"].as_s? || ""
|
||||
DETAILS_CURRENT_HOURS.text = Utils.hours_for_day(response.hours[0], Time.local.day_of_week, ", ")
|
||||
DETAILS_PRICING.text = response.price || ""
|
||||
DETAILS_ADDRESS.markup = Utils.format_address(response.location.display_address)
|
||||
DETAILS_PHONE.text = response.display_phone || "no phone number"
|
||||
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 LEAFLET.folded
|
||||
DETAILS_BACK.visible = true
|
||||
else
|
||||
DETAILS_BACK.visible = false
|
||||
end
|
||||
|
||||
DETAILS_BOX.visible = true
|
||||
LEAFLET.visible_child = DETAILS_BOX
|
||||
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
|
||||
|
||||
APP.activate_signal.connect(->activate(Adw::Application))
|
||||
exit(APP.run(ARGV))
|
||||
end
|
||||
end
|
||||
|
||||
12
src/wince.cr
12
src/wince.cr
@@ -1,4 +1,6 @@
|
||||
require "libadwaita"
|
||||
require "../lib/gi-crystal/src/auto/geoclue-2.0/*"
|
||||
require "../lib/gi-crystal/src/auto/shumate-1.0/*"
|
||||
|
||||
require "./modules/prerequisites.cr"
|
||||
require "./modules/views/main.cr"
|
||||
@@ -8,10 +10,12 @@ module Wince
|
||||
|
||||
SEARCH_ENTRY = Gtk::Entry.cast(B_UI["searchEntry"])
|
||||
LOCATION_ENTRY = Gtk::Entry.cast(B_UI["locationEntry"])
|
||||
GEOLOCATE_BUTTON = Gtk::Button.cast(B_UI["geolocateButton"])
|
||||
SEARCH_BUTTON = Gtk::Button.cast(B_UI["searchButton"])
|
||||
BUSINESS_LIST = Gtk::ListBox.cast(B_UI["searchResults"])
|
||||
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"])
|
||||
DETAILS_BOX = Gtk::Box.cast(B_UI["detailsBox"])
|
||||
DETAILS_TITLE = Gtk::Label.cast(B_UI["detailsTitle"])
|
||||
@@ -21,6 +25,10 @@ module Wince
|
||||
DETAILS_ADDRESS = Gtk::Label.cast(B_UI["detailsAddress"])
|
||||
DETAILS_PHONE = Gtk::Label.cast(B_UI["detailsPhone"])
|
||||
DETAILS_URL = Gtk::LinkButton.cast(B_UI["detailsUrl"])
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user