calculator & benitlux: remove obsolete explicit observer declaration
[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 star_buttons.add but
67 end
68 end
69
70 redef fun on_event(event)
71 do
72 assert event isa ButtonPressEvent
73
74 var sender = event.sender
75 if sender isa StarButton then
76 on_review sender.rating
77 end
78 end
79
80 # Post a user review
81 fun on_review(rating: Int)
82 do
83 var beer_id = beer_info.beer.id
84 (new ReviewAction(app.window, "rest/review?token={app.token}&beer={beer_id}&rating={rating}")).start
85
86 # Update UI
87 var i = 1
88 for but in star_buttons do
89 but.on = i <= rating
90 i += 1
91 end
92 end
93 end
94
95 # Beers pane listing the available beers
96 class BeersWindow
97 super Window
98
99 private var layout = new VerticalLayout(parent=self)
100 private var list_beers = new ListLayout(parent=layout)
101
102 init
103 do
104 if debug then print "BenitluxWindow::init"
105
106 action_list_beers
107 end
108
109 # Send HTTP request to list beers
110 fun action_list_beers
111 do (new ListBeersAction(self, "rest/list?token={app.token}")).start
112 end
113
114 # ---
115 # Customized buttons
116
117 # View to describe and rate a eer
118 class RatingView
119 super View
120
121 autoinit beer, init_rating, parent, enabled
122
123 # Beer id
124 var beer: Beer
125
126 # Previous rating, 0 for none
127 var init_rating: Int
128
129 redef fun parent=(layout) do end
130
131 redef fun enabled=(value) do end
132 end
133
134 # Button with a star, filled or not, for rating beers
135 class StarButton
136 super Button
137
138 autoinit beer, rating, on, parent, enabled
139
140 # Info on the beer to rate
141 var beer: Beer
142
143 # Rating of `beer`
144 var rating: Int
145
146 # Set if the star is filled
147 fun on=(on: Bool) is autoinit do text = if on then "★" else "☆"
148 end
149
150 redef class BeerAndRatings
151 # Text version of the ratings
152 fun rating_text: String
153 do
154 var txt = new Array[String]
155
156 var global = global
157 if global != null and global.count > 0 then
158 txt.add "%0★ %1 reviews".t.format(global.average.to_precision(1), global.count)
159 else txt.add "No reviews yet".t
160
161 var local = followed
162 if local != null and local.count > 0 then
163 txt.add ", friends: %0☆ %1 reviews".t.format(local.average.to_precision(1), local.count)
164 end
165
166 return txt.join
167 end
168 end
169
170 redef class Beer
171 # Capitalize first letter for a prettier display
172 redef fun desc
173 do
174 var desc = super
175 if desc.length == 0 then return desc
176
177 var first_letter = desc.first.to_upper
178 return first_letter.to_s + desc.substring_from(1)
179 end
180 end
181
182 # Comparator of beers
183 class BeerComparator
184 super Comparator
185
186 redef type COMPARED: BeerAndRatings
187
188 redef fun compare(a, b) do return value_of(a) <=> value_of(b)
189
190 private fun value_of(beer: COMPARED): Float
191 do
192 var max = 0.0
193 var value = 0.0
194
195 var rating = beer.user_rating
196 if rating != null then
197 max += 20.0
198 value += rating.to_f * 4.0
199 end
200
201 var followed = beer.followed
202 if followed != null then
203 max += 10.0
204 value += followed.average * 2.0
205 end
206
207 var global = beer.global
208 if global != null then
209 max += 5.0
210 value += global.average
211 end
212
213 return (max - value)/max
214 end
215 end
216
217 # Async request to submit a review
218 class ReviewAction
219 super WindowHttpRequest
220
221 redef fun on_load(res, status)
222 do
223 if intercept_error(res) then return
224 end
225 end
226
227 # Async request to update the beer list
228 class ListBeersAction
229 super WindowHttpRequest
230
231 redef type W: BeersWindow
232
233 redef fun on_load(beers, status)
234 do
235 window.layout.remove window.list_beers
236 window.list_beers = new ListLayout(parent=window.layout)
237
238 if intercept_error(beers) then return
239
240 if not beers isa Array[BeerAndRatings] then
241 app.feedback "Communication Error".t
242 return
243 end
244
245 # Sort beers per preference
246 var comparator = new BeerComparator
247 comparator.sort beers
248
249 # Populate the list
250 for beer_and_rating in beers do
251 var view = new BeerView(beer_and_rating, parent=window.list_beers)
252 end
253 end
254 end
255
256 redef class BestBeerBadge
257 redef fun to_s do return "Favorite beer on the menu.".t
258 end
259
260 redef class FavoriteBeerBadge
261 redef fun to_s do return "Favorite of %0.".t.format(users.join(", ", " & "))
262 end
263
264 redef class SimilarBeerBadge
265 redef fun to_s do return "Similar to %0.".t.format(beers.join(", ", " & "))
266 end
267
268 redef class Array[E]
269 # Pretty compressed list of this list of beer as a pseudo diff
270 #
271 # Require: `self isa Array[BeerAndRatings]`
272 fun beers_to_notification: String
273 do
274 assert self isa Array[BeerAndRatings]
275
276 # Sort beers per preference
277 var comparator = new BeerComparator
278 comparator.sort self
279
280 # Organize the notification line per line
281 # First the new beers, then the fixed one.
282 var lines = new Array[String]
283 var fix_beers = new Array[String]
284 for bar in self do
285 var beer = bar.beer
286 if bar.is_new then
287 lines.add "+ {beer.name}: {beer.desc}"
288 else fix_beers.add beer.name
289 end
290
291 # Show a few fixed beers per line
292 if fix_beers.not_empty then
293 var line = new FlatBuffer
294 line.append "= "
295 for i in fix_beers.length.times, beer in fix_beers do
296
297 if i > 0 then line.append ", "
298
299 var l = line.length + beer.length
300 if l < 42 then # Very approximate width of a notification on Android
301 line.append beer
302 continue
303 end
304
305 lines.add line.to_s
306
307 line = new FlatBuffer
308 line.append "= "
309 line.append beer
310 end
311
312 lines.add line.to_s
313 end
314
315 return lines.join("\n")
316 end
317
318 # Does `self` has a new beer?
319 #
320 # Require: `self isa Array[BeerAndRatings]`
321 fun has_new_beers: Bool
322 do
323 assert self isa Array[BeerAndRatings]
324
325 for beer in self do if beer.is_new then return true
326 return false
327 end
328 end