contrib/benitlux: intro Android apps, pure prototype and adapted
authorAlexis Laferrière <alexis.laf@xymus.net>
Wed, 18 May 2016 15:21:33 +0000 (11:21 -0400)
committerAlexis Laferrière <alexis.laf@xymus.net>
Thu, 19 May 2016 14:16:37 +0000 (10:16 -0400)
Signed-off-by: Alexis Laferrière <alexis.laf@xymus.net>

contrib/benitlux/Makefile
contrib/benitlux/android/res/.gitignore [new file with mode: 0644]
contrib/benitlux/android/res/values/styles.xml [new file with mode: 0644]
contrib/benitlux/art/icon.svg [new file with mode: 0644]
contrib/benitlux/art/notif.svg [new file with mode: 0644]
contrib/benitlux/src/client/android.nit [new file with mode: 0644]
contrib/benitlux/src/client/android_proto.nit [new file with mode: 0644]

index 12c65b3..fd7d3be 100644 (file)
@@ -30,3 +30,35 @@ report: bin/report
 bin/benitlux: $(shell ../../bin/nitls -M src/client/client.nit)
        mkdir -p bin/
        ../../bin/nitc -o bin/benitlux src/client/client.nit -m linux -D benitlux_rest_server_uri=http://$(SERVER)/
+
+# ---
+# Android
+
+# Main icon
+android/res/drawable-hdpi/icon.png:
+       ../inkscape_tools/bin/svg_to_icons art/icon.svg --android --out android/res/
+
+# Notification icon, white only
+android/res/drawable-hdpi/notif.png:
+       ../inkscape_tools/bin/svg_to_icons art/notif.svg --android --out android/res/ --name notif
+
+android-res: android/res/drawable-hdpi/icon.png android/res/drawable-hdpi/notif.png
+
+# Dev / debug app
+android: bin/benitlux.apk
+bin/benitlux.apk: $(shell ../../bin/nitls -M src/client/android.nit) android-res
+       mkdir -p bin/ res/
+       ../../bin/nitc -o $@ src/client/android.nit -m src/client/features/debug.nit \
+               -D benitlux_rest_server_uri=http://$(SERVER)/
+
+# Pure portable prototype, for comparison
+bin/proto.apk: $(shell ../../bin/nitls -M src/client/android_proto.nit) android-res
+       mkdir -p bin/ res/
+       ../../bin/nitc -o $@ src/client/android_proto.nit \
+               -D benitlux_rest_server_uri=http://$(SERVER)/
+
+# Release version
+android-release: $(shell ../../bin/nitls -M src/client/android.nit) android-res
+       mkdir -p bin/ res/
+       ../../bin/nitc -o bin/benitlux.apk src/client/android.nit \
+               -D benitlux_rest_server_uri=http://xymus.net/benitlux/ --release
diff --git a/contrib/benitlux/android/res/.gitignore b/contrib/benitlux/android/res/.gitignore
new file mode 100644 (file)
index 0000000..46ce728
--- /dev/null
@@ -0,0 +1 @@
+drawable*
diff --git a/contrib/benitlux/android/res/values/styles.xml b/contrib/benitlux/android/res/values/styles.xml
new file mode 100644 (file)
index 0000000..c435c5f
--- /dev/null
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+       <color name="item_background">#000000</color>
+</resources>
diff --git a/contrib/benitlux/art/icon.svg b/contrib/benitlux/art/icon.svg
new file mode 100644 (file)
index 0000000..779a3e3
--- /dev/null
@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="512"
+   height="512"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="icon.svg">
+  <defs
+     id="defs4" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="0.7"
+     inkscape:cx="239.85532"
+     inkscape:cy="187.76324"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     inkscape:window-width="1196"
+     inkscape:window-height="1109"
+     inkscape:window-x="2732"
+     inkscape:window-y="1283"
+     inkscape:window-maximized="0" />
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-540.36218)">
+    <path
+       sodipodi:type="arc"
+       style="fill:#000000;fill-opacity:1;stroke:none"
+       id="path2986"
+       sodipodi:cx="270.72089"
+       sodipodi:cy="251.38065"
+       sodipodi:rx="241.42645"
+       sodipodi:ry="241.42645"
+       d="m 512.14734,251.38065 a 241.42645,241.42645 0 1 1 -482.852906,0 241.42645,241.42645 0 1 1 482.852906,0 z"
+       transform="matrix(1.0603643,0,0,1.0603643,-31.062773,529.80711)" />
+    <text
+       xml:space="preserve"
+       style="font-size:394.38067627px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;font-family:Sans"
+       x="120.70995"
+       y="938.47345"
+       id="text2987"
+       sodipodi:linespacing="125%"><tspan
+         sodipodi:role="line"
+         id="tspan2989"
+         x="120.70995"
+         y="938.47345"
+         style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:Webdings;-inkscape-font-specification:Webdings Bold">B</tspan></text>
+    <path
+       transform="matrix(0.96890975,0,0,0.96890975,-6.3041178,552.79701)"
+       d="m 512.14734,251.38065 a 241.42645,241.42645 0 1 1 -482.852906,0 241.42645,241.42645 0 1 1 482.852906,0 z"
+       sodipodi:ry="241.42645"
+       sodipodi:rx="241.42645"
+       sodipodi:cy="251.38065"
+       sodipodi:cx="270.72089"
+       id="path2988"
+       style="fill:none;stroke:#ffffff;stroke-width:10.02402973;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       sodipodi:type="arc" />
+  </g>
+</svg>
diff --git a/contrib/benitlux/art/notif.svg b/contrib/benitlux/art/notif.svg
new file mode 100644 (file)
index 0000000..871b01c
--- /dev/null
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="512"
+   height="512"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.5 r10040"
+   sodipodi:docname="icon.svg">
+  <defs
+     id="defs4" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="0.7"
+     inkscape:cx="331.40838"
+     inkscape:cy="413.97896"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     inkscape:window-width="1196"
+     inkscape:window-height="1109"
+     inkscape:window-x="2732"
+     inkscape:window-y="297"
+     inkscape:window-maximized="0" />
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-540.36218)">
+    <text
+       xml:space="preserve"
+       style="font-size:419.40737915px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;font-family:Sans"
+       x="112.12482"
+       y="947.49194"
+       id="text2987"
+       sodipodi:linespacing="125%"><tspan
+         sodipodi:role="line"
+         id="tspan2989"
+         x="112.12482"
+         y="947.49194"
+         style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:Webdings;-inkscape-font-specification:Webdings Bold">B</tspan></text>
+    <path
+       style="font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:10.02402973;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Sans;-inkscape-font-specification:Sans"
+       d="M 256 2.0625 C 115.82007 2.0625 2.0625 115.82007 2.0625 256 C 2.0625 396.17993 115.82007 509.9375 256 509.9375 C 396.17993 509.9375 509.9375 396.17993 509.9375 256 C 509.9375 115.82007 396.17993 2.0625 256 2.0625 z M 256 37.0625 C 376.96769 37.0625 474.9375 135.03232 474.9375 256 C 474.9375 376.96769 376.96769 474.90625 256 474.90625 C 135.03232 474.90625 37.0625 376.96769 37.0625 256 C 37.0625 135.03232 135.03232 37.0625 256 37.0625 z "
+       transform="translate(0,540.36218)"
+       id="path2988" />
+  </g>
+</svg>
diff --git a/contrib/benitlux/src/client/android.nit b/contrib/benitlux/src/client/android.nit
new file mode 100644 (file)
index 0000000..f3391bf
--- /dev/null
@@ -0,0 +1,268 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Android variant improved with platform specific services
+module android is
+       android_manifest_activity """android:theme="@android:style/Theme.DeviceDefault" """
+       android_api_min 16 # For BigTextStyle
+       android_api_target 16
+end
+
+import ::android::portrait
+import ::android::toast
+import ::android::wifi
+import ::android::service::at_boot
+
+import client
+import push
+import checkins
+
+redef class App
+
+       redef fun on_create
+       do
+               super
+
+               # Launch service with app, if it wasn't already launched at boot
+               start_service
+       end
+
+       # Use Android toasts if there is an activity, otherwise fallback on the log
+       redef fun feedback(text)
+       do
+               if activities.not_empty then
+                       app.toast(text.to_s, false)
+               else super
+       end
+
+       # Register to callback `async_wifi_scan_available` when a wifi scan is available
+       private fun notify_on_wifi_scan(context: NativeContext)
+       import async_wifi_scan_available in "Java" `{
+
+               android.content.IntentFilter filter = new android.content.IntentFilter();
+               filter.addAction(android.net.wifi.WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
+               final int final_self = self;
+
+               context.registerReceiver(
+                       new android.content.BroadcastReceiver() {
+                               @Override
+                               public void onReceive(android.content.Context context, android.content.Intent intent) {
+                                       if (intent.getAction().equals(android.net.wifi.WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) {
+                                               App_async_wifi_scan_available(final_self);
+                                       }
+                               }
+                       }, filter);
+       `}
+
+       private fun async_wifi_scan_available do run_on_ui_thread task_on_wifi_scan_available
+
+       private var task_on_wifi_scan_available = new WifiScanAvailable is lazy
+end
+
+redef class Service
+       redef fun on_start_command(intent, flags, id)
+       do
+               app.notify_on_wifi_scan native
+
+               # Check token validity
+               (new PushHttpRequest("push/check_token?token={app.token}")).start
+
+               return start_sticky
+       end
+end
+
+# Task ran on the UI thread when a wifi scan is available
+private class WifiScanAvailable
+       super Task
+
+       redef fun main
+       do
+               jni_env.push_local_frame 4
+               var manager = app.native_context.wifi_manager
+               var networks = manager.get_scan_results
+               var found_ben = false
+               for i in networks.length.times do
+                       jni_env.push_local_frame 4
+                       var net = networks[i]
+                       var ssid = net.ssid.to_s
+
+                       # TODO use BSSID instead
+                       #var bssid = net.bssid.to_s
+                       var target_ssids = ["Benelux"]
+                       if target_ssids.has(ssid) then # and bssid == "C8:F7:33:81:B0:E6" then
+                               found_ben = true
+                               break
+                       end
+                       jni_env.pop_local_frame
+               end
+               jni_env.pop_local_frame
+
+               if found_ben then
+                       app.on_check_in
+               else app.on_check_out
+       end
+end
+
+redef class SectionTitle
+       init do set_text_style(native, app.native_context)
+
+       private fun set_text_style(view: NativeTextView, context: NativeContext) in "Java" `{
+               view.setTextAppearance(context, android.R.style.TextAppearance_Large);
+       `}
+end
+
+redef class ItemView
+       init do set_backgroud(native, app.native_context)
+
+       private fun set_backgroud(view: NativeView, context: NativeContext) in "Java" `{
+               int color = context.getResources().getIdentifier("item_background", "color", context.getPackageName());
+               view.setBackgroundResource(color);
+       `}
+end
+
+# Use Android notifications
+redef fun notify(title, content, id)
+do
+       var service = app.service
+       assert service != null
+       native_notify(service.native, id, title.to_java_string, content.to_java_string)
+end
+
+private fun native_notify(context: NativeService, id: Int, title, content: JavaString)
+in "Java" `{
+       int icon = context.getResources().getIdentifier(
+               "notif", "drawable", context.getPackageName());
+
+       android.app.Notification.BigTextStyle style =
+               new android.app.Notification.BigTextStyle();
+       style.bigText(content);
+
+       android.content.Intent intent = new android.content.Intent(
+               context, nit.app.NitActivity.class);
+       android.app.PendingIntent pendingIntent = android.app.PendingIntent.getActivity(
+               context, 0, intent, android.app.PendingIntent.FLAG_UPDATE_CURRENT);
+
+       android.app.Notification notif = new android.app.Notification.Builder(context)
+               .setContentTitle(title)
+               .setContentText(content)
+               .setSmallIcon(icon)
+               .setAutoCancel(true)
+               .setOngoing(false)
+               .setStyle(style)
+               .setContentIntent(pendingIntent)
+               .setDefaults(android.app.Notification.DEFAULT_SOUND |
+                            android.app.Notification.DEFAULT_LIGHTS)
+               .build();
+
+       android.app.NotificationManager notificationManager =
+         (android.app.NotificationManager)context.getSystemService(android.content.Context.NOTIFICATION_SERVICE);
+
+       notificationManager.notify((int)id, notif);
+`}
+
+
+# Use `RatingBar` as the beer rating control
+redef class BeerView
+       redef fun setup_stars(rating)
+       do
+               var title = "Review %0".t.format(beer_info.beer.name).to_java_string
+               native_setup_stars(app.native_context, top_line_layout.native, rating, title, app.user != null)
+       end
+
+       private fun native_setup_stars(context: NativeContext, layout: NativeViewGroup, rating: Int, title: JavaString, loggedin: Bool)
+       import on_review in "Java" `{
+               // Set an indicator/non-interactive display
+               final android.widget.RatingBar view = new android.widget.RatingBar(
+                       context, null, android.R.attr.ratingBarStyleIndicator);
+               view.setNumStars(5);
+               view.setRating(rating);
+               view.setIsIndicator(true);
+
+               final android.view.ViewGroup.MarginLayoutParams params = new android.view.ViewGroup.MarginLayoutParams(
+                       android.widget.LinearLayout.LayoutParams.WRAP_CONTENT,
+                       android.widget.LinearLayout.LayoutParams.FILL_PARENT);
+               layout.addView(view, params);
+
+               // Make some variables final to used in anonymous class and delayed methods
+               final android.content.Context final_context = context;
+               final long final_rating = rating;
+               final String final_title = title;
+               final boolean final_loggedin = loggedin;
+
+               final int final_self = self;
+               BeerView_incr_ref(self); // Nit GC
+
+               view.setOnTouchListener(new android.view.View.OnTouchListener() {
+                       @Override
+                       public boolean onTouch(android.view.View v, android.view.MotionEvent event) {
+                               if (event.getAction() != android.view.MotionEvent.ACTION_UP) return true;
+
+                               // Don't show dialog if not logged in
+                               if (!final_loggedin) {
+                                       android.widget.Toast toast = android.widget.Toast.makeText(
+                                               final_context, "You must login first to post reviews",
+                                               android.widget.Toast.LENGTH_SHORT);
+                                       toast.show();
+                                       return true;
+                               }
+
+                               // Build dialog with a simple interactive RatingBar
+                               final android.app.AlertDialog.Builder dialog_builder = new android.app.AlertDialog.Builder(final_context);
+                               final android.widget.RatingBar rating = new android.widget.RatingBar(final_context);
+                               rating.setNumStars(5);
+                               rating.setStepSize(1.0f);
+                               rating.setRating(final_rating);
+
+                               // Header bar
+                               int icon = final_context.getResources().getIdentifier("notif", "drawable", final_context.getPackageName());
+                               dialog_builder.setIcon(icon);
+                               dialog_builder.setTitle(final_title);
+
+                               // Rating control
+                               android.widget.LinearLayout l = new android.widget.LinearLayout(final_context);
+                               l.addView(rating, params);
+                               l.setHorizontalGravity(android.view.Gravity.CENTER_HORIZONTAL);
+                               dialog_builder.setView(l);
+
+                               // OK button
+                               dialog_builder.setPositiveButton(android.R.string.ok,
+                                       new android.content.DialogInterface.OnClickListener() {
+                                               public void onClick(android.content.DialogInterface dialog, int which) {
+                                                       dialog.dismiss();
+
+                                                       long r = (long)rating.getRating();
+                                                       view.setRating(r); // Update static control
+                                                       view.invalidate(); // For not refreshing bug
+
+                                                       BeerView_on_review(final_self, r); // Callback
+                                                       BeerView_decr_ref(final_self); // Nit GC
+                                               }
+                                       });
+
+                               // Cancel button
+                               dialog_builder.setNegativeButton(android.R.string.cancel,
+                                       new android.content.DialogInterface.OnClickListener() {
+                                               public void onClick(android.content.DialogInterface dialog, int id) {
+                                                       dialog.cancel();
+                                                       BeerView_decr_ref(final_self); // Nit GC
+                                               }
+                                       });
+
+                               dialog_builder.create();
+                               dialog_builder.show();
+                               return true;
+                       }
+               });
+       `}
+end
diff --git a/contrib/benitlux/src/client/android_proto.nit b/contrib/benitlux/src/client/android_proto.nit
new file mode 100644 (file)
index 0000000..22f0953
--- /dev/null
@@ -0,0 +1,28 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Android variant without modification, pure prototype
+#
+# Usually, compiling with `nitc -m android client.nit` is enough.
+# In this case, for research purposes we set a different `app_namespace`.
+# This allows both the proto and the adaptation to be installed on the same device.
+module android_proto is
+       app_name "Ben Proto"
+       app_namespace "net.xymus.benitlux_proto"
+       android_api_target 16
+end
+
+import ::android
+
+import client