Merge branch 'develop' of http://scm.ujaen.es/softuno/pictogram into fernando_branch

parent 5c9d3a95
Showing with 241 additions and 3350 deletions
...@@ -43,8 +43,14 @@ sails/upload.zip ...@@ -43,8 +43,14 @@ sails/upload.zip
########### ###########
.idea .idea
android/Pictogram/.idea android/Pictogram/.idea
android/Pictogram/app/app.iml android/Pictogram/commonlibrary/commonlibrary.iml
android/Pictogram/tablet/tablet.iml
android/Pictogram/watch/watch.iml
android/Pictogram/Pictogrammar.iml android/Pictogram/Pictogrammar.iml
android/Pictogram/commonlibrary/build
android/Pictogram/tablet/build
android/Pictogram/watch/build
android/Pictogram/build
# OS generated files # # OS generated files #
###################### ######################
......
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in e:\Users\Fernando\AppData\Local\Android\sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
package com.yottacode.net;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.X509TrustManager;
/**
* Created by amontejo on 27/01/16.
*/
public class FakeSSLTrustManager implements X509TrustManager {
@Override
public void checkClientTrusted(final X509Certificate[] arg0,
final String arg1) throws CertificateException {
// TODO Auto-generated method stub
}
@Override
public void checkServerTrusted(final X509Certificate[] arg0,
final String arg1) throws CertificateException {
// TODO Auto-generated method stub
}
@Override
public final X509Certificate[] getAcceptedIssuers() {
// TODO Auto-generated method stub
return null;
}
}
\ No newline at end of file
package com.yottacode.net;
import java.io.InputStream;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import android.util.Log;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
class RelaxedHostNameVerifier implements HostnameVerifier {
public boolean verify(String hostname, SSLSession session) {
return true;
}
}
public class SSLDummyContext {
static SSLContext mySSLContext = null;
public static void init(boolean ssl) {
try {
mySSLContext = SSLContext.getInstance("TLS");
if (!ssl) {
mySSLContext.init(null, new TrustManager[]{new FakeSSLTrustManager()}, new SecureRandom());
HttpsURLConnection.setDefaultHostnameVerifier(new RelaxedHostNameVerifier());
HttpsURLConnection.setDefaultSSLSocketFactory(mySSLContext.getSocketFactory());
} else
mySSLContext.init(null, null, null);
} catch (NoSuchAlgorithmException e) {
Log.e(e.getClass().getName(), e.getClass().getCanonicalName() + ": " + e.getMessage());
} catch (KeyManagementException e) {
Log.e(e.getClass().getName(), e.getClass().getCanonicalName() + ": " + e.getMessage());
}
}
public static SSLContext get() {
return mySSLContext;
}
}
\ No newline at end of file
package com.yottacode.net;
import android.util.Log;
import com.github.nkzawa.socketio.client.Ack;
import com.github.nkzawa.socketio.client.IO;
import com.github.nkzawa.socketio.client.Socket;
import com.github.nkzawa.emitter.Emitter;
import org.json.JSONException;
import org.json.JSONObject;
import java.net.URISyntaxException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import java.security.SecureRandom;
/**
* This listener logs all received answers from the message it is registered to
* It is usually not used
*/
class DefaultListener implements Emitter.Listener {
private String msg;
public DefaultListener(String msg) {
super();
this.msg = msg;
}
@Override
public void call(Object... args) {
Log.d(this.getClass().getName(),"Received message:"+this.msg);
for (Object arg : args)
Log.d(this.getClass().getName(), "Argument:" + arg);
}
}
/**
* Websocket Room based on Socket IO
* @author Fernando Martinez Santiago
* @version 1.0
*/
public class SailsSocketsIO {
private Socket socket=null;
private static final String EMIT_METHOD="post";
/**
* Registers a connection listener to default connection messages
* @param socket
* @param connectListener
*/
private void registerMessages(Socket socket, Emitter.Listener connectListener, Emitter.Listener errorListener) {
socket.on(Socket.EVENT_CONNECT, connectListener)
.on(Socket.EVENT_CONNECT_ERROR, errorListener)
.on(Socket.EVENT_CONNECT_TIMEOUT, errorListener)
.on(Socket.EVENT_ERROR, errorListener)
.on(Socket.EVENT_DISCONNECT, errorListener);
}
/**
* Registers a connection listener to a given message
* @param message
* @param listener
*/
public void registerMessage(String message, Emitter.Listener listener) {
Log.d(this.getClass().getName(), " Listening:"+message);
socket.on(message, listener);
}
public SailsSocketsIO(String sails_socket_io_url, String ptransport, Emitter.Listener connectListener, Emitter.Listener errorListener) {
final String sdk_version= "0.11.0";
final String sdk_module="node";
final String sdk_language="java";
final String transport=ptransport;
final String query_version =
"__sails_io_sdk_version"+"=" + sdk_version + '&' +
"__sails_io_sdk_platform"+"=" + sdk_module+ '&' +
"__sails_io_sdk_language" + '=' + sdk_language;
try {
SSLContext mySSLContext = SSLDummyContext.get();
IO.setDefaultSSLContext(mySSLContext);
IO.Options opts = new IO.Options();
opts.forceNew = true;
opts.reconnection = false;
opts.sslContext = mySSLContext;
opts.secure=true;
opts.query = query_version;
opts.transports = new String[] {transport};
this.socket = IO.socket(sails_socket_io_url, opts);
this.socket.connect();
this.registerMessages(socket, connectListener, errorListener);
} catch (URISyntaxException e) {
Log.e(e.getClass().getName(), e.getClass().getCanonicalName()+": "+e.getMessage() );
}
}
public void emit(String msg, Object params, Ack ack) throws JSONException {
JSONObject obj=new JSONObject().put("url", msg).put("data", params);
Log.d(this.getClass().getName(), "Emitted messsage:" + obj);
socket.emit(this.EMIT_METHOD, obj, ack);
}
public void destroy() {
this.socket.disconnect();
this.socket.off();
}
}
package com.yottacode.net;
import org.json.JSONArray;
import org.json.JSONObject;
/**
* This interface specifies which are the methods to be implemented by classes receiving
* responses to API requests.
*
* Generally, it is extended by Activities from which API requests are performed
*
* @author dofer
*/
public interface iRestapiListener {
public void preExecute();
public void result(JSONArray result);
public void result(JSONObject result);
public void error(Exception e);
}
package com.yottacode.pictogram.action;
import android.util.Log;
import com.yottacode.pictogram.dao.Picto;
import com.yottacode.pictogram.tools.PCBcontext;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedList;
/**
* User actions that happens when the user is online --> they are sent to the server by websockets (Room class)
* It is required, inter alia, to support session state diagram such
* as is depicted at http://scm.ujaen.es/files/note/1042/Estados_y_acciones.pdf
* and http://scm.ujaen.es/softuno/pictogram/wikis/LUActions
* @author Fernando Martinez Santiago
* @version 1.0
* @see Room
*/
public abstract class Action {
protected String type;
/**
* It creates an action for any user, both online and offline actions.
*
* @param type
*/
public Action(String type){
this.type=type;
}
public String get_type() { return this.type;}
public abstract String get_action();
protected JSONObject get_json() {
final String param_id_stu="id_stu";
final String param_id_sup="id_sup";
final String param_id_dev="id_dev";
final String param_timestamp="timestamp";
final Date currentTime = new Date();
SimpleDateFormat datetime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSZ");
try {
JSONObject jsonObject = new JSONObject()
.put(param_id_stu, PCBcontext.getPcbdb().getCurrentUser().get_id_stu())
.put(param_timestamp, datetime.format(currentTime));
Log.d("TIMESTAMP-----------> ", datetime.format(currentTime));
if (PCBcontext.getPcbdb().getCurrentUser().has_supervisor())
jsonObject.put(param_id_sup,PCBcontext.getPcbdb().getCurrentUser().get_id_sup());
//TODO Decidir qué almacenar con DEVICE
//if (PCBcontext.getDevice().getDeviceID()!=null)
//jsonObject.put(param_id_dev, PCBcontext.getDevice().getDeviceID());
return jsonObject;
}catch(JSONException e) {
Log.e(this.getClass().getCanonicalName(),e.getMessage());
return null;
}
}
/**
*
* @return the whole description of the action, including the action type (required for sending batch actions,
* more info at http://scm.ujaen.es/softuno/pictogram/wikis/RestapivalidActionsBatch)
*/
public JSONObject getDescription() {
final String param_action= "action";
final String param_attributes= "attributes";
try {
return (new JSONObject().put(param_action, this.type)
.put(param_attributes, this.get_json()));
} catch (JSONException e) {
Log.e(this.getClass().getCanonicalName(),e.getMessage());
}
return null;
}
}
\ No newline at end of file
package com.yottacode.pictogram.action;
import android.util.Log;
import com.yottacode.net.iRestapiListener;
import com.yottacode.pictogram.dao.Picto;
import com.yottacode.pictogram.tools.PCBcontext;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.Hashtable;
import java.util.Vector;
/**
* Created by emblanco on 5/10/15.
* Modified by Fernando on 10/11/15
* More info at http://scm.ujaen.es/softuno/pictogram/wikis/LUActions
*/
public class ActionLog implements iRestapiListener {
Vector<JSONObject> actions_buffer;
public ActionLog() {
actions_buffer=PCBcontext.getPcbdb().loadActions();
if (PCBcontext.getNetService().online()) batch();
}
// String constant for logs
private static final String LOG_TAG = ActionLog.class.getCanonicalName();
/**
* Online action. It is send by using web sockets
* @param action
*/
public void log(Action action) {
if (PCBcontext.getRoom().inRoom())
PCBcontext.getRoom().emit(action);
else {
// If there is no room, the action is stored into the local DB
PCBcontext.getPcbdb().insertAction(action);
actions_buffer.add(action.getDescription());
Log.i(this.getClass().getCanonicalName(), " Batch action included: " + action.getDescription());
}
}
public void batch() {
if (!actions_buffer.isEmpty()) {
Hashtable<String, String> params=new Hashtable<>(1);
String url="stu/actions_batch";
JSONArray actions= new JSONArray();
for (JSONObject action: actions_buffer)
actions.put(action);
//actions= "{actions: [" + actions.substring(1) + "]}";
try {
params.put("json",new JSONObject().put("actions",actions).toString());
} catch (JSONException e) {
e.printStackTrace();
Log.e(this.getClass().getCanonicalName(), " Batch action error: " + e.getMessage());
}
Log.i(this.getClass().getCanonicalName()," Sending batch actions: "+url+": "+actions);
PCBcontext.getRestapiWrapper().ask(url, params, "post", true, this);
}
}
@Override
public void preExecute() {
}
@Override
public void result(JSONArray result) {
}
@Override
public void result(JSONObject result) {
try {
Log.i(this.getClass().getName(),
result.has("total") ? "Uploaded actions (batch):" + result.getString("total")
: "Error uploading actions. Message from server: "+result.getString("error"));
}catch (JSONException e) {
Log.e(this.getClass().getCanonicalName(),e.getLocalizedMessage());
}
PCBcontext.getPcbdb().deleteActions();
actions_buffer.clear();
}
@Override
public void error(Exception e) {
Log.e(this.getClass().getCanonicalName(),"Error:"+e.getMessage()+" on actions="+this.actions_buffer);
}
}
package com.yottacode.pictogram.action;
import android.util.Log;
import com.yottacode.pictogram.dao.Picto;
import com.yottacode.pictogram.tools.PCBcontext;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedList;
/**
* User actions regarding a pictogram that happens when the user is online --> they are sent to the server by websockets (Room class)
* It is required, inter alia, to support session state diagram such
* as is depicted at http://scm.ujaen.es/files/note/1042/Estados_y_acciones.pdf
* and http://scm.ujaen.es/softuno/pictogram/wikis/LUActions
* @author Fernando Martinez Santiago
* @version 1.0
* @see Room
*/
public abstract class PictoAction extends Action {
private Picto picto;
private float gps_lat;
private float gps_lon;
/**
* It creates an action for any user regarding a picto, both online and offline actions.
*
* @param type
* @param picto
*/
public PictoAction(String type, Picto picto){
super(type);
this.picto=picto;
}
@Override
public JSONObject get_json(){
final String param_picto="picto";
try {
return super.get_json().put(param_picto, this.get_json_picto());
}catch(JSONException e) {
Log.e(this.getClass().getCanonicalName(),e.getLocalizedMessage());
return null;
}
}
public JSONObject get_json_picto() throws JSONException {
final String param_id_json="id";
final String param_picto="picto";
final String param_expression="expression";
final String param_expr_lang="lang";
final String param_expr_text="text";
final String param_attrs="attributes";
final String param_picto_id="id";
final String param_picto_uri="uri";
final String param_picto_cat="category";
JSONObject subsubPicto = new JSONObject().put(param_picto_id, picto.get_id())
.put(param_picto_uri, picto.get_url())
.put(param_picto_cat, picto.get_category());
JSONObject attributes = new JSONObject(picto.get_json_attrs());
JSONObject expression = new JSONObject().put(param_expr_lang,PCBcontext.getPcbdb().getCurrentUser().get_lang_stu())
.put(param_expr_text,picto.get_translation());
JSONObject subPicto = new JSONObject().put(param_id_json, 1470)
.put(param_picto, subsubPicto)
.put(param_expression,expression)
.put(param_attrs, attributes);
return subPicto;
}
}
\ No newline at end of file
package com.yottacode.pictogram.action;
import android.util.Log;
import com.yottacode.pictogram.dao.Picto;
import com.yottacode.pictogram.tools.PCBcontext;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedList;
/**
* User actions regarding a pictogram that happens when the user is online --> they are sent to the server by websockets (Room class)
* It is required, inter alia, to support session state diagram such
* as is depicted at http://scm.ujaen.es/files/note/1042/Estados_y_acciones.pdf
* and http://scm.ujaen.es/softuno/pictogram/wikis/LUActions
* @author Fernando Martinez Santiago
* @version 1.0
* @see Room
*/
public class PictosAction extends Action {
LinkedList<Picto> pictos;
private float gps_lat;
private float gps_lon;
//Pictos Action types
private static final String SHOW="Show";
private static final String ACTION="/stu/action";
/**
* It creates an action for any user regarding a set of pictos, both online and offline actions.
*
* @param pictos
*/
public PictosAction( LinkedList<Picto> pictos){
super(SHOW);
this.pictos=pictos;
}
@Override
public JSONObject get_json() {
final String param_pictos="pictos";
try{
JSONArray ja = new JSONArray();
for(Picto picto : pictos) {
JSONObject jsonpicto = get_json_picto(picto);
ja.put(jsonpicto);
}
return super.get_json().put(param_pictos, ja);
} catch (JSONException e) {
Log.e(PictosAction.class.getCanonicalName(),e.getMessage());
return null;
}
}
private JSONObject get_json_picto(Picto picto) throws JSONException {
final String param_id_json="id";
final String param_picto="picto";
final String param_attrs="attributes";
final String param_picto_id="id";
final String param_picto_uri="uri";
final String param_picto_cat="category";
JSONObject subsubPicto = new JSONObject().put(param_picto_id, picto.get_id())
.put(param_picto_uri, picto.get_url())
.put(param_picto_cat, picto.get_category());
JSONObject attributes = new JSONObject(picto.get_json_attrs());
// Here add sup, dev...
JSONObject subPicto = new JSONObject().put(param_id_json, 1470)
.put(param_picto, subsubPicto)
.put(param_attrs, attributes);
return subPicto;
}
public String get_action() {return ACTION;}
}
package com.yottacode.pictogram.action;
import android.util.Log;
import com.github.nkzawa.emitter.Emitter;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import com.github.nkzawa.socketio.client.Ack;
import com.yottacode.pictogram.dao.PCBDBHelper;
import com.yottacode.pictogram.dao.Picto;
import com.yottacode.net.SailsSocketsIO;
import com.yottacode.pictogram.tools.PCBcontext;
import java.sql.SQLDataException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.LinkedList;
/**
* Websocket Room based on SailsSocketsIO
* It is required, inter alia, to support session state diagram such
* as is depicted at http://scm.ujaen.es/files/note/1042/Estados_y_acciones.pdf
* and http://scm.ujaen.es/softuno/pictogram/wikis/LUActions
* @author Fernando Martinez Santiago
* @version 1.0
*/
public class Room {
protected SailsSocketsIO socket=null;
protected boolean inRoom=false;
protected Hashtable<String, Emitter.Listener> listeners;
public Room( ) {
listeners=new Hashtable<>();
reconnect();
}
private JSONObject common_data(String action, JSONObject attributes) throws JSONException {
final String action_param="action";
final String attributes_param="attributes";
return new JSONObject().put(action_param, action).put(attributes_param, attributes);
}
public void emit(final Action action) {
String token=PCBcontext.getRestapiWrapper().getToken();
final String token_param="token";
Log.d(this.getClass().getName(), "Action: " + action.get_type() + " / Attributes emitted: " + action.get_json().toString());
try{
this.socket.emit(action.get_action(), this.common_data(action.get_type(), action.get_json()).put(token_param, token), new Ack() {
@Override
public void call(Object... args) {
for (Object arg : args)
Log.d(this.getClass().getName(), "Ack messsage:" + arg);
}
});
} catch (JSONException e) {
Log.e(this.getClass().getCanonicalName(), e.getClass().getCanonicalName() + e.getMessage() + "--" + e.getLocalizedMessage());
}
}
/**
* Reconnect to the room. It is useful if the server connection is lost for a time
*/
public void reconnect() {
final String transport="polling";
if (this.socket!=null) this.socket.destroy();
this.socket=new SailsSocketsIO(PCBcontext.getRestapiWrapper().getServer(), transport, new Emitter.Listener() {
@Override
public void call(Object... args) {
Log.i(this.getClass().getName(), "Reconnect successful");
subscribe();
listen_again();
inRoom=true;
}
}, new Emitter.Listener() {
@Override
public void call(Object... args) {
Log.i(this.getClass().getName(), "Reconnect unsuccesful: "+args[0]);
inRoom=false;
}
});
}
public boolean inRoom() {return this.inRoom;}
public void listen(String msg, Emitter.Listener listener) {
this.socket.registerMessage(msg, listener);
this.listeners.put(msg, listener);
}
private void listen_again() {
for (String msg: this.listeners.keySet()) {
Log.i(this.getClass().getName(), "Listening to "+msg);
this.socket.registerMessage(msg, this.listeners.get(msg));
}
}
void subscribe() {
try {
SubscribeAction action = new SubscribeAction();
this.emit(action);
}catch (Exception e) {
Log.e(this.getClass().getCanonicalName(), e.getMessage());
}
}
public void finalize() throws java.lang.Throwable {
super.finalize();
if (this.socket!=null) this.socket.destroy();
}
}
package com.yottacode.pictogram.action;
import com.yottacode.pictogram.dao.Picto;
/**
* User actions regarding a pictogram that happens when the user is online --> they are sent to the server by websockets (Room class)
* It is required, inter alia, to support session state diagram such
* as is depicted at http://scm.ujaen.es/files/note/1042/Estados_y_acciones.pdf
* and http://scm.ujaen.es/softuno/pictogram/wikis/LUActions
* @author Fernando Martinez Santiago
* @version 1.0
* @see Room
*/
public class SubscribeAction extends Action {
//Picto Action types
public static final String SUBSCRIBE="subscribe";
public static final String ACTION="/stu/subscribe";
public SubscribeAction( ){
super(SUBSCRIBE);
}
public String get_action() {return ACTION;}
}
\ No newline at end of file
package com.yottacode.pictogram.action;
import android.util.Log;
import com.yottacode.pictogram.dao.Picto;
import com.yottacode.pictogram.tools.PCBcontext;
import org.json.JSONException;
import org.json.JSONObject;
/**
* User actions regarding a pictogram that happens when the user is online --> they are sent to the server by websockets (Room class)
* It is required, inter alia, to support session state diagram such
* as is depicted at http://scm.ujaen.es/files/note/1042/Estados_y_acciones.pdf
* and http://scm.ujaen.es/softuno/pictogram/wikis/LUActions
* @author Fernando Martinez Santiago
* @version 1.0
* @see Room
*/
public class TalkAction extends PictoAction {
//Picto Action types
public static final String ADD="Add";
public static final String DELETE="Delete";
public static final String SELECT="Select";
private static final String ACTION="/stu/action";
public TalkAction(String type, Picto picto){
super(type, picto);
}
public String get_action() {return ACTION;}
}
\ No newline at end of file
package com.yottacode.pictogram.action;
import android.util.Log;
import com.yottacode.pictogram.dao.Picto;
import com.yottacode.pictogram.tools.PCBcontext;
import org.json.JSONException;
import org.json.JSONObject;
/**
* User actions regarding a pictogram that happens when the user is online --> they are sent to the server by websockets (Room class)
* It is required, inter alia, to support session state diagram such
* as is depicted at http://scm.ujaen.es/files/note/1042/Estados_y_acciones.pdf
* and http://scm.ujaen.es/softuno/pictogram/wikis/LUActions
* @author Fernando Martinez Santiago
* @version 1.0
* @see Room
*/
public class VocabularyAction extends PictoAction {
//Picto Action types
public static final String ADD="add";
public static final String ALTERATTRS="update";
private static final String ACTION="/stu/vocabulary";
public VocabularyAction(String type, Picto picto){
super(type, picto);
}
public String get_action() {return ACTION;}
}
\ No newline at end of file
package com.yottacode.pictogram.dao;
/**
* Created by Fernando on 15/03/2016.
*/
public class LoginException extends Exception{
public static final int BAD_LOGIN=1;
public static final int NO_STUDENTS=2;
public static final int TIME_OUT=3;
int code;
public LoginException(String msg, int code) {
super(msg);
this.code=code;
}
public boolean login_failed() {return this.code==LoginException.BAD_LOGIN;}
public boolean no_supervisor_students() {return this.code==LoginException.NO_STUDENTS;}
public boolean time_out() {return this.code==LoginException.TIME_OUT;}
public String getLocalizedMessage() {
return super.getLocalizedMessage()+" Login ok:"+!login_failed()+" Supervisor without students:"+no_supervisor_students();
}
}
package com.yottacode.pictogram.dao;
import java.io.IOException;
import org.json.JSONObject;
import org.json.JSONException;
import android.content.Context;
import android.graphics.Bitmap;
import com.yottacode.pictogram.tools.Img;
import com.yottacode.pictogram.grammar.Vocabulary;
/**
* A user of the PCB. A user can be an Student or a Supervisor using a student configuration
* *
* @author Fernando Martinez Santiago
* @version 1.1
*/
public class User {
public static final int NO_SUPERVISOR = -1;
public boolean has_supervisor() {
return this.get_id_sup()!=User.NO_SUPERVISOR;
}
public final static class JSON_STUDENT_ATTTRS{
static String CATEGORIES = "categories";
static String INPUT_FEEDBACK = "input feedback";
static String INPUT_SELECTION = "input selection";
static String PICTOGRAM_SIZE = "pictogram size";
static String TTS_ENGINE = "tts engine";
static String TTS_VOICE = "tts voice";
}
private Img img_stu;
private String nickname_stu, pwd_stu, name_stu, surname_stu, gender_stu, lang_stu;
private JSONObject attributes_stu;
private Img img_sup;
private String email_sup, pwd_sup, name_sup, surname_sup, gender_sup, lang_sup, tts_engine_sup;
public User(int id_stu, String nickname_stu, String pwd_stu, String name_stu, String surname_stu, String url_img_stu, String gender_stu, String lang_stu, String attributes_stu) throws JSONException {
this(id_stu, nickname_stu, pwd_stu, name_stu, surname_stu, url_img_stu, gender_stu, lang_stu, attributes_stu,User.NO_SUPERVISOR,"","","","","","M","es-es",null);
}
public User(int id_stu, String nickname_stu, String pwd_stu, String name_stu, String surname_stu, String url_img_stu, String gender_stu, String lang_stu, String attributes_stu,
int id_sup, String email_sup, String pwd_sup, String name_sup, String surname_sup, String url_img_sup, String gender_sup, String lang_sup, String tts_engine_sup) throws JSONException {
this.img_stu=new Img(id_stu,url_img_stu,Img.STUDENT);
this.nickname_stu=nickname_stu;
this.pwd_stu=pwd_stu;
this.name_stu=name_stu;
this.surname_stu=surname_stu;
this.gender_stu=gender_stu;
this.lang_stu=lang_stu;
this.attributes_stu= attributes_stu!=null && attributes_stu.trim().length()>0 ? new JSONObject(attributes_stu) : new JSONObject();
this.img_sup=new Img(id_sup,url_img_sup,Img.SUPERVISOR);
this.email_sup=email_sup;
this.pwd_sup=pwd_sup;
this.name_sup=name_sup;
this.surname_sup=surname_sup;
this.gender_sup=gender_sup;
this.lang_sup=lang_sup;
this.tts_engine_sup=tts_engine_sup;
}
/**
*
* @return id of the student
*/
public int get_id_stu() {return this.img_stu.get_id();}
/**
*
* @return nickname (username) of the student
*/
public String get_nickname_stu() {return this.nickname_stu;}
/**
*
* @return pwd of the student
*/
public String get_pwd_stu() {return this.pwd_stu;}
/**
* @ return the RESTAPI operator for the current student
*/
public String get_restapi_operation_stu() {
return "stu/"+this.get_id_stu();
}
/**
*
* @return Img of the student
*/
public Img get_img_stu() {return this.img_stu;}
/**
*
* @return name of the student
*/
public String get_name_stu() {return this.name_stu;}
/**
*
* @return surname of the student
*/
public String get_surname_stu() {return this.surname_stu;}
/**
*
* @return url of the picture of the student profile
*/
public String get_url_img_stu() {return this.img_stu.get_url();}
/**
*
* @return Img of the student
*/
public Bitmap get_bitmap_stu(Context context) throws IOException {return this.img_stu.get_bitmap(context);}
/**
*
* @return Img of the student
*/
public Img get_Img_stu() {return this.img_stu;}
/**
*
* @return gender of the student
*/
public String get_gender_stu() {return this.gender_stu;}
/**
*
* @return language of the student
*/
public String get_lang_stu() {return this.lang_stu;}
/**
*
* @return id of the supervisor
*/
public int get_id_sup() {return this.img_sup.get_id();}
/**
*
* @return nickname (username) of the student
*/
public String get_email_sup() {return this.email_sup;}
/**
*
* @return pwd of the student
*/
public String get_pwd_sup() {return this.pwd_sup;}
/**
*
* @return name of the supervisor
*/
public String get_name_sup() {return this.name_sup;}
/**
*
* @return surname of the supervisor
*/
public String get_surname_sup() {return this.surname_sup;}
/**
*
* @return url of image of the supervisor profile
*/
public String get_url_img_sup() {return this.img_sup.get_url();}
/**
*
* @return Bitmap of the supervisor
*/
public Bitmap get_bitmap_sup(Context context) throws IOException {return this.img_sup.get_bitmap(context);}
/**
*
* @return Img of the supervisor
*/
public Img get_Img_sup() {return this.img_sup;}
/**
*
* @return gender of the supervisor
*/
public String get_gender_sup() {return this.gender_sup;}
/**
*
* @return language of the supervisor
*/
public String get_lang_sup() {return this.lang_sup;}
/**
*
* @return TTS engine of the supervisor
*/
public String get_tts_engine_sup() {return this.tts_engine_sup;}
/**
*
* @return a JSON attributes of the user
*/
public String get_json_attrs() {
return this.attributes_stu.toString();
}
/**
*
* @return a JSON attribute of the student or NULL if the given attribute is not available
*/
public String get_json_attr(String attr) {
try {
return this.attributes_stu.getString(attr);
} catch (JSONException e) {
return null;
}
}
/**
*
* @return true if the collection is organized by categories (default: True)
*/
public boolean has_categories() {
return this.attributes_stu.optBoolean(JSON_STUDENT_ATTTRS.CATEGORIES, true);
}
/**
*
* @return input feedback of the student configuration (default: "vibration")
*/
public String get_input_feedback() {
try {
return this.attributes_stu.getString(JSON_STUDENT_ATTTRS.INPUT_FEEDBACK);
} catch (JSONException e) {
return "vibration";
}
}
/**
*
* @return input selection of the student configuration (default: "click")
*/
public String get_input_selection() {
try {
return this.attributes_stu.getString(JSON_STUDENT_ATTTRS.INPUT_SELECTION);
} catch (JSONException e) {
return "click";
}
}
/**
*
* @return pictogram size of the student configuration (default: "normal")
*/
public String get_pictogram_size() {
try {
return this.attributes_stu.getString(JSON_STUDENT_ATTTRS.PICTOGRAM_SIZE);
} catch (JSONException e) {
return "normal";
}
}
public boolean is_supervisor() {
return get_id_sup()!=User.NO_SUPERVISOR;
}
/**
*
* @overide
* Two user are equals whenever they have the same student and supervisor id
*/
public boolean equals(Object o) {
return ((User)o).img_stu.get_id()==this.img_stu.get_id() && ((User)o).img_sup.get_id()==this.img_sup.get_id();
}
}
\ No newline at end of file
package com.yottacode.pictogram.grammar;
import android.util.Log;
import com.yottacode.pictogram.dao.Picto;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedList;
/**
* PCB Vocabulary manager
* @author Fernando Martinez Santiago
* @version 1.0
*/
public class VocabularyIterator implements Iterator<Picto> {
Enumeration<Integer> keys;
Hashtable<Integer,LinkedList<Picto>> pictos;
Integer category;
int location;
VocabularyIterator(Hashtable<Integer,LinkedList<Picto>> pictos) {
this.keys = pictos.keys();
if (pictos.size()>1) {
this.pictos=pictos;
this.category = this.keys.nextElement();
}
else {
this.category=-1;
this.pictos=new Hashtable<>(1);
this.pictos.put(this.category, new LinkedList<Picto>());
}
this.location=0;
}
@Override
public boolean hasNext() {
return this.location<pictos.get(category).size() || keys.hasMoreElements();
}
@Override
public Picto next() {
Picto picto;
if ( this.location==pictos.get(category).size()) {
this.location = 0;
this.category = this.keys.nextElement();
}
picto=pictos.get(category).get(location++);
return picto;
}
@Override
public void remove() {
}
}
\ No newline at end of file
package com.yottacode.pictogram.grammar; import android.util.Log; import com.github.nkzawa.emitter.Emitter; import org.json.JSONException;import org.json.JSONObject; import com.yottacode.pictogram.dao.Picto;import com.yottacode.pictogram.action.Room; /** * Websocket Vocabulary Room based on Room * @author Fernando Martinez Santiago * @version 1.0 */public class VocabularyTalk implements Emitter.Listener { private static final String URL ="vocabulary"; private Room room; iVocabularyListener listeners[]; public VocabularyTalk(Room room, iVocabularyListener listeners[]) { this.room = room; this.room.listen(URL, this); this.listeners=listeners; } @Override public void call(Object... args) { final String param_action="action"; final String param_attributes="attributes"; final String param_picto="picto"; final String param_stu_picto="stu_picto"; final String param_picto_id="id"; final String param_picto_cat="id_cat"; final String action_update="update"; final String action_add="add"; final String action_delete="delete"; JSONObject msg = (JSONObject) args[0]; try { Log.i(this.getClass().getName(), "raw Received message " +msg.toString()); String action = msg.getString(param_action).toLowerCase(); JSONObject stu_picto= msg.getJSONObject(param_attributes).getJSONObject(param_stu_picto); JSONObject attrs_stu_picto = stu_picto.optJSONObject(param_attributes); JSONObject attrs_picto = stu_picto.optJSONObject(param_picto); int picto_id = attrs_picto.getInt(param_picto_id); int picto_cat = attrs_stu_picto!=null ? attrs_stu_picto.optInt(param_picto_cat, Picto.NO_CATEGORY) : 0; Log.i(this.getClass().getName(), "Received message '" + action + "' for picto " + picto_id + " (cat " + picto_cat + ", attrs: " + attrs_picto); for (iVocabularyListener listener: this.listeners) listener.change(action.equals(action_update) ? iVocabularyListener.action.update : action.equals(action_add) ? iVocabularyListener.action.add : iVocabularyListener.action.delete , picto_cat, picto_id, stu_picto); } catch (JSONException e) { Log.e(this.getClass().getCanonicalName(), e.getClass().getCanonicalName() + "--" + e); } }}
\ No newline at end of file
package com.yottacode.pictogram.grammar;
import com.yottacode.pictogram.dao.Picto;
/**
* Created by Fernando on 14/03/2016.
*/
public interface iLocalPicto {
public void saved(Picto localPicto);
}
package com.yottacode.pictogram.grammar;
import com.yottacode.pictogram.dao.Picto;
import org.json.JSONArray;
import org.json.JSONObject;
import java.util.Hashtable;
import java.util.LinkedList;
/**
* Vocabulary Listener
* @author Fernando Martinez Santiago
* @version 1.0
*/
public interface iVocabularyListener {
public enum action {delete,add,update}
public void change(iVocabularyListener.action action, int picto_cat, int picto_id, JSONObject args);
}
package com.yottacode.pictogram.gui;
import android.app.Activity;
import android.graphics.Bitmap;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import com.yottacode.pictogram.R;
import java.util.Vector;
/**
* Creates a View for each student on the list with a photo and his/her name.
* It uses list_single.xml for the layout creation.
*/
public class CustomList extends ArrayAdapter<String>{
private final Activity context;
private final String[] name_surname;
private final Vector<Bitmap> images;
/**
* @param context Context used for rendering the view
* @param name_surname List of students' names
* @param images List of students' photos
*/
public CustomList(Activity context,
String[] name_surname, Vector<Bitmap> images) {
super(context, R.layout.list_single, name_surname);
this.context = context;
this.name_surname = name_surname;
this.images = images;
}
/**
* @param position Student position in the name_surname/images arrays
* @param view @TODO not being used
* @param parent @TODO not being used
* @return The rendered student view
*/
@Override
public View getView(int position, View view, ViewGroup parent) {
LayoutInflater inflater = context.getLayoutInflater();
View rowView= inflater.inflate(R.layout.list_single, null, true);
TextView txtTitle = (TextView) rowView.findViewById(R.id.loginStudentName);
ImageView imageView = (ImageView) rowView.findViewById(R.id.loginStudentPhoto);
txtTitle.setText(name_surname[position]);
//imageView.setImageResource(R.drawable.user);
imageView.setImageBitmap(images.elementAt(position));
return rowView;
}
}
\ No newline at end of file
package com.yottacode.pictogram.gui;
import android.annotation.TargetApi;
import android.content.Intent;
import android.os.AsyncTask;
import android.support.v4.app.FragmentActivity;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.Window;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import com.yottacode.pictogram.R;
import com.yottacode.pictogram.net.ImgDownloader;
import com.yottacode.pictogram.net.iImgDownloaderListener;
import com.yottacode.pictogram.tools.Img;
import com.yottacode.pictogram.tools.PCBcontext;
import com.yottacode.tools.GUITools;
import java.io.IOException;
import java.util.Vector;
/**
* LoginActivity show the login window to select the student and supervisor
* It uses device to read the token value
*/
public class LoginActivity extends FragmentActivity {
// String constant for logs
private final String LOG_TAG = this.getClass().getSimpleName(); // Or .getCanonicalName()
/**
* If there is Internet connection it calls the WS to recover the pairs student-supervisor
* @param savedInstanceState
*/
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_login);
// Enable logout button
final Button logoutButton = (Button) findViewById(R.id.loginTopbarLogout);
logoutButton.setEnabled(true);
logoutButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent serialActivity = new Intent(getBaseContext(), SerialActivity.class);
serialActivity.putExtra("resetPrevUser", true);
startActivity(serialActivity);
}
});
// Set supervisor information on topbar
final TextView supervisorFullNameView = (TextView) findViewById(R.id.loginTopbarSupervisorFullName);
final TextView supervisorUserNameView = (TextView) findViewById(R.id.loginTopbarSupervisorUserName);
final ImageView supervisorPhotoView = (ImageView) findViewById(R.id.loginTopbarSupervisorPhoto);
supervisorFullNameView.setText(
this.getIntent().getStringExtra("name") + " " +
this.getIntent().getStringExtra("surname"));
supervisorUserNameView.setText(this.getIntent().getStringExtra("email"));
final Vector<Img> imgs = new Vector<>(1);
imgs.add(new Img(
this.getIntent().getIntExtra("sup_id", -1),
this.getIntent().getStringExtra("pic"),
Img.SUPERVISOR
));
ImgDownloader downloader = new ImgDownloader(this, new iImgDownloaderListener() {
@Override
public void loadComplete() {
try {
supervisorPhotoView.setImageBitmap(
imgs.get(0).get_bitmap(PCBcontext.getContext())
);
supervisorPhotoView.invalidate();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void loadImg(Img image) {}
@Override
public void error(Exception e) {
GUITools.show_alert(PCBcontext.getContext(), R.string.serverError, e.getMessage());
Log.e(this.getClass().getCanonicalName(), "Server error:"+ e.getLocalizedMessage());
}
}, ImgDownloader.tsource.remote);
downloader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, imgs);
}
@Override
protected void onResume() {
super.onResume();
}
}
package com.yottacode.pictogram.gui;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.provider.Settings;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Toast;
import com.yottacode.pictogram.R;
import com.yottacode.pictogram.dao.Device;
import com.yottacode.pictogram.dao.LoginException;
import com.yottacode.pictogram.dao.PCBDBHelper;
import com.yottacode.pictogram.dao.User;
import com.yottacode.pictogram.tools.PCBcontext;
import org.json.JSONException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Vector;
public class MainActivity extends Activity {
// String constant for logs
private final String LOG_TAG = this.getClass().getSimpleName(); // Or .getCanonicalName()
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
// For deactivating the lock screen (just before setContentView)
getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
PCBcontext.init(this);
Log.d(LOG_TAG, "PCBcontext iniciado.");
Intent serialActivity = new Intent(this, SerialActivity.class);
serialActivity.putExtra("resetPrevUser", false);
startActivity(serialActivity);
}
@Override
protected void onDestroy() {
super.onDestroy();
PCBcontext.getNetService().closeNotifyStatus();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}
package com.yottacode.pictogram.gui;
import android.annotation.TargetApi;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Build;
import android.os.Bundle;
import android.speech.tts.TextToSpeech;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.FrameLayout;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import com.yottacode.pictogram.R;
import com.yottacode.pictogram.dao.Picto;
import com.yottacode.pictogram.tools.PCBcontext;
import java.io.IOException;
import java.util.LinkedList;
public class PictoGridAdapter extends ArrayAdapter {
private LinkedList<Picto> pictoLinkedList;
private final String LOG_TAG = this.getClass().getSimpleName();
public PictoGridAdapter(LinkedList<Picto> pictoLinkedList){
super(PCBcontext.getContext(), PictoItemViewGenerator.LAYOUT, pictoLinkedList);
this.pictoLinkedList = pictoLinkedList;
}
@Override
public int getCount() {
return this.pictoLinkedList.size();
}
@Override
public Picto getItem(int position) {
return this.pictoLinkedList.get(position);
}
@Override
public long getItemId(int position) {
return 0;
}
public void deleteAll() {
this.pictoLinkedList.clear();
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
return PictoItemViewGenerator.getPictoView(
this.pictoLinkedList.get(position),
convertView,
parent
);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public void ttsPicto(Picto p, TextToSpeech tts) {
if (p.is_enabled()) {
String input = p.get_translation();
Bundle params = new Bundle();
params.putString(TextToSpeech.Engine.KEY_PARAM_VOLUME, "1");
tts.speak(input, TextToSpeech.QUEUE_FLUSH, params, null);
}
}
}
package com.yottacode.pictogram.gui;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import com.yottacode.pictogram.R;
import com.yottacode.pictogram.dao.Picto;
import com.yottacode.pictogram.tools.PCBcontext;
import java.io.IOException;
/**
* This class is used for generating PictoViews which will be inserted inside a picto grid
* or a picto tape.
*/
public class PictoItemViewGenerator {
public static final int LAYOUT = R.layout.picto_grid_item;
public static View getPictoView(Picto picto, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = LayoutInflater.from(parent.getContext()).inflate(LAYOUT, parent, false);
}
RelativeLayout layoutWrapper = (RelativeLayout) convertView.findViewById(R.id.picto_grid_item_layout_wrapper);
FrameLayout layout = (FrameLayout) convertView.findViewById(R.id.picto_grid_item_layout);
ImageView pictoImage = (ImageView) convertView.findViewById(R.id.picto_grid_item_image);
ImageView redCrossImage = (ImageView) convertView.findViewById(R.id.picto_grid_item_redcross);
layoutWrapper.setVisibility(View.GONE);
layoutWrapper.setBackground(null);
layoutWrapper.setAlpha(0.25f);
layout.setBackgroundColor(convertView.getResources()
.getColor(R.color.picto_default_background));
redCrossImage.setVisibility(View.GONE);
pictoImage.setScaleX(1.0f);
pictoImage.setScaleY(1.0f);
pictoImage.setVisibility(View.GONE);
if (picto == null) {
if (PCBcontext.getPcbdb().getCurrentUser().is_supervisor()) {
layoutWrapper.setVisibility(View.VISIBLE);
layoutWrapper.setBackground(convertView.getResources()
.getDrawable(R.drawable.picto_grid_item_border));
}
} else {
if (!picto.is_invisible() && !picto.is_disabled()) {
layoutWrapper.setAlpha(1.00f);
}
try {
pictoImage.setImageBitmap(picto.get_bitmap(PCBcontext.getContext()));
if (!picto.is_invisible() || PCBcontext.getPcbdb().getCurrentUser().is_supervisor()) {
layoutWrapper.setVisibility(View.VISIBLE);
pictoImage.setVisibility(View.VISIBLE);
layoutWrapper.setBackground(convertView.getResources()
.getDrawable(R.drawable.picto_grid_item_border));
if (picto.is_magnify()) {
pictoImage.setScaleX(1.2f);
pictoImage.setScaleY(1.2f);
}
if (picto.is_disabled()) {
redCrossImage.setVisibility(View.VISIBLE);
}
if (picto.is_category()) {
layout.setBackgroundColor(picto.get_color());
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
return convertView;
}
}
package com.yottacode.pictogram.gui;
import java.util.Timer;
import java.util.TimerTask;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.os.Bundle;
import android.view.Window;
import android.view.WindowManager;
import com.yottacode.pictogram.R;
public class SplashScreenActivity extends Activity {
// Set the duration of the splash screen
private static final long SPLASH_SCREEN_DELAY = 3000;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Set landscape orientation
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
// Hide title bar
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_splash_screen);
TimerTask task = new TimerTask() {
@Override
public void run() {
// Start the next activity
Intent mainIntent = new Intent().setClass(
SplashScreenActivity.this, MainActivity.class);
startActivity(mainIntent);
// Intent pictogramActivity = new Intent().setClass(
// SplashScreenActivity.this, PictogramActivity.class);
//pictogramActivity.putExtra();
// seguir: hay que pasarle el estudiante y el supervisor, para cargar la colección, etc...
// startActivity(pictogramActivity);
// Close the activity so the user won't able to go back this
// activity pressing Back button
finish();
}
};
// Simulate a long loading process on application startup.
Timer timer = new Timer();
timer.schedule(task, SPLASH_SCREEN_DELAY);
}
}
\ No newline at end of file
package com.yottacode.pictogram.gui;
import android.annotation.TargetApi;
import android.os.Build;
import android.os.Bundle;
import android.speech.tts.TextToSpeech;
import android.speech.tts.UtteranceProgressListener;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.FrameLayout;
import android.widget.ImageView;
import com.yottacode.pictogram.R;
import com.yottacode.pictogram.dao.Picto;
import com.yottacode.pictogram.tools.PCBcontext;
import java.util.Iterator;
import java.util.LinkedList;
public class TapeAdapter extends BaseAdapter {
//private Context mContext;
private LinkedList<Picto> pictoLinkedList;
public TapeAdapter(){
//mContext = c;
pictoLinkedList = new LinkedList<Picto>(); // the list begins empty
}
@Override
public int getCount(){
return pictoLinkedList.size();
}
public Picto getItem(int position) {
// este método debería devolver el objeto que esta en esa posición del
// adapter.
return pictoLinkedList.get(position);
}
public long getItemId(int position) {
// este método debería devolver el id de fila del item que esta en esa
// posición del adapter. No es necesario en este caso más que devolver 0.
return 0;
}
// AÑADIR ITEM AL ADAPTADOR
public void addItem(Picto p){
pictoLinkedList.add(p);
}
// ELIMINAR ITEM DEL ADAPTADOR
public void deleteItem(int position){
pictoLinkedList.remove(position);
}
// ELIMINAR el último ITEM DEL ADAPTADOR
public void deleteLastView(){
// Controlar excepcion al intentar eliminar el último cuando no hay elementos
try{
pictoLinkedList.removeLast();
}catch(ArrayIndexOutOfBoundsException exception){
Log.e("Excepción", "ArrayIndexOutOfBounds: " + exception.getMessage());
}
}
// ELIMINAR TODOS LOS ITEMS DEL ADAPTADOR
public void deleteAll(){
pictoLinkedList.clear();
}
// DEVUELVE TODOS LOS ELEMENTOS
public LinkedList<Picto> getAll(){ return pictoLinkedList; }
// Devuelvo la cadena actual como un String
public String getAllAsString(){
String complete = "";
Iterator<Picto> iterator = pictoLinkedList.iterator();
while (iterator.hasNext()) {
Picto current = iterator.next();
complete += " " + current.get_translation();
}
return complete;
}
// DEVUELVE último elemento
public Picto getLastItem(){
return pictoLinkedList.getLast();
}
// Devuelve true o false si tiene o no elementos la lista de pictos
public boolean hasElements(){
return (pictoLinkedList.size() > 0);
}
@Override
public View getView(int position, View convertView, ViewGroup parent){
return PictoItemViewGenerator.getPictoView(
this.pictoLinkedList.get(position),
convertView,
parent
);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public void ttsAllNew(TextToSpeech tts) {
String input = getAllAsString();
Bundle params = new Bundle();
params.putString(TextToSpeech.Engine.KEY_PARAM_VOLUME, "1");
params.putString(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, "TAPE_READ");
tts.speak(input, TextToSpeech.QUEUE_FLUSH, params, "TAPE_READ");
}
}
package com.yottacode.pictogram.kiosk;
import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.PowerManager;
/**
* Created by emblanco on 14/10/15.
*/
public class AppContext extends Application {
private AppContext instance;
private PowerManager.WakeLock wakeLock;
private OnScreenOffReceiver onScreenOffReceiver;
@Override
public void onCreate() {
super.onCreate();
instance = this;
registerKioskModeScreenOffReceiver();
startKioskService(); // Service for restarting the app when another app go to foreground
}
private void registerKioskModeScreenOffReceiver() {
// register screen off receiver
final IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
onScreenOffReceiver = new OnScreenOffReceiver();
registerReceiver(onScreenOffReceiver, filter);
}
public PowerManager.WakeLock getWakeLock() {
if(wakeLock == null) {
// lazy loading: first call, create wakeLock via PowerManager.
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
wakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, "wakeup");
}
return wakeLock;
}
// Service for restarting the app when another app go to foreground
private void startKioskService() {
startService(new Intent(this, KioskService.class));
}
}
\ No newline at end of file
package com.yottacode.pictogram.kiosk;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import com.yottacode.pictogram.gui.MainActivity;
/**
* Created by emblanco on 13/10/15.
*
*
* Recieve event when the device boot and starts MainActivity
*
*
*/
public class BootReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Intent myIntent = new Intent(context, MainActivity.class);
myIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(myIntent);
}
}
\ No newline at end of file
package com.yottacode.pictogram.kiosk;
import android.app.ActivityManager;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.util.Log;
import com.yottacode.pictogram.gui.PictogramActivity;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* Created by emblanco on 15/10/15.
*
* Since Android 4 there is no effective method to deactivate the home button.
* That is the reason why we need to do a little hack.
* In general the idea is to detect when a new application is in foreground and restart
* your activity immediately.
*
* The thread checks every two seconds if the app is running in foreground.
* If not, the thread will immediately recreate your activity.
*
*/
public class KioskService extends Service {
private static final long INTERVAL = TimeUnit.SECONDS.toMillis(2); // periodic interval to check in seconds -> 2 seconds
private static final String TAG = KioskService.class.getSimpleName();
private static final String PREF_KIOSK_MODE = "pref_kiosk_mode";
private Thread t = null;
private Context ctx = null;
private boolean running = false;
@Override
public void onDestroy() {
Log.i(TAG, "Stopping service 'KioskService'");
running =false;
super.onDestroy();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG, "Starting service 'KioskService'");
running = true;
ctx = this;
// start a thread that periodically checks if your app is in the foreground
t = new Thread(new Runnable() {
@Override
public void run() {
do {
handleKioskMode();
try {
Thread.sleep(INTERVAL);
} catch (InterruptedException e) {
Log.i(TAG, "Thread interrupted: 'KioskService'");
}
}while(running);
stopSelf();
}
});
t.start();
return Service.START_NOT_STICKY;
}
private void handleKioskMode() {
// is Kiosk Mode active?
if(isKioskModeActive(this)) {
// is App in background?
if(isInBackground()) {
restoreApp(); // restore!
}
}
}
private boolean isInBackground() {
ActivityManager am = (ActivityManager) ctx.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningTaskInfo> taskInfo = am.getRunningTasks(1);
ComponentName componentInfo = taskInfo.get(0).topActivity;
return (!ctx.getApplicationContext().getPackageName().equals(componentInfo.getPackageName()));
}
private void restoreApp() {
// Restart activity
Intent i = new Intent(ctx, PictogramActivity.class);
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
ctx.startActivity(i);
}
public boolean isKioskModeActive(final Context context) {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
return sp.getBoolean(PREF_KIOSK_MODE, false);
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
\ No newline at end of file
package com.yottacode.pictogram.kiosk;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.PowerManager;
import android.preference.PreferenceManager;
/**
* Created by emblanco on 14/10/15.
*
*
Short power button press:
It can detect a short button press by handling the ACTION_SCREEN_OFF intent and kick the screen
back to life with acquiring a wake lock.
*
*
*
*/
public class OnScreenOffReceiver extends BroadcastReceiver {
private static final String PREF_KIOSK_MODE = "pref_kiosk_mode";
@Override
public void onReceive(Context context, Intent intent) {
if(Intent.ACTION_SCREEN_OFF.equals(intent.getAction())){
AppContext ctx = (AppContext) context.getApplicationContext();
// is Kiosk Mode active?
if(isKioskModeActive(ctx)) {
wakeUpDevice(ctx);
}
}
}
private void wakeUpDevice(AppContext context) {
PowerManager.WakeLock wakeLock = context.getWakeLock(); // get WakeLock reference via AppContext
if (wakeLock.isHeld()) {
wakeLock.release(); // release old wake lock
}
// create a new wake lock...
wakeLock.acquire();
// ... and release again
wakeLock.release();
}
private boolean isKioskModeActive(final Context context) {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
return sp.getBoolean(PREF_KIOSK_MODE, false);
}
}
package com.yottacode.pictogram.net;
import android.app.ActivityManager;
import android.content.Context;
import android.os.AsyncTask;
import android.util.Log;
import com.yottacode.pictogram.R;
import com.yottacode.pictogram.tools.Img;
import com.yottacode.pictogram.tools.PCBcontext;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Vector;
/**
* Image downloader from the server or local for pictograms and student's and supervisor's profile pictures.
* Permissions required in AndroidManifest.xml:
* <uses-permission android:name="android.permission.INTERNET" />
*/
public class ImgDownloader extends AsyncTask<Vector<Img>, Void, Img> {
iImgDownloaderListener imgListener;
public static enum status {downloading, downloaded_ok, downloaded_failed}
public static enum tsource{remote,local}
public status current;
private boolean force_download;
Context context;
ActivityManager.MemoryInfo mi;
ActivityManager activityManager;
tsource source;
public ImgDownloader(Context context, iImgDownloaderListener listener, tsource source) {
this.imgListener = listener;
this.context=context;
this.mi = new ActivityManager.MemoryInfo();
this.activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
this.force_download=context.getResources().getBoolean(R.bool.force_img_download);
this.source=source;
}
@Override
protected void onPreExecute() {
}
protected Img doInBackground( Vector<Img> imgs) {
InputStream is=null;
Log.d(this.getClass().getCanonicalName(), "Required images: " + imgs.size());
this.current= ImgDownloader.status.downloading;
int i=0,j=0,allsize=0;
Long availableMegs = mi.availMem / 1048576L;
int seconds = Calendar.getInstance().get(Calendar.SECOND);
try {
for (Img img: imgs) {
if (!img.exists_bitmap(this.context) || this.force_download) try {
this.activityManager.getMemoryInfo(mi);
if (this.source==source.remote) {
String surl = context.getResources().getString(R.string.server) + "/" + img.get_url();
URL url = new URL(surl);
URLConnection ucon = url.openConnection();
is = ucon.getInputStream();
}else {
File file=new File(img.get_url());
is=new FileInputStream(file);
}
int size=img.save_bitmap(this.context, is);
allsize+=size;
i++;
} catch (IOException e) {
j++;
Log.e(this.getClass().getCanonicalName(), img.get_url() + " was not downloaded: "+e);
} finally {
if (is != null) is.close();
}
}
this.current= status.downloaded_ok;
} catch (IOException e) {
Log.d(this.getClass().getCanonicalName(), "Error: " + e);
this.current= status.downloaded_failed;
}
seconds=Calendar.getInstance().get(Calendar.SECOND)-seconds;
Log.i(this.getClass().getCanonicalName(),
"Images Downloaded: " + i+"/"+imgs.size()+ " (size:"+allsize/1024+" Kbytes)"+
". Cached: "+ (imgs.size()-i)+"/"+imgs.size()+
". Download failed: "+ j+"/"+imgs.size()+
". Memory required:"+((mi.availMem / 1048576L)-availableMegs)+" MB"+
". Used time: "+seconds+" seconds at "+new SimpleDateFormat("HH:mm:ss"));
return imgs.size() > 1 ? null
: imgs.get(0);
}
@Override
protected Img doInBackground(Vector... params) {
doInBackground(params[0]);
return null;
}
@Override
protected void onPostExecute(Img img) {
if (imgListener!=null)
if(img == null) imgListener.loadComplete();
else imgListener.loadImg(img);
}
}
package com.yottacode.pictogram.net;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.TaskStackBuilder;
import android.content.Intent;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import com.yottacode.net.RestapiWrapper;
import com.yottacode.net.iRestapiListener;
import com.yottacode.pictogram.R;
import com.yottacode.pictogram.dao.LoginException;
import com.yottacode.pictogram.dao.User;
import com.yottacode.pictogram.gui.PictogramActivity;
import com.yottacode.pictogram.tools.PCBcontext;
import org.json.JSONArray;
import org.json.JSONObject;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* Background services to be executed every "delay" seconds. Tasks to be executed:
* If offline mode (there is no server connection):
* - keep local record of actions
* If online mode:
* - reload vocabulary
* - move records into the server of actions and purge the actions pool
* @author Fernando Martinez Santiago
* @version 1.0
*/
public class NetService implements Runnable {
public static final String PREFS_NAME = "MyPrefsFile";
private static NotificationCompat.Builder builder;
static final String ping_session="server/ping";
private boolean updated;
public NetService(int delay) {
this.updated=RestapiWrapper.ping(PCBcontext.getContext().getResources().getString(R.string.server), ping_session);
this.builder = new NotificationCompat.Builder(PCBcontext.getContext());
Intent resultIntent = new Intent(PCBcontext.getContext(), PictogramActivity.class);
TaskStackBuilder stackBuilder = TaskStackBuilder.create(PCBcontext.getContext());
stackBuilder.addParentStack(PictogramActivity.class);
stackBuilder.addNextIntent(resultIntent);
PendingIntent resultPendingIntent =
stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
builder.setContentIntent(resultPendingIntent);
Log.i(this.getClass().getName(), "Checking Pictogram server access...");
Log.i(this.getClass().getName(), this.updated ? "Pictogram server access ok" : "Pictogram server access failed, Internet connection available?");
ScheduledThreadPoolExecutor exec = new ScheduledThreadPoolExecutor(1);
exec.scheduleWithFixedDelay(this, 0, delay, TimeUnit.SECONDS);
}
public void login() {
User user=PCBcontext.getPcbdb().getCurrentUser();
if (user.is_supervisor())
ServerLogin.login_supervisor(user.get_email_sup(), user.get_pwd_sup(), new iRestapiListener() {
@Override
public void preExecute() {
}
@Override
public void result(JSONArray result) {
PCBcontext.getPcbdb().user_online(true);
}
@Override
public void result(JSONObject result) {
}
@Override
public void error(Exception e) {
Log.e(this.getClass().getSimpleName(), "Error un when server login:" + e.getMessage());
if (e instanceof LoginException) PCBcontext.restart_app();
}
});
else ServerLogin.login_student(user.get_nickname_stu(), user.get_pwd_stu(), new iRestapiListener() {
@Override
public void preExecute() {
}
@Override
public void result(JSONArray result) {
PCBcontext.getPcbdb().user_online(true);
}
@Override
public void result(JSONObject result) {
}
@Override
public void error(Exception e) {
Log.e(this.getClass().getSimpleName(),"Error un when server login:"+e.getMessage());
if (e instanceof LoginException) PCBcontext.restart_app();;
}
});
}
public boolean online() {return updated;}
/**
* ping to the server by using a restapi call. If ok, the call will return the empty set
*/
@Override
public void run() {
PCBcontext.getRestapiWrapper().ask(ping_session, new iRestapiListener() {
@Override
public void preExecute() {
}
@Override
public void result(JSONArray result) {
}
@Override
public void result(JSONObject result) {
if (!updated) {
updated = true;
if (PCBcontext.is_user_logged()) //si el usuario aun no hizo login, en realidad no es necesario hacer nada
// Comprobar si hay usuario offline, para hacer login transparente
if (PCBcontext.is_user_offline()){
login();
} else if (PCBcontext.is_user_online()){
Log.i(this.getClass().getName(), "PCB reconnect");
PCBcontext.getRoom().reconnect();
PCBcontext.getVocabulary().synchronize();
PCBcontext.getActionLog().batch();
}
}
Log.i(this.getClass().getName(), "PCB status checked. Updated? " + updated);
notifyStatus();
}
@Override
public void error(Exception e) {
updated = false;
Log.i(this.getClass().getName(), "PCB offline because exception happens: " + e.getMessage());
notifyStatus();
}
});
}
public void notifyStatus() {
int notifyID = 1;
String user="";
if (PCBcontext.getPcbdb()!=null) {
user=PCBcontext.getPcbdb().getCurrentUser().get_name_stu();
if (PCBcontext.getPcbdb().getCurrentUser().is_supervisor())
user += " (" + PCBcontext.getPcbdb().getCurrentUser().get_name_sup() + ")";
}
if (updated)
builder.setSmallIcon(R.drawable.application_online)
.setContentTitle(PCBcontext.getContext().getResources().getString(R.string.pictogram_online))
.setContentText(user);
else
builder.setSmallIcon(R.drawable.application_offline)
.setContentTitle(PCBcontext.getContext().getResources().getString(R.string.pictogram_offline))
.setContentText(user);
NotificationManager mNotificationManager =
(NotificationManager) PCBcontext.getContext().getSystemService(PCBcontext.getContext().NOTIFICATION_SERVICE);
mNotificationManager.notify(notifyID, builder.build());
}
public void closeNotifyStatus(){
int notifyID = 1;
NotificationManager mNotificationManager =
(NotificationManager) PCBcontext.getContext().getSystemService(PCBcontext.getContext().NOTIFICATION_SERVICE);
mNotificationManager.cancel(notifyID);
}
}
package com.yottacode.pictogram.net;
import android.util.Log;
import com.yottacode.net.RestapiWrapper;
import com.yottacode.net.iRestapiListener;
import com.yottacode.pictogram.dao.LoginException;
import com.yottacode.pictogram.tools.PCBcontext;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.Hashtable;
/**
* Created by Fernando on 01/04/2016.
*/
public class ServerLogin {
public static void login_student(String username, String password, iRestapiListener listener) {
login("stu/login", null, username, password, listener);
}
public static void login_supervisor(String email, String password, iRestapiListener listener) {
login("sup/login", email, null, password, listener);
}
private static void login(String operation, String email, String username, String password, final iRestapiListener listener){
Hashtable<String, String> postDataParams = new Hashtable<String, String>();
if (email!=null) postDataParams.put("email", email);
if (username!=null) postDataParams.put("username", username);
postDataParams.put("password", password);
PCBcontext.getRestapiWrapper().ask(operation, postDataParams, "post", new iRestapiListener() {
@Override
public void preExecute() {
listener.preExecute();
}
@Override
public void result(JSONArray result) {
listener.result(result);
}
@Override
public void result(JSONObject result) {
try {
if (PCBcontext.is_user_offline()) {
final String TAG_TOKEN="token";
PCBcontext.getPcbdb().user_online(true);
PCBcontext.getRestapiWrapper().setToken(result.getString(TAG_TOKEN));
PCBcontext.getVocabulary().synchronize();
}
listener.result(result);
} catch (JSONException e) {
listener.error(e);
}
}
@Override
public void error(Exception e) {
listener.error(new LoginException(e.getMessage(),e.getMessage().contains("User without students")
? LoginException.NO_STUDENTS
: e.getMessage().contains("failed to connect")
? LoginException.TIME_OUT
: LoginException.BAD_LOGIN));
}
});
}
}
package com.yottacode.pictogram.net;
import com.yottacode.pictogram.dao.Picto;
import com.yottacode.pictogram.tools.Img;
import java.util.LinkedList;
/**
* Created by emblanco on 24/09/15.
* MOdified by dofer on 27/06/16.
*/
public interface iImgDownloaderListener {
public void loadComplete(); // for loading the vocabulary
public void loadImg(Img image); // for loading one image
public void error(Exception err); //error happens
}
package com.yottacode.pictogram.net;
/**
* Created by Fernando on 28/07/2016.
*/
public interface iPictoUploaderListener {
void success(boolean success);
}
package com.yottacode.pictogram.tools;
/**
* Created by miguelangelgarciacumbreras on 25/7/15.
*/
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.util.Log;
/**
* Image with support for filtering.
*/
public class FilteredImage {
private Bitmap image, mutableBitmap;
private int width;
private int height;
private Bitmap.Config config;
private int[] colorArray;
/**
* Constructor.
*
* @param img the original image
*/
public FilteredImage(Bitmap img) {
this.image = img;
width = img.getWidth();
height = img.getHeight();
config = img.getConfig();
colorArray = new int[width * height];
image.getPixels(colorArray, 0, width, 0, 0, width, height);
mutableBitmap = image.copy(Bitmap.Config.ARGB_8888, true);
applyHighlightFilter();
//addBorder(5, Color.RED);
}
/**
* Get the color for a specified pixel.
*
* @param x x
* @param y y
* @return color
*/
public int getPixelColor(int x, int y) {
return colorArray[y * width + x];
}
/**
* Gets the image.
*
* @return Returns the image.
*/
public Bitmap getImage() {
return mutableBitmap;
}
/**
* Applies highlight filter to the image.
*/
private void applyHighlightFilter() {
int a;
int r;
int g;
int b;
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
int c = getPixelColor(x, y);
a = Color.alpha(c);
r = Color.red(c);
g = Color.green(c);
b = Color.blue(c);
r = (int) (r * 0.4);
g = (int) (g * 0.4);
b = (int) (b * 1.6);
if (r > 255) {
r = 255;
}
if (r < 0) {
r = 0;
}
if (g > 255) {
g = 255;
}
if (g < 0) {
g = 0;
}
if (b > 255) {
b = 255;
}
if (b < 0) {
b = 0;
}
int resultColor = Color.argb(a, r, g, b);
//Log.e("applyHighlightFilter", "x:" + x + "/y:" + y + "/resultColor:" + resultColor);
mutableBitmap.setPixel(x, y, resultColor);
}
}
}
private void addBorder(int borderSize, int color) {
mutableBitmap = Bitmap.createBitmap(this.width + borderSize * 2, this.height + borderSize * 2, this.config);
Canvas canvas = new Canvas(mutableBitmap);
canvas.drawColor(color);
canvas.drawBitmap(image, borderSize, borderSize, null);
}
}
\ No newline at end of file
package com.yottacode.pictogram.tools;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.util.Log;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import com.yottacode.tools.FileTools;
import com.yottacode.tools.BitmapTools;
/**
* Img
* @author Fernando
*/
public class Img {
static public String VOCABULARY="vocabulary";
static public String STUDENT="student";
static public String SUPERVISOR="supervisor";
static public float MAX_WIDTH=100;
public static final void mkDirs(Context context) {
File file;
file = new File(path(context, Img.VOCABULARY));
FileTools.deleteDirectory(file);
file.mkdirs();
file = new File(path(context, Img.STUDENT));
FileTools.deleteDirectory(file);
file.mkdirs();
file = new File(path(context, Img.SUPERVISOR));
FileTools.deleteDirectory(file);
file.mkdirs();
}
protected int id;
protected String url;
String type;
final static String FILETYPE="png";
Bitmap bitmap;
/**
*
* @param id
* @param url
* @param type is Vocabulary, Student or Supervisor
*/
public Img(int id, String url, String type) {
this.id = id;
this.url = url;
this.type=type;
this.bitmap=null;
}
public String file_name() { return this.id+"."+Img.FILETYPE; }
public int get_id() { return this.id;}
public String get_url() { return this.url;}
public void set_url(String url) {this.url=url;}
public String get_type() { return this.type;}
public String get_filetype() { return Img.FILETYPE;}
/**
* Load if necessary the bitmap from disk, and it is returned. IIf it is not available, return null
* @param context
* @return the bitmap
* @throws FileNotFoundException
*/
public Bitmap get_bitmap(Context context) throws IOException {
if (this.bitmap==null) {
File file = file(context);
if (file.exists()) {
FileInputStream is = new FileInputStream(file);
this.bitmap = BitmapFactory.decodeStream(is);
is.close();
}
}
return this.bitmap;
}
/**
* Delete the bitmap from disk
* @param context
*/
public void delete_bitmap(Context context) {
File file = file(context);
file.delete();
}
/**
*
* @param context
* @return true if the bitmap is locally stored
*/
public boolean exists_bitmap(Context context) {
return file(context).exists();
}
public static String path(Context context, String type) {
String path = context.getFilesDir()+File.separator +"pictures" + File.separatorChar + type + File.separatorChar;
return path;
}
/**
* The local file where the img is saved. It depends on the image type
* @param context
* @return
*/
public File file(Context context) {
String path=path(context, this.type);
return new File(path, file_name());
}
/**
* Save an image in disk
* @param context
* @param is the stream where the image is available
* @throws IOException
*/
public int save_bitmap(Context context, InputStream is) throws IOException {
File file = file(context);
FileOutputStream os = new FileOutputStream(file);
try {
this.bitmap=BitmapFactory.decodeStream(is);
if (this.bitmap.getWidth()>MAX_WIDTH) {
this.bitmap = new BitmapTools(this.bitmap)
.rescale(MAX_WIDTH / (float) this.bitmap.getWidth())
.get();
}
}catch(java.lang.OutOfMemoryError err) {
Log.e(Img.class.getCanonicalName(), "Out of memory when decoding "+this.get_url());
}
ByteArrayOutputStream outstream = new ByteArrayOutputStream();
this.bitmap.setHasAlpha(true);
this.bitmap.compress(Bitmap.CompressFormat.PNG, 50, outstream);
byte[] byteArray = outstream.toByteArray();
os.write(byteArray);
outstream.close();
os.close();
System.gc();
return byteArray.length;
}
/**
*
* @overide
* Two img are equals whenever they have the same id and type
*/
public boolean equals(Object o) {
return ((Img)o).id==this.id && ((Img)o).type.equals(this.type);
}
/**
* @overide
*/
public String toString() {return this.type+"."+this.id;}
}
package com.yottacode.pictogram.tools;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import com.yottacode.net.RestapiWrapper;
import com.yottacode.net.SSLDummyContext;
import com.yottacode.pictogram.R;
import com.yottacode.pictogram.action.ActionLog;
import com.yottacode.pictogram.dao.Device;
import com.yottacode.pictogram.dao.PCBDBHelper;
import com.yottacode.pictogram.dao.User;
import com.yottacode.pictogram.grammar.Vocabulary;
import com.yottacode.pictogram.gui.SerialActivity;
import com.yottacode.pictogram.net.NetService;
import com.yottacode.pictogram.action.Room;
import com.yottacode.pictogram.net.iImgDownloaderListener;
public final class PCBcontext {
private static Context context;
private static PCBDBHelper pcbdb;
private static Device device=null;
private static NetService service;
private static Room room;
private static RestapiWrapper wrapper;
private static Vocabulary vocabulary;
private static ActionLog actionLog;
private static boolean init=false;
/**
* Init method for passing params to the singleton
* @param c @TODO application or activity context?
*/
public static void init(Context c){
if (!init) {
init = true;
context = c;
device = new Device(c, null, 1);
SSLDummyContext.init(context.getResources().getBoolean(R.bool.ssl_connect));
wrapper = new RestapiWrapper(context.getResources().getString(R.string.server), null);
service = new NetService(context.getResources().getInteger(R.integer.netservice_timing));
device.deleteDeprecatedImgs();
Log.i(PCBcontext.class.getCanonicalName(), "PCB context started. It's required" +
"set_user method call");
} else {
Log.e(PCBcontext.class.getClass().getCanonicalName(), "Init method was previously" +
"invoked! Please, check your code");
}
}
/**
* @param student
* @param listener
*/
public static void set_user(User student, String token, iImgDownloaderListener listener) {
if (!init) {
throw new java.lang.AssertionError("init must be called once previously ");
}
Log.i(PCBcontext.class.getCanonicalName(), "User set at student " + student.get_name_stu());
wrapper.setToken(token);
pcbdb = new PCBDBHelper(null, 1, student);
pcbdb.user_online(token!=null);
room = new Room();
actionLog = new ActionLog();
vocabulary = new Vocabulary(listener);
getNetService().notifyStatus();
}
/**
*
* @return true if a given user has been logged into the system (the login window was successfully filled)
*/
public static boolean is_user_logged() {
return (init && pcbdb!=null);
}
/**
*
*/
public static void unset_user() {
pcbdb = null;
room = null;
vocabulary = null;
getNetService().notifyStatus();
}
/**
* if the user becomes from offline to online and the login is failed, then the user relogin in
* the app. The most frequent reason is a password change while the user have been logged
* offline
*/
public static void restart_app() {
SerialActivity.resetDefaultUser();
Intent serialActivity = new Intent(PCBcontext.getContext(), SerialActivity.class);
serialActivity.putExtra("resetPrevUser", true);
PCBcontext.getContext().startActivity(serialActivity);
}
/**
* @return true if the user is logged offline and it has not been online previously. False in
* other case (user not logged, user logged online, user offline but it was previously online
* logged
*/
public static boolean is_user_offline() {
if (pcbdb == null) {
return false;
} else {
return !getPcbdb().isUser_online();
}
}
public static boolean is_user_online() {
return pcbdb != null && getPcbdb().isUser_online();
}
/**
* @return context
* @TODO complete documentation
*/
public static Context getContext() {
if (context == null) {
throw new java.lang.NullPointerException("Context is null. PCBcontext.init must be" +
"invoked previously");
}
return context;
}
/**
* @return device
* @TODO complete documentation
*/
public static Device getDevice() {
if (device == null) {
throw new java.lang.NullPointerException("Device is null. PCBcontext.init must be" +
"invoked previously");
}
return device;
}
/**
* @return PCBDB
* @TODO complete documentation
*/
public static PCBDBHelper getPcbdb() {
if (context == null) {
throw new java.lang.NullPointerException("PCBDB is null. PCBcontext.set_user must be" +
"invoked previously");
}
return pcbdb;
}
/**
* @return NetService
* @TODO complete documentation
*/
public static NetService getNetService(){
if (service == null) {
throw new java.lang.NullPointerException("NetService is null. PCBcontext.set_user" +
"must be invoked previously");
}
return service;
}
/**
* @return RestapiWrapper
* @TODO complete documentation
*/
public static RestapiWrapper getRestapiWrapper(){
if (wrapper == null) {
throw new java.lang.NullPointerException("RestapiWrapper is null. PCBcontext.init" +
"must be invoked previously");
}
return wrapper;
}
/**
* @return Room
* @TODO complete documentation
*/
public static Room getRoom(){
if (room == null) {
throw new java.lang.NullPointerException("Room is null. PCBcontext.set_user must" +
"be invoked previously");
}
return room;
}
/**
* @return Vocabulary
* @TODO complete documentation
*/
public static Vocabulary getVocabulary(){
if (vocabulary == null) {
throw new java.lang.NullPointerException("Vocabulary is null. PCBcontext.set_user" +
"must be invoked previously");
}
return vocabulary;
}
/**
* @return ActionLog
* @TODO complete documentation
*/
public static ActionLog getActionLog(){
if (actionLog == null) {
throw new java.lang.NullPointerException("ActionLog is null. PCBcontext.set_user" +
"must be invoked previously");
}
return actionLog;
}
}
package com.yottacode.pictogram.tts;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import android.annotation.TargetApi;
import android.os.Build;
import android.speech.tts.TextToSpeech;
import android.content.Context;
import android.speech.tts.Voice;
import android.util.Log;
import android.widget.ArrayAdapter;
/**
* PCB TTS service
* @author Fernando Martinez Santiago
* @version 1.0
*/
public class TTSHelper {
TextToSpeech ttobj;
public boolean engine_ok;
public boolean voice_ok=true;
Voice voice;
public void createTTS(Context context, String engine) {
this.destroy();
/* this.ttobj = new TextToSpeech(
context,
new TextToSpeech.OnInitListener() {
@Override
public void onInit(int status) {
if (status != TextToSpeech.ERROR) {
ttobj.setLanguage(Locale.getDefault());
}
}
}, engine);*/
this.engine_ok=true;
Log.e(context.getApplicationInfo().processName,"TTS engine "+engine+" loaded");
}
public TTSHelper(Context context) {
createTTS(context ,null);
}
public TTSHelper(Context context, String engine) {
try {
createTTS(context ,engine);
}catch (Exception e) {
createTTS(context ,null);
Log.e(context.getApplicationInfo().processName, "Engine "+engine+" error",e);
this.engine_ok=false;
}
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public void setVoice(Context context, String voice) {
try {
Set<Voice> voices = this.ttobj.getVoices();
if (voices!=null)
for (Voice avoice : voices) {
Log.e(context.getApplicationInfo().processName,"Voice name "+avoice.getName());
if (avoice.getName().compareTo(voice) == 0) {
this.ttobj.setVoice(avoice);
this.voice_ok=true;
Log.e(context.getApplicationInfo().processName,"Voice "+voice+" loaded");
break;
}
}
else
Log.e(context.getApplicationInfo().processName,"Voice "+voice+" is not available");
}catch(Exception e) {
Log.e(context.getApplicationInfo().processName,"Voice "+voice+" error",e);
this.voice_ok=false;
}
}
public boolean isVoice_ok() { return this.voice_ok;}
public boolean isEngine_ok() { return this.engine_ok;}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public void speakText(String toSpeak){
this.ttobj.speak(toSpeak, TextToSpeech.QUEUE_FLUSH,null, toSpeak.hashCode()+"");
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public void show(ArrayAdapter mArrayAdapter) {
String tts_info[] = new String[25];
int i=0;
List<TextToSpeech.EngineInfo> engines = this.ttobj.getEngines();
for (Iterator<TextToSpeech.EngineInfo> iter = engines.iterator(); iter.hasNext(); ) {
TextToSpeech.EngineInfo element = iter.next();
Log.i("TTSHelper","TTS: "+element.name + " label:"+element.label);
mArrayAdapter.add("TTS: "+element.name + " label:"+element.label);
}
if (this.ttobj.getVoice()!=null) {
Log.i("TTSHelper", "Default voice:" + this.ttobj.getDefaultVoice().getName());
mArrayAdapter.add("Current voice:" + this.ttobj.getVoice().getName());
Set<Voice> voices = this.ttobj.getVoices();
if (voices != null)
for (Voice voice : voices) {
Log.i("TTSHelper", "Voice: " + voice.getName() + " locale:" + voice.getLocale());
mArrayAdapter.add("Voice: " + voice.getName() + " locale:" + voice.getLocale());
}
}
}
public void destroy() {
if(ttobj !=null){
ttobj.stop();
ttobj.shutdown();
}
this.ttobj=null;
}
}
\ No newline at end of file
package com.yottacode.tools;
import android.graphics.Bitmap;
import android.graphics.Matrix;
/**
* Created by Fernando on 15/03/2016.
*/
public class BitmapTools {
Bitmap bitmap;
public BitmapTools(Bitmap bitmap) { this.bitmap=bitmap;}
public Bitmap get() {return this.bitmap;}
public BitmapTools resize(int w, int h) {
int width = bitmap.getWidth();
int height = bitmap.getHeight();
int newWidth = w;
int newHeight = h;
// calculamos el escalado de la imagen destino
float scaleWidth = ((float) newWidth) / width;
float scaleHeight = ((float) newHeight) / height;
// para poder manipular la imagen
// debemos crear una matriz
Matrix matrix = new Matrix();
// resize the Bitmap
matrix.postScale(scaleWidth, scaleHeight);
// volvemos a crear la imagen con los nuevos valores
Bitmap resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0,
width, height, matrix, true);
this.bitmap=resizedBitmap;
return this;
}
public BitmapTools rescale(float scale) {
int width = (int) (scale * (float) this.bitmap.getWidth());
int height = (int) (scale * (float) this.bitmap.getHeight());
return resize(width,height);
}
public BitmapTools paintSquare(int thickness, int color ) {
for (int i = 0; i < this.bitmap.getWidth(); i++)
for (int t = 1; t <= thickness; t++) {
this.bitmap.setPixel(i, 0 + t-1, color);
this.bitmap.setPixel(i, this.bitmap.getHeight() - t, color);
}
for (int i = 0; i < this.bitmap.getHeight(); i++)
for (int t = 1; t <= thickness; t++) {
this.bitmap.setPixel(0 + t-1, i, color);
this.bitmap.setPixel(this.bitmap.getWidth() - t, i, color);
}
return this;
}
}
package com.yottacode.tools;
import java.io.File;
/**
* Created by Fernando on 14/03/2016.
*/
public class FileTools {
public static boolean deleteDirectory(File path) {
if( path.exists() ) {
File[] files = path.listFiles();
if (files == null) {
return true;
}
for(int i=0; i<files.length; i++) {
if(files[i].isDirectory()) {
deleteDirectory(files[i]);
}
else {
files[i].delete();
}
}
}
return( path.delete() );
}
}
package com.yottacode.tools;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.util.Log;
/**
* Created by Fernando on 01/04/2016.
*/
public class GUITools {
public interface iOKListener {
public void ok();
}
public static void show_alert(Context context, int resource_msg, String additional_msg, final iOKListener oklistener) {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
String msg = context.getString(resource_msg);
if (additional_msg != null) msg += ": " + additional_msg;
Log.i(GUITools.class.getName(), "Alert:" + msg);
builder.setMessage(msg)
.setCancelable(false)
.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
if (oklistener != null) oklistener.ok();
}
});
AlertDialog alert = builder.create();
alert.show();
}
public static void show_alert(Context context, int resource_msg) {
show_alert(context, resource_msg, null);
}
public static void show_alert(Context context, int resource_msg, String additional_msg) {
show_alert(context, resource_msg, additional_msg,null);
}
}
...@@ -5,7 +5,7 @@ buildscript { ...@@ -5,7 +5,7 @@ buildscript {
jcenter() jcenter()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:2.1.2' classpath 'com.android.tools.build:gradle:2.1.3'
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files
......
#Mon Jun 06 22:38:39 CEST 2016 #Mon Aug 29 01:05:09 CEST 2016
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
...@@ -22,7 +22,7 @@ android { ...@@ -22,7 +22,7 @@ android {
resValue "string", "server", "https://127.0.0.1:9944" resValue "string", "server", "https://127.0.0.1:9944"
} }
LocalFlavor { LocalFlavor {
resValue "string", "server", "https://192.168.1.37:1337" resValue "string", "server", "https://192.168.1.36:1337"
} }
DevFlavor { DevFlavor {
resValue "string", "server", "https://dev.yottacode.com" resValue "string", "server", "https://dev.yottacode.com"
......
...@@ -368,7 +368,7 @@ CREATE TABLE IF NOT EXISTS `try` ( ...@@ -368,7 +368,7 @@ CREATE TABLE IF NOT EXISTS `try` (
`id_ws` int(11) NOT NULL, `id_ws` int(11) NOT NULL,
`begin` timestamp(3) DEFAULT CURRENT_TIMESTAMP(3), `begin` timestamp(3) DEFAULT CURRENT_TIMESTAMP(3),
`end` timestamp(3) NULL, `end` timestamp(3) NULL,
`result` varchar(20) COLLATE utf8_unicode_ci DEFAULT NULL, `result` varchar(20) COLLATE utf8_unicode_ci DEFAULT NULL CHECK (result IN ('SUCCESS','SPONTANEUS SUCCESS', 'SUPERVISED SUCCESS', 'FAIL','DISCARDED','MODEL')),
`description` varchar(4096) COLLATE utf8_unicode_ci DEFAULT NULL, `description` varchar(4096) COLLATE utf8_unicode_ci DEFAULT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
KEY `id_ws` (`id_ws`) KEY `id_ws` (`id_ws`)
......
...@@ -128,7 +128,7 @@ END;; ...@@ -128,7 +128,7 @@ END;;
-- Integrity rule 2: when a session is closed, last try must be discharged -- Integrity rule 2: when a session is closed, last try must be discharged
-- when: state COM, event a4 -- when: state COM, event a4
-- current is to NULL (integrity rule 6) -- current is set NULL (integrity rule 6)
-- --
DROP TRIGGER IF EXISTS TRG_SESSION_CLOSED; DROP TRIGGER IF EXISTS TRG_SESSION_CLOSED;
CREATE TRIGGER TRG_SESSION_CLOSED CREATE TRIGGER TRG_SESSION_CLOSED
...@@ -181,7 +181,7 @@ END;; ...@@ -181,7 +181,7 @@ END;;
-- when: state COM, event a4 -- when: state COM, event a4
-- Integrity rule 5: when a session is continued after a pause and new try is created -- Integrity rule 5: when a session is continued after a pause and new try is created
-- when: state PAU, event a3 -- when: state PAU, event a3
-- Integrity rule 4: when a session is paused, last try must be discharged -- Integrity rule 4: when a session is paused, last try must be saved as not valid
-- when: state SES, event a3 -- when: state SES, event a3
DROP TRIGGER IF EXISTS TRG_NEW_EVENT; DROP TRIGGER IF EXISTS TRG_NEW_EVENT;
CREATE TRIGGER TRG_NEW_EVENT CREATE TRIGGER TRG_NEW_EVENT
...@@ -219,7 +219,7 @@ thisTrigger: BEGIN ...@@ -219,7 +219,7 @@ thisTrigger: BEGIN
WHERE WHERE
id_stu = NEW.id_stu; id_stu = NEW.id_stu;
IF (idopentry IS NOT NULL) THEN IF (idopentry IS NOT NULL) THEN
call deleteOpenTry(idws); UPDATE try set end=NOW(), result='DISCARDED' where id=idopentry;
END IF; END IF;
WHEN 'resumesession' THEN WHEN 'resumesession' THEN
SELECT id_ws, id_opentry INTO idws, idopentry SELECT id_ws, id_opentry INTO idws, idopentry
......
...@@ -113,11 +113,10 @@ module.exports = { ...@@ -113,11 +113,10 @@ module.exports = {
*/ */
destroy: function (req, res) { destroy: function (req, res) {
Instruction.destroy({ id: req.params.id }).exec(function (error) { Instruction.destroy({ id: req.params.id }).exec(function (error) {
if (error) { if (error)
res.badRequest(); return res.badRequest();
throw error; else
} return res.ok();
res.ok();
}); });
}, },
......
...@@ -35,84 +35,65 @@ module.exports = { ...@@ -35,84 +35,65 @@ module.exports = {
create: function(req, res) { create: function(req, res) {
var params = req.allParams(); var params = req.allParams();
if (!params.id_mmethod) return res.json(500, {error: "No meta method defined"}); if (!params.id_mmethod) return res.badRequest("No meta method defined");
if (!params.id_stu) return res.json(500, {error: "No student defined"}); if (!params.id_stu) return res.badRequest("No student defined");
// Find meta method // Find meta method
MetaMethod.findOne({id: params.id_mmethod}).exec(function(err, mmethod) { MetaMethod.findOne({id: params.id_mmethod})
if (err || !mmethod){ .then(mmethod => {
sails.log.debug("Finding meta method: " + err); if (!mmethod)
return; throw new Error("Meta method not found");
} // Create new method by meta method
return Method.create(
sails.log.debug("Meta method found:" + mmethod); {
name: mmethod.name,
// Create new method by meta method description: mmethod.description,
Method.create({ name: mmethod.name, description: mmethod.description, student: params.id_stu}).exec(function (err, created) { student: params.id_stu
if (err) { })
sails.log.debug("Creating new method: " + err); .then(created => created)
return res.json(500, {error: 'Method not created'}); .fail(err => {throw err});
} })
if (created) { .then(created => {
// Find meta instructions associated to meta method // Find meta instructions associated to meta method
MetaInstruction.find({id_met: params.id_mmethod}).exec(function(err, minstructions) { MetaInstruction.find({id_met: params.id_mmethod})
.then(minstructions => {
if (err || !minstructions || minstructions.length == 0){ if (!minstructions)
sails.log.debug("Finding meta instructions of a meta method: " + err); minstructions = [];
return;
} var l_ins = [];
sails.log.debug("Meta instructions found: " + JSON.stringify(minstructions)); // Every meta instruction is going to be created in 'Instruction'
// with .eachSeries the order of resulting array will be equal
// to the original array
var l_ins = []; async.eachSeries(minstructions, function(mins, next) {
Instruction.create({
// Every meta instruction is going to be created in 'Instruction' method: created.id,
// with .eachSeries the order of resulting array will be equal name: mins.name,
// to the original array objective: mins.objective
async.eachSeries(minstructions, function(mins, callback) { })
.then(added => {
sails.log.debug("Loop adding meta instruction: " + mins.name + " to method " + created.id); l_ins.push(added);
sails.log.debug("Instruction " + added.name + " added to method " + created.id);
Instruction.create({ })
method: created.id, .fail(err => {throw err})
name: mins.name, .done(() => {next()});
objective: mins.objective },
}).exec(function(err, added){ function(err, results) {
if(err) sails.log.debug("Instruction.create: " + err); if (err)
if(added){ throw new Error("Error while looping through instructions");
l_ins.push(added); return res.ok({
sails.log.debug("Instruction " + added.name + " added to method " + created.id); "name": created.name,
"description": created.description,
} "id": created.id,
callback(); "student": created.id_stu,
}); "instructions": l_ins
// Finish function when each callback is done
// Optionaly it can be passed and err parameter
}, function(err){
if( err ) {
// One of the iterations produced an error.
// All processing will now stop.
} else {
return res.json({
"name": created.name,
"description": created.description,
"id": created.id,
"student": created.id_stu,
"instructions": l_ins
});
}
});
}); });
});
} })
}); .fail( err => {throw err});
})
.fail(err => {
return res.badRequest(err);
}); });
}, },
......
...@@ -608,6 +608,7 @@ module.exports = { ...@@ -608,6 +608,7 @@ module.exports = {
return res.json(l_met); return res.json(l_met);
} }
}); });
} }
}); });
}, },
...@@ -1005,7 +1006,11 @@ module.exports = { ...@@ -1005,7 +1006,11 @@ module.exports = {
// Leave all rooms // Leave all rooms
for (var i = 0; i < rooms.length; i++) { for (var i = 0; i < rooms.length; i++) {
sails.sockets.leave(req.socket, rooms[i]); //sails.sockets.leave(req.socket, rooms[i]); MODIFICADO POR FERNANDO. SI NO, NO SE ACTUALIZA UPDATE_PEERS
sails.hooks.rooms.unsubscribeFromRoom(
rooms[i],
req.socket
);
sails.log.debug("Unsusbscribe from room " + rooms[i]); sails.log.debug("Unsusbscribe from room " + rooms[i]);
} }
......
...@@ -55,7 +55,7 @@ module.exports = { ...@@ -55,7 +55,7 @@ module.exports = {
create: function(req, res) { create: function(req, res) {
var params = req.allParams(); var params = req.allParams();
console.log(JSON.stringify(params)); sails.debug.log(JSON.stringify(params));
if (!params.id_sup) res.badRequest("No supervisor defined"); if (!params.id_sup) res.badRequest("No supervisor defined");
if (!params.id_ins) res.badRequest("No instruction defined"); if (!params.id_ins) res.badRequest("No instruction defined");
...@@ -66,7 +66,7 @@ module.exports = { ...@@ -66,7 +66,7 @@ module.exports = {
data.supervisor = params.id_sup; data.supervisor = params.id_sup;
data.instruction = params.id_ins; data.instruction = params.id_ins;
data.description = params.desc; data.description = params.desc;
console.log("BEGIN " + params.begin); sails.debug.log("BEGIN " + params.begin);
data.begin = params.begin; // data comes in ISO format data.begin = params.begin; // data comes in ISO format
StuOpenTry.findOne({or: [ // pending open try? StuOpenTry.findOne({or: [ // pending open try?
......
/** /**
* This hook is used for managing the rooms IDs. * This hook is used for managing the rooms IDs.
* Every room created should call one of this functions in order * Every room created should call one of this functions in order
* to obtain an ID. * to obtain an ID.
* @type {Object} * @type {Object}
*/ */
var socketRooms={};
module.exports = function roomsHook (sails) { module.exports = function roomsHook (sails) {
return { return {
getRoom: function (socket) {
return socketRooms[sails.sockets.getId(socket)];
},
/** /**
* Special function that subscribes a socket to a given room. * Special function that subscribes a socket to a given room.
* This creates the connection and logs to debug with this format: * This creates the connection and logs to debug with this format:
...@@ -18,16 +25,20 @@ module.exports = function roomsHook (sails) { ...@@ -18,16 +25,20 @@ module.exports = function roomsHook (sails) {
* } * }
* *
* @param {RoomID} room Room to subscribe * @param {RoomID} room Room to subscribe
* @param {Socket} socket Socket added to the subscription * @param {Socket} socket Socket added to the subscription<
*/ */
subscribeToRoom: function (room, socket) { subscribeToRoom: function (room, socket) {
sails.log.debug('"websocketSubscribe":', JSON.stringify({ sails.log.debug('"websocketSubscribe":', JSON.stringify({
room: room, room: room,
socket: socket.id socket: socket.id
})); }));
sails.sockets.join(socket, room, function () { sails.sockets.join(socket, room, function () {
sails.io.sockets.in(room).clients(function(error, ids) { sails.io.sockets.in(room).clients(function(error, ids) {
if (!error) { if (!error) {
socketRooms[sails.sockets.getId(socket)] =
socketRooms[sails.sockets.getId(socket)] ? [socketRooms[sails.sockets.getId(socket)],room]
: [room];
sails.hooks.events.broadcastEvent( sails.hooks.events.broadcastEvent(
room, room,
sails.hooks.events.roomSubscribersChange(ids.length) sails.hooks.events.roomSubscribersChange(ids.length)
...@@ -63,6 +74,12 @@ module.exports = function roomsHook (sails) { ...@@ -63,6 +74,12 @@ module.exports = function roomsHook (sails) {
room, room,
sails.hooks.events.roomSubscribersChange(ids.length) sails.hooks.events.roomSubscribersChange(ids.length)
); );
if (socketRooms[sails.sockets.getId(socket)]) {
var index=socketRooms[sails.sockets.getId(socket)].indexOf(room);
if (index > -1) {
socketRooms[sails.sockets.getId(socket)].splice(index, 1);
}
}
} }
}); });
}); });
......
...@@ -98,9 +98,11 @@ module.exports = { ...@@ -98,9 +98,11 @@ module.exports = {
}); });
}, },
// 3rd final function when everything is ready // 3rd final function when everything is ready
function (err){ function (err){
console.log("Final function recovering working sessions / tries / actions"); if (err) {
//console.log(JSON.stringify(l_ws)); console.log("Error recovering working sessions / tries / actions");
console.log("Error detail:"+JSON.stringify(err));
}
return callback(err, l_ws); return callback(err, l_ws);
// If one iteration give an error it is sent to the controller // If one iteration give an error it is sent to the controller
// with the list // with the list
......
...@@ -41,6 +41,7 @@ module.exports = { ...@@ -41,6 +41,7 @@ module.exports = {
}, },
current: { current: {
type: "integer", type: "integer",
defaultsTo: 1
}, },
description: { description: {
type: "string", type: "string",
...@@ -55,7 +56,7 @@ module.exports = { ...@@ -55,7 +56,7 @@ module.exports = {
/** /**
* Checks a previous session is not opened for that supervisor. If so, * Checks a previous session is not opened for that supervisor. If so,
* session is closed (current is set to 0) * session is closed (current is set NULL)
* @param {Object} attrs All session properties to be stored * @param {Object} attrs All session properties to be stored
* @param {Function} next Function to be executed when the check process * @param {Function} next Function to be executed when the check process
* has been completed (an error object will be passed * has been completed (an error object will be passed
...@@ -66,9 +67,8 @@ module.exports = { ...@@ -66,9 +67,8 @@ module.exports = {
.then(function(wss) { .then(function(wss) {
if (wss) { if (wss) {
async.each(wss, function(ws, cb) { async.each(wss, function(ws, cb) {
ws.current = 0; WorkingSession.update(ws.id, {current:null}, function(err, update) {
WorkingSession.update(ws.id, ws, function(err, update) { if (err) throw new Error("Error when udpating open sessions: "+JSON.stringify(err));
if (err) throw new Error("Error when udpating open sessions");
cb(); cb();
}); });
}); });
......
...@@ -36,6 +36,7 @@ ...@@ -36,6 +36,7 @@
"birthdate": "Birthdate", "birthdate": "Birthdate",
"cancel": "Cancel", "cancel": "Cancel",
"cannot_delete_method": "Method could not be deleted, maybe due to existing recorded sessions.", "cannot_delete_method": "Method could not be deleted, maybe due to existing recorded sessions.",
"cannot_delete_instruction": "Instruction could not be deleted, maybe due to existing recorded sessions.",
"categories": "Categories", "categories": "Categories",
"category_pictograms": "Category's pictograms", "category_pictograms": "Category's pictograms",
"change_password": "Change password", "change_password": "Change password",
...@@ -200,6 +201,7 @@ ...@@ -200,6 +201,7 @@
"register": "Sign in", "register": "Sign in",
"remember": "Remember me", "remember": "Remember me",
"reports": "Reports", "reports": "Reports",
"room_changed": "A partner is offline. Session paused.",
"save": "Save", "save": "Save",
"save_as_template": "Save as template", "save_as_template": "Save as template",
"search": "Search", "search": "Search",
...@@ -216,6 +218,7 @@ ...@@ -216,6 +218,7 @@
"serial_list": "Free serial numbers", "serial_list": "Free serial numbers",
"serial_not_created": "Serial number couldn't be created", "serial_not_created": "Serial number couldn't be created",
"session": "Session", "session": "Session",
"sessions": "Sessions",
"session_mean_length": "Session mean length: {{hours}} hours", "session_mean_length": "Session mean length: {{hours}} hours",
"session_notes": "Session notes", "session_notes": "Session notes",
"sessions": "Sessions", "sessions": "Sessions",
...@@ -258,6 +261,7 @@ ...@@ -258,6 +261,7 @@
"surname": "Surname", "surname": "Surname",
"tag_deleted": "Tag deleted", "tag_deleted": "Tag deleted",
"tape_background": "Tape background", "tape_background": "Tape background",
"template_deleted": "Template deleted",
"time_instruction_method": "Time instructions of method", "time_instruction_method": "Time instructions of method",
"time_hours": "Time: {{hours}} hours", "time_hours": "Time: {{hours}} hours",
"time_sessions_per_days": "Time of sessions per days in", "time_sessions_per_days": "Time of sessions per days in",
...@@ -298,7 +302,8 @@ ...@@ -298,7 +302,8 @@
"vibration": "Vibration", "vibration": "Vibration",
"view": "View", "view": "View",
"voice": "Voice", "voice": "Voice",
"warning_last_session_bad": "&nbsp;Last session was bad closed. <p/> &nbsp;Please, you must evaluate last tries before starting a new session", "warning_last_session_bad": "&nbsp;Last session was bad closed. <p/> &nbsp;Please, you must evaluate last tries and press 'close seesion' button",
"warning_no_tablet_online":"No Pictogran Tablet online detected",
"woman": "Woman", "woman": "Woman",
"year_totals": "Year totals", "year_totals": "Year totals",
"yes": "Yes", "yes": "Yes",
......
...@@ -36,6 +36,7 @@ ...@@ -36,6 +36,7 @@
"birthdate": "Fecha de nacimiento", "birthdate": "Fecha de nacimiento",
"cancel": "Cancelar", "cancel": "Cancelar",
"cannot_delete_method": "No se pudo eliminar el método, tal vez porque existen sesiones asociadas.", "cannot_delete_method": "No se pudo eliminar el método, tal vez porque existen sesiones asociadas.",
"cannot_delete_instruction": "No se pudo eliminar la instrucción, tal vez porque existen sesiones asociadas.",
"categories": "Categorías", "categories": "Categorías",
"category_pictograms": "Pictogramas de la categoría", "category_pictograms": "Pictogramas de la categoría",
"change_password": "Cambiar contraseña", "change_password": "Cambiar contraseña",
...@@ -201,6 +202,7 @@ ...@@ -201,6 +202,7 @@
"register_button": "Registrar", "register_button": "Registrar",
"remember": "No cerrar sesión", "remember": "No cerrar sesión",
"reports": "Informes", "reports": "Informes",
"room_changed":"Un participante abandonó la sesión. Sesión en pausa.",
"save": "Guardar", "save": "Guardar",
"save_as_template": "Guardar como plantilla", "save_as_template": "Guardar como plantilla",
"search": "Buscar", "search": "Buscar",
...@@ -217,6 +219,7 @@ ...@@ -217,6 +219,7 @@
"serial_list": "Números de serie libres", "serial_list": "Números de serie libres",
"serial_not_created": "No se ha podido crear el número de serie", "serial_not_created": "No se ha podido crear el número de serie",
"session": "Sesión", "session": "Sesión",
"sessions": "Sesiones",
"session_mean_length": "Duración media de sesión: {{hours}} horas", "session_mean_length": "Duración media de sesión: {{hours}} horas",
"session_notes": "Notas de sesión", "session_notes": "Notas de sesión",
"sessions": "Sesiones", "sessions": "Sesiones",
...@@ -259,6 +262,7 @@ ...@@ -259,6 +262,7 @@
"surname": "Apellidos", "surname": "Apellidos",
"tag_deleted": "Etiqueta borrada", "tag_deleted": "Etiqueta borrada",
"tape_background": "Fondo de la cinta", "tape_background": "Fondo de la cinta",
"template_deleted": "Plantilla eliminada",
"time_hours": "Tiempo: {{hours}} horas", "time_hours": "Tiempo: {{hours}} horas",
"time_instruction_method": "Tiempo instrucciones del método", "time_instruction_method": "Tiempo instrucciones del método",
"time_sessions_per_days": "Tiempo de sesiones por días en", "time_sessions_per_days": "Tiempo de sesiones por días en",
...@@ -299,7 +303,8 @@ ...@@ -299,7 +303,8 @@
"vibration": "Vibración", "vibration": "Vibración",
"view": "Vista", "view": "Vista",
"voice": "Voz", "voice": "Voz",
"warning_last_session_bad": "&nbsp;La última sesión no se cerró correctamente. <p/> &nbsp;Por favor, evalúe los ensayos correspondientes antes de iniciar una nueva sesión ", "warning_last_session_bad": "&nbsp;La última sesión no se cerró correctamente. <p/> &nbsp;Por favor, evalúe los ensayos y pulse 'cerrar sesión' ",
"warning_no_tablet_online":"No se detectó ningún usuario de Pictogram Tablet online",
"woman": "Mujer", "woman": "Mujer",
"year_totals": "Totales año", "year_totals": "Totales año",
"yes": "Sí", "yes": "Sí",
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
<title>Pictogram Dashboard</title> <title>Pictogram Dashboard</title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link rel="stylesheet" href="css/main.css"> <link rel="stylesheet" href="css/main.css">
<link rel="icon" href="img/logo_pictogram.png" type='image/png'>
<script src="js/libs.js"></script> <script src="js/libs.js"></script>
<script src="js/main.js"></script> <script src="js/main.js"></script>
</head> </head>
......
...@@ -12,6 +12,8 @@ String.prototype.hashCode = function() { ...@@ -12,6 +12,8 @@ String.prototype.hashCode = function() {
return hash; return hash;
}; };
io.sails.transports=['websocket'];
// Creamos el módulo dashboardControllers, de donde colgarán todos los controladores // Creamos el módulo dashboardControllers, de donde colgarán todos los controladores
var dashboardControllers = angular.module('dashboardControllers', ['dashboardConfig']); var dashboardControllers = angular.module('dashboardControllers', ['dashboardConfig']);
......
...@@ -34,12 +34,12 @@ dashboardControllers.controller('StudentInstructionsCtrl', function StudentInstr ...@@ -34,12 +34,12 @@ dashboardControllers.controller('StudentInstructionsCtrl', function StudentInstr
.error(function(data, status, headers, config) { .error(function(data, status, headers, config) {
console.log("Error from API: " + data.error); console.log("Error from API: " + data.error);
}); });
// //
// Array with student methods (with instructions) // Array with student methods (with instructions)
// //
$scope.methods = []; $scope.methods = [];
// Query to obtain an array of student methods // Query to obtain an array of student methods
$http $http
.get(config.backend+'/stu/'+ $scope.studentData.id +'/methods') .get(config.backend+'/stu/'+ $scope.studentData.id +'/methods')
...@@ -53,7 +53,7 @@ dashboardControllers.controller('StudentInstructionsCtrl', function StudentInstr ...@@ -53,7 +53,7 @@ dashboardControllers.controller('StudentInstructionsCtrl', function StudentInstr
console.log("Error from API: " + data.error); console.log("Error from API: " + data.error);
}); });
// //
// Get last method/instruction for this student // Get last method/instruction for this student
// //
/* $http /* $http
...@@ -75,7 +75,7 @@ dashboardControllers.controller('StudentInstructionsCtrl', function StudentInstr ...@@ -75,7 +75,7 @@ dashboardControllers.controller('StudentInstructionsCtrl', function StudentInstr
if($scope.method_selected.id == 0){ if($scope.method_selected.id == 0){
$http $http
.post(config.backend+'/method/new', { .post(config.backend+'/method/new', {
'id_stu': $scope.studentData.id, 'id_stu': $scope.studentData.id,
'name': "Nuevo método (cambiar nombre)" 'name': "Nuevo método (cambiar nombre)"
}) })
...@@ -89,11 +89,11 @@ dashboardControllers.controller('StudentInstructionsCtrl', function StudentInstr ...@@ -89,11 +89,11 @@ dashboardControllers.controller('StudentInstructionsCtrl', function StudentInstr
console.log("Error from API: " + data.error); console.log("Error from API: " + data.error);
}); });
}else{ } else {
// Add method from templates // Add method from templates
$http $http
.post(config.backend+'/method', { .post(config.backend+'/method', {
'id_mmethod': $scope.method_selected.id, 'id_mmethod': $scope.method_selected.id,
'id_stu': $scope.studentData.id 'id_stu': $scope.studentData.id
}) })
.success(function(data, status, headers, config) { .success(function(data, status, headers, config) {
...@@ -113,40 +113,40 @@ dashboardControllers.controller('StudentInstructionsCtrl', function StudentInstr ...@@ -113,40 +113,40 @@ dashboardControllers.controller('StudentInstructionsCtrl', function StudentInstr
// Delete template // Delete template
$scope.delete_template = function(){ $scope.delete_template = function(){
var deleteTemp = $window.confirm('Are you absolutely sure you want to delete?'); $translate('confirmation').then(function(translation) {
// If the method template is property of the supervisor newconfirm(translation).then(function() {
if(deleteTemp && $scope.method_selected.supervisor == $scope.user.id){ if($scope.method_selected.supervisor == $scope.user.id){
$http $http
.delete(config.backend+'/method/template/' + $scope.method_selected.id) .delete(config.backend+'/method/template/' + $scope.method_selected.id)
.success(function(data, status, headers, config) { .success(function(data, status, headers, config) {
console.log('Delete Method Template and its Instructions'); console.log('Delete Method Template and its Instructions');
var myToastMsg = ngToast.success({ $translate('template_deleted').then(t => {ngToast.success({content: t})});
content: 'Eliminado método de plantillas',
timeout: 6000 // By default 4000 // Delete in select
}); for(var i=0; i<$scope.methods_available.length; i++) {
if($scope.methods_available[i].id == $scope.method_selected.id){
// Delete in select $scope.methods_available.splice(i,1);
for(var i=0; i<$scope.methods_available.length; i++) { $scope.method_selected = null;
if($scope.methods_available[i].id == $scope.method_selected.id){ break;
$scope.methods_available.splice(i,1); }
$scope.method_selected = null; }
break; })
} .error(function(data, status, headers, config) {
console.log("Error from API: " + data.error);
});
} }
}) });
.error(function(data, status, headers, config) { });
console.log("Error from API: " + data.error);
});
}
}; };
// Update method // Update method
$scope.update_method = function(method){ $scope.update_method = function(method){
// Remove instructions as we only update title or description // Remove instructions as we only update title or description
var method_to_save = {}; var method_to_save = {};
Object.assign(method_to_save, method); Object.assign(method_to_save, method);
delete method_to_save.instructions; delete method_to_save.instructions;
$http $http
...@@ -158,8 +158,8 @@ dashboardControllers.controller('StudentInstructionsCtrl', function StudentInstr ...@@ -158,8 +158,8 @@ dashboardControllers.controller('StudentInstructionsCtrl', function StudentInstr
console.log("Error from API: " + JSON.stringify(data)); console.log("Error from API: " + JSON.stringify(data));
}); });
}; };
// Save method and its instructions as template in metamethods and metainstructions // Save method and its instructions as template in metamethods and metainstructions
$scope.save_as_template = function(method){ $scope.save_as_template = function(method){
$http $http
...@@ -167,10 +167,10 @@ dashboardControllers.controller('StudentInstructionsCtrl', function StudentInstr ...@@ -167,10 +167,10 @@ dashboardControllers.controller('StudentInstructionsCtrl', function StudentInstr
.then( //success .then( //success
function(data, status, headers, config) { function(data, status, headers, config) {
console.log('Saved method as template:' + JSON.stringify(data)); console.log('Saved method as template:' + JSON.stringify(data));
// Add to select method // Add to select method
$scope.methods_available.push({ id: method.id, name: method.name }); $scope.methods_available.push({ id: method.id, name: method.name });
// creating a toast using "success", "info", "warning" or "danger" shortcut methods: // creating a toast using "success", "info", "warning" or "danger" shortcut methods:
var myToastMsg = $translate('method_save').then(function (translation) { var myToastMsg = $translate('method_save').then(function (translation) {
// Show message // Show message
...@@ -179,7 +179,7 @@ dashboardControllers.controller('StudentInstructionsCtrl', function StudentInstr ...@@ -179,7 +179,7 @@ dashboardControllers.controller('StudentInstructionsCtrl', function StudentInstr
timeout: 6000 // By default 4000 timeout: 6000 // By default 4000
}); });
}); });
} }
,function(data, status, headers, config) { //error ,function(data, status, headers, config) { //error
console.log("Error from API: " + data.error+"("+data.status+")"); console.log("Error from API: " + data.error+"("+data.status+")");
...@@ -212,18 +212,18 @@ dashboardControllers.controller('StudentInstructionsCtrl', function StudentInstr ...@@ -212,18 +212,18 @@ dashboardControllers.controller('StudentInstructionsCtrl', function StudentInstr
break; break;
} }
} }
}) })
.error(function(data, status, headers, config) { .error(function(data, status, headers, config) {
console.log("Error from API: " + data.error); console.log("Error from API: " + data.error);
var myToastMsg = $translate('cannot_delete_method').then(function (translation) { var myToastMsg = $translate('cannot_delete_method').then(function (translation) {
// Show message // Show message
ngToast.warning({ ngToast.warning({
content: translation, content: translation,
timeout: 6000 // By default 4000 timeout: 6000 // By default 4000
});
}); });
}); });
});
}); });
}); });
}; };
...@@ -276,21 +276,28 @@ dashboardControllers.controller('StudentInstructionsCtrl', function StudentInstr ...@@ -276,21 +276,28 @@ dashboardControllers.controller('StudentInstructionsCtrl', function StudentInstr
// Buscar method // Buscar method
for(var i=0; i<$scope.methods.length; i++) { for(var i=0; i<$scope.methods.length; i++) {
var m = $scope.methods[i]; var m = $scope.methods[i];
if(ins.method == m.id){ if(ins.method == m.id){
// Buscar la instrucción y eliminar de la vista // Buscar la instrucción y eliminar de la vista
for(var j=0; j<m.instructions.length; j++) { for(var j=0; j<m.instructions.length; j++) {
if(ins.id == m.instructions[j].id){ if(ins.id == m.instructions[j].id){
$scope.methods[i].instructions.splice(j,1); $scope.methods[i].instructions.splice(j,1);
break; break;
} }
} }
break; break;
} }
} }
}) })
.error(function(data, status, headers, config) { .error(function(data, status, headers, config) {
console.log("Error from API: " + data.error); console.log("Error from API: " + data.error);
var myToastMsg = $translate('cannot_delete_instruction').then(function (translation) {
// Show message
ngToast.warning({
content: translation,
timeout: 6000 // By default 4000
});
});
}); });
}; };
...@@ -319,4 +326,4 @@ dashboardControllers.controller('StudentInstructionsCtrl', function StudentInstr ...@@ -319,4 +326,4 @@ dashboardControllers.controller('StudentInstructionsCtrl', function StudentInstr
}; };
// End Modal window to view instruction details // End Modal window to view instruction details
}); });
\ No newline at end of file
...@@ -297,7 +297,8 @@ dashboardControllers.controller('StudentSessionCtrl', function StudentSessionCtr ...@@ -297,7 +297,8 @@ dashboardControllers.controller('StudentSessionCtrl', function StudentSessionCtr
.post(config.backend+'/action', { .post(config.backend+'/action', {
"type": "pausesession", "type": "pausesession",
"student": $scope.studentData.id, "student": $scope.studentData.id,
"supervisor": $scope.user.id "supervisor": $scope.user.id,
"timestamp": (new Date()).toISOString()
}) })
.success(function(data, status, headers, config) { .success(function(data, status, headers, config) {
// Gettinf rid of last try and adding pause action to the list of actions // Gettinf rid of last try and adding pause action to the list of actions
...@@ -318,7 +319,8 @@ dashboardControllers.controller('StudentSessionCtrl', function StudentSessionCtr ...@@ -318,7 +319,8 @@ dashboardControllers.controller('StudentSessionCtrl', function StudentSessionCtr
.post(config.backend+'/action', { .post(config.backend+'/action', {
"type": "resumesession", "type": "resumesession",
"student": $scope.studentData.id, "student": $scope.studentData.id,
"supervisor": $scope.user.id "supervisor": $scope.user.id,
"timestamp": (new Date()).toISOString()
}) })
.success(function(data, status, headers, config) { .success(function(data, status, headers, config) {
// Adding pause action to the list of actions // Adding pause action to the list of actions
...@@ -378,6 +380,22 @@ dashboardControllers.controller('StudentSessionCtrl', function StudentSessionCtr ...@@ -378,6 +380,22 @@ dashboardControllers.controller('StudentSessionCtrl', function StudentSessionCtr
// //
io.socket.on('update_peers', function (data) {
if($scope.ws && $scope.actual_try.actions && $scope.paused == false && data.count<$scope.studentData.prev_num_peers) {
$scope.pause_ws();
$scope.pauseTimer();
$translate('room_changed').then(function (translation) {
ngToast.create({
className: 'warning',
content: translation,
dismissOnTimeout: false,
dismissButton: true
});
});
}
});
// Remove all listeners to this event // Remove all listeners to this event
io.socket.off('action'); io.socket.off('action');
...@@ -388,7 +406,7 @@ dashboardControllers.controller('StudentSessionCtrl', function StudentSessionCtr ...@@ -388,7 +406,7 @@ dashboardControllers.controller('StudentSessionCtrl', function StudentSessionCtr
// doesn't know new data has come in. Inside the socket.io callback, // doesn't know new data has come in. Inside the socket.io callback,
// as the last action, added $scope.apply() that lets angular know // as the last action, added $scope.apply() that lets angular know
// what data has updated, and refresh what needs to be refreshed. // what data has updated, and refresh what needs to be refreshed.
//only events (i) from a student (and ii)when a try is opened are managed
if($scope.ws && $scope.actual_try.actions && !data.attributes.id_sup && $scope.paused == false){ if($scope.ws && $scope.actual_try.actions && !data.attributes.id_sup && $scope.paused == false){
switch(data.action){ switch(data.action){
...@@ -410,6 +428,7 @@ dashboardControllers.controller('StudentSessionCtrl', function StudentSessionCtr ...@@ -410,6 +428,7 @@ dashboardControllers.controller('StudentSessionCtrl', function StudentSessionCtr
} }
} }
$scope.$apply(); $scope.$apply();
}); });
}); });
...@@ -33,7 +33,8 @@ dashboardControllers.controller('StudentCtrl', function StudentCtrl( ...@@ -33,7 +33,8 @@ dashboardControllers.controller('StudentCtrl', function StudentCtrl(
name: '' name: ''
}, },
stuSup: [], stuSup: [],
num_peers: 1 num_peers: 1,
prev_num_peers: 1
}; };
// For the user form data in setup section // For the user form data in setup section
...@@ -52,7 +53,10 @@ dashboardControllers.controller('StudentCtrl', function StudentCtrl( ...@@ -52,7 +53,10 @@ dashboardControllers.controller('StudentCtrl', function StudentCtrl(
// $translate('num_peers').then(function (translation) { // $translate('num_peers').then(function (translation) {
// ngToast.success(translation + ': ' + data.count); // ngToast.success(translation + ': ' + data.count);
//}); //});
$scope.studentData.prev_num_peers = $scope.studentData.num_peers;
$scope.studentData.num_peers = data.count; $scope.studentData.num_peers = data.count;
console.log('Usuarios conectados:'+$scope.studentData.num_peers+ " previamente:"+$scope.studentData.prev_num_peers);
$scope.$apply();
}); });
io.socket.on('reconnect', function () { io.socket.on('reconnect', function () {
......
...@@ -36,7 +36,7 @@ ...@@ -36,7 +36,7 @@
<a href="/app/#/student/{{studentData.id}}/instructions" ng-click="nav.tab = 'instructions'"><span class="glyphicon glyphicon-tasks" aria-hidden="true"></span> {{ 'instructions' | translate }}</a> <a href="/app/#/student/{{studentData.id}}/instructions" ng-click="nav.tab = 'instructions'"><span class="glyphicon glyphicon-tasks" aria-hidden="true"></span> {{ 'instructions' | translate }}</a>
</li> </li>
<li role="presentation" ng-class="{'active' : nav.tab == 'session'}" ng-if="studentData.supervision == 2"> <li role="presentation" ng-class="{'active' : nav.tab == 'session'}" ng-if="studentData.supervision == 2">
<a href="/app/#/student/{{studentData.id}}/session" ng-click="nav.tab = 'session'"><span class="glyphicon glyphicon-transfer" aria-hidden="true"></span> {{ 'session' | translate }}</a> <a href="/app/#/student/{{studentData.id}}/session" ng-click="nav.tab = 'session'"><span class="glyphicon glyphicon-transfer" aria-hidden="true"></span> {{ 'sessions' | translate }}</a>
</li> </li>
<li role="presentation" ng-class="{'active' : nav.tab == 'reports'}" ng-if="studentData.supervision != 1"> <li role="presentation" ng-class="{'active' : nav.tab == 'reports'}" ng-if="studentData.supervision != 1">
<a href="/app/#/student/{{studentData.id}}/reports" ng-click="nav.tab = 'reports'"><span class="glyphicon glyphicon-file" aria-hidden="true"></span> {{ 'reports' | translate }}</a> <a href="/app/#/student/{{studentData.id}}/reports" ng-click="nav.tab = 'reports'"><span class="glyphicon glyphicon-file" aria-hidden="true"></span> {{ 'reports' | translate }}</a>
......
<div class="panel panel-default student_tab_panel"> <div class="panel panel-default student_tab_panel">
<div class="panel-body"> <div class="panel-body">
<div ng-show="studentData.num_peers<2" >
<table style="border: 1px solid #666666; padding:5px; background-color:#f5f5f5;" width="50%">
<tr>
<td><h4 translate>warning_no_tablet_online</h4></td>
</tr>
</table>
</div>
<div class="row"> <div class="row">
<div class="col-md-6"> <div class="col-md-6">
<form role="form" action="#"> <form role="form" action="#" ng-hide="ws_recover ">
<select class="form-control" name="met_select" id="met_select" ng-model="selectedMethod" ng-options="m.name for m in methods" ng-change="load_instructions(selectedMethod)"> <select class="form-control" name="met_select" id="met_select" ng-model="selectedMethod" ng-options="m.name for m in methods" ng-change="load_instructions(selectedMethod)">
<option value="" translate>select_a_method</option> <option value="" translate>select_a_method</option>
</select> </select>
...@@ -15,7 +22,7 @@ ...@@ -15,7 +22,7 @@
<div class="row" ng-hide="!selectedIns"> <div class="row" ng-hide="!selectedIns">
<p class="session_controls"> <p class="session_controls">
<div class="col-md-4"> <div class="col-md-4">
<a ng-click="startTimer(); new_ws()" ng-disabled="!selectedIns" ng-hide="sessionRunning" class="btn btn-success btn-sm" role="button" id="session_new" translate>new_session</a> <a ng-click="startTimer(); new_ws()" ng-disabled="!selectedIns " ng-hide="sessionRunning || studentData.num_peers<2" class="btn btn-success btn-sm" role="button" id="session_new" translate>new_session</a>
<a class="text_large" ng-click="pause_ws(); pauseTimer();" ng-hide="!sessionRunning || paused" id="session_pause" popover="{{ 'pause_session' | translate}}" popover-trigger="mouseenter"> <a class="text_large" ng-click="pause_ws(); pauseTimer();" ng-hide="!sessionRunning || paused" id="session_pause" popover="{{ 'pause_session' | translate}}" popover-trigger="mouseenter">
<span class="glyphicon glyphicon-pause" aria-hidden="true" title="{{ 'pause_session' | translate }}"></span> <span class="glyphicon glyphicon-pause" aria-hidden="true" title="{{ 'pause_session' | translate }}"></span>
</a> </a>
...@@ -115,12 +122,9 @@ ...@@ -115,12 +122,9 @@
<span class="glyphicon glyphicon-chevron-up" aria-hidden="true" popover="{{ 'next_sessions' | translate}}" popover-trigger="mouseenter"></span> <span class="glyphicon glyphicon-chevron-up" aria-hidden="true" popover="{{ 'next_sessions' | translate}}" popover-trigger="mouseenter"></span>
</a> </a>
</div> </div>
<div class="list-group">
<div class="list-group-item" ng-repeat="s in wsessions | orderBy: '-begin' | limitTo: numPerPage:(currentPage-1)*numPerPage"> <div ng-show="ws_recover" >
<div ng-show="showLastTry && wsessions.length > 0"> <table style="border: 1px solid #666666; padding:5px; background-color:#f5f5f5;" width="50%">
<h4><strong>{{ 'last_session' | translate}}</strong>: {{ studentData.current_method }}, {{ studentData.current_instruction }}</h4>
<div ng-show="ws_recover" >
<table style="border: 1px solid #666666; padding:5px; background-color:#f5f5f5;" width="100%">
<tr> <tr>
<td><h4 translate>warning_last_session_bad</h4></td> <td><h4 translate>warning_last_session_bad</h4></td>
<td> <td>
...@@ -128,7 +132,11 @@ ...@@ -128,7 +132,11 @@
</td> </td>
</tr> </tr>
</table> </table>
</div> </div>
<div class="list-group">
<div class="list-group-item" ng-repeat="s in wsessions | orderBy: '-begin' | limitTo: numPerPage:(currentPage-1)*numPerPage">
<div ng-show="showLastTry && wsessions.length > 0">
<h4><strong>{{ 'last_session' | translate}}</strong>: {{ studentData.current_method }}, {{ studentData.current_instruction }}</h4>
</div> </div>
<button class="btn btn-primary pull-right" type="button" ng-click="showTries = !showTries"> <button class="btn btn-primary pull-right" type="button" ng-click="showTries = !showTries">
{{ 'tries' | translate }} {{ 'tries' | translate }}
......
/** /**
* WebSocket Server Settings * WebSocket Server Settings
* (sails.config.sockets) * (sails.config.sockets)
...@@ -12,7 +13,7 @@ ...@@ -12,7 +13,7 @@
module.exports.sockets = { module.exports.sockets = {
pingTimeout: 300000, // set timeout to 5 minutes pingTimeout: 20000, // set timeout to 20 secs
/*************************************************************************** /***************************************************************************
* * * *
...@@ -23,8 +24,7 @@ module.exports.sockets = { ...@@ -23,8 +24,7 @@ module.exports.sockets = {
* automatically. * * automatically. *
* * * *
***************************************************************************/ ***************************************************************************/
/*onConnect: function(session, socket) { /* onConnect: function(session, socket) {
}, },
*/ */
...@@ -34,11 +34,16 @@ module.exports.sockets = { ...@@ -34,11 +34,16 @@ module.exports.sockets = {
* disconnects * * disconnects *
* * * *
***************************************************************************/ ***************************************************************************/
/*onDisconnect: function(session, socket) { afterDisconnect: function (session, socket, cb) {
// By default: do nothing. var rooms=sails.hooks.rooms.getRoom(socket);
if (rooms)
for (var i = 0; i < rooms.length; i++) {
console.log("Unubscribed room in socket afterDisconnect: " + rooms[i]);
sails.hooks.rooms.unsubscribeFromRoom(rooms[i], socket);
}
}, },
*/
/*************************************************************************** /***************************************************************************
* * * *
...@@ -49,12 +54,12 @@ module.exports.sockets = { ...@@ -49,12 +54,12 @@ module.exports.sockets = {
* flashsockets by adding 'flashsocket' to this list: * * flashsockets by adding 'flashsocket' to this list: *
* * * *
***************************************************************************/ ***************************************************************************/
transports: [ // transports: [
// 'websocket' // 'websocket'
// 'htmlfile', // 'htmlfile',
// 'xhr-polling' // 'xhr-polling'
'polling' // 'polling'
] // ]
/*************************************************************************** /***************************************************************************
* * * *
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment