2c09795808b83ac8b8ea34543b4cb9762a034ff0
[nit.git] / contrib / benitlux / src / client / views / beer_views.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 # Window to list beers and other beer-related views
16 module beer_views
17
18 import base
19
20 # View about a beer, with its name, description and rating
21 class BeerView
22 super VerticalLayout
23 super ItemView
24
25 autoinit beer_info, parent
26
27 # Beer information
28 var beer_info: BeerAndRatings
29
30 # Buttons to realize the rating buttons
31 var star_buttons = new Array[StarButton]
32
33 # Layout of the first line with the name and `star_buttons`
34 var top_line_layout = new HorizontalLayout(parent=self)
35
36 init
37 do
38 var lbl_name = new Label(parent=top_line_layout, text=beer_info.beer.name)
39 lbl_name.size = 1.25
40
41 var desc = beer_info.beer.desc
42 if beer_info.is_new then desc += " (New)".t
43 var lbl_desc = new Label(parent=self, text=desc)
44
45 var lbl_stats = new Label(parent=self, text=beer_info.rating_text)
46 lbl_stats.size = 0.5
47
48 var badges = beer_info.badges
49 if badges != null then
50 var lbl_comment = new Label(parent=self, text=badges.join(" "))
51 lbl_comment.size = 0.5
52 end
53
54 var rating = beer_info.user_rating or else 0
55 setup_stars rating
56 end
57
58 # Prepare and display the rating controls
59 fun setup_stars(rating: Int)
60 do
61 var l_stars = new HorizontalLayout(parent=top_line_layout)
62
63 for i in [1..5] do
64 var but = new StarButton(beer_info.beer, i, i <= rating, parent=l_stars)
65 but.size = 1.5
66 but.observers.add self
67 star_buttons.add but
68 end
69 end
70
71 redef fun on_event(event)
72 do
73 assert event isa ButtonPressEvent
74
75 var sender = event.sender
76 if sender isa StarButton then
77 on_review sender.rating
78 end
79 end
80
81 # Post a user review
82 fun on_review(rating: Int)
83 do
84 var beer_id = beer_info.beer.id
85 (new ReviewAction(app.window, "rest/review?token={app.token}&beer={beer_id}&rating={rating}")).start
86
87 # Update UI
88 var i = 1
89 for but in star_buttons do
90 but.on = i <= rating
91 i += 1
92 end
93 end
94 end
95
96 # Beers pane listing the available beers
97 class BeersWindow
98 super Window
99
100 private var layout = new VerticalLayout(parent=self)
101 private var list_beers = new ListLayout(parent=layout)
102
103 init
104 do
105 if debug then print "BenitluxWindow::init"
106
107 action_list_beers
108 end
109
110 # Send HTTP request to list beers
111 fun action_list_beers
112 do (new ListBeersAction(self, "rest/list?token={app.token}")).start
113 end
114
115 # ---
116 # Customized buttons
117
118 # View to describe and rate a eer
119 class RatingView
120 super View
121
122 autoinit beer, init_rating, parent, enabled
123
124 # Beer id
125 var beer: Beer
126
127 # Previous rating, 0 for none
128 var init_rating: Int
129
130 redef fun parent=(layout) do end
131
132 redef fun enabled=(value) do end
133 end
134
135 # Button with a star, filled or not, for rating beers
136 class StarButton
137 super Button
138
139 autoinit beer, rating, on, parent, enabled
140
141 # Info on the beer to rate
142 var beer: Beer
143
144 # Rating of `beer`
145 var rating: Int
146
147 # Set if the star is filled
148 fun on=(on: Bool) is autoinit do text = if on then "★" else "☆"
149 end
150
151 redef class BeerAndRatings
152 # Text version of the ratings
153 fun rating_text: String
154 do
155 var txt = new Array[String]
156
157 var global = global
158 if global != null and global.count > 0 then
159 txt.add "%0★ %1 reviews".t.format(global.average.to_precision(1), global.count)
160 else txt.add "No reviews yet".t
161
162 var local = followed
163 if local != null and local.count > 0 then
164 txt.add ", friends: %0☆ %1 reviews".t.format(local.average.to_precision(1), local.count)
165 end
166
167 return txt.join
168 end
169 end
170
171 redef class Beer
172 # Capitalize first letter for a prettier display
173 redef fun desc
174 do
175 var desc = super
176 if desc.length == 0 then return desc
177
178 var first_letter = desc.first.to_upper
179 return first_letter.to_s + desc.substring_from(1)
180 end
181 end
182
183 # Comparator of beers
184 class BeerComparator
185 super Comparator
186
187 redef type COMPARED: BeerAndRatings
188
189 redef fun compare(a, b) do return value_of(a) <=> value_of(b)
190
191 private fun value_of(beer: COMPARED): Float
192 do
193 var max = 0.0
194 var value = 0.0
195
196 var rating = beer.user_rating
197 if rating != null then
198 max += 20.0
199 value += rating.to_f * 4.0
200 end
201
202 var followed = beer.followed
203 if followed != null then
204 max += 10.0
205 value += followed.average * 2.0
206 end
207
208 var global = beer.global
209 if global != null then
210 max += 5.0
211 value += global.average
212 end
213
214 return (max - value)/max
215 end
216 end
217
218 # Async request to submit a review
219 class ReviewAction
220 super WindowHttpRequest
221
222 redef fun on_load(res, status)
223 do
224 if intercept_error(res) then return
225 end
226 end
227
228 # Async request to update the beer list
229 class ListBeersAction
230 super WindowHttpRequest
231
232 redef type W: BeersWindow
233
234 redef fun on_load(beers, status)
235 do
236 window.layout.remove window.list_beers
237 window.list_beers = new ListLayout(parent=window.layout)
238
239 if intercept_error(beers) then return
240
241 if not beers isa Array[BeerAndRatings] then
242 app.feedback "Communication Error".t
243 return
244 end
245
246 # Sort beers per preference
247 var comparator = new BeerComparator
248 comparator.sort beers
249
250 # Populate the list
251 for beer_and_rating in beers do
252 var view = new BeerView(beer_and_rating, parent=window.list_beers)
253 end
254 end
255 end
256
257 redef class BestBeerBadge
258 redef fun to_s do return "Favorite beer on the menu.".t
259 end
260
261 redef class FavoriteBeerBadge
262 redef fun to_s do return "Favorite of %0.".t.format(users.join(", ", " & "))
263 end
264
265 redef class SimilarBeerBadge
266 redef fun to_s do return "Similar to %0.".t.format(beers.join(", ", " & "))
267 end
268
269 redef class Array[E]
270 # Pretty compressed list of this list of beer as a pseudo diff
271 #
272 # Require: `self isa Array[BeerAndRatings]`
273 fun beers_to_notification: String
274 do
275 assert self isa Array[BeerAndRatings]
276
277 # Sort beers per preference
278 var comparator = new BeerComparator
279 comparator.sort self
280
281 # Organize the notification line per line
282 # First the new beers, then the fixed one.
283 var lines = new Array[String]
284 var fix_beers = new Array[String]
285 for bar in self do
286 var beer = bar.beer
287 if bar.is_new then
288 lines.add "+ {beer.name}: {beer.desc}"
289 else fix_beers.add beer.name
290 end
291
292 # Show a few fixed beers per line
293 if fix_beers.not_empty then
294 var line = new FlatBuffer
295 line.append "= "
296 for i in fix_beers.length.times, beer in fix_beers do
297
298 if i > 0 then line.append ", "
299
300 var l = line.length + beer.length
301 if l < 42 then # Very approximate width of a notification on Android
302 line.append beer
303 continue
304 end
305
306 lines.add line.to_s
307
308 line = new FlatBuffer
309 line.append "= "
310 line.append beer
311 end
312
313 lines.add line.to_s
314 end
315
316 return lines.join("\n")
317 end
318
319 # Does `self` has a new beer?
320 #
321 # Require: `self isa Array[BeerAndRatings]`
322 fun has_new_beers: Bool
323 do
324 assert self isa Array[BeerAndRatings]
325
326 for beer in self do if beer.is_new then return true
327 return false
328 end
329 end