GUI refactorizacion working: issues…

GUI refactorizacion working: issues #348,#350,#351,#352,#352,#356,#360,#361,#362,#363,#364,#365,#366,#367 closed
parent ea32bf48
Showing with 332 additions and 641 deletions
......@@ -31,7 +31,7 @@ android {
debug {
resValue "string", "db_name", "PCB.db"
resValue "bool", "force_db_create", "true"
resValue "bool", "force_db_create", "false"
resValue "bool", "ssl_connect", "false"
resValue "bool", "force_img_download", "false"
resValue "integer", "netservice_timing", "20"
......
......@@ -52,7 +52,8 @@ public class RestapiWrapper {
this.token=token;
}
public String getToken() {
if (token==null) throw new java.lang.NullPointerException("Token has no value. Use constructor properly or RestapiWrapper.setToken must be invoked previously"); return token;
if (token==null) throw new java.lang.NullPointerException("Token has no value. Use constructor properly or RestapiWrapper.setToken must be invoked previously");
return token;
}
public String getServer() {
return server;
......@@ -117,11 +118,11 @@ public class RestapiWrapper {
pingResult = GET(server + "/" + ping_op, null)!=null;
} catch (UnknownHostException e){
//e.printStackTrace();
Log.e(RestapiWrapper.class.getName(), "ping failed at "+ping_op);
Log.i(RestapiWrapper.class.getName(), "ping failed at "+ping_op);
return false;
} catch (IOException e) {
e.printStackTrace();
Log.e(com.yottacode.net.RestapiWrapper.class.getName(), "ping failed at"+ping_op);
Log.i(com.yottacode.net.RestapiWrapper.class.getName(), "ping failed at"+ping_op);
//error_listener.error(e);
}
return pingResult;
......
......@@ -17,6 +17,7 @@ import java.io.InputStream;
import java.util.Vector;
import com.yottacode.pictogram.gui.LoginActivity;
import com.yottacode.pictogram.tools.Img;
import com.yottacode.pictogram.net.ImgDownloader;
import com.yottacode.pictogram.net.iImgDownloaderListener;
......@@ -240,9 +241,9 @@ public class Device extends SQLiteOpenHelper {
if (cursor.getString(2).equals(apwd)) {
users = recoverStudents(cursor.getInt(0));
if (users.size() == 0)
throw new LoginException("Supervisor hasn't got students", false, true, true);
throw new LoginException("Supervisor hasn't got students", LoginException.NO_STUDENTS);
}
else throw new LoginException("Supervisor password incorrect", false, true,false);
else throw new LoginException("Supervisor password incorrect", LoginException.BAD_PASSWORD);
}
cursor.close();
if (!user_found) {
......@@ -255,12 +256,12 @@ public class Device extends SQLiteOpenHelper {
users.add(new User(cursor.getInt(0), cursor.getString(1), cursor.getString(2), cursor.getString(3), cursor.getString(4), cursor.getString(5), cursor.getString(6), cursor.getString(7), cursor.getString(8),
User.NO_SUPERVISOR, "", "", "", "", "", "", "", ""));
}
else throw new LoginException("Student password incorrect", true, false,false);
else throw new LoginException("Student password incorrect", LoginException.BAD_PASSWORD);
}
cursor.close();
}
if (!user_found) throw new LoginException("User not found", false, false,false);
if (!user_found) throw new LoginException("User not found", LoginException.UNKNOWN_USERNAME);
db.close();
return users;
......@@ -292,26 +293,7 @@ public class Device extends SQLiteOpenHelper {
public void insertUser(User user) {
SQLiteDatabase db = this.getWritableDatabase();
Log.i("FERNANDO","INSERT INTO users_detail values (" +
user.get_id_stu() + ", " +
"'" + user.get_nickname_stu() + "', " +
(user.get_pwd_stu()==null ? "null, " : ("'" + user.get_pwd_stu() + "', " +
"'")) + user.get_name_stu() + "', " +
"'" + user.get_surname_stu() + "', " +
"'" + user.get_url_img_stu() + "', " +
"'" + user.get_gender_stu() + "', " +
"'" + user.get_lang_stu() + "', " +
"'" + user.get_json_attrs() + "', " +
"'" + user.get_id_sup() + "', " +
"'" + user.get_email_sup() + "', " +
"'" + user.get_pwd_sup() + "', " +
"'" + user.get_name_sup() + "', " +
"'" + user.get_surname_sup() + "', " +
"'" + user.get_url_img_sup() + "'," +
"'" + user.get_gender_sup() + "'," +
"'" + user.get_lang_sup() + "'," +
"'" + user.get_tts_engine_sup() + "'" +
")");
db.execSQL("INSERT INTO users_detail values (" +
user.get_id_stu() + ", " +
"'" + user.get_nickname_stu() + "', " +
......
......@@ -4,17 +4,18 @@ package com.yottacode.pictogram.dao;
* Created by Fernando on 15/03/2016.
*/
public class LoginException extends Exception{
boolean student;
boolean supervisor;
boolean no_students;
public LoginException(String msg, boolean student, boolean supervisor, boolean no_students) {
public static final int UNKNOWN_USERNAME=0;
public static final int BAD_PASSWORD=1;
public static final int NO_STUDENTS=2;
int code;
public LoginException(String msg, int code) {
super(msg);
this.student=student;
this.supervisor=supervisor;
this.no_students=no_students;
this.code=code;
}
public boolean knonwn_student() {return student;}
public boolean known_supervisor() {return supervisor;}
public boolean no_students() {return no_students;}
public boolean no_username_found() {return this.code==LoginException.UNKNOWN_USERNAME;}
public boolean no_pwd_found() {return this.code==LoginException.BAD_PASSWORD;}
public boolean no_supervisor_students() {return this.code==LoginException.NO_STUDENTS;}
}
......@@ -63,7 +63,7 @@ class DeviceHelper {
public class PCBDBHelper extends SQLiteOpenHelper {
User currentUser;
boolean user_online; //true if the given user logged into the server
/**
* Create a helper object to create, open, and/or manage a database.
......@@ -74,9 +74,13 @@ public class PCBDBHelper extends SQLiteOpenHelper {
*/
public PCBDBHelper(CursorFactory factory, int version, User user) {
super(PCBcontext.getContext(), DeviceHelper.getDBName(PCBcontext.getContext()), factory, version);
this.user_online=false;
this.setCurrentUser(user == null ? this.getCurrentUser() : user);
}
public boolean isUser_online() {return this.user_online;}
public void user_online(boolean user_online) {this.user_online=user_online;}
/**
* Save the current user of the PCB. It is required to retrieve the last user.
*
......
......@@ -181,6 +181,8 @@ public class Picto extends Img {
*/
public boolean is_invisible() {
try {
Log.i("FERNANDO 2",this.attributes.getString(JSON_ATTTRS.STATUS).equals(JSON_ATTTR_STATUS_VALUES.INVISIBLE) + "||" +
this.is_category()+ "&&"+ !PCBcontext.getVocabulary().isVisibleCategory(this.get_id()));
return this.attributes.getString(JSON_ATTTRS.STATUS).equals(JSON_ATTTR_STATUS_VALUES.INVISIBLE) ||
(this.is_category() && !PCBcontext.getVocabulary().isVisibleCategory(this.get_id()));
} catch (JSONException e) {
......@@ -303,7 +305,8 @@ public class Picto extends Img {
* @return
*/
public boolean is_category() {
return this.get_category()==Picto.NO_CATEGORY && this.get_row()!=Picto.ROW_UNCATEGORIZED_CONCEPTS;
Log.i("FERNANDO 3",(this.get_category()==Picto.NO_CATEGORY)+"&&"+(this.get_column()!=Picto.ROW_UNCATEGORIZED_CONCEPTS)+":"+this.get_column());
return this.get_category()==Picto.NO_CATEGORY && this.get_column()!=Picto.ROW_UNCATEGORIZED_CONCEPTS;
}
/**
*
......
package com.yottacode.pictogram.dao;
/**
* Created by Fernando on 15/03/2016.
*/
public class Supervisor {
}
package com.yottacode.pictogram.gui;
import android.annotation.TargetApi;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.util.Log;
import android.view.Window;
import android.widget.Toast;
import com.yottacode.pictogram.dao.LoginException;
import com.yottacode.pictogram.dao.User;
import com.yottacode.pictogram.R;
import com.yottacode.net.iRestapiListener;
import com.yottacode.net.SSLDummyContext;
import com.yottacode.net.RestapiWrapper;
import com.yottacode.pictogram.net.ImgDownloader;
import com.yottacode.pictogram.tools.Img;
import com.yottacode.pictogram.tools.PCBcontext;
import com.yottacode.pictogram.net.iImgDownloaderListener;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Vector;
/**
* LoginActivity show the login window to select the student and supervisor
* It uses device to read the token value
* @author Miguel Ángel García Cumbreras
* @author Fernando Martínez santiago
* @version 1.0
*/
......
package com.yottacode.pictogram.gui;
/**
* Created by miguelangel on 29/4/15.
*/
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import org.apache.http.conn.ssl.SSLSocketFactory;
public class MySSLSocketFactory extends SSLSocketFactory {
SSLContext sslContext = SSLContext.getInstance("TLS");
public MySSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
super(truststore);
TrustManager tm = new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
};
sslContext.init(null, new TrustManager[] { tm }, null);
}
@Override
public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException {
return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose);
}
@Override
public Socket createSocket() throws IOException {
return sslContext.getSocketFactory().createSocket();
}
}
......@@ -95,7 +95,7 @@ public class PanelAdapter extends ArrayAdapter {
//if (position==0 || position==2)
if (pictogramSize==0)
imageView.setLayoutParams(new GridView.LayoutParams(getPx(110), getPx(70)));// ancho y alto
imageView.setLayoutParams(new GridView.LayoutParams(110,70));// ancho y alto
else if (pictogramSize==1)
imageView.setLayoutParams(new GridView.LayoutParams(140, 140));// ancho y alto
......
package com.yottacode.pictogram.gui;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.GridView;
import android.widget.ImageView;
import java.util.ArrayList;
/**
* Created by magc on 17/03/15.
*/
public class PanelAdapterUsers extends BaseAdapter{
private Context mContext;
//private int mNumRows;
//private ArrayList<Integer> imagenes;
private ArrayList<UserBasicInfo> users;
public PanelAdapterUsers(Context context){
mContext = context;
//imagenes = new ArrayList<Integer>();
users = new ArrayList<UserBasicInfo>();
// Añado los objetos de usuario, desde la bbdd
// ...
// Pongo una imagen dinámica de prueba
//users.add("http://wwwdi.ujaen.es/sites/default/files/yo.jpg");
}
@Override
public int getCount(){
return users.size();
}
// AÑADIR ITEM AL ADAPTADOR
public void addView(UserBasicInfo user){
users.add(user);
}
// ELIMINAR ITEM DEL ADAPTADOR
public void deleteView(int position){
users.remove(position);
}
// Devolver la URL de la imagen por posición
@Override
public UserBasicInfo getItem(int position){
return users.get(position);
}
@Override
public long getItemId(int position){
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent){
//TextView view;
//Button view;
ImageView view;
if(convertView == null){
//view = new TextView(this.mContext);
//view = new Button(this.mContext);
//view.setTextSize(18);
view = new ImageView(this.mContext);
// Ancho y alto de la imagen en la vista donde se va a colocar (en pixels)
view.setLayoutParams(new GridView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getPx(100)));
view.setScaleType(ImageView.ScaleType.CENTER_CROP);
view.setPadding(5,5,5,5);
}else{
//view = (TextView) convertView;
//view = (Button) convertView;
view = (ImageView) convertView;
}
//view.setText(" Fila " + position);
//view.setTextColor(Color.rgb(position * 10, 255 - position * 10, 200));
//Q-view.setImageResource(imagenes.get(position));
//Drawable drawable = DownloadFullFromUrl(imagenes.get(position));
//view.setImageBitmap(DownloadFullFromUrl("http://www.androidpeople.com/wp-content/uploads/2010/03/android.png"));
return view;
}
// Función que devuelve los píxeles equivalentes a los dps pasados
public int getPx(int dimensionDp) {
float density = mContext.getResources().getDisplayMetrics().density;
return (int) (dimensionDp * density + 0.5f);
}
}
\ No newline at end of file
......@@ -204,6 +204,7 @@ public class PictogramActivity extends Activity implements iVocabularyListener,
//rowNumberAdapter.notifyDataSetChanged();
Picto p = panelAdapter.getItem(position);
Log.i("PA FERNANDO 1",(p != null) +"&&"+(!p.is_invisible())+"&&"+ p.is_enabled());
if (p != null && !p.is_invisible() && p.is_enabled()) {
Log.d(LOG_TAG, "Clic en picto: " + p.toString());
//Log.d(LOG_TAG, "STATUS: " + p.get_status());
......
package com.yottacode.pictogram.gui;
import android.app.ListFragment;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.view.View;
import android.widget.ListView;
import com.yottacode.pictogram.R;
import com.yottacode.pictogram.dao.Device;
import com.yottacode.pictogram.dao.User;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.Vector;
/**
* StudentFragment show the list of the students of the device
* @author Miguel Ángel García Cumbreras
* @version 1.0
*/
public class StudentFragment extends ListFragment {
// Almaceno los estudiantes, los ids de estudiantes y supervisores
String[] students;
Vector<Integer> students_ids;
Vector<Integer> supervisor_ids;
Map<String, List<String>> map_students = new TreeMap<String, List<String>>();
Map<String, Bitmap> map_supervisor_pictures = new TreeMap<String, Bitmap>();
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Vector<User> users;
Vector<Bitmap> imageStudents = new Vector<Bitmap>();
students_ids = new Vector<Integer>();
supervisor_ids = new Vector<Integer>();
try {
Device device = new Device(getActivity(), null, 1);
// Cargo los datos de los estudiantes
users = device.getUsers();
Set<String> s_students;
List<String> l_supervisors = new ArrayList<String>();
if (users!=null){
// Recorro el vector para sacar los nombres de estudiantes y las imágenes
for( int i = 0 ; i < users.size() ; i++ ){
int st_id = users.elementAt(i).get_id_stu();
int su_id = users.elementAt(i).get_id_sup();
String st_name = users.elementAt(i).get_name_stu();
String st_surname = users.elementAt(i).get_surname_stu();
Bitmap st_picture = users.elementAt(i).get_bitmap_stu(getActivity());
String su_name = users.elementAt(i).get_name_sup();
String su_surname = users.elementAt(i).get_surname_sup();
Bitmap su_picture = users.elementAt(i).get_bitmap_sup(getActivity());
if (!map_students.containsKey(st_name + " " + st_surname)){
// Creo un nuevo supervisor y lo añado
l_supervisors = new ArrayList<String>();
l_supervisors.add(su_name + " " + su_surname);
imageStudents.add(st_picture);
map_supervisor_pictures.put(su_name + " " + su_surname, su_picture);
map_students.put(st_name + " " + st_surname, l_supervisors);
students_ids.add(st_id);
supervisor_ids.add(su_id);
} else{
// Saco los supervisores, añado uno más y grabo
l_supervisors = map_students.get(st_name + " " + st_surname);
l_supervisors.add(su_name + " " + su_surname);
map_students.put(st_name + " " + st_surname, l_supervisors);
supervisor_ids.add(su_id);
}
}
// Saco los estudiantes
s_students = map_students.keySet();
students = s_students.toArray(new String[s_students.size()]);
} else{
// TRATO EL ERROR. COMO ?????
}
CustomList adapter = new CustomList(getActivity(), students, imageStudents);
setListAdapter(adapter);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* When an student is selected it calls the supervisor fragment
* @param l
* @param v
* @param position
* @param id
*/
/*
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
// Guardo en device el id del estudiante
Device device = new Device(getActivity(), null, 1);
device.setStuId(students_ids.elementAt(position));
// Llamo al fragment de los supervisores para cargar sus datos
SupervisorFragment supervisores = (SupervisorFragment)getFragmentManager().findFragmentById(R.id.supervisores);
// Cojo el nombre y apellidos de ese estudiante
String name_surname_st = students[position];
supervisores.change(map_students, name_surname_st, map_supervisor_pictures, supervisor_ids);
getListView().setSelector(android.R.color.holo_blue_dark);
}
*/
}
\ No newline at end of file
......@@ -5,6 +5,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
import java.util.zip.Inflater;
import android.app.Fragment;
......@@ -29,6 +30,7 @@ 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 org.json.JSONArray;
import org.json.JSONException;
......@@ -55,22 +57,21 @@ public class StudentFragmentGrid extends Fragment{
String nameStudents[];
Vector<Bitmap> imageStudents;
private Vector<JSONObject> downloaded_students;
GridView gridView;
private final String LOG_TAG = this.getClass().getSimpleName(); // Or .getCanonicalName()
GridView gridView;
boolean onlineStudentsOK=false;
private void refreshGridView() {
CustomList adapter = new CustomList(getActivity(), nameStudents, imageStudents);
gridView.setAdapter(adapter);
gridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
private void showStudentsGrid(){
CustomList adapter = new CustomList(getActivity(), nameStudents, imageStudents);
gridView.setAdapter(adapter);
gridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> arg0, View arg1, int pos, long arg3) {
set_user(pos);
}
});
gridView.invalidateViews();
}
private void set_user(int i) {
Boolean offline = getActivity().getIntent().getBooleanExtra("offline", false);
......@@ -83,7 +84,10 @@ public class StudentFragmentGrid extends Fragment{
Log.e(StudentFragmentGrid.this.getClass().getCanonicalName(), e.getMessage());
}
PCBcontext.set_user(currentUser, null, null);
} else new_user(i);
Intent pictogramActivity = new Intent(getActivity(), PictogramActivity.class);
startActivity(pictogramActivity);
} else
new_user(i);
}
private void new_user(int i) {
......@@ -111,7 +115,8 @@ public class StudentFragmentGrid extends Fragment{
"");
PCBcontext.getDevice().insertUser(new_user);
final ProgressDialog progressDialog= ProgressDialog.show(getActivity(), getString(R.string.loadingGrammar),
Log.i(this.getClass().getCanonicalName(),"Loading vocabulary for "+new_user.get_name_stu());
final ProgressDialog progressDialog=ProgressDialog.show(getActivity(), getString(R.string.loadingGrammar),
getString(R.string.userLoadingTxt), false, false);
PCBcontext.set_user(new_user, intent.getStringExtra("token"), new iImgDownloaderListener() {
@Override
......@@ -131,7 +136,7 @@ public class StudentFragmentGrid extends Fragment{
Log.e(StudentFragmentGrid.this.getClass().getCanonicalName(), e.getMessage());
}
}
private void create_student_list_online() {
private void show_student_list_online() {
final Vector<Img> imgs = new Vector<>(downloaded_students.size());
this.nameStudents=new String[downloaded_students.size()];
this.idStudents=new Vector<>(downloaded_students.size());
......@@ -169,12 +174,11 @@ public class StudentFragmentGrid extends Fragment{
e.printStackTrace();
Log.e(StudentFragmentGrid.this.getClass().getCanonicalName(),e.getMessage());
}
refreshGridView();
}
if (StudentFragmentGrid.super.getView()!=null)
showStudentsGrid();
else
gridView.setAdapter(null);
onlineStudentsOK=true;
}
@Override
......@@ -186,11 +190,9 @@ public class StudentFragmentGrid extends Fragment{
}
private void download_students() {
int sup_id = getActivity().getIntent().getExtras().getInt("sup_id");
private void download_students(int sup_id ) {
String token = getActivity().getIntent().getExtras().getString("token");
RestapiWrapper wrapper = new RestapiWrapper(
getActivity().getApplicationContext().getResources().getString(R.string.server), token);
String operation = "sup/" + sup_id + "/students";
......@@ -218,10 +220,10 @@ public class StudentFragmentGrid extends Fragment{
e.printStackTrace();
}
}
create_student_list_online();
show_student_list_online();
switch (students.length()) {
case 0:
((LoginActivity) getActivity()).show_login_failed(R.string.noStudentsError);
GUITools.show_alert(getActivity(),R.string.noStudentsError);
break;
case 1:
new_user(0);
......@@ -241,10 +243,13 @@ public class StudentFragmentGrid extends Fragment{
setRetainInstance(true);
Intent intent=getActivity().getIntent();
Boolean offline = intent.getBooleanExtra("offline", false);
int sup_id=intent.getIntExtra("sup_id", 0);
if (offline) {
Vector<User> users;
try {
users = PCBcontext.getDevice().recoverStudents(intent.getIntExtra("sup_id", 0));
users = PCBcontext.getDevice().recoverStudents(sup_id);
Log.i(this.getClass().getCanonicalName(),"Recovering "+users.size()+" students list for "+ sup_id+" from local DB");
} catch (JSONException e) {
e.printStackTrace();
users=null;
......@@ -260,22 +265,21 @@ public class StudentFragmentGrid extends Fragment{
this.imageStudents.add(user.get_bitmap_stu(getActivity().getBaseContext()));
} catch (IOException e) {
e.printStackTrace();
Log.e(this.getClass().getName(), " Server restapi error: " + e.getLocalizedMessage());
Log.e(this.getClass().getName(), " Getting images error: " + e.getLocalizedMessage());
}
}
}
}
else
download_students();
download_students(sup_id);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_new_student, container, false);
gridView = (GridView)v.findViewById(R.id.gridview);
if (getActivity() == null || gridView == null) return null;
return v;
View view = inflater.inflate(R.layout.fragment_new_student, container, false);
gridView = (GridView)view.findViewById(R.id.gridview);
Boolean offline = getActivity().getIntent().getBooleanExtra("offline", false);
if (offline || onlineStudentsOK) showStudentsGrid();
return view;
}
}
}
\ No newline at end of file
package com.yottacode.pictogram.gui;
import android.app.FragmentTransaction;
import android.app.ListFragment;
import android.content.Intent;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ListView;
import com.yottacode.pictogram.R;
import com.yottacode.pictogram.dao.Device;
import com.yottacode.pictogram.tools.PCBcontext;
import java.util.List;
import java.util.Map;
import java.util.Vector;
/**
* SupervisorFragment show the list of the supervisors of the device when an student has been selected
* @author Miguel Ángel García Cumbreras
* @version 1.0
*/
public class SupervisorFragment extends ListFragment {
Vector<Integer> supervisor_ids;
PCBcontext pcb;
@Override
public View onCreateView(LayoutInflater inflater,ViewGroup container, Bundle savedInstanceState) {
View view =inflater.inflate(R.layout.list_fragment, container, false);
// Singleton getInstance
pcb = PCBcontext.getInstance();
return view;
}
/**
*
* @param map_students list of students
* @param name_surname_st name and surname of the student selected
* @param map_supervisor_pictures pictures of the supervisors of the student selected
* @param sup_ids ids of the supervisors
*/
public void change(Map<String, List<String>> map_students, String name_surname_st, Map<String, Bitmap> map_supervisor_pictures, Vector<Integer> sup_ids){
// Saco los supervisores de ese estudiante
List<String> l_supervisors = map_students.get(name_surname_st);
String[] supervisors = l_supervisors.toArray(new String[l_supervisors.size()]);
Vector<Bitmap> imageSupervisors = new Vector<Bitmap>();
supervisor_ids = sup_ids;
for (int i=0; i<supervisors.length; i++){
String supervisor = supervisors[i];
imageSupervisors.add(map_supervisor_pictures.get(supervisor));
}
CustomList adapter = new CustomList(getActivity(), supervisors, imageSupervisors);
setListAdapter(adapter);
}
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
// Guardo en device el id del supervisor
pcb.getDevice().setLastSupId(supervisor_ids.elementAt(position));
Intent pictogramActivity = new Intent(getActivity(), PictogramActivity.class);
startActivity(pictogramActivity);
}
}
\ No newline at end of file
package com.yottacode.pictogram.gui;
import android.app.Fragment;
import android.content.Intent;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.GridView;
import android.widget.Toast;
import com.yottacode.pictogram.R;
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.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.Vector;
/**
* SupervisorFragmentGrid implements the gridview with the supervisors
* @author Miguel Ángel García Cumbreras
* @version 1.0
*/
public class SupervisorFragmentGrid extends Fragment {
Vector<Integer> supervisor_ids;
PCBcontext pcb;
GridView gridView;
// String constant for logs
private final String LOG_TAG = this.getClass().getSimpleName(); // Or .getCanonicalName()
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
// Singleton getInstance
pcb = PCBcontext.getInstance();
}
@Override
public View onCreateView(LayoutInflater inflater,ViewGroup container, Bundle savedInstanceState) {
View view =inflater.inflate(R.layout.fragment_new_student, container, false);
gridView = (GridView)view.findViewById(R.id.gridview);
return view;
}
/**
*
*/
public void change(Map<Integer, Vector<Integer>> ids_stu_sup, int pos){
// Saco el stu_id de esa posicion
Vector<Integer> students_ids = new Vector<Integer>();
for(Map.Entry<Integer,Vector<Integer>> entry : ids_stu_sup.entrySet()) {
Integer id_stu = entry.getKey();
students_ids.add(id_stu);
}
Integer id_stu_selected = students_ids.elementAt(pos);
// Saco los supervisores de ese estudiante
supervisor_ids = ids_stu_sup.get(id_stu_selected);
// De cada par id_stu, id_sup saco el nombre y la imagen
List<String> l_supervisors_name = new ArrayList<String>();
Vector<Bitmap> imageSupervisors = new Vector<Bitmap>();
Iterator itr = supervisor_ids.iterator();
while(itr.hasNext()) {
Integer id_sup = (Integer) itr.next();
try {
User currentUser = pcb.getDevice().findUser(id_stu_selected, id_sup);
String su_name = currentUser.get_name_sup();
Bitmap su_picture = currentUser.get_bitmap_sup(getActivity());
l_supervisors_name.add(su_name);
imageSupervisors.add(su_picture);
String[] supervisors_name = l_supervisors_name.toArray(new String[l_supervisors_name.size()]);
CustomList adapter = new CustomList(getActivity(), supervisors_name, imageSupervisors);
gridView.setAdapter(adapter);
gridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> arg0, View arg1, int pos, long arg3) {
pcb.getDevice().setLastSupId(supervisor_ids.elementAt(pos));
Intent pictogramActivity = new Intent(getActivity(), PictogramActivity.class);
startActivity(pictogramActivity);
}
});
} catch (JSONException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
......@@ -112,9 +112,9 @@ public class TapeAdapter extends BaseAdapter {
if(convertView == null){
view = new ImageView(PCBcontext.getContext());
// Ancho y alto de la imagen en la vista donde se va a colocar (en pixels)
view.setLayoutParams(new GridView.LayoutParams(getPx(80), getPx(60)));
view.setLayoutParams(new GridView.LayoutParams(80, 60));
view.setScaleType(ImageView.ScaleType.CENTER_CROP);
view.setPadding(5,10,5,5);
view.setPadding(5,5,5,5);
//view.setPadding(10,15,10,10);
}else{
view = (ImageView) convertView;
......
package com.yottacode.pictogram.gui;
/**
* Created by miguelangel on 16/3/15.
*/
public class UserBasicInfo {
private String FirstName;
private String Id;
private String ImageUrl;
public UserBasicInfo(String firstname, String id, String imageUrl) {
this.FirstName = firstname;
this.Id = id;
this.ImageUrl = imageUrl;
}
public String getFirstName() {
return this.FirstName;
}
public void setFirstName(String firstname) {
this.FirstName = firstname;
}
public String getUsertId() {
return this.Id;
}
public void setUserId(String Id) {
this.Id = Id;
}
public String getImageUrl() {
return this.ImageUrl;
}
public void setImageUrl(String imageUrl) {
this.ImageUrl = imageUrl;
}
}
\ No newline at end of file
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.Hashtable;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
......@@ -40,7 +49,65 @@ public class NetService implements Runnable {
exec.scheduleWithFixedDelay(this, 0, delay, TimeUnit.SECONDS);
}
/**
* 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
*/
private void restart_app() {
Intent i = PCBcontext.getContext().getPackageManager()
.getLaunchIntentForPackage(PCBcontext.getContext().getPackageName());
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
PCBcontext.getContext().startActivity(i);
}
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) 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) restart_app();
}
});
}
public boolean online() {return updated;}
/**
......@@ -64,16 +131,10 @@ public class NetService implements Runnable {
if (result == null) {
updated = false;
} else if (!updated) {
// Comprobar si no tiene token, para hacer login transparente
if (PCBcontext.getRestapiWrapper().getToken() == null){
// Login transparente para el token
// Saco username y pass
//SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0);
//String username = settings.getString("username", "");
// ....
} else{
// MOVER ESTO AL LISTENER DE RESULT
// 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();
......@@ -82,14 +143,40 @@ public class NetService implements Runnable {
}
}
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();
}
});
}
private void notifyStatus() {
NotificationCompat.Builder builder;
int notifyID = 1;
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);
if (updated)
builder.setSmallIcon(R.drawable.picton)
.setContentTitle("Pictogram online")
.setContentText(PCBcontext.getContext().getResources().getString(R.string.pictogram_online));
else
builder.setSmallIcon(R.drawable.pictoff)
.setContentTitle("Pictogram offline")
.setContentText(PCBcontext.getContext().getResources().getString(R.string.pictogram_offline));
NotificationManager mNotificationManager =
(NotificationManager) PCBcontext.getContext().getSystemService(PCBcontext.getContext().NOTIFICATION_SERVICE);
// mId allows you to update the notification later on.
mNotificationManager.notify(notifyID, builder.build());
}
}
\ No newline at end of file
package com.yottacode.pictogram.net;
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 {
private static void checkLogin(JSONObject result) throws LoginException {
String error;
try {
error=result.has("error")
? result.getString("error")
: null;
} catch (JSONException e) {
e.printStackTrace();
error=null;
}
if (error!=null){
if (error.contains("Invalid password") || error.contains("No credentials sent"))
throw new LoginException(error, LoginException.BAD_PASSWORD);
else if (error.contains("User not found"))
throw new LoginException(error, LoginException.UNKNOWN_USERNAME);
}
}
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 {
checkLogin(result);
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 (LoginException e) {
listener.error(e);
} catch (JSONException e) {
listener.error(e);
}
}
@Override
public void error(Exception e) {
listener.error(e);
}
});
}
}
......@@ -4,6 +4,7 @@ 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;
......@@ -13,10 +14,9 @@ import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import com.yottacode.tools.FileTools;
import com.yottacode.tools.ImgTools;
import com.yottacode.tools.BitmapTools;
/**
* Img
......@@ -132,7 +132,8 @@ public class Img {
try {
this.bitmap=BitmapFactory.decodeStream(is);
if (this.bitmap.getWidth()>MAX_WIDTH) {
this.bitmap = new ImgTools(this.bitmap).rescale(MAX_WIDTH / (float) this.bitmap.getWidth());
this.bitmap = new BitmapTools(this.bitmap).rescale(MAX_WIDTH / (float) this.bitmap.getWidth()).paintSquare(3, Color.DKGRAY).get();
}
}catch(java.lang.OutOfMemoryError err) {
Log.e(Img.class.getCanonicalName(), "Out of memory when decoding "+this.get_url());
......@@ -140,6 +141,7 @@ public class Img {
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();
......
......@@ -76,12 +76,25 @@ public final class PCBcontext {
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);
}
/**
*
* @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() {
return pcbdb == null ? false //no hay usuario aun
: !getPcbdb().isUser_online();
}
public static boolean is_user_online() {
return pcbdb != null && getPcbdb().isUser_online();
}
// Return the context
// modified by Fernando
public static Context getContext(){ if (context==null) throw new java.lang.NullPointerException("Context is null. PCBcontext.init must be invoked previously"); return context; }
......
package com.yottacode.tools;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import java.util.BitSet;
/**
* Created by Fernando on 15/03/2016.
*/
public class ImgTools {
public class BitmapTools {
Bitmap bitmap;
public ImgTools(Bitmap bitmap ) { this.bitmap=bitmap;}
public BitmapTools(Bitmap bitmap) { this.bitmap=bitmap;}
public Bitmap get() {return this.bitmap;}
public Bitmap resize(int w, int h) {
public BitmapTools resize(int w, int h) {
int width = bitmap.getWidth();
int height = bitmap.getHeight();
int newWidth = w;
......@@ -35,15 +33,29 @@ public class ImgTools {
Bitmap resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0,
width, height, matrix, true);
// si queremos poder mostrar nuestra imagen tenemos que crear un
// objeto drawable y así asignarlo a un botón, imageview...
return resizedBitmap;
this.bitmap=resizedBitmap;
return this;
}
public Bitmap rescale(float scale) {
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 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 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) {
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(), "FERNANDO Alert:" + msg);
builder.setMessage(msg)
.setCancelable(false)
.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
}
});
AlertDialog alert = builder.create();
alert.show();
}
}
......@@ -20,7 +20,7 @@ translation VARCHAR(60) NOT NULL
CREATE TABLE student (
id INTEGER PRIMARY KEY,
nickname TEXT(40) NOT NULL,
pwd TEXT(40) NOT NULL,
pwd TEXT(40) NULL,
name TEXT(40) NOT NULL,
surname TEXT(60) NOT NULL,
url_img VARCHAR(250) NOT NULL,
......@@ -200,7 +200,7 @@ WHEN NEW.pwd_stu IS NOT NULL
BEGIN
INSERT OR REPLACE INTO student VALUES (NEW.id_stu, NEW.nickname_stu, NEW.pwd_stu, NEW.name_stu, NEW.surname_stu, NEW.url_img_stu, NEW.gender_stu, NEW.lang_stu, NEW.attributes_stu);
INSERT OR REPLACE INTO supervisor VALUES (NEW.id_sup, NEW.email_sup, NEW.pwd_sup, NEW.name_sup, NEW.surname_sup, NEW.url_img_sup, NEW.gender_sup, NEW.lang_sup, NEW.tts_engine_sup);
INSERT INTO users VALUES (NEW.id_stu,NEW.id_sup);
INSERT OR REPLACE INTO users VALUES (NEW.id_stu,NEW.id_sup);
END
;--
......@@ -209,20 +209,12 @@ INSTEAD OF INSERT ON users_detail
FOR EACH ROW
WHEN NEW.pwd_stu IS NULL
BEGIN
UPDATE
student
SET
nickname=NEW.nickname_stu,
name=NEW.name_stu,
surname=NEW.surname_stu,
url_img=NEW.url_img_stu,
gender=NEW.gender_stu,
lang=NEW.lang_stu,
attributes=NEW.attributes_stu
WHERE
id=NEW.id_stu;
INSERT OR REPLACE INTO
student (id, nickname,name,surname,url_img,gender,lang,attributes)
VALUES (
NEW.id_stu, NEW.nickname_stu, NEW.name_stu, NEW.surname_stu, NEW.url_img_stu, NEW.gender_stu, NEW.lang_stu, NEW.attributes_stu);
INSERT OR REPLACE INTO supervisor VALUES (NEW.id_sup, NEW.email_sup, NEW.pwd_sup, NEW.name_sup, NEW.surname_sup, NEW.url_img_sup, NEW.gender_sup, NEW.lang_sup, NEW.tts_engine_sup);
INSERT INTO users VALUES (NEW.id_stu,NEW.id_sup);
INSERT OR REPLACE INTO users VALUES (NEW.id_stu,NEW.id_sup);
END
;--
......
......@@ -19,7 +19,7 @@
<string name="LoginError">Login</string>
<string name="passErrorMsg">This password is not correct. Try again.</string>
<string name="userErrorTxt">User not found</string>
<string name="userErrorMsg">The user is not correct. Try again.</string>
<string name="userErrorMsg">Unknown user name</string>
<string name="userLoadingTxt">Loading</string>
<string name="userLoadingMsg">Loading students. Please wait.</string>
<string name="imguserLoadingMsg">Loading images students. Please wait.</string>
......@@ -36,8 +36,6 @@
<string name="codesNotEqual">The codes entered are not equal</string>
<string name="ok">Ok</string>
<string name="cancel">Cancel</string>
<string name="tabletNoYotta">Important: writes this ID number for your cabinet</string>
<string name="loginNoUsers">There are no students assigned with your tablet. Please, ask your cabinet</string>
<!-- Login -->
<string name="systemMessage">System message</string>
......
......@@ -20,7 +20,7 @@
<string name="LoginError">Login</string>
<string name="passErrorMsg">La contraseña indicada no es correcta. Inténtelo de nuevo.</string>
<string name="userErrorTxt">User not found</string>
<string name="userErrorMsg">El usuario indicado no es correcto. Inténtelo de nuevo.</string>
<string name="userErrorMsg">Nombre de usuario desconocido</string>
<string name="userLoadingTxt">Cargando</string>
<string name="userLoadingMsg">Cargando alumnos. Por favor espere.</string>
<string name="imguserLoadingMsg">Cargando imágenes de los alumnos. Por favor espere.</string>
......@@ -38,8 +38,6 @@
<string name="codesNotEqual">Los codigos introducidos no coinciden</string>
<string name="ok">Aceptar</string>
<string name="cancel">Cancelar</string>
<string name="tabletNoYotta">Importante: anote este nº de dispositivo para indicárselo al responsable de su asociación o gabinete</string>
<string name="loginNoUsers">No está asignado ningún alumno con tu tablet. Consulte con el responsable de su asociación o gabinete</string>
<!-- Login -->
<string name="systemMessage">Mensaje del sistema</string>
......
......@@ -20,7 +20,7 @@
<string name="LoginError">Login</string>
<string name="passErrorMsg">La contraseña indicada no es correcta. Inténtelo de nuevo.</string>
<string name="userErrorTxt">Usuario no encontrado</string>
<string name="userErrorMsg">El usuario indicado no es correcto. Inténtelo de nuevo.</string>
<string name="userErrorMsg">Nombre de usuario desconocido</string>
<string name="userLoadingTxt">Cargando</string>
<string name="userLoadingMsg">Cargando alumnos. Por favor espere.</string>
<string name="imguserLoadingMsg">Cargando imágenes de los alumnos. Por favor espere.</string>
......@@ -38,8 +38,6 @@
<string name="codesNotEqual">Los codigos introducidos no coinciden</string>
<string name="ok">Aceptar</string>
<string name="cancel">Cancelar</string>
<string name="tabletNoYotta">Importante: anote este nº de dispositivo para indicárselo al responsable de su asociación o gabinete</string>
<string name="loginNoUsers">No está asignado ningún alumno con tu tablet. Consulte con el responsable de su asociación o gabinete</string>
<!-- Login -->
<string name="systemMessage">Mensaje del sistema</string>
......
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