JavascriptProva

Visualizzazione post con etichetta applicazione. Mostra tutti i post
Visualizzazione post con etichetta applicazione. Mostra tutti i post

domenica 13 novembre 2016

Conflitti nell'attivazione del timer.

Vado cercando il modo di schematizzare quando sia necessario avere il timer in funzione e quando no.
Cerchiamo di iniziare dalle cose più semplici.

Innanzitutto, quando si avvia il programma il timer è già in funzione.
Questo innesca, dopo una quantità di minuti prestabilita, la comparsa della Activity B.
L'Activity B, una volta chiusa, innnesca di nuovo il timer per la ricomparsa di se stessa, e così via, fin quando non si interrompa il timer mediante un pulsante presente sull'Activity A.
Il primo timer è innescato da A, il secondo sempre da B che si richiama da solo alla scadenza del tempo prestabilito.

E' possibile che, se si schiaccia due volte il pulsante su A che innesca il timer, si abbiano due timer, in modo che B appaia due volte, ossia che ne vengano aperte più istanze?
Questo sarebbe problematico, e devo verificarlo.
Nel mio caso, il timer si avvia all'accensione dello switch.
Questo timer dovrebbe essere l'unico avviato dall'activity A.
Quindi dovrebbe proseguire l'innesco automatico del timer da parte dell'activity B che richiama se stessa.
Ci sono però altre tre occasioni in cui viene innescato il timer esternamente al normale ciclo dell'activity B che richiama se stessa:
  1. alla conferma di una nuova voce;
  2. alla pressione del tasto VISUALIZZA (indirettamente mediante apertura di activity B con successivo innesco del timer);
  3. dall'activity SETTINGS nell'evento onPause (se questo prelude al ritorno all'Activity A).
Me li rinfresco da codice:
        bttConferma=(Button)findViewById(R.id.bttConferma);
        bttConferma.setEnabled(false);
        bttConferma.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View v) {

                helper.save(lista.get(0));
                bttConferma.setEnabled(false);
                bottone.setEnabled(true);
                avviaTimer();
                mOnAlarmChangeListener.OnAlarmChange();
            }
        });
        bttVisualizza=(Button)findViewById(R.id.bttVisualizza);
        bttVisualizza.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View v) {
                cancellaTimer();
                Intent intent=new Intent(getApplicationContext(),Lista.class);
                startActivity(intent);
            }
        });


Activity SETTINGS:
    @Override
    protected void onPause(){
        super.onPause();

        if(permessoAvvioTimer)avviaTimer();
    }
Se il timer viene innescato da una di queste e successivamente dall'altra, si hanno due cicli di Timer sovrapposti.
Provo a causarlo coscientemente togliendo cancellaTimer() quando viene visualizzata l'altra activity dal pulsante su A.
        bttVisualizza=(Button)findViewById(R.id.bttVisualizza);
        bttVisualizza.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View v) {
                //cancellaTimer();
                Intent intent=new Intent(getApplicationContext(),Lista.class);
                startActivity(intent);
            }
        });
E proviamo ad accendere l'interruttore e prima che si apra B a visualizzarla col pulsante.

Sì, ottengo un conflitto e vengono aperte due istanze di B!
Forse l'unica è inserire un cancellaTimer() ogni volta prima che il timer venga innescato esternamente.
Lo faccio nei punti prima considerati:
1) Interruttore:
        switch1.setOnCheckedChangeListener(new Switch.OnCheckedChangeListener(){

            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                if(isChecked){
                    cancellaTimer();
                    avviaTimer();
                    mOnAlarmChangeListener.OnAlarmChange();
                }else{
                    cancellaTimer();
                    mOnAlarmChangeListener.OnAlarmChange();
                }
            }
        });


2) Conferma di una nuova voce:
        bttConferma.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View v) {

                helper.save(lista.get(0));
                bttConferma.setEnabled(false);
                bottone.setEnabled(true);
                txtVocale.setText(null);
                cancellaTimer();
                avviaTimer();
                mOnAlarmChangeListener.OnAlarmChange();
            }
        });


3) Pressione del tasto Visualizza:
        bttVisualizza.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View v) {
                cancellaTimer();
                Intent intent=new Intent(getApplicationContext(),Lista.class);
                startActivity(intent);
            }
        });
4) Da SETTINGS in onPause:
    @Override
    protected void onPause(){
        super.onPause();

        if(permessoAvvioTimer){
            cancellaTimer();
            avviaTimer();
        }
    }


Dovrei essere piuttosto sicuro, adesso...

Semplifico il codice dell'interruttore:
        switch1.setOnCheckedChangeListener(new Switch.OnCheckedChangeListener(){

            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                cancellaTimer();
                if(isChecked) avviaTimer();
                
                mOnAlarmChangeListener.OnAlarmChange();
                
                
            }
        });
E ora sperimentiamo nel tempo...

lunedì 31 ottobre 2016

Evoluzioni nell'applicazione

Ho subordinato la possibilità di spostare i folderLayout allo stato di settingMode:
        onImageTouchListener = new View.OnTouchListener(){

            @Override
            public boolean onTouch(View view, MotionEvent motionEvent) {


                X=(int)motionEvent.getRawX();
                Y=(int)motionEvent.getRawY();
                switch(motionEvent.getAction()){
                    case MotionEvent.ACTION_DOWN:
                        if(settingMode) {
                            scrollView.requestDisallowInterceptTouchEvent(true);
                            RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) view.getLayoutParams();
                            deltaX = X - params.leftMargin;
                            deltaY = Y - params.topMargin;
                        }
                        break;

                    case MotionEvent.ACTION_MOVE:
                        if(settingMode) {
                            RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) view.getLayoutParams();
                            if (X - deltaX > 0 && X - deltaX + params.width < scrollView.getWidth())
                                params.leftMargin = X - deltaX;
                            if (Y - deltaY > 0 )
                                params.topMargin = Y - deltaY;

                            System.out.println("move " + params.leftMargin);
                            System.out.println("move " + params.topMargin);
                            view.setLayoutParams(params);
                        }
                        break;

                }


                return true;
            }
        }; 
E il comportamento è adeguato.

Ora (ho fatto un intermezzo per realizzare il collegamento di questa App al database Firebase) devo gestire il comportamento al tocco in caso di settingMode=false.

Devo ricostruire il tts.
Ma prima voglio provare l'introduzione delle textViews nascoste contenenti il TIPO e la CATEGORIA del folderLayout.
In precedenza avevo castato il layout a viewgroup, e vediamo se si può fare lo stesso... Ecco, sì, ci sono riuscito:
                    case MotionEvent.ACTION_DOWN:
                        if(settingMode) {
                            scrollView.requestDisallowInterceptTouchEvent(true);
                            RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) view.getLayoutParams();
                            deltaX = X - params.leftMargin;
                            deltaY = Y - params.topMargin;
                        }
                        else{
                            String tipo=(String)((TextView)((ViewGroup)view).getChildAt(2)).getText();
                            String categoria=(String)((TextView)((ViewGroup)view).getChildAt(3)).getText();
                        }
                        break;
Provando con System.out.println ottengo la stampa a video di tipo e categoria.
Ora devo sistemare il tts.

Ho fatto tanta strada senza documentarla.
tts risistemato, sistemate le coordinate delle immagini, sistemato il cambio di schermata...

giovedì 27 ottobre 2016

Considerazioni.

L'immagine sarebbe salvata come stringa.
Ecco JImage:
public class JImage {
    public String etichetta;
    public String categoria;
    public String immagine;
    public String tipo;
    public int coordX;
    public int coordY;
}


Per memorizzare il database:
STRING ETICHETTA
STRING CATEGORIA
STRING IMMAGINE
STRING TIPO


INT COORDX
INT COORDY


Riguardo il codice in cui il programma legge il database e carica le immagini nel controllo ImagView.
Per fare questo però la String deve essere ritradotta in immagine, e il codice non lo ricordo...
Vediamo...

Ecco, l'avevo già creata per la funzione di salvataggio da ImagePicker, insieme al suo reverse.
    public static String BitmapToString(Bitmap bmp, int quality){
        ByteArrayOutputStream stream=new ByteArrayOutputStream();
        bmp.compress(Bitmap.CompressFormat.JPEG, quality, stream);
        byte[] b=stream.toByteArray();
        String s= Base64.encodeToString(b,Base64.DEFAULT);
        return s;
    }

 public static Bitmap StringToBitmap(String stringImage){
        byte[] b=Base64.decode(stringImage,Base64.DEFAULT);
        Bitmap bmp=BitmapFactory.decodeByteArray(b,0,b.length);
        return bmp;
    }
Per convertire Bitmap a String bisogna trasformare una bitmap in un array di bytes.
Per riconvertire String a Bitmap bisogna trasformare la String in un array di bytes.

La trasformazione passa sempre per un array di bytes.

Il campo IMMAGINE nel database è il terzo, quindi considerando che c'è una chiave primaria devo estrarre il numero 3.
Modifico la chiamata della folderLayout aggiungendovi come terzo parametro la stringa IMMAGINE presa dal database:
FolderLayout folderLayout=new FolderLayout(this,isFolder,crs.getString(3));
e ora devo andare a modificarne di conseguenza il costruttore:
public FolderLayout(Context context,boolean isFolder,String stringImage) {
...
Nel corpo di folderLayout ripristino l'immagine attraverso la funzione che ho già creato:
    private boolean isFolder;
    public FolderLayout(Context context,boolean isFolder,String stringImage) {
        super(context);
        this.context=context;
        this.isFolder=isFolder;
        setWillNotDraw(false);

        if(isFolder) {
            this.setBackground(getResources().getDrawable(R.drawable.cartella));
        }else{
            this.setBackground(getResources().getDrawable(R.drawable.document));
        }

        Bitmap b=Funzioni.StringToBitmap(stringImage);
        ImageView immagine=new ImageView(context);
        immagine.setImageBitmap(b);
        this.addView(immagine);
    }

Ecco il risultato finale, dopo una serie di aggiustamenti estetici:
Ho rimaneggiato completamente il codice di folderLayout:
public class FolderLayout extends RelativeLayout {
    Context context;




    private boolean isFolder;
    public FolderLayout(Context context,boolean isFolder,String stringImage,String etichetta) {
        super(context);
        this.context=context;
        this.isFolder=isFolder;
        setWillNotDraw(false);

        if(isFolder) {
            this.setBackground(getResources().getDrawable(R.drawable.cartella));
        }else{
            this.setBackground(getResources().getDrawable(R.drawable.document));
        }


        Bitmap b=Funzioni.StringToBitmap(stringImage);
        ImageView immagine=new ImageView(context);
        immagine.setImageBitmap(b);
        this.addView(immagine);
        RelativeLayout.LayoutParams p=(RelativeLayout.LayoutParams) immagine.getLayoutParams();
        p.topMargin=30;
        p.height=180;
        p.width=180;
        p.addRule(RelativeLayout.CENTER_HORIZONTAL);

        TextView txtEtichetta=new TextView(context);
        txtEtichetta.setText(etichetta);
        txtEtichetta.setTextSize(15);
        txtEtichetta.setSingleLine(false);
        txtEtichetta.setAllCaps(true);
        txtEtichetta.setTypeface(null, Typeface.BOLD);
        this.addView(txtEtichetta);
        System.out.println(etichetta);
        RelativeLayout.LayoutParams params =(RelativeLayout.LayoutParams)txtEtichetta.getLayoutParams();
        params.topMargin=210;
        params.leftMargin=10;
        params.addRule(RelativeLayout.CENTER_HORIZONTAL);
        params.width=250;
        params.height=150;

    }
}
ed ecco la funzione che carica il database nella finestra, allo stato attuale:
    private void leggiDaDatabase(String categoria){

        Cursor crs=helper.query(categoria);
        crs.moveToFirst();
        do{
            boolean isFolder=false;
            if(crs.getString(4).equals("categoria")) isFolder=true;
            FolderLayout folderLayout=new FolderLayout(this,false,crs.getString(3),crs.getString(1));
            mainLayout.addView(folderLayout);
            RelativeLayout.LayoutParams flParams=(RelativeLayout.LayoutParams)folderLayout.getLayoutParams();
            flParams.leftMargin=crs.getInt(5);
            flParams.topMargin=crs.getInt(6);
            flParams.width=(int)(250);
            flParams.height=(int)(300);
            folderLayout.setOnTouchListener(onImageTouchListener);


        }while(crs.moveToNext());

    }

Quest'ultima funzione è la prima che deve partire.
O, meglio, ho ricostruito la sequenza.
Non appena si avvia il programma, con l'evento OnStart, parte questo codice:
    @Override
    protected void onStart(){
        super.onStart();
        mLucchettiChangeListener.onLucchettiChange(leftLock,rightLock);
        refreshScreen();
    }
Viene innescato l'evento onLucchettiChange... (che vedrò poi);
viene chiamata la funzione refreshScreen().
    //Ricarica la schermata.
    private void refreshScreen(){

        try {
            leggiDaDatabase(currentCat);
        }catch(Exception e){}ae
        txtBarraCategoria.setText(currentCat);
    }
Questo refreshScreen innesca leggiDaDatabase avendo per parametro currentCat, che sarebbe la categoria corrente.
In più, intitola la barra categoria con il nome di questa categoria corrente.

Importante: currentCat è impostata a "iniziale":
String currentCat="iniziale";
Ed ecco leggiDaDatabase.
    private void leggiDaDatabase(String categoria){

        Cursor crs=helper.query(categoria);
        crs.moveToFirst();
        do{
            boolean isFolder=false;
            if(crs.getString(4).equals("categoria")) isFolder=true;
            FolderLayout folderLayout=new FolderLayout(this,false,crs.getString(3),crs.getString(1));
            mainLayout.addView(folderLayout);
            RelativeLayout.LayoutParams flParams=(RelativeLayout.LayoutParams)folderLayout.getLayoutParams();
            flParams.leftMargin=crs.getInt(5);
            flParams.topMargin=crs.getInt(6);
            flParams.width=(int)(250);
            flParams.height=(int)(300);
            folderLayout.setOnTouchListener(onImageTouchListener);


        }while(crs.moveToNext());

    }
Questo scorre tutto il database selezionando le sole immagini che abbiano il campo categoria impostato al valore di currentCat.
Quindi inizialmente seleziona soltanto le immagini che appartengono alla schermata iniziale.

Per ogni record crea un folderLayout contenente un'immagine e un'etichetta.
E questo è il codice che ho rimaneggiato finora.

domenica 21 febbraio 2016

Altro errore

Il passaggio alla schermata ImagePick viene sempre fatto con il passaggio della categoria iniziale.
Vediamo bene il codice che chiama questa "schermata".
  mainLayout.setOnLongClickListener(new View.OnLongClickListener() {
   
   @Override
   public boolean onLongClick(View v) {
    if(settingMode){
     Intent intent=new Intent(MainActivity.this,ImagePick.class);
     intent.putExtra("Categoria", currentCat);
     intent.putExtra("CoordX", mlX);
     intent.putExtra("CoordY", mlY);
     startActivity(intent);
    }
    return true;
   }
  });
Trasmette alla schermata (activity) ImagePick il valore di currentCat, la categoria corrente, che quando si parte dalla schermata iniziale è "iniziale", e sta bene, ma che probabilmente resta "iniziale" anche quando si verifica il cambiamento apparente di schermata con lo scarico e il carico di immagini dal database.
Verifichiamo se con il cambio di schermata la categoria corrente resta sempre la stessa.
Prendiamo il codice che cambia la schermata.
.....
.....
case MotionEvent.ACTION_UP:
    if(settingMode){
     TextView et=(TextView)((ViewGroup)v).getChildAt(numEtichetta);
     TextView cat=(TextView)((ViewGroup)v).getChildAt(numCategoria);
     helper.Update(et.getText().toString(), cat.getText().toString(),v.getLeft(), v.getTop());
    }
    else{
     TextView t=(TextView) ((ViewGroup)v).getChildAt(numTipo);
     if(t.getText().equals("categoria")){
      String s=(String) ((TextView)((ViewGroup)v).getChildAt(numEtichetta)).getText();
      mainLayout.removeAllViews();
      try{
       leggiDaDatabase(s);
       currentCat=s;
      }catch(Exception e){}
     }
     
      
     
    }
Traduzione della parte successiva a else:
Prendi una TextView t e identificala con la TextView che rappresenta il Tipo dell'immagine.
Se il suo testo è "categoria", allora: prendi una stringa s e dalle il valore dell'etichetta dell'immagine.
Togli tutte le immagini dalla schermata.
Se ci riesci, leggi dal database le immagini che hanno come tipo la stringa s e imposta la categoria corrente al valore dell'etichetta.
E forse ho individuato il "topo"! Io dico "se ci riesci leggi dal database E cambia la categoria corrente". Ora, se il database è vuoto, l'operazione di lettura fallisce e l'eccezione viene gestita senza messaggio di errore, ma dal momento che nel Try...catch è compresa anche l'istruzione di cambiare la categoria corrente, questo cambio non viene fatto, mentre le immagini sono cancellate passando così virtualmente alla seconda schermata.
Quindi mi trovo su una seconda schermata, vuota perché non vi sono salvate immagini, ma con la categoria di partenza, in quanto il cambio di categoria è stato "trascinato nel fallimento della operazione di lettura dal database".
Provvedo e provo...