diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index fe915179..05b7de3b 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -1,4 +1,4 @@ --keep class app.familygem.Settings, app.familygem.Chiesa, app.familygem.Podio # for R8.fullMode +-keep class app.familygem.Settings, app.familygem.ChurchFragment, app.familygem.ListOfAuthorsFragment # for R8.fullMode -keepclassmembernames class app.familygem.Settings, app.familygem.Settings$Tree, app.familygem.Settings$Diagram, app.familygem.Settings$ZippedTree, app.familygem.Settings$Share { *; } -keepclassmembers class org.folg.gedcom.model.* { *; } #-keeppackagenames org.folg.gedcom.model # Gedcom parser lo chiama come stringa eppure funziona anche senza diff --git a/app/src/androidTest/java/app/familygem/ExportTest.java b/app/src/androidTest/java/app/familygem/ExportTest.java index 77e26f8a..f6a73ba1 100644 --- a/app/src/androidTest/java/app/familygem/ExportTest.java +++ b/app/src/androidTest/java/app/familygem/ExportTest.java @@ -130,24 +130,24 @@ void esportaGedcom() { if( !documentsDir.exists() ) documentsDir.mkdir(); File fileGedcom = new File( documentsDir, "Küçük ağaç.ged" ); - Esportatore esp = new Esportatore( appContext ); - assertTrue( esp.apriAlbero( idAlbero ) ); - assertNull( esp.messaggioSuccesso ); - assertNull( esp.messaggioErrore ); - assertTrue( esp.esportaGedcom(Uri.fromFile(fileGedcom)) ); + Exporter esp = new Exporter( appContext ); + assertTrue( esp.openTree( idAlbero ) ); + assertNull( esp.successMessage); + assertNull( esp.errorMessage); + assertTrue( esp.exportGedcom(Uri.fromFile(fileGedcom)) ); assertTrue( fileGedcom.isFile() ); - assertEquals( esp.messaggioSuccesso, appContext.getString(R.string.gedcom_exported_ok) ); - s.l( esp.messaggioSuccesso ); + assertEquals( esp.successMessage, appContext.getString(R.string.gedcom_exported_ok) ); + s.l( esp.successMessage); File fileGedcomZip = new File( documentsDir, "ਸੰਕੁਚਿਤ.zip" ); - Esportatore esp2 = new Esportatore( appContext ); - assertTrue( esp2.apriAlbero( idAlbero ) ); - boolean result = esp2.esportaGedcomZippato(Uri.fromFile(fileGedcomZip)); - s.l( esp2.messaggioErrore ); + Exporter esp2 = new Exporter( appContext ); + assertTrue( esp2.openTree( idAlbero ) ); + boolean result = esp2.exportGedcomToZip(Uri.fromFile(fileGedcomZip)); + s.l( esp2.errorMessage); assertTrue( result ); - assertEquals( esp2.messaggioSuccesso, appContext.getString(R.string.zip_exported_ok) ); + assertEquals( esp2.successMessage, appContext.getString(R.string.zip_exported_ok) ); assertTrue( fileGedcomZip.isFile() ); - s.l( esp2.messaggioSuccesso ); + s.l( esp2.successMessage); } // Esporta in /Documents l'ultimo albero come backup ZIP @@ -155,14 +155,14 @@ void esportaBackup() { File documentsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS); if( !documentsDir.exists() ) documentsDir.mkdir(); File fileBackup = new File( documentsDir, "Becàp olè.zip" ); - Esportatore esp = new Esportatore( appContext ); + Exporter esp = new Exporter( appContext ); Settings.Tree ultimoAlb = Global.settings.trees.get(Global.settings.trees.size()-1); - assertTrue( esp.apriAlbero( ultimoAlb.id ) ); - boolean result = esp.esportaBackupZip( null, -1, Uri.fromFile(fileBackup) ); - s.l( esp.messaggioErrore ); + assertTrue( esp.openTree( ultimoAlb.id ) ); + boolean result = esp.exportBackupZip( null, -1, Uri.fromFile(fileBackup) ); + s.l( esp.errorMessage); assertTrue( result ); - assertEquals( esp.messaggioSuccesso, appContext.getString(R.string.zip_exported_ok) ); + assertEquals( esp.successMessage, appContext.getString(R.string.zip_exported_ok) ); assertTrue( fileBackup.isFile() ); - s.l( esp.messaggioSuccesso ); + s.l( esp.successMessage); } } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ccfccb50..af6df590 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -29,7 +29,7 @@ android:resource="@xml/provider_paths"/> @@ -56,84 +56,84 @@ + android:parentActivityName=".TreesActivity" /> + android:parentActivityName=".TreesActivity"/> + android:parentActivityName=".OptionsActivity" /> + android:parentActivityName=".TreesActivity" /> + android:parentActivityName=".TreesActivity" /> + android:parentActivityName=".TreesActivity" /> + android:parentActivityName=".CompareActivity" /> + android:parentActivityName=".CompareActivity" /> + android:name=".detail.FamilyActivity" /> + android:name=".detail.NameActivity" /> + android:name=".detail.RepositoryActivity" /> + android:name=".detail.RepositoryRefActivity" /> + android:name=".detail.NoteActivity" /> + android:name=".detail.SourceActivity" /> + android:name=".detail.SourceCitationActivity" /> + android:name=".detail.ImageActivity" /> + android:name=".detail.EventActivity" /> + android:name=".detail.AddressActivity" /> + android:name=".detail.AuthorActivity" /> + android:name=".detail.ExtensionActivity" /> + android:name=".detail.ChangesActivity" /> - + { - - private List listaMedia; - private boolean dettagli; - - AdattatoreGalleriaMedia( List listaMedia, boolean dettagli ) { - this.listaMedia = listaMedia; - this.dettagli = dettagli; - } - - @Override - public gestoreVistaMedia onCreateViewHolder( ViewGroup parent, int tipo ) { - View vista = LayoutInflater.from(parent.getContext()).inflate( R.layout.pezzo_media, parent, false ); - return new gestoreVistaMedia( vista, dettagli ); - } - @Override - public void onBindViewHolder( final gestoreVistaMedia gestore, int posizione ) { - gestore.setta( posizione ); - } - @Override - public int getItemCount() { - return listaMedia.size(); - } - @Override - public long getItemId(int position) { - return position; - } - @Override - public int getItemViewType(int position) { - return position; - } - - class gestoreVistaMedia extends RecyclerView.ViewHolder implements View.OnClickListener { - View vista; - boolean dettagli; - Media media; - Object contenitore; - ImageView vistaImmagine; - TextView vistaTesto; - TextView vistaNumero; - gestoreVistaMedia( View vista, boolean dettagli ) { - super(vista); - this.vista = vista; - this.dettagli = dettagli; - vistaImmagine = vista.findViewById( R.id.media_img ); - vistaTesto = vista.findViewById( R.id.media_testo ); - vistaNumero = vista.findViewById( R.id.media_num ); - } - void setta( int posizione ) { - media = listaMedia.get( posizione ).media; - contenitore = listaMedia.get( posizione ).contenitore; - if( dettagli ) { - arredaMedia( media, vistaTesto, vistaNumero ); - vista.setOnClickListener( this ); - ((Activity)vista.getContext()).registerForContextMenu( vista ); - vista.setTag( R.id.tag_oggetto, media ); - vista.setTag( R.id.tag_contenitore, contenitore ); - // Registra menu contestuale - final AppCompatActivity attiva = (AppCompatActivity) vista.getContext(); - if( vista.getContext() instanceof Individuo ) { // Fragment individuoMedia - attiva.getSupportFragmentManager() - .findFragmentByTag( "android:switcher:" + R.id.schede_persona + ":0" ) // non garantito in futuro - .registerForContextMenu( vista ); - } else if( vista.getContext() instanceof Principal ) // Fragment Galleria - attiva.getSupportFragmentManager().findFragmentById( R.id.contenitore_fragment ).registerForContextMenu( vista ); - else // nelle AppCompatActivity - attiva.registerForContextMenu( vista ); - } else { - RecyclerView.LayoutParams parami = new RecyclerView.LayoutParams( RecyclerView.LayoutParams.WRAP_CONTENT, U.dpToPx(110) ); - int margin = U.dpToPx(5); - parami.setMargins( margin, margin, margin, margin ); - vista.setLayoutParams( parami ); - vistaTesto.setVisibility( View.GONE ); - vistaNumero.setVisibility( View.GONE ); - } - F.dipingiMedia( media, vistaImmagine, vista.findViewById(R.id.media_circolo) ); - } - @Override - public void onClick( View v ) { - AppCompatActivity attiva = (AppCompatActivity) v.getContext(); - // Galleria in modalità scelta dell'oggetto media - // Restituisce l'id di un oggetto media a IndividuoMedia - if( attiva.getIntent().getBooleanExtra( "galleriaScegliMedia", false ) ) { - Intent intent = new Intent(); - intent.putExtra( "idMedia", media.getId() ); - attiva.setResult( Activity.RESULT_OK, intent ); - attiva.finish(); - // Galleria in modalità normale apre Immagine - } else { - Intent intento = new Intent( v.getContext(), Immagine.class ); - if( media.getId() != null ) { // tutti i Media record - Memoria.setPrimo( media ); - } else if( (attiva instanceof Individuo && contenitore instanceof Person) // media di primo livello nell'Indi - || attiva instanceof Dettaglio ) { // normale apertura nei Dettagli - Memoria.aggiungi( media ); - } else { // da Galleria tutti i media semplici, o da IndividuoMedia i media sotto molteplici livelli - new TrovaPila( Global.gc, media ); - if( attiva instanceof Principal ) // Solo in Galleria - intento.putExtra( "daSolo", true ); // così poi Immagine mostra la dispensa - } - v.getContext().startActivity( intento ); - } - } - } - - static void arredaMedia( Media media, TextView vistaTesto, TextView vistaNumero ) { - String testo = ""; - if( media.getTitle() != null ) - testo = media.getTitle() + "\n"; - if( Global.settings.expert && media.getFile() != null ) { - String file = media.getFile(); - file = file.replace( '\\', '/' ); - if( file.lastIndexOf('/') > -1 ) { - if( file.length() > 1 && file.endsWith("/") ) // rimuove l'ultima barra - file = file.substring( 0, file.length()-1 ); - file = file.substring( file.lastIndexOf('/') + 1 ); - } - testo += file; - } - if( testo.isEmpty() ) - vistaTesto.setVisibility( View.GONE ); - else { - if( testo.endsWith("\n") ) - testo = testo.substring( 0, testo.length()-1 ); - vistaTesto.setText( testo ); - } - if( media.getId() != null ) { - vistaNumero.setText( String.valueOf(Galleria.popolarita(media)) ); - vistaNumero.setVisibility( View.VISIBLE ); - } else - vistaNumero.setVisibility( View.GONE ); - } - - // Questa serve solo per creare una RecyclerView con le iconcine dei media che risulti trasparente ai click - // todo però impedisce lo scroll in Dettaglio - static class RiciclaVista extends RecyclerView { - boolean dettagli; - public RiciclaVista( Context context, boolean dettagli) { - super(context); - this.dettagli = dettagli; - } - @Override - public boolean onTouchEvent( MotionEvent e ) { - super.onTouchEvent( e ); - return dettagli; // quando è false la griglia non intercetta il click - } - } -} \ No newline at end of file diff --git a/app/src/main/java/app/familygem/AlberoNuovo.java b/app/src/main/java/app/familygem/AlberoNuovo.java deleted file mode 100644 index 5e952760..00000000 --- a/app/src/main/java/app/familygem/AlberoNuovo.java +++ /dev/null @@ -1,427 +0,0 @@ -package app.familygem; - -import android.Manifest; -import android.app.Activity; -import android.app.DownloadManager; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.PackageManager; -import android.content.res.ColorStateList; -import android.database.Cursor; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import androidx.core.app.ActivityCompat; -import androidx.core.content.ContextCompat; -import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.app.AppCompatActivity; -import android.view.LayoutInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputMethodManager; -import android.widget.Button; -import android.widget.EditText; -import android.widget.TextView; -import android.widget.Toast; -import com.google.gson.Gson; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.PrintWriter; -import java.util.Locale; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; -import org.apache.commons.io.FileUtils; -import org.folg.gedcom.model.CharacterSet; -import org.folg.gedcom.model.Gedcom; -import org.folg.gedcom.model.GedcomVersion; -import org.folg.gedcom.model.Generator; -import org.folg.gedcom.model.Header; -import org.folg.gedcom.parser.JsonParser; -import org.folg.gedcom.parser.ModelParser; - -public class AlberoNuovo extends BaseActivity { - - View rotella; - - @Override - protected void onCreate(Bundle bundle) { - super.onCreate(bundle); - setContentView(R.layout.albero_nuovo); - rotella = findViewById(R.id.nuovo_circolo); - String referrer = Global.settings.referrer; // Dataid proveniente da una condivisione - boolean esisteDataId = referrer != null && referrer.matches("[0-9]{14}"); - - // Scarica l'albero condiviso - Button scaricaCondiviso = findViewById( R.id.bottone_scarica_condiviso ); - if( esisteDataId ) - // Non ha bisogno di permessi perché scarica e decomprime solo nello storage esterno dell'app - scaricaCondiviso.setOnClickListener( v -> Facciata.scaricaCondiviso(this, referrer, rotella) ); - else - scaricaCondiviso.setVisibility( View.GONE ); - - // Crea un albero vuoto - Button alberoVuoto = findViewById( R.id.bottone_albero_vuoto ); - if( esisteDataId ) { - if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ) - alberoVuoto.setBackgroundTintList( ColorStateList.valueOf(getResources().getColor(R.color.primarioChiaro)) ); - } - alberoVuoto.setOnClickListener( v -> { - View vistaMessaggio = LayoutInflater.from(this).inflate(R.layout.albero_nomina, null); - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setView( vistaMessaggio ).setTitle( R.string.title ); - TextView vistaTesto = vistaMessaggio.findViewById( R.id.nuovo_nome_testo ); - vistaTesto.setText( R.string.modify_later ); - vistaTesto.setVisibility( View.VISIBLE ); - EditText nuovoNome = vistaMessaggio.findViewById( R.id.nuovo_nome_albero ); - builder.setPositiveButton( R.string.create, (dialog, id) -> newTree(nuovoNome.getText().toString()) ) - .setNeutralButton( R.string.cancel, null ).create().show(); - nuovoNome.setOnEditorActionListener( (view, action, event) -> { - if( action == EditorInfo.IME_ACTION_DONE ) { - newTree( nuovoNome.getText().toString() ); - return true; // completa le azioni di salva() - } - return false; // Eventuali altri action che non esistono - }); - vistaMessaggio.postDelayed( () -> { - nuovoNome.requestFocus(); - InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - inputMethodManager.showSoftInput(nuovoNome, InputMethodManager.SHOW_IMPLICIT); - }, 300); - }); - - Button scaricaEsempio = findViewById(R.id.bottone_scarica_esempio); - // Non ha bisogno di permessi - scaricaEsempio.setOnClickListener( v -> scaricaEsempio() ); - - Button importaGedcom = findViewById(R.id.bottone_importa_gedcom); - importaGedcom.setOnClickListener( v -> { - int perm = ContextCompat.checkSelfPermission(v.getContext(),Manifest.permission.READ_EXTERNAL_STORAGE); - if( perm == PackageManager.PERMISSION_DENIED ) - ActivityCompat.requestPermissions( (AppCompatActivity)v.getContext(), new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 1390 ); - else if( perm == PackageManager.PERMISSION_GRANTED ) - importaGedcom(); - }); - - Button recuperaBackup = findViewById(R.id.bottone_recupera_backup); - recuperaBackup.setOnClickListener( v -> { - Intent intent = new Intent( Intent.ACTION_GET_CONTENT ); - intent.setType( "application/zip" ); - startActivityForResult( intent, 219 ); - }); - } - - // Elabora la risposta alle richieste di permesso - @Override - public void onRequestPermissionsResult( int codice, String[] permessi, int[] accordi ) { // If request is cancelled, the result arrays are empty - super.onRequestPermissionsResult(codice, permessi, accordi); - if( accordi.length > 0 && accordi[0] == PackageManager.PERMISSION_GRANTED ) { - if( codice == 1390 ) { - importaGedcom(); - } - } - } - - // Create a brand new tree - void newTree(String title) { - int num = Global.settings.max() + 1; - File jsonFile = new File(getFilesDir(), num + ".json"); - Global.gc = new Gedcom(); - Global.gc.setHeader(creaTestata(jsonFile.getName())); - Global.gc.createIndexes(); - JsonParser jp = new JsonParser(); - try { - FileUtils.writeStringToFile(jsonFile, jp.toJson(Global.gc), "UTF-8"); - } catch( Exception e ) { - Toast.makeText(this, e.getLocalizedMessage(), Toast.LENGTH_LONG).show(); - return; - } - Global.settings.aggiungi(new Settings.Tree(num, title, null, 0, 0, null, null, 0)); - Global.settings.openTree = num; - Global.settings.save(); - onBackPressed(); - Toast.makeText(this, R.string.tree_created, Toast.LENGTH_SHORT).show(); - } - - // Scarica da Google Drive il file zip dei Simpson nella cache esterna dell'app, quindi senza bisogno di permessi - void scaricaEsempio() { - rotella.setVisibility( View.VISIBLE ); - DownloadManager gestoreScarico = (DownloadManager) getSystemService(DOWNLOAD_SERVICE); - // Evita download multipli - Cursor curso = gestoreScarico.query( new DownloadManager.Query().setFilterByStatus(DownloadManager.STATUS_RUNNING) ); - if( curso.moveToFirst() ) { - curso.close(); - findViewById(R.id.bottone_scarica_esempio).setEnabled(false); - return; - } - String url = "https://drive.google.com/uc?export=download&id=1FT-60avkxrHv6G62pxXs9S6Liv5WkkKf"; - String percorsoZip = getExternalCacheDir() + "/the_Simpsons.zip"; - File fileZip = new File(percorsoZip); - if( fileZip.exists() ) - fileZip.delete(); - DownloadManager.Request richiesta = new DownloadManager.Request( Uri.parse( url ) ) - .setTitle( getString(R.string.simpsons_tree) ) - .setDescription( getString(R.string.family_gem_example) ) - .setMimeType( "application/zip" ) - .setNotificationVisibility( DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) - .setDestinationUri( Uri.parse( "file://" + percorsoZip ) ); - gestoreScarico.enqueue( richiesta ); - BroadcastReceiver alCompletamento = new BroadcastReceiver() { - @Override - public void onReceive( Context contesto, Intent intento ) { - unZip( contesto, percorsoZip, null ); - unregisterReceiver( this ); - } - }; - registerReceiver( alCompletamento, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE) ); - // ACTION_DOWNLOAD_COMPLETE intende il completamento di QUALSIASI download che è in corso, non solo questo. - } - - // Unzip a ZIP file in the device storage - // Used equally by: Simpsons example, backup files and shared trees - static boolean unZip(Context context, String zipPath, Uri zipUri) { - int treeNumber = Global.settings.max() + 1; - File mediaDir = context.getExternalFilesDir(String.valueOf(treeNumber)); - String sourceDir = context.getApplicationInfo().sourceDir; - if( !sourceDir.startsWith("/data/") ) { // App installed not in internal memory (hopefully moved to SD-card) - File[] externalFilesDirs = context.getExternalFilesDirs(String.valueOf(treeNumber)); - if( externalFilesDirs.length > 1 ) { - mediaDir = externalFilesDirs[1]; - } - } - try { - InputStream is; - if( zipPath != null ) - is = new FileInputStream(zipPath); - else - is = context.getContentResolver().openInputStream(zipUri); - ZipInputStream zis = new ZipInputStream(is); - ZipEntry zipEntry; - int len; - byte[] buffer = new byte[1024]; - File newFile; - while( (zipEntry = zis.getNextEntry()) != null ) { - if( zipEntry.getName().equals("tree.json") ) - newFile = new File(context.getFilesDir(), treeNumber + ".json"); - else if( zipEntry.getName().equals("settings.json") ) - newFile = new File(context.getCacheDir(), "settings.json"); - else // It's a file from the 'media' folder - newFile = new File(mediaDir, zipEntry.getName().replace("media/", "")); - FileOutputStream fos = new FileOutputStream(newFile); - while( (len = zis.read(buffer)) > 0 ) { - fos.write(buffer, 0, len); - } - fos.close(); - } - zis.closeEntry(); - zis.close(); - // Legge le impostazioni e le salva nelle preferenze - File settingsFile = new File(context.getCacheDir(), "settings.json"); - String json = FileUtils.readFileToString(settingsFile, "UTF-8"); - json = updateLanguage(json); - Gson gson = new Gson(); - Settings.ZippedTree zipped = gson.fromJson(json, Settings.ZippedTree.class); - Settings.Tree tree = new Settings.Tree(treeNumber, zipped.title, mediaDir.getPath(), - zipped.persons, zipped.generations, zipped.root, zipped.shares, zipped.grade); - Global.settings.aggiungi(tree); - settingsFile.delete(); - // Albero proveniente da condivisione destinato al confronto - if( zipped.grade == 9 && confronta(context, tree, false) ) { - tree.grade = 20; // lo marchia come derivato - } - // Il download è avvenuto dal dialogo del referrer in Alberi - if( context instanceof Alberi ) { - Alberi treesPage = (Alberi)context; - treesPage.runOnUiThread( () -> { - treesPage.rotella.setVisibility(View.GONE); - treesPage.aggiornaLista(); - }); - } else // Albero di esempio (Simpson) o di backup (da Facciata o da AlberoNuovo) - context.startActivity(new Intent(context, Alberi.class)); - Global.settings.save(); - U.toast((Activity)context, R.string.tree_imported_ok); - return true; - } catch( Exception e ) { - U.toast((Activity)context, e.getLocalizedMessage()); - } - return false; - } - - // Replace Italian with English in the Json settings of ZIP backup - // Added in Family Gem 0.8 - static String updateLanguage(String json) { - json = json.replace("\"generazioni\":", "\"generations\":"); - json = json.replace("\"grado\":", "\"grade\":"); - json = json.replace("\"individui\":", "\"persons\":"); - json = json.replace("\"radice\":", "\"root\":"); - json = json.replace("\"titolo\":", "\"title\":"); - json = json.replace("\"condivisioni\":", "\"shares\":"); - json = json.replace("\"data\":", "\"dateId\":"); - return json; - } - - // Fa scegliere un file Gedcom da importare - void importaGedcom() { - Intent intent = new Intent(Intent.ACTION_GET_CONTENT); - // KitKat disabilita i file .ged nella cartella Download se il type è 'application/*' - if( Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT ) - intent.setType("*/*"); - else - intent.setType("application/*"); - startActivityForResult(intent, 630); - } - - @Override - protected void onActivityResult( int requestCode, int resultCode, final Intent data ) { - super.onActivityResult( requestCode, resultCode, data ); - // Importa un file Gedcom scelto con SAF - if( resultCode == RESULT_OK && requestCode == 630 ){ - try { - // Legge l'input - Uri uri = data.getData(); - InputStream input = getContentResolver().openInputStream(uri); - Gedcom gedcom = new ModelParser().parseGedcom(input); - if( gedcom.getHeader() == null ) { - Toast.makeText(this, R.string.invalid_gedcom, Toast.LENGTH_LONG).show(); - return; - } - gedcom.createIndexes(); // necessario per poi calcolare le generazioni - // Salva il file Json - int newNumber = Global.settings.max() + 1; - PrintWriter printWriter = new PrintWriter(getFilesDir() + "/" + newNumber + ".json"); - JsonParser jsonParser = new JsonParser(); - printWriter.print(jsonParser.toJson(gedcom)); - printWriter.close(); - // Nome albero e percorso della cartella - String percorso = F.uriPercorsoFile(uri); - String nomeAlbero; - String percorsoCartella = null; - if( percorso != null && percorso.lastIndexOf('/') > 0 ) { // è un percorso completo del file gedcom - File fileGedcom = new File(percorso); - percorsoCartella = fileGedcom.getParent(); - nomeAlbero = fileGedcom.getName(); - } else if( percorso != null ) { // È solo il nome del file 'famiglia.ged' - nomeAlbero = percorso; - } else // percorso null - nomeAlbero = getString(R.string.tree) + " " + newNumber; - if( nomeAlbero.lastIndexOf('.') > 0 ) // Toglie l'estensione - nomeAlbero = nomeAlbero.substring(0, nomeAlbero.lastIndexOf('.')); - // Salva le impostazioni in preferenze - String idRadice = U.trovaRadice(gedcom); - Global.settings.aggiungi(new Settings.Tree(newNumber, nomeAlbero, percorsoCartella, - gedcom.getPeople().size(), InfoAlbero.quanteGenerazioni(gedcom, idRadice), idRadice, null, 0)); - new Notifier(this, gedcom, newNumber, Notifier.What.CREATE); - // Se necessario propone di mostrare le funzioni avanzate - if( !gedcom.getSources().isEmpty() && !Global.settings.expert ) { - new AlertDialog.Builder(this).setMessage(R.string.complex_tree_advanced_tools) - .setPositiveButton(android.R.string.ok, (dialog, i) -> { - Global.settings.expert = true; - Global.settings.save(); - concludiImportaGedcom(); - }).setNegativeButton(android.R.string.cancel, (dialog, i) -> concludiImportaGedcom()) - .show(); - } else - concludiImportaGedcom(); - } catch( Exception e ) { - Toast.makeText(this, e.getLocalizedMessage(), Toast.LENGTH_LONG).show(); - } - } - - // Try to unzip the retrieved backup ZIP file - if( resultCode == RESULT_OK && requestCode == 219 ) { - try { - Uri uri = data.getData(); - boolean settingsFileExists = false; - final ZipInputStream zis = new ZipInputStream(getContentResolver().openInputStream(uri)); - ZipEntry zipEntry; - while( (zipEntry = zis.getNextEntry()) != null ) { - if( zipEntry.getName().equals("settings.json") ) { - settingsFileExists = true; - break; - } - } - zis.closeEntry(); - zis.close(); - if( settingsFileExists ) { - unZip(this, null, uri); - /* todo nello strano caso che viene importato col backup ZIP lo stesso albero suggerito dal referrer - bisognerebbe annullare il referrer: - if( decomprimiZip( this, null, uri ) ){ - String idData = Esportatore.estraiNome(uri); // che però non è statico - if( Global.preferenze.referrer.equals(idData) ) { - Global.preferenze.referrer = null; - Global.preferenze.salva(); - }} - */ - } else - Toast.makeText(AlberoNuovo.this, R.string.backup_invalid, Toast.LENGTH_LONG).show(); - } catch( Exception e ) { - Toast.makeText(AlberoNuovo.this, e.getLocalizedMessage(), Toast.LENGTH_LONG).show(); - } - } - } - - void concludiImportaGedcom() { - onBackPressed(); - Toast.makeText( this, R.string.tree_imported_ok, Toast.LENGTH_SHORT ).show(); - } - - // Confronta le date di invio degli alberi esistenti - // Se trova almeno un albero originario tra quelli esistenti restituisce true - // ed eventualmente apre il comparatore - static boolean confronta(Context contesto, Settings.Tree albero2, boolean apriCompara) { - if( albero2.shares != null ) - for( Settings.Tree alb : Global.settings.trees ) - if( alb.id != albero2.id && alb.shares != null && alb.grade != 20 && alb.grade != 30 ) - for( int i = alb.shares.size() - 1; i >= 0; i-- ) { // Le condivisioni dall'ultima alla prima - Settings.Share share = alb.shares.get(i); - for( Settings.Share share2 : albero2.shares ) - if( share.dateId != null && share.dateId.equals(share2.dateId) ) { - if( apriCompara ) - contesto.startActivity(new Intent(contesto, Compara.class) - .putExtra("idAlbero", alb.id) - .putExtra("idAlbero2", albero2.id) - .putExtra("idData", share.dateId) - ); - return true; - } - } - return false; - } - - // Crea l'intestazione standard per questa app - public static Header creaTestata(String nomeFile) { - Header testa = new Header(); - Generator app = new Generator(); - app.setValue("FAMILY_GEM"); - app.setName("Family Gem"); - app.setVersion(BuildConfig.VERSION_NAME); - testa.setGenerator(app); - testa.setFile(nomeFile); - GedcomVersion versione = new GedcomVersion(); - versione.setForm("LINEAGE-LINKED"); - versione.setVersion("5.5.1"); - testa.setGedcomVersion(versione); - CharacterSet codifica = new CharacterSet(); - codifica.setValue("UTF-8"); - testa.setCharacterSet(codifica); - Locale loc = new Locale(Locale.getDefault().getLanguage()); - // C'è anche Resources.getSystem().getConfiguration().locale.getLanguage() che ritorna lo stesso 'it' - testa.setLanguage(loc.getDisplayLanguage(Locale.ENGLISH)); // ok prende la lingua di sistema in inglese, non nella lingua locale - // in header ci sono due campi data: TRANSMISSION_DATE un po' forzatamente può contenere la data di ultima modifica - testa.setDateTime(U.actualDateTime()); - return testa; - } - - // Freccia indietro nella toolbar come quella hardware - @Override - public boolean onOptionsItemSelected( MenuItem i ) { - onBackPressed(); - return true; - } -} \ No newline at end of file diff --git a/app/src/main/java/app/familygem/Biblioteca.java b/app/src/main/java/app/familygem/Biblioteca.java deleted file mode 100644 index 15772eec..00000000 --- a/app/src/main/java/app/familygem/Biblioteca.java +++ /dev/null @@ -1,355 +0,0 @@ -// Lista delle Fonti (Sources) -// A differenza di Chiesa utilizza un adapter per il RecyclerView - -package app.familygem; - -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.widget.SearchView; -import androidx.recyclerview.widget.RecyclerView; -import android.view.ContextMenu; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.SubMenu; -import android.view.View; -import android.view.ViewGroup; -import androidx.fragment.app.Fragment; -import android.widget.Filter; -import android.widget.Filterable; -import android.widget.TextView; -import org.folg.gedcom.model.EventFact; -import org.folg.gedcom.model.Family; -import org.folg.gedcom.model.Name; -import org.folg.gedcom.model.Note; -import org.folg.gedcom.model.NoteContainer; -import org.folg.gedcom.model.Person; -import org.folg.gedcom.model.Source; -import org.folg.gedcom.model.SourceCitation; -import org.folg.gedcom.model.SourceCitationContainer; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import app.familygem.detail.Fonte; -import app.familygem.visitor.ListaCitazioniFonte; -import static app.familygem.Global.gc; - -public class Biblioteca extends Fragment { - - private List listaFonti; - private BibliotecAdapter adattatore; - private int ordine; - - @Override - public View onCreateView( LayoutInflater inflater, ViewGroup container, Bundle bandolo ) { - listaFonti = gc.getSources(); - ((AppCompatActivity) getActivity()).getSupportActionBar().setTitle( listaFonti.size() + " " + - getString(listaFonti.size()==1 ? R.string.source : R.string.sources).toLowerCase() ); - if( listaFonti.size() > 1 ) - setHasOptionsMenu(true); - View vista = inflater.inflate(R.layout.biblioteca, container, false); - RecyclerView vistaFonti = vista.findViewById( R.id.riciclatore ); - adattatore = new BibliotecAdapter(); - vistaFonti.setAdapter( adattatore ); - vista.findViewById( R.id.fab ).setOnClickListener( v -> nuovaFonte( getContext(), null ) ); - return vista; - } - - public class BibliotecAdapter extends RecyclerView.Adapter implements Filterable { - @Override - public GestoreFonte onCreateViewHolder( ViewGroup parent, int tipo ) { - View vistaFonte = LayoutInflater.from( parent.getContext() ) - .inflate(R.layout.biblioteca_pezzo, parent, false); - registerForContextMenu( vistaFonte ); - return new GestoreFonte( vistaFonte ); - } - @Override - public void onBindViewHolder( GestoreFonte gestore, int posizione ) { - Source fonte = listaFonti.get(posizione); - gestore.vistaId.setText( fonte.getId() ); - gestore.vistaId.setVisibility( ordine == 1 || ordine == 2 ? View.VISIBLE : View.GONE ); - gestore.vistaTitolo.setText( titoloFonte(fonte) ); - Object volte = fonte.getExtension("citaz"); - // Conta delle citazioni con il mio metodo - if( volte == null ) { - volte = quanteCitazioni( fonte ); - fonte.putExtension("citaz", volte ); - } - gestore.vistaVolte.setText( String.valueOf(volte) ); - } - // Filtra i titoli delle fonti in base alle parole cercate - @Override - public Filter getFilter() { - return new Filter() { - @Override - protected FilterResults performFiltering(CharSequence charSequence) { - String query = charSequence.toString(); - if (query.isEmpty()) { - listaFonti = gc.getSources(); - } else { - List filteredList = new ArrayList<>(); - for (Source font : gc.getSources()) { - if( titoloFonte(font).toLowerCase().contains(query.toLowerCase()) ) { - filteredList.add(font); - } - } - listaFonti = filteredList; - } - ordinaFonti(); // Riducendo la query riordina quelli che appaiono - FilterResults filterResults = new FilterResults(); - filterResults.values = listaFonti; - return filterResults; - } - @Override - protected void publishResults(CharSequence cs, FilterResults fr) { - notifyDataSetChanged(); - } - }; - } - @Override - public int getItemCount() { - return listaFonti.size(); - } - } - - class GestoreFonte extends RecyclerView.ViewHolder implements View.OnClickListener { - TextView vistaId; - TextView vistaTitolo; - TextView vistaVolte; - GestoreFonte( View vista ) { - super( vista ); - vistaId = vista.findViewById( R.id.biblioteca_id ); - vistaTitolo = vista.findViewById( R.id.biblioteca_titolo ); - vistaVolte = vista.findViewById( R.id.biblioteca_volte ); - vista.setOnClickListener(this); - } - @Override - public void onClick( View v ) { - // Restituisce l'id di una fonte a Individuo e Dettaglio - if( getActivity().getIntent().getBooleanExtra("bibliotecaScegliFonte",false) ) { - Intent intent = new Intent(); - intent.putExtra("idFonte", vistaId.getText().toString() ); - getActivity().setResult( Activity.RESULT_OK, intent ); - getActivity().finish(); - } else { - Source fonte = gc.getSource( vistaId.getText().toString() ); - Memoria.setPrimo( fonte ); - startActivity( new Intent( getContext(), Fonte.class ) ); - } - } - } - - @Override - public void onPause() { - super.onPause(); - getActivity().getIntent().removeExtra("bibliotecaScegliFonte"); - } - - // Mette in ordine le fonti secondo uno dei criteri - // L'ordine poi diventa permanente nel Json - private void ordinaFonti() { - if( ordine > 0 ) { - if( ordine == 5 || ordine == 6 ) { - for( Source fonte : listaFonti ) { - if( fonte.getExtension("citaz") == null ) - fonte.putExtension( "citaz", quanteCitazioni(fonte) ); - } - } - Collections.sort( listaFonti, (f1, f2) -> { - switch( ordine ) { - case 1: // Ordina per id numerico - return U.soloNumeri(f1.getId()) - U.soloNumeri(f2.getId()); - case 2: - return U.soloNumeri(f2.getId()) - U.soloNumeri(f1.getId()); - case 3: // Ordine alfabeto dei titoli - return titoloFonte(f1).compareToIgnoreCase( titoloFonte(f2) ); - case 4: - return titoloFonte(f2).compareToIgnoreCase( titoloFonte(f1) ); - case 5: // Ordina per numero di citazioni - return U.castJsonInt(f1.getExtension("citaz")) - U.castJsonInt(f2.getExtension("citaz")); - case 6: - return U.castJsonInt(f2.getExtension("citaz")) - U.castJsonInt(f1.getExtension("citaz")); - } - return 0; - }); - } - } - - // Restituisce il titolo della fonte - static String titoloFonte( Source fon ) { - String tit = ""; - if( fon != null ) - if( fon.getAbbreviation() != null ) - tit = fon.getAbbreviation(); - else if( fon.getTitle() != null ) - tit = fon.getTitle(); - else if( fon.getText() != null ) { - tit = fon.getText().replaceAll("\n", " "); - //tit = tit.length() > 35 ? tit.substring(0,35)+"…" : tit; - } else if( fon.getPublicationFacts() != null ) { - tit = fon.getPublicationFacts().replaceAll("\n", " "); - } - return tit; - } - - // Restituisce quante volte una fonte viene citata nel Gedcom - // Ho provato a riscriverlo come Visitor, che però è molto più lento - private int quante; - private int quanteCitazioni( Source fon ) { - quante = 0; - for( Person p : Global.gc.getPeople() ) { - cita( p, fon ); - for( Name n : p.getNames() ) - cita( n, fon ); - for( EventFact ef : p.getEventsFacts() ) - cita( ef, fon ); - } - for( Family f : Global.gc.getFamilies() ) { - cita( f, fon ); - for( EventFact ef : f.getEventsFacts() ) - cita( ef, fon ); - } - for( Note n : Global.gc.getNotes() ) - cita( n, fon ); - return quante; - } - - // riceve un Object (Person, Name, EventFact...) e conta quante volte è citata la fonte - private void cita( Object ogg, Source fonte ) { - List listaSc; - if( ogg instanceof Note ) // se è una Nota - listaSc = ((Note) ogg).getSourceCitations(); - else { - for( Note n : ((NoteContainer) ogg).getNotes() ) - cita( n, fonte ); - listaSc = ((SourceCitationContainer) ogg).getSourceCitations(); - } - for( SourceCitation sc : listaSc ) { - if( sc.getRef() != null ) - if( sc.getRef().equals(fonte.getId()) ) - quante++; - } - } - - static void nuovaFonte( Context contesto, Object contenitore ){ - Source fonte = new Source(); - fonte.setId( U.nuovoId( gc, Source.class ) ); - fonte.setTitle( "" ); - gc.addSource( fonte ); - if( contenitore != null ) { - SourceCitation citaFonte = new SourceCitation(); - citaFonte.setRef( fonte.getId() ); - if( contenitore instanceof Note ) ((Note)contenitore).addSourceCitation( citaFonte ); - else ((SourceCitationContainer)contenitore).addSourceCitation( citaFonte ); - } - U.save( true, fonte ); - Memoria.setPrimo( fonte ); - contesto.startActivity( new Intent( contesto, Fonte.class ) ); - } - - // Elimina la fonte, i Ref in tutte le SourceCitation che puntano ad essa, e le SourceCitation vuote - // Restituisce un array dei capostipiti modificati - // Todo le citazioni alla Source eliminata diventano Fonte-nota a cui bisognerebbe poter riattaccare una Source - public static Object[] eliminaFonte( Source fon ) { - ListaCitazioniFonte citazioni = new ListaCitazioniFonte( gc, fon.getId() ); - for( ListaCitazioniFonte.Tripletta cita : citazioni.lista ) { - SourceCitation sc = cita.citazione; - sc.setRef( null ); - // Se la SourceCitation non contiene altro si può eliminare - boolean eliminabile = true; - if( sc.getPage()!=null || sc.getDate()!=null || sc.getText()!=null || sc.getQuality()!=null - || !sc.getAllNotes(gc).isEmpty() || !sc.getAllMedia(gc).isEmpty() || !sc.getExtensions().isEmpty() ) - eliminabile = false; - if( eliminabile ) { - Object contenitore = cita.contenitore; - List lista; - if( contenitore instanceof Note ) - lista = ((Note)contenitore).getSourceCitations(); - else - lista = ((SourceCitationContainer)contenitore).getSourceCitations(); - lista.remove( sc ); - if( lista.isEmpty() ) { - if( contenitore instanceof Note ) - ((Note)contenitore).setSourceCitations( null ); - else - ((SourceCitationContainer)contenitore).setSourceCitations( null ); - } - } - } - gc.getSources().remove( fon ); - if( gc.getSources().isEmpty() ) - gc.setSources( null ); - gc.createIndexes(); // necessario - Memoria.annullaIstanze( fon ); - return citazioni.getCapi(); - } - - // menu opzioni nella toolbar - @Override - public void onCreateOptionsMenu( Menu menu, MenuInflater inflater ) { - SubMenu subMenu = menu.addSubMenu(R.string.order_by); - if( Global.settings.expert ) - subMenu.add(0, 1, 0, R.string.id); - subMenu.add(0, 2, 0, R.string.title); - subMenu.add(0, 3, 0, R.string.citations); - - // Ricerca nella Biblioteca - inflater.inflate(R.menu.cerca, menu); - final SearchView vistaCerca = (SearchView)menu.findItem(R.id.ricerca).getActionView(); - vistaCerca.setOnQueryTextListener(new SearchView.OnQueryTextListener() { - @Override - public boolean onQueryTextChange(String query) { - adattatore.getFilter().filter(query); - return true; - } - @Override - public boolean onQueryTextSubmit(String q) { - vistaCerca.clearFocus(); - return false; - } - }); - } - - @Override - public boolean onOptionsItemSelected( MenuItem item ) { - int id = item.getItemId(); - if( id > 0 && id <= 3 ) { - if( ordine == id*2-1 ) - ordine++; - else if( ordine == id*2 ) - ordine--; - else - ordine = id*2-1; - ordinaFonti(); - adattatore.notifyDataSetChanged(); - return true; - } - return false; - } - - private Source source; - @Override - public void onCreateContextMenu(ContextMenu menu, View vista, ContextMenu.ContextMenuInfo info) { - source = gc.getSource(((TextView)vista.findViewById(R.id.biblioteca_id)).getText().toString()); - if( Global.settings.expert ) - menu.add(0, 0, 0, R.string.edit_id); - menu.add(0, 1, 0, R.string.delete); - } - @Override - public boolean onContextItemSelected(MenuItem item) { - if( item.getItemId() == 0 ) { // Edit source ID - U.editId(getContext(), source, getActivity()::recreate); - } else if( item.getItemId() == 1 ) { // Delete source - Object[] objects = eliminaFonte(source); - U.save(false, objects); - getActivity().recreate(); - } else { - return false; - } - return true; - } -} \ No newline at end of file diff --git a/app/src/main/java/app/familygem/Lavagna.java b/app/src/main/java/app/familygem/BlackboardActivity.java similarity index 56% rename from app/src/main/java/app/familygem/Lavagna.java rename to app/src/main/java/app/familygem/BlackboardActivity.java index 41a0f8d8..44bfd5c4 100644 --- a/app/src/main/java/app/familygem/Lavagna.java +++ b/app/src/main/java/app/familygem/BlackboardActivity.java @@ -10,23 +10,23 @@ import java.io.File; -public class Lavagna extends AppCompatActivity { +public class BlackboardActivity extends AppCompatActivity { @Override - protected void onCreate( Bundle bandolo ) { - super.onCreate( bandolo ); + protected void onCreate( Bundle bundle ) { + super.onCreate( bundle ); getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); setContentView( R.layout.lavagna ); - // Mostra il file a piena risoluzione - String percorso = getIntent().getStringExtra( "percorso" ); + // Show the file in full resolution + String path = getIntent().getStringExtra( "path" ); Picasso picasso = Picasso.get(); - RequestCreator creatore; - if( percorso != null ) { - creatore = picasso.load( new File(percorso) ); + RequestCreator creator; + if( path != null ) { + creator = picasso.load( new File(path) ); } else { Uri uri = Uri.parse( getIntent().getStringExtra("uri") ); - creatore = picasso.load( uri ); + creator = picasso.load( uri ); } - creatore.into( (ImageView)findViewById(R.id.lavagna_immagine) ); + creator.into( (ImageView)findViewById(R.id.lavagna_immagine) ); } -} \ No newline at end of file +} diff --git a/app/src/main/java/app/familygem/CartelleMedia.java b/app/src/main/java/app/familygem/CartelleMedia.java deleted file mode 100644 index 1b5f1c24..00000000 --- a/app/src/main/java/app/familygem/CartelleMedia.java +++ /dev/null @@ -1,206 +0,0 @@ -// Activity in cui è possibile vedere la lista delle cartelle, aggiungerne, eliminarne - -package app.familygem; - -import android.Manifest; -import android.app.Activity; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.view.ContextMenu; -import android.view.MenuItem; -import android.view.View; -import android.widget.LinearLayout; -import android.widget.TextView; -import android.widget.Toast; -import androidx.appcompat.app.AlertDialog; -import androidx.core.app.ActivityCompat; -import androidx.core.content.ContextCompat; -import androidx.documentfile.provider.DocumentFile; -import java.util.ArrayList; -import java.util.List; - -public class CartelleMedia extends BaseActivity { - - int idAlbero; - List cartelle; - List uris; - - @Override - protected void onCreate( Bundle bandolo ) { - super.onCreate( bandolo ); - setContentView( R.layout.cartelle_media ); - idAlbero = getIntent().getIntExtra( "idAlbero", 0 ); - cartelle = new ArrayList<>( Global.settings.getTree(idAlbero).dirs); - uris = new ArrayList<>( Global.settings.getTree(idAlbero).uris ); - aggiornaLista(); - getSupportActionBar().setDisplayHomeAsUpEnabled( true ); - findViewById( R.id.fab ).setOnClickListener( v -> { - int perm = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE); - if( perm == PackageManager.PERMISSION_DENIED ) - ActivityCompat.requestPermissions(this, new String[]{ Manifest.permission.READ_EXTERNAL_STORAGE }, 3517); - else if( perm == PackageManager.PERMISSION_GRANTED ) - faiScegliereCartella(); - }); - if( Global.settings.getTree(idAlbero).dirs.isEmpty() && Global.settings.getTree(idAlbero).uris.isEmpty() ) - new Fabuloso( this, R.string.add_device_folder ).show(); - } - - void faiScegliereCartella() { - if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ) { - Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); - intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - startActivityForResult( intent, 123 ); - } else { - // KitKat utilizza la selezione di un file per risalire alla cartella - Intent intent = new Intent(Intent.ACTION_GET_CONTENT); - intent.setType( "*/*"); - startActivityForResult( intent, 456 ); - } - } - - void aggiornaLista() { - LinearLayout scatola = findViewById( R.id.cartelle_scatola ); - scatola.removeAllViews(); - for( String cart : cartelle ) { - View vistaCartella = getLayoutInflater().inflate( R.layout.pezzo_cartella, scatola, false ); - scatola.addView( vistaCartella ); - TextView vistaNome = vistaCartella.findViewById( R.id.cartella_nome ); - TextView vistaUrl = vistaCartella.findViewById( R.id.cartella_url ); - vistaUrl.setText( cart ); - if( Global.settings.expert ) - vistaUrl.setSingleLine( false ); - View bottoneElimina = vistaCartella.findViewById( R.id.cartella_elimina ); - // La cartella '/storage/.../Android/data/app.familygem/files/X' va preservata inquanto è quella di default dei media copiati - // Oltretutto in Android 11 non è più raggiungibile dall'utente con SAF - if( cart.equals(getExternalFilesDir(null) + "/" + idAlbero) ) { - vistaNome.setText( R.string.app_storage ); - bottoneElimina.setVisibility( View.GONE ); - } else { - vistaNome.setText( nomeCartella(cart) ); - bottoneElimina.setOnClickListener( v -> { - new AlertDialog.Builder(this).setMessage( R.string.sure_delete ) - .setPositiveButton( R.string.yes, (di,id) -> { - cartelle.remove( cart ); - salva(); - }).setNeutralButton( R.string.cancel, null ).show(); - }); - } - registerForContextMenu( vistaCartella ); - } - for( String stringUri : uris ) { - View vistaUri = getLayoutInflater().inflate( R.layout.pezzo_cartella, scatola, false ); - scatola.addView( vistaUri ); - DocumentFile documentDir = DocumentFile.fromTreeUri( this, Uri.parse(stringUri) ); - String nome = null; - if( documentDir != null ) - nome = documentDir.getName(); - ((TextView)vistaUri.findViewById(R.id.cartella_nome)).setText( nome ); - TextView vistaUrl = vistaUri.findViewById( R.id.cartella_url ); - if( Global.settings.expert ) { - vistaUrl.setSingleLine( false ); - vistaUrl.setText( stringUri ); - } else - vistaUrl.setText( Uri.decode(stringUri) ); // lo mostra decodificato cioè un po' più leggibile - vistaUri.findViewById( R.id.cartella_elimina ).setOnClickListener( v -> { - new AlertDialog.Builder(this).setMessage( R.string.sure_delete ) - .setPositiveButton( R.string.yes, (di,id) -> { - // Revoca il permesso per questo uri, se l'uri non è usato in nessun altro albero - boolean uriEsisteAltrove = false; - for( Settings.Tree albero : Global.settings.trees ) { - for( String uri : albero.uris ) - if( uri.equals(stringUri) && albero.id != idAlbero ) { - uriEsisteAltrove = true; - break; - } - } - if( !uriEsisteAltrove ) - revokeUriPermission( Uri.parse(stringUri), Intent.FLAG_GRANT_READ_URI_PERMISSION ); - uris.remove( stringUri ); - salva(); - }).setNeutralButton( R.string.cancel, null ).show(); - }); - registerForContextMenu( vistaUri ); - } - } - - String nomeCartella( String url ) { - if( url.lastIndexOf('/') > 0 ) - return url.substring( url.lastIndexOf('/') + 1 ); - return url; - } - - void salva() { - Global.settings.getTree(idAlbero).dirs.clear(); - for( String path : cartelle ) - Global.settings.getTree(idAlbero).dirs.add( path ); - Global.settings.getTree(idAlbero).uris.clear(); - for( String uri : uris ) - Global.settings.getTree(idAlbero).uris.add( uri ); - Global.settings.save(); - aggiornaLista(); - } - - @Override - public boolean onOptionsItemSelected( MenuItem item ) { - onBackPressed(); - return true; - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - if( resultCode == Activity.RESULT_OK ) { - Uri uri = data.getData(); - if( uri != null ) { - // in KitKat è stato selezionato un file e ne ricaviamo il percorso della cartella - if( requestCode == 456 ) { - String percorso = F.uriPercorsoCartellaKitKat( this, uri ); - if( percorso != null ) { - cartelle.add( percorso ); - salva(); - } - } else if( requestCode == 123 ) { - String percorso = F.uriPercorsoCartella( uri ); - if( percorso != null ) { - cartelle.add( percorso ); - salva(); - } else { - getContentResolver().takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION); - DocumentFile docDir = DocumentFile.fromTreeUri( this, uri ); - if( docDir != null && docDir.canRead() ) { - uris.add( uri.toString() ); - salva(); - } else - Toast.makeText( this, "Could not read this position.", Toast.LENGTH_SHORT ).show(); - } - } - } else - Toast.makeText( this, R.string.something_wrong, Toast.LENGTH_SHORT ).show(); - } - } - - View vistaScelta; - @Override - public void onCreateContextMenu( ContextMenu menu, View vista, ContextMenu.ContextMenuInfo info ) { - vistaScelta = vista; - menu.add(0, 0, 0, R.string.copy ); - } - @Override - public boolean onContextItemSelected( MenuItem item ) { - if( item.getItemId() == 0 ) { // Copia - U.copiaNegliAppunti( getText(android.R.string.copyUrl), ((TextView)vistaScelta.findViewById(R.id.cartella_url)).getText() ); - return true; - } - return false; - } - - @Override - public void onRequestPermissionsResult( int codice, String[] permessi, int[] accordi ) { - if( accordi.length > 0 && accordi[0] == PackageManager.PERMISSION_GRANTED && codice == 3517 ) - faiScegliereCartella(); - } -} \ No newline at end of file diff --git a/app/src/main/java/app/familygem/Chiesa.java b/app/src/main/java/app/familygem/ChurchFragment.java similarity index 70% rename from app/src/main/java/app/familygem/Chiesa.java rename to app/src/main/java/app/familygem/ChurchFragment.java index 2fa1dd91..7e69ce5e 100644 --- a/app/src/main/java/app/familygem/Chiesa.java +++ b/app/src/main/java/app/familygem/ChurchFragment.java @@ -25,10 +25,10 @@ import java.util.Iterator; import java.util.List; import java.util.Set; -import app.familygem.detail.Famiglia; +import app.familygem.detail.FamilyActivity; import static app.familygem.Global.gc; -public class Chiesa extends Fragment { +public class ChurchFragment extends Fragment { private LinearLayout layout; private List familyList; @@ -44,13 +44,13 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bu refresh(What.RELOAD); if( familyList.size() > 1 ) setHasOptionsMenu(true); - idsAreNumeric = verificaIdNumerici(); + idsAreNumeric = verifyIdsAreNumeric(); view.findViewById(R.id.fab).setOnClickListener(v -> { - Family newFamily = nuovaFamiglia(true); + Family newFamily = newFamily(true); U.save(true, newFamily); - // Se torna subito indietro in Chiesa rinfresca la lista con la famiglia vuota - Memoria.setPrimo(newFamily); - startActivity(new Intent(getContext(), Famiglia.class)); + // If he(user?) goes straight back to the church he refreshes the list with the empty family //Se torna subito indietro in Chiesa rinfresca la lista con la famiglia vuota + Memory.setFirst(newFamily); + startActivity(new Intent(getContext(), FamilyActivity.class)); }); } return view; @@ -79,82 +79,82 @@ void placeFamily(LinearLayout layout, FamilyWrapper wrapper) { default: infoView.setVisibility(View.GONE); } - String parents = ""; + StringBuilder parents = new StringBuilder(); for( Person husband : wrapper.family.getHusbands(gc) ) - parents += U.epiteto(husband) + "\n"; + parents.append(U.properName(husband)).append("\n"); for( Person wife : wrapper.family.getWives(gc) ) - parents += U.epiteto(wife) + "\n"; - if( !parents.isEmpty() ) - parents = parents.substring(0, parents.length() - 1); - ((TextView)familyView.findViewById(R.id.family_parents)).setText(parents); - String children = ""; + parents.append(U.properName(wife)).append("\n"); + if(parents.length() > 0) + parents = new StringBuilder(parents.substring(0, parents.length() - 1)); + ((TextView)familyView.findViewById(R.id.family_parents)).setText(parents.toString()); + StringBuilder children = new StringBuilder(); for( Person child : wrapper.family.getChildren(gc) ) - children += U.epiteto(child) + "\n"; - if( !children.isEmpty() ) - children = children.substring(0, children.length() - 1); + children.append(U.properName(child)).append("\n"); + if(children.length() > 0) + children = new StringBuilder(children.substring(0, children.length() - 1)); TextView childrenView = familyView.findViewById(R.id.family_children); - if( children.isEmpty() ) { + if(children.length() == 0) { familyView.findViewById(R.id.family_strut).setVisibility(View.GONE); childrenView.setVisibility(View.GONE); } else - childrenView.setText(children); + childrenView.setText(children.toString()); registerForContextMenu(familyView); familyView.setOnClickListener(v -> { - Memoria.setPrimo(wrapper.family); - layout.getContext().startActivity(new Intent(layout.getContext(), Famiglia.class)); + Memory.setFirst(wrapper.family); + layout.getContext().startActivity(new Intent(layout.getContext(), FamilyActivity.class)); }); - familyView.setTag(wrapper.id); // solo per il menu contestuale Elimina qui in Chiesa + familyView.setTag(wrapper.id); // only for the context menu Delete here in the Church //solo per il menu contestuale Elimina qui in Chiesa } // Delete a family, removing the refs from members static void deleteFamily(Family family) { if( family == null ) return; - Set membri = new HashSet<>(); + Set members = new HashSet<>(); // Remove references to the family from family members - for( Person marito : family.getHusbands(gc) ) { - Iterator refi = marito.getSpouseFamilyRefs().iterator(); - while( refi.hasNext() ) { - SpouseFamilyRef sfr = refi.next(); + for( Person husband : family.getHusbands(gc) ) { + Iterator refs = husband.getSpouseFamilyRefs().iterator(); + while( refs.hasNext() ) { + SpouseFamilyRef sfr = refs.next(); if( sfr.getRef().equals(family.getId()) ) { - refi.remove(); - membri.add( marito ); + refs.remove(); + members.add( husband ); } } } - for( Person moglie : family.getWives(gc) ) { - Iterator refi = moglie.getSpouseFamilyRefs().iterator(); - while( refi.hasNext() ) { - SpouseFamilyRef sfr = refi.next(); + for( Person wife : family.getWives(gc) ) { + Iterator refs = wife.getSpouseFamilyRefs().iterator(); + while( refs.hasNext() ) { + SpouseFamilyRef sfr = refs.next(); if( sfr.getRef().equals(family.getId()) ) { - refi.remove(); - membri.add( moglie ); + refs.remove(); + members.add( wife ); } } } - for( Person figlio : family.getChildren(gc) ) { - Iterator refi = figlio.getParentFamilyRefs().iterator(); + for( Person children : family.getChildren(gc) ) { + Iterator refi = children.getParentFamilyRefs().iterator(); while( refi.hasNext() ) { ParentFamilyRef pfr = refi.next(); if( pfr.getRef().equals(family.getId()) ) { refi.remove(); - membri.add( figlio ); + members.add( children ); } } } // The family is deleted gc.getFamilies().remove(family); - gc.createIndexes(); // necessario per aggiornare gli individui - Memoria.annullaIstanze(family); - Global.familyNum = 0; // Nel caso fortuito che sia stata eliminata proprio questa famiglia - U.save(true, membri.toArray(new Object[0])); + gc.createIndexes(); // necessary to update individuals + Memory.setInstanceAndAllSubsequentToNull(family); + Global.familyNum = 0; // In the unlikely event that this family was eliminated //Nel caso fortuito che sia stata eliminata proprio questa famiglia + U.save(true, members.toArray(new Object[0])); } - static Family nuovaFamiglia(boolean aggiungi) { - Family nuova = new Family(); - nuova.setId(U.nuovoId(gc, Family.class)); - if( aggiungi ) - gc.addFamily(nuova); - return nuova; + static Family newFamily(boolean add) { + Family newFamily = new Family(); + newFamily.setId(U.newID(gc, Family.class)); + if( add ) + gc.addFamily(newFamily); + return newFamily; } private Family selected; @@ -169,7 +169,7 @@ public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.Context public boolean onContextItemSelected( MenuItem item ) { if( item.getItemId() == 0 ) { // Edit ID U.editId(getContext(), selected, () -> this.refresh(What.UPDATE)); - } else if( item.getItemId() == 1 ) { // Elimina + } else if( item.getItemId() == 1 ) { // Delete if( selected.getHusbandRefs().size() + selected.getWifeRefs().size() + selected.getChildRefs().size() > 0 ) { new AlertDialog.Builder(getContext()).setMessage(R.string.really_delete_family) .setPositiveButton(android.R.string.yes, (dialog, i) -> { @@ -186,14 +186,16 @@ public boolean onContextItemSelected( MenuItem item ) { return true; } - // Verifica se tutti gli id delle famiglie contengono numeri - // Appena un id contiene solo lettere restituisce false - boolean verificaIdNumerici() { - esterno: + /** + * Check if all family ids contain numbers + * As soon as an id contains only letters it returns false + * */ + boolean verifyIdsAreNumeric() { + outer: for( Family f : gc.getFamilies() ) { for( char c : f.getId().toCharArray() ) { if (Character.isDigit(c)) - continue esterno; + continue outer; } return false; } @@ -204,18 +206,18 @@ void sortFamilies() { if( order > 0 ) { // 0 keeps actual sorting Collections.sort(familyList, (f1, f2 ) -> { switch( order ) { - case 1: // Ordina per ID + case 1: // Sort by ID if( idsAreNumeric ) - return U.soloNumeri(f1.id) - U.soloNumeri(f2.id); + return U.extractNum(f1.id) - U.extractNum(f2.id); else return f1.id.compareToIgnoreCase(f2.id); case 2: if( idsAreNumeric ) - return U.soloNumeri(f2.id) - U.soloNumeri(f1.id); + return U.extractNum(f2.id) - U.extractNum(f1.id); else return f2.id.compareToIgnoreCase(f1.id); - case 3: // Ordina per cognome - if (f1.lowerSurname == null) // i nomi null vanno in fondo + case 3: // Sort by surname + if (f1.lowerSurname == null) // null names go to the bottom return f2.lowerSurname == null ? 0 : 1; if (f2.lowerSurname == null) return -1; @@ -226,7 +228,7 @@ void sortFamilies() { if (f2.lowerSurname == null) return -1; return f2.lowerSurname.compareTo(f1.lowerSurname); - case 5: // Ordina per numero di familiari + case 5: // Sort by number of family members return f1.members - f2.members; case 6: return f2.members - f1.members; @@ -271,7 +273,9 @@ public FamilyWrapper(Family family) { members = countMembers(); } - // Main surname of the family + /** + * Main surname of the family + * */ private String familySurname(boolean lowerCase) { if( !family.getHusbands(gc).isEmpty() ) return U.surname(family.getHusbands(gc).get(0), lowerCase); @@ -282,7 +286,9 @@ private String familySurname(boolean lowerCase) { return null; } - // Count how many family members + /** + * Count how many family members + * */ private int countMembers() { return family.getHusbandRefs().size() + family.getWifeRefs().size() + family.getChildRefs().size(); } @@ -308,7 +314,7 @@ else if( order == id * 2 ) order = id * 2 - 1; sortFamilies(); refresh(What.BASIC); - //U.salvaJson( false ); // dubbio se metterlo per salvare subito il riordino delle famiglie + //U.salvaJson( false ); // doubt whether to put it to immediately save the reorganization of families //dubbio se metterlo per salvare subito il riordino delle famiglie return true; } return false; diff --git a/app/src/main/java/app/familygem/Compara.java b/app/src/main/java/app/familygem/Compara.java deleted file mode 100644 index 932c7f37..00000000 --- a/app/src/main/java/app/familygem/Compara.java +++ /dev/null @@ -1,288 +0,0 @@ -// Attività di introduzione all'importazione delle novità in un albero già esistente - -package app.familygem; - -import android.content.Intent; -import android.os.Bundle; -import android.view.MenuItem; -import android.view.View; -import android.widget.Button; -import android.widget.TextView; -import android.widget.Toast; -import androidx.appcompat.app.AlertDialog; -import androidx.cardview.widget.CardView; -import org.folg.gedcom.model.Change; -import org.folg.gedcom.model.Family; -import org.folg.gedcom.model.Gedcom; -import org.folg.gedcom.model.Media; -import org.folg.gedcom.model.Note; -import org.folg.gedcom.model.Person; -import org.folg.gedcom.model.Repository; -import org.folg.gedcom.model.Source; -import org.folg.gedcom.model.Submitter; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Locale; -import java.util.TimeZone; - -public class Compara extends BaseActivity { - - Date sharingDate; - SimpleDateFormat changeDateFormat; - - @Override - protected void onCreate( Bundle bandolo ) { - super.onCreate( bandolo ); - setContentView( R.layout.compara ); - int idAlbero = getIntent().getIntExtra("idAlbero",1); // Albero vecchio - int idAlbero2 = getIntent().getIntExtra("idAlbero2",1); // Albero nuovo ricevuto in condivisione - Global.treeId2 = idAlbero2; // servirà alle immagini di Confrontatore e a Conferma - Global.gc = Alberi.apriGedcomTemporaneo( idAlbero, true ); - Global.gc2 = Alberi.apriGedcomTemporaneo( idAlbero2, false ); - if( Global.gc == null || Global.gc2 == null ) { - Toast.makeText( this, R.string.no_useful_data, Toast.LENGTH_LONG ).show(); - onBackPressed(); - return; - } - - TimeZone.setDefault( TimeZone.getTimeZone("Europe/Rome") ); // riconduce tutte le date al fuso orario di Aruba - try { - SimpleDateFormat formatoDataId = new SimpleDateFormat("yyyyMMddHHmmss", Locale.ENGLISH); - sharingDate = formatoDataId.parse(getIntent().getStringExtra("idData")); - } catch( ParseException e ) { - e.printStackTrace(); - } - - changeDateFormat = new SimpleDateFormat( "d MMM yyyyHH:mm:ss", Locale.ENGLISH ); - Confronto.reset(); // Necessario svuotarlo, ad esempio dopo un cambio di configurazione - - // Confronta tutti i record dei due Gedcom - for( Family o2 : Global.gc2.getFamilies() ) - confronta( Global.gc.getFamily(o2.getId()), o2, 7 ); - for( Family o : Global.gc.getFamilies() ) - riconfronta( o, Global.gc2.getFamily(o.getId()), 7 ); - - for( Person o2 : Global.gc2.getPeople() ) - confronta( Global.gc.getPerson(o2.getId()), o2, 6 ); - for( Person o : Global.gc.getPeople() ) - riconfronta( o, Global.gc2.getPerson(o.getId()), 6 ); - - for( Source o2 : Global.gc2.getSources() ) - confronta( Global.gc.getSource(o2.getId()), o2, 5 ); - for( Source o : Global.gc.getSources() ) - riconfronta( o, Global.gc2.getSource(o.getId()), 5 ); - - for( Media o2 : Global.gc2.getMedia() ) - confronta( Global.gc.getMedia(o2.getId()), o2, 4 ); - for( Media o : Global.gc.getMedia() ) - riconfronta( o, Global.gc2.getMedia(o.getId()), 4 ); - - for( Repository o2 : Global.gc2.getRepositories() ) - confronta( Global.gc.getRepository(o2.getId()), o2, 3 ); - for( Repository o : Global.gc.getRepositories() ) - riconfronta( o, Global.gc2.getRepository(o.getId()), 3 ); - - for( Submitter o2 : Global.gc2.getSubmitters() ) - confronta( Global.gc.getSubmitter(o2.getId()), o2, 2 ); - for( Submitter o : Global.gc.getSubmitters() ) - riconfronta( o, Global.gc2.getSubmitter(o.getId()), 2 ); - - for( Note o2 : Global.gc2.getNotes() ) - confronta( Global.gc.getNote(o2.getId()), o2, 1 ); - for( Note o : Global.gc.getNotes() ) - riconfronta( o, Global.gc2.getNote(o.getId()), 1 ); - - Settings.Tree tree2 = Global.settings.getTree(idAlbero2); - if( Confronto.getLista().isEmpty() ) { - setTitle(R.string.tree_without_news); - if( tree2.grade != 30 ) { - tree2.grade = 30; - Global.settings.save(); - } - } else if( tree2.grade != 20 ) { - tree2.grade = 20; - Global.settings.save(); - } - - arredaScheda(Global.gc, idAlbero, R.id.compara_vecchio); - arredaScheda(Global.gc2, idAlbero2, R.id.compara_nuovo); - - ((TextView)findViewById(R.id.compara_testo)).setText(getString(R.string.tree_news_imported, Confronto.getLista().size())); - - Button botton1 = findViewById(R.id.compara_bottone1); - Button botton2 = findViewById(R.id.compara_bottone2); - if( Confronto.getLista().size() > 0 ) { - // Rivedi singolarmente - botton1.setOnClickListener(v -> { - startActivity(new Intent(Compara.this, Confrontatore.class).putExtra("posizione", 1)); - }); - // Accetta tutto - botton2.setOnClickListener(v -> { - v.setEnabled(false); - Confronto.get().quanteScelte = 0; - for( Confronto.Fronte fronte : Confronto.getLista() ) { - if( fronte.doppiaOpzione ) - Confronto.get().quanteScelte++; - } - Intent intent = new Intent(Compara.this, Confrontatore.class); - intent.putExtra("posizione", 1); - if( Confronto.get().quanteScelte > 0 ) { // Dialogo di richiesta revisione - new AlertDialog.Builder(this) - .setTitle( Confronto.get().quanteScelte == 1 ? getString(R.string.one_update_choice) - : getString(R.string.many_updates_choice, Confronto.get().quanteScelte) ) - .setMessage(R.string.updates_replace_add) - .setPositiveButton( android.R.string.ok, (dialog,id) -> { - Confronto.get().autoProsegui = true; - Confronto.get().scelteFatte = 1; - startActivity( intent ); - }).setNeutralButton( android.R.string.cancel, (dialog,id) -> botton2.setEnabled(true) ) - .setOnCancelListener( dialog -> botton2.setEnabled(true) ).show(); - } else { // Avvio in automatico - Confronto.get().autoProsegui = true; - startActivity( intent ); - } - }); - } else { - botton1.setText(R.string.delete_imported_tree); - botton1.setOnClickListener(v -> { - Alberi.deleteTree(Compara.this, idAlbero2); - onBackPressed(); - }); - botton2.setVisibility(View.GONE); - } - } - - @Override - protected void onRestart() { - super.onRestart(); - findViewById(R.id.compara_bottone2 ).setEnabled( true ); // se eventualmente - Confronto.get().autoProsegui = false; // Lo resetta se eventualmente era stato scelto l'automatismo - } - - // Vede se aggiungere i due oggetti alla lista di quelli da valutare - private void confronta( Object o, Object o2, int tipo ) { - Change c = getCambi(o); - Change c2 = getCambi(o2); - int modifica = 0; - if( o == null && isRecent(c2) ) // o2 è stata aggiunto nel nuovo albero --> AGGIUNGI - modifica = 1; - else { - if( c == null && c2 != null ) - modifica = 1; - else if( c != null && c2 != null && - !(c.getDateTime().getValue().equals(c2.getDateTime().getValue()) // le due date devono essere diverse - && c.getDateTime().getTime().equals(c2.getDateTime().getTime())) ) { - if( isRecent(c) && isRecent(c2) ) { // entrambi modificati dopo la condivisione --> AGGIUNGI/SOSTITUISCI - modifica = 2; - } else if( isRecent(c2) ) // solo o2 è stato modificato --> SOSTITUISCI - modifica = 1; - } - } - if( modifica > 0 ) { - Confronto.Fronte fronte = Confronto.addFronte( o, o2, tipo ); - if( modifica == 2 ) - fronte.doppiaOpzione = true; - } - } - - // Idem per i rimanenti oggetti eliminati nell'albero vecchio - private void riconfronta( Object o, Object o2, int tipo ) { - if( o2 == null && !isRecent(getCambi(o)) ) - Confronto.addFronte( o, null, tipo ); - } - - /** Find if a top-level record has been modified after the date of sharing - * @param change Actual change date of the top-level record - * @return true if the record is more recent than the date of sharing - */ - private boolean isRecent(Change change) { - boolean itIs = false; - if( change != null && change.getDateTime() != null ) { - try { // todo con time null - String zoneId = U.castJsonString(change.getExtension("zone")); - if( zoneId == null ) - zoneId = "UTC"; - TimeZone timeZone = TimeZone.getTimeZone(zoneId); - changeDateFormat.setTimeZone(timeZone); - Date recordDate = changeDateFormat.parse(change.getDateTime().getValue() + change.getDateTime().getTime()); - itIs = recordDate.after(sharingDate); - //long oreSfaso = TimeUnit.MILLISECONDS.toMinutes( timeZone.getOffset(dataOggetto.getTime()) ); - //s.l( dataOggetto+"\t"+ ok +"\t"+ (oreSfaso>0?"+":"")+oreSfaso +"\t"+ timeZone.getID() ); - } catch( ParseException e ) {} - } - return itIs; - } - - Change getCambi( Object ogg ) { - Change cambio = null; - try { - cambio = (Change) ogg.getClass().getMethod( "getChange" ).invoke( ogg ); - } catch( Exception e ) {} - return cambio; - } - - void arredaScheda( Gedcom gc, int idAlbero, int idScheda ) { - CardView carta = findViewById(idScheda); - Settings.Tree tree = Global.settings.getTree(idAlbero); - TextView title = carta.findViewById(R.id.confronto_titolo); - TextView data = carta.findViewById(R.id.confronto_testo); - title.setText(tree.title); - data.setText(Alberi.scriviDati(this, tree)); - if( idScheda == R.id.compara_nuovo ) { - if( tree.grade == 30 ) { - carta.setCardBackgroundColor(getResources().getColor(R.color.consumed)); - title.setTextColor(getResources().getColor(R.color.grayText)); - data.setTextColor(getResources().getColor(R.color.grayText)); - } else - carta.setCardBackgroundColor(getResources().getColor(R.color.evidenziaMedio)); - Submitter autore = gc.getSubmitter(tree.shares.get(tree.shares.size() - 1).submitter); - String txt = ""; - if( autore != null ) { - String nome = autore.getName(); - if( nome == null || nome.isEmpty() ) - nome = getString(android.R.string.unknownName); - txt += getString(R.string.sent_by, nome) + "\n"; - } - //if( Confronto.getLista().size() > 0 ) - // txt += "Updates:\t"; - for( int i = 7; i > 0; i-- ) { - txt += scriviDifferenze( i ); - } - if( txt.endsWith("\n") ) txt = txt.substring( 0, txt.length()-1 ); - ((TextView)carta.findViewById( R.id.confronto_sottotesto )).setText( txt ); - carta.findViewById( R.id.confronto_sottotesto ).setVisibility( View.VISIBLE ); - } - carta.findViewById( R.id.confronto_data ).setVisibility( View.GONE ); - } - - int[] singolari = { R.string.shared_note, R.string.submitter, R.string.repository, R.string.shared_media, R.string.source, R.string.person, R.string.family }; - int[] plurali = { R.string.shared_notes, R.string.submitters, R.string.repositories, R.string.shared_medias, R.string.sources, R.string.persons, R.string.families }; - String scriviDifferenze( int tipo ) { - int modifiche = 0; - for( Confronto.Fronte fronte : Confronto.getLista() ) { - if( fronte.tipo == tipo ) { - modifiche++; - } - } - String testo = ""; - if( modifiche > 0 ) { - tipo--; - int definizione = modifiche==1 ? singolari[tipo] : plurali[tipo]; - testo = "\t\t+" + modifiche + " " + getString( definizione ).toLowerCase() + "\n"; - } - return testo; - } - - @Override - public boolean onOptionsItemSelected( MenuItem i ) { - onBackPressed(); - return true; - } - - @Override - public void onBackPressed() { - super.onBackPressed(); - Confronto.reset(); // resetta il singleton Confronto - } -} \ No newline at end of file diff --git a/app/src/main/java/app/familygem/CompareActivity.java b/app/src/main/java/app/familygem/CompareActivity.java new file mode 100644 index 00000000..0ccc644e --- /dev/null +++ b/app/src/main/java/app/familygem/CompareActivity.java @@ -0,0 +1,304 @@ +package app.familygem; + +import android.content.Intent; +import android.os.Bundle; +import android.view.MenuItem; +import android.view.View; +import android.widget.Button; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.appcompat.app.AlertDialog; +import androidx.cardview.widget.CardView; + +import org.folg.gedcom.model.Change; +import org.folg.gedcom.model.Family; +import org.folg.gedcom.model.Gedcom; +import org.folg.gedcom.model.Media; +import org.folg.gedcom.model.Note; +import org.folg.gedcom.model.Person; +import org.folg.gedcom.model.Repository; +import org.folg.gedcom.model.Source; +import org.folg.gedcom.model.Submitter; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; + +/** + * Activity for importing news in an existing tree + */ +public class CompareActivity extends BaseActivity { + + Date sharingDate; + SimpleDateFormat changeDateFormat; + + @Override + protected void onCreate(Bundle bundle) { + super.onCreate(bundle); + setContentView(R.layout.compara); + int idTree1 = getIntent().getIntExtra("idAlbero", 1); // Old tree + int idTree2 = getIntent().getIntExtra("idAlbero2", 1); // New tree received in sharing + Global.treeId2 = idTree2; // it will be used for the Comparator and Confirmation images + Global.gc = TreesActivity.openGedcomTemporarily(idTree1, true); + Global.gc2 = TreesActivity.openGedcomTemporarily(idTree2, false); + if (Global.gc == null || Global.gc2 == null) { + Toast.makeText(this, R.string.no_useful_data, Toast.LENGTH_LONG).show(); + onBackPressed(); + return; + } + + TimeZone.setDefault(TimeZone.getTimeZone("Europe/Rome")); // brings all dates back to the Aruba time zone //riconduce tutte le date al fuso orario di Aruba + try { + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss", Locale.ENGLISH); + sharingDate = dateFormat.parse(getIntent().getStringExtra("idData")); + } catch (ParseException e) { + e.printStackTrace(); + } + + changeDateFormat = new SimpleDateFormat("d MMM yyyyHH:mm:ss", Locale.ENGLISH); + Comparison.reset(); // Necessary to empty it, for example after a configuration change //Necessario svuotarlo, ad esempio dopo un cambio di configurazione + + // Compare all the records of the two Gedcoms + for (Family o2 : Global.gc2.getFamilies()) + compare(Global.gc.getFamily(o2.getId()), o2, 7); + for (Family o : Global.gc.getFamilies()) + reconcile(o, Global.gc2.getFamily(o.getId()), 7); + + for (Person o2 : Global.gc2.getPeople()) + compare(Global.gc.getPerson(o2.getId()), o2, 6); + for (Person o : Global.gc.getPeople()) + reconcile(o, Global.gc2.getPerson(o.getId()), 6); + + for (Source o2 : Global.gc2.getSources()) + compare(Global.gc.getSource(o2.getId()), o2, 5); + for (Source o : Global.gc.getSources()) + reconcile(o, Global.gc2.getSource(o.getId()), 5); + + for (Media o2 : Global.gc2.getMedia()) + compare(Global.gc.getMedia(o2.getId()), o2, 4); + for (Media o : Global.gc.getMedia()) + reconcile(o, Global.gc2.getMedia(o.getId()), 4); + + for (Repository o2 : Global.gc2.getRepositories()) + compare(Global.gc.getRepository(o2.getId()), o2, 3); + for (Repository o : Global.gc.getRepositories()) + reconcile(o, Global.gc2.getRepository(o.getId()), 3); + + for (Submitter o2 : Global.gc2.getSubmitters()) + compare(Global.gc.getSubmitter(o2.getId()), o2, 2); + for (Submitter o : Global.gc.getSubmitters()) + reconcile(o, Global.gc2.getSubmitter(o.getId()), 2); + + for (Note o2 : Global.gc2.getNotes()) + compare(Global.gc.getNote(o2.getId()), o2, 1); + for (Note o : Global.gc.getNotes()) + reconcile(o, Global.gc2.getNote(o.getId()), 1); + + Settings.Tree tree2 = Global.settings.getTree(idTree2); + if (Comparison.getList().isEmpty()) { + setTitle(R.string.tree_without_news); + if (tree2.grade != 30) { + tree2.grade = 30; + Global.settings.save(); + } + } else if (tree2.grade != 20) { + tree2.grade = 20; + Global.settings.save(); + } + + populateCard(Global.gc, idTree1, R.id.compara_vecchio); + populateCard(Global.gc2, idTree2, R.id.compara_nuovo); + + ((TextView) findViewById(R.id.compara_testo)).setText(getString(R.string.tree_news_imported, Comparison.getList().size())); + + Button button1 = findViewById(R.id.compara_bottone1); + Button button2 = findViewById(R.id.compara_bottone2); + if (Comparison.getList().size() > 0) { + // Review individually //Rivedi singolarmente + button1.setOnClickListener(v -> { + startActivity(new Intent(CompareActivity.this, TreeComparatorActivity.class).putExtra("posizione", 1)); + }); + // Accept everything //Accetta tutto + button2.setOnClickListener(v -> { + v.setEnabled(false); + Comparison.get().numChoices = 0; + for (Comparison.Front front : Comparison.getList()) { + if (front.canBothAddAndReplace) + Comparison.get().numChoices++; + } + Intent intent = new Intent(CompareActivity.this, TreeComparatorActivity.class); + intent.putExtra("posizione", 1); + if (Comparison.get().numChoices > 0) { // Revision request dialog //Dialogo di richiesta revisione + new AlertDialog.Builder(this) + .setTitle(Comparison.get().numChoices == 1 ? getString(R.string.one_update_choice) + : getString(R.string.many_updates_choice, Comparison.get().numChoices)) + .setMessage(R.string.updates_replace_add) + .setPositiveButton(android.R.string.ok, (dialog, id) -> { + Comparison.get().autoContinue = true; + Comparison.get().choicesMade = 1; + startActivity(intent); + }).setNeutralButton(android.R.string.cancel, (dialog, id) -> button2.setEnabled(true)) + .setOnCancelListener(dialog -> button2.setEnabled(true)).show(); + } else { // Start automatically //Avvio in automatico + Comparison.get().autoContinue = true; + startActivity(intent); + } + }); + } else { + button1.setText(R.string.delete_imported_tree); + button1.setOnClickListener(v -> { + TreesActivity.deleteTree(CompareActivity.this, idTree2); + onBackPressed(); + }); + button2.setVisibility(View.GONE); + } + } + + @Override + protected void onRestart() { + super.onRestart(); + findViewById(R.id.compara_bottone2).setEnabled(true); // if possibly(?) //se eventualmente + Comparison.get().autoContinue = false; // It resets it if the automatism(?) was eventually chosen //Lo resetta se eventualmente era stato scelto l'automatismo + } + + /** + * See whether to add the two objects to the list of those to be evaluated + * Vede se aggiungere i due oggetti alla lista di quelli da valutare + */ + private void compare(Object o, Object o2, int type) { + Change c = getChange(o); + Change c2 = getChange(o2); + int modification = 0; + if (o == null && isRecent(c2)) // o2 has been added in the new tree -> ADD + modification = 1; + else { + if (c == null && c2 != null) + modification = 1; + else if (c != null && c2 != null && + !(c.getDateTime().getValue().equals(c2.getDateTime().getValue()) // the two dates must be different + && c.getDateTime().getTime().equals(c2.getDateTime().getTime()))) { + if (isRecent(c) && isRecent(c2)) { // both changed after sharing -> ADD / REPLACE //entrambi modificati dopo la condivisione --> AGGIUNGI/SOSTITUISCI + modification = 2; + } else if (isRecent(c2)) // only o2 has been changed -> REPLACE + modification = 1; + } + } + if (modification > 0) { + Comparison.Front front = Comparison.addFront(o, o2, type); + if (modification == 2) + front.canBothAddAndReplace = true; + } + } + + /** + * Ditto for the remaining objects deleted in the old tree + * Idem per i rimanenti oggetti eliminati nell'albero vecchio + */ + private void reconcile(Object o, Object o2, int type) { + if (o2 == null && !isRecent(getChange(o))) + Comparison.addFront(o, null, type); + } + + /** + * Find if a top-level record has been modified after the date of sharing + * + * @param change Actual change date of the top-level record + * @return true if the record is more recent than the date of sharing + */ + private boolean isRecent(Change change) { + boolean itIs = false; + if (change != null && change.getDateTime() != null) { + try { // TODO with null time(?) //con time null + String zoneId = U.castJsonString(change.getExtension("zone")); + if (zoneId == null) + zoneId = "UTC"; + TimeZone timeZone = TimeZone.getTimeZone(zoneId); + changeDateFormat.setTimeZone(timeZone); + Date recordDate = changeDateFormat.parse(change.getDateTime().getValue() + change.getDateTime().getTime()); + itIs = recordDate.after(sharingDate); + //long oreSfaso = TimeUnit.MILLISECONDS.toMinutes( timeZone.getOffset(dataobject.getTime()) ); + //s.l( dataobject+"\t"+ ok +"\t"+ (oreSfaso>0?"+":"")+oreSfaso +"\t"+ timeZone.getID() ); + } catch (ParseException e) { + } + } + return itIs; + } + + Change getChange(Object ogg) { + Change change = null; + try { + change = (Change) ogg.getClass().getMethod("getChange").invoke(ogg); //TODO change doesn't have this function...? + } catch (Exception e) { + } + return change; + } + + void populateCard(Gedcom gc, int treeId, int cardId) { + CardView card = findViewById(cardId); + Settings.Tree tree = Global.settings.getTree(treeId); + TextView title = card.findViewById(R.id.confronto_titolo); + TextView data = card.findViewById(R.id.confronto_testo); + title.setText(tree.title); + data.setText(TreesActivity.writeData(this, tree)); + if (cardId == R.id.compara_nuovo) { + if (tree.grade == 30) { + card.setCardBackgroundColor(getResources().getColor(R.color.consumed)); + title.setTextColor(getResources().getColor(R.color.grayText)); + data.setTextColor(getResources().getColor(R.color.grayText)); + } else + card.setCardBackgroundColor(getResources().getColor(R.color.evidenziaMedio)); + Submitter submitter = gc.getSubmitter(tree.shares.get(tree.shares.size() - 1).submitter); + StringBuilder txt = new StringBuilder(); + if (submitter != null) { + String name = submitter.getName(); + if (name == null || name.isEmpty()) + name = getString(android.R.string.unknownName); + txt.append(getString(R.string.sent_by, name)).append("\n"); + } + //if( Confronto.getLista().size() > 0 ) + // txt += "Updates:\t"; + for (int i = 7; i > 0; i--) { + txt.append(writeDifferences(i)); + } + if (txt.toString().endsWith("\n")) + txt = new StringBuilder(txt.substring(0, txt.length() - 1)); + ((TextView) card.findViewById(R.id.confronto_sottotesto)).setText(txt.toString()); + card.findViewById(R.id.confronto_sottotesto).setVisibility(View.VISIBLE); + } + card.findViewById(R.id.confronto_data).setVisibility(View.GONE); + } + + int[] singulars = {R.string.shared_note, R.string.submitter, R.string.repository, R.string.shared_media, R.string.source, R.string.person, R.string.family}; + int[] plurals = {R.string.shared_notes, R.string.submitters, R.string.repositories, R.string.shared_medias, R.string.sources, R.string.persons, R.string.families}; + + String writeDifferences(int type) { + int changes = 0; + for (Comparison.Front front : Comparison.getList()) { + if (front.type == type) { + changes++; + } + } + String testo = ""; + if (changes > 0) { + type--; + int definizione = changes == 1 ? singulars[type] : plurals[type]; + testo = "\t\t+" + changes + " " + getString(definizione).toLowerCase() + "\n"; + } + return testo; + } + + @Override + public boolean onOptionsItemSelected(MenuItem i) { + onBackPressed(); + return true; + } + + @Override + public void onBackPressed() { + super.onBackPressed(); + Comparison.reset(); // resets the Comparison singleton + } +} \ No newline at end of file diff --git a/app/src/main/java/app/familygem/Comparison.java b/app/src/main/java/app/familygem/Comparison.java new file mode 100644 index 00000000..5b7a8d12 --- /dev/null +++ b/app/src/main/java/app/familygem/Comparison.java @@ -0,0 +1,72 @@ +package app.familygem; + +import android.app.Activity; + +import java.util.ArrayList; +import java.util.List; + +/** + * Singleton that manages the objects of the 2 Gedcoms during the import of updates + * Singleton che gestisce gli oggetti dei 2 Gedcom durante l'importazione degli aggiornamenti + */ +public class Comparison { + + private static final Comparison comparison = new Comparison(); + private List list = new ArrayList<>(); + boolean autoContinue; // determines whether to automatically accept all updates + int numChoices; // Total choices in case of autoContinue + int choicesMade; // Position in case of autoContinue //Posizione in caso di autoProsegui + + static Comparison get() { + return comparison; + } + + public static List getList() { + return get().list; + } + + static Front addFront(Object object, Object object2, int type) { + Front front = new Front(); + front.object = object; + front.object2 = object2; + front.type = type; + getList().add(front); + return front; + } + + /** + * Returns the currently active front + * */ + static Front getFront(Activity activity) { + return getList().get(activity.getIntent().getIntExtra("posizione", 0) - 1); + } + + /** + * To call when exiting the comparison process + * */ + static void reset() { + getList().clear(); + get().autoContinue = false; + } + + static class Front { + Object object; + Object object2; + int type; // number from 1 to 7 that defines the type: 1 Note -> 7 Family + boolean canBothAddAndReplace; // has the option to add + replace + /** + * what to do with this pair of objects: + * 0 nothing + * 1 object2 is added to the tree + * 2 object2 replaces object + * 3 object is deleted + * + * che fare di questa coppia di oggetti: + * 0 niente + * 1 object2 viene aggiunto ad albero + * 2 object2 sostituisce object + * 3 object viene eliminato + */ + int destiny; + } +} \ No newline at end of file diff --git a/app/src/main/java/app/familygem/Condivisione.java b/app/src/main/java/app/familygem/Condivisione.java deleted file mode 100644 index 46d9dbae..00000000 --- a/app/src/main/java/app/familygem/Condivisione.java +++ /dev/null @@ -1,316 +0,0 @@ -package app.familygem; - -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.Bundle; -import android.view.MenuItem; -import android.view.View; -import android.view.inputmethod.InputMethodManager; -import android.widget.CheckBox; -import android.widget.EditText; -import android.widget.LinearLayout; -import android.widget.TextView; -import android.widget.Toast; -import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.app.AppCompatActivity; -import org.apache.commons.net.ftp.FTP; -import org.apache.commons.net.ftp.FTPClient; -import org.folg.gedcom.model.Gedcom; -import org.folg.gedcom.model.Header; -import org.folg.gedcom.model.Person; -import org.folg.gedcom.model.Submitter; -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileInputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.net.HttpURLConnection; -import java.net.URL; -import java.net.URLEncoder; - -public class Condivisione extends BaseActivity { - - Gedcom gc; - Settings.Tree tree; - Esportatore esporter; - String nomeAutore; - int accessible; // 0 = false, 1 = true - String dataId; - String idAutore; - boolean uploadSuccesso; - - @Override - protected void onCreate(Bundle bundle) { - super.onCreate(bundle); - setContentView(R.layout.condivisione); - - final int treeId = getIntent().getIntExtra("idAlbero", 1); - tree = Global.settings.getTree(treeId); - - // Titolo dell'albero - final EditText editaTitolo = findViewById(R.id.condividi_titolo); - editaTitolo.setText(tree.title); - - if( tree.grade == 10 ) - ((TextView)findViewById( R.id.condividi_tit_autore )).setText( R.string.changes_submitter ); - - esporter = new Esportatore( this ); - esporter.apriAlbero( treeId ); - gc = Global.gc; - if( gc != null ) { - displayShareRoot(); - // Nome autore - final Submitter[] autore = new Submitter[1]; - // albero in Italia con submitter referenziato - if( tree.grade == 0 && gc.getHeader() != null && gc.getHeader().getSubmitter(gc) != null ) - autore[0] = gc.getHeader().getSubmitter( gc ); - // in Italia ci sono autori ma nessuno referenziato, prende l'ultimo - else if( tree.grade == 0 && !gc.getSubmitters().isEmpty() ) - autore[0] = gc.getSubmitters().get(gc.getSubmitters().size()-1); - // in Australia ci sono autori freschi, ne prende uno - else if( tree.grade == 10 && U.autoreFresco(gc) != null ) - autore[0] = U.autoreFresco(gc); - final EditText editaAutore = findViewById(R.id.condividi_autore); - nomeAutore = autore[0] == null ? "" : autore[0].getName(); - editaAutore.setText( nomeAutore ); - - // Display an alert for the acknowledgment of sharing - if( !Global.settings.shareAgreement ) { - new AlertDialog.Builder(this).setTitle(R.string.share_sensitive) - .setMessage(R.string.aware_upload_server) - .setPositiveButton(android.R.string.ok, (dialog, id) -> { - Global.settings.shareAgreement = true; - Global.settings.save(); - }).setNeutralButton(R.string.remind_later, null).show(); - } - - // Raccoglie i dati della condivisione e posta al database - findViewById( R.id.bottone_condividi ).setOnClickListener( v -> { - if( uploadSuccesso ) - concludi(); - else { - if( controlla(editaTitolo, R.string.please_title) || controlla(editaAutore, R.string.please_name) ) - return; - - v.setEnabled(false); - findViewById(R.id.condividi_circolo).setVisibility(View.VISIBLE); - - // Titolo dell'albero - String titoloEditato = editaTitolo.getText().toString(); - if( !tree.title.equals(titoloEditato) ) { - tree.title = titoloEditato; - Global.settings.save(); - } - - // Aggiornamento del submitter - Header header = gc.getHeader(); - if( header == null ) { - header = AlberoNuovo.creaTestata(tree.id + ".json"); - gc.setHeader(header); - } else - header.setDateTime(U.actualDateTime()); - if( autore[0] == null ) { - autore[0] = Podio.nuovoAutore(null); - } - if( header.getSubmitterRef() == null ) { - header.setSubmitterRef(autore[0].getId()); - } - String nomeAutoreEditato = editaAutore.getText().toString(); - if( !nomeAutoreEditato.equals(nomeAutore) ) { - nomeAutore = nomeAutoreEditato; - autore[0].setName(nomeAutore); - U.updateChangeDate(autore[0]); - } - idAutore = autore[0].getId(); - U.saveJson(gc, treeId); // baypassando la preferenza di non salvare in atomatico - - // Tree accessibility for app developer - CheckBox accessibleTree = findViewById(R.id.condividi_allow); - accessible = accessibleTree.isChecked() ? 1 : 0; - - // Invia i dati - if( !BuildConfig.utenteAruba.isEmpty() ) - new PostaDatiCondivisione().execute( this ); - } - }); - } else - findViewById( R.id.condividi_scatola ).setVisibility( View.GONE ); - } - - // The person root of the tree - View rootView; - void displayShareRoot() { - String rootId; - if( tree.shareRoot != null && gc.getPerson(tree.shareRoot) != null ) - rootId = tree.shareRoot; - else if( tree.root != null && gc.getPerson(tree.root) != null ) { - rootId = tree.root; - tree.shareRoot = rootId; // per poter condividere subito l'albero senza cambiare la radice - } else { - rootId = U.trovaRadice(gc); - tree.shareRoot = rootId; - } - Person person = gc.getPerson(rootId); - if( person != null && tree.grade < 10 ) { // viene mostrata solo alla prima condivisione, non al ritorno - LinearLayout rootLayout = findViewById(R.id.condividi_radice); - rootLayout.removeView(rootView); - rootLayout.setVisibility(View.VISIBLE); - rootView = U.linkaPersona(rootLayout, person, 1); - rootView.setOnClickListener(v -> { - Intent intent = new Intent(this, Principal.class); - intent.putExtra("anagrafeScegliParente", true); - startActivityForResult(intent, 5007); - }); - } - } - - // Verifica che un campo sia compilato - boolean controlla(EditText campo, int msg) { - String testo = campo.getText().toString(); - if( testo.isEmpty() ) { - campo.requestFocus(); - InputMethodManager imm = (InputMethodManager) getSystemService( Context.INPUT_METHOD_SERVICE); - imm.showSoftInput(campo, InputMethodManager.SHOW_IMPLICIT); - Toast.makeText(this, msg, Toast.LENGTH_SHORT ).show(); - return true; - } - return false; - } - - // Inserisce il sommario della condivisione nel database di www.familygem.app - // Se tutto va bene crea il file zip con l'albero e le immagini - static class PostaDatiCondivisione extends AsyncTask { - @Override - protected Condivisione doInBackground(Condivisione... contesti) { - Condivisione questo = contesti[0]; - try { - URL url = new URL("https://www.familygem.app/inserisci.php"); - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); - conn.setRequestMethod("POST"); - OutputStream out = new BufferedOutputStream( conn.getOutputStream() ); - BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out, "UTF-8")); - String dati = "password=" + URLEncoder.encode( BuildConfig.passwordAruba, "UTF-8") + - "&titoloAlbero=" + URLEncoder.encode( questo.tree.title, "UTF-8") + - "&nomeAutore=" + URLEncoder.encode( questo.nomeAutore, "UTF-8") + - "&accessibile=" + questo.accessible; - writer.write( dati ); - writer.flush(); - writer.close(); - out.close(); - - // Risposta - BufferedReader lettore = new BufferedReader( new InputStreamReader(conn.getInputStream()) ); - String linea1 = lettore.readLine(); - lettore.close(); - conn.disconnect(); - if( linea1.startsWith("20") ) { - questo.dataId = linea1.replaceAll( "[-: ]", "" ); - Settings.Share share = new Settings.Share( questo.dataId, questo.idAutore ); - questo.tree.aggiungiCondivisione(share); - Global.settings.save(); - } - } catch( Exception e ) { - U.toast( questo, e.getLocalizedMessage() ); - } - return questo; - } - - @Override - protected void onPostExecute(Condivisione questo) { - if( questo.dataId != null && questo.dataId.startsWith("20") ) { - File fileTree = new File( questo.getCacheDir(), questo.dataId + ".zip" ); - if( questo.esporter.esportaBackupZip(questo.tree.shareRoot, 9, Uri.fromFile(fileTree)) ) { - new InvioFTP().execute( questo ); - return; - } else - Toast.makeText( questo, questo.esporter.messaggioErrore, Toast.LENGTH_LONG ).show(); - } - // Un Toast di errore qui sostituirebbe il messaggio di tosta() in catch() - questo.findViewById( R.id.bottone_condividi ).setEnabled(true); - questo.findViewById( R.id.condividi_circolo ).setVisibility(View.INVISIBLE); - } - } - - // Carica in ftp il file zip con l'albero condiviso - static class InvioFTP extends AsyncTask { - protected Condivisione doInBackground(Condivisione... contesti) { - Condivisione questo = contesti[0]; - try { - FTPClient ftpClient = new FTPClient(); - ftpClient.connect( "89.46.104.211", 21 ); - ftpClient.enterLocalPassiveMode(); - ftpClient.login( BuildConfig.utenteAruba, BuildConfig.passwordAruba ); - ftpClient.changeWorkingDirectory("/www.familygem.app/condivisi"); - ftpClient.setFileType( FTP.BINARY_FILE_TYPE ); - BufferedInputStream buffIn; - String nomeZip = questo.dataId + ".zip"; - buffIn = new BufferedInputStream( new FileInputStream( questo.getCacheDir() + "/" + nomeZip ) ); - questo.uploadSuccesso = ftpClient.storeFile( nomeZip, buffIn ); - buffIn.close(); - ftpClient.logout(); - ftpClient.disconnect(); - } catch( Exception e ) { - U.toast( questo, e.getLocalizedMessage() ); - } - return questo; - } - protected void onPostExecute(Condivisione questo) { - if( questo.uploadSuccesso ) { - Toast.makeText( questo, R.string.correctly_uploaded, Toast.LENGTH_SHORT ).show(); - questo.concludi(); - } else { - questo.findViewById( R.id.bottone_condividi ).setEnabled(true); - questo.findViewById( R.id.condividi_circolo ).setVisibility( View.INVISIBLE ); - } - } - } - - // Mostra le app per condividere il link - void concludi() { - Intent intento = new Intent( Intent.ACTION_SEND ); - intento.setType( "text/plain" ); - intento.putExtra( Intent.EXTRA_SUBJECT, getString( R.string.sharing_tree ) ); - intento.putExtra( Intent.EXTRA_TEXT, getString( R.string.click_this_link, - "https://www.familygem.app/share.php?tree=" + dataId ) ); - //startActivity( Intent.createChooser( intento, "Condividi con" ) ); - /* Tornando indietro da una app di messaggistica il requestCode 35417 arriva sempre corretto - Invece il resultCode può essere RESULT_OK o RESULT_CANCELED a capocchia - Ad esempio da Gmail ritorna indietro sempre con RESULT_CANCELED sia che l'email è stata inviata o no - anche inviando un Sms ritorna RESULT_CANCELED anche se l'sms è stato inviato - oppure da Whatsapp è RESULT_OK sia che il messaggio è stato inviato o no - In pratica non c'è modo di sapere se nella app di messaggistica il messaggio è stato inviato */ - startActivityForResult( Intent.createChooser(intento,getText(R.string.share_with)),35417 ); - findViewById( R.id.bottone_condividi ).setEnabled(true); - findViewById( R.id.condividi_circolo ).setVisibility( View.INVISIBLE ); - } - - // Aggiorna le preferenze così da mostrare la nuova radice scelta in Anagrafe - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - if( resultCode == AppCompatActivity.RESULT_OK ) { - if( requestCode == 5007 ) { - tree.shareRoot = data.getStringExtra("idParente"); - Global.settings.save(); - displayShareRoot(); - } - } - // Ritorno indietro da qualsiasi app di condivisione, nella quale il messaggio è stato inviato oppure no - if( requestCode == 35417 ) { - // Todo chiudi tastiera - Toast.makeText(getApplicationContext(), R.string.sharing_completed, Toast.LENGTH_LONG).show(); - } - } - - @Override - public boolean onOptionsItemSelected( MenuItem i ) { - onBackPressed(); - return true; - } -} \ No newline at end of file diff --git a/app/src/main/java/app/familygem/Conferma.java b/app/src/main/java/app/familygem/Conferma.java deleted file mode 100644 index 04bac512..00000000 --- a/app/src/main/java/app/familygem/Conferma.java +++ /dev/null @@ -1,279 +0,0 @@ -// Attività finale all'importazione delle novità in un albero già esistente - -package app.familygem; - -import android.content.Intent; -import android.os.Bundle; -import android.view.MenuItem; -import android.view.View; -import android.widget.TextView; -import androidx.appcompat.app.AlertDialog; -import androidx.cardview.widget.CardView; -import org.apache.commons.io.FileUtils; -import org.folg.gedcom.model.ChildRef; -import org.folg.gedcom.model.Family; -import org.folg.gedcom.model.Media; -import org.folg.gedcom.model.Note; -import org.folg.gedcom.model.ParentFamilyRef; -import org.folg.gedcom.model.Person; -import org.folg.gedcom.model.Repository; -import org.folg.gedcom.model.Source; -import org.folg.gedcom.model.SpouseFamilyRef; -import org.folg.gedcom.model.SpouseRef; -import org.folg.gedcom.model.Submitter; -import org.folg.gedcom.model.Visitable; -import java.io.File; -import java.io.IOException; -import app.familygem.visitor.ContenitoriMedia; -import app.familygem.visitor.ContenitoriNota; -import app.familygem.visitor.ListaCitazioniFonte; -import app.familygem.visitor.ListaMedia; - -public class Conferma extends BaseActivity { - - @Override - protected void onCreate( Bundle bandolo ) { - super.onCreate( bandolo ); - setContentView( R.layout.conferma ); - if( !Confronto.getLista().isEmpty() ) { - - // Albero vecchio - CardView carta = findViewById( R.id.conferma_vecchio ); - Settings.Tree tree = Global.settings.getTree( Global.settings.openTree); - ((TextView)carta.findViewById(R.id.confronto_titolo )).setText( tree.title); - String txt = Alberi.scriviDati( this, tree); - ((TextView)carta.findViewById(R.id.confronto_testo )).setText( txt ); - carta.findViewById( R.id.confronto_data ).setVisibility( View.GONE ); - - int aggiungi = 0; - int sostitui = 0; - int elimina = 0; - for( Confronto.Fronte fronte : Confronto.getLista() ) { - switch( fronte.destino ) { - case 1: aggiungi++; - break; - case 2: sostitui++; - break; - case 3: elimina++; - } - } - String testo = getString( R.string.accepted_news, aggiungi+sostitui+elimina, aggiungi, sostitui, elimina ); - ((TextView)findViewById(R.id.conferma_testo )).setText( testo ); - - findViewById(R.id.conferma_annulla ).setOnClickListener( v -> { - Confronto.reset(); - startActivity( new Intent( Conferma.this, Alberi.class ) ); - }); - - findViewById(R.id.conferma_ok ).setOnClickListener( v -> { - // Modifica l'id e tutti i ref agli oggetti con doppiaOpzione e destino da aggiungere - boolean fattoQualcosa = false; - for( Confronto.Fronte fronte : Confronto.getLista() ) { - if( fronte.doppiaOpzione && fronte.destino == 1 ) { - String idNuovo; - fattoQualcosa = true; - switch( fronte.tipo ) { - case 1: // Note - idNuovo = idMassimo( Note.class ); - Note n2 = (Note) fronte.oggetto2; - new ContenitoriNota( Global.gc2, n2, idNuovo ); // aggiorna tutti i ref alla nota - n2.setId( idNuovo ); // poi aggiorna l'id della nota - break; - case 2: // Submitter - idNuovo = idMassimo( Submitter.class ); - ((Submitter)fronte.oggetto2).setId( idNuovo ); - break; - case 3: // Repository - idNuovo = idMassimo( Repository.class ); - Repository repo2 = (Repository)fronte.oggetto2; - for( Source fon : Global.gc2.getSources() ) - if( fon.getRepositoryRef() != null && fon.getRepositoryRef().getRef().equals(repo2.getId()) ) - fon.getRepositoryRef().setRef( idNuovo ); - repo2.setId( idNuovo ); - break; - case 4: // Media - idNuovo = idMassimo( Media.class ); - Media m2 = (Media) fronte.oggetto2; - new ContenitoriMedia( Global.gc2, m2, idNuovo ); - m2.setId( idNuovo ); - break; - case 5: // Source - idNuovo = idMassimo( Source.class ); - Source s2 = (Source) fronte.oggetto2; - ListaCitazioniFonte citaFonte = new ListaCitazioniFonte( Global.gc2, s2.getId() ); - for( ListaCitazioniFonte.Tripletta tri : citaFonte.lista ) - tri.citazione.setRef( idNuovo ); - s2.setId( idNuovo ); - break; - case 6: // Person - idNuovo = idMassimo( Person.class ); - Person p2 = (Person) fronte.oggetto2; - for( Family fam : Global.gc2.getFamilies() ) { - for( SpouseRef sr : fam.getHusbandRefs() ) - if( sr.getRef().equals(p2.getId()) ) - sr.setRef( idNuovo ); - for( SpouseRef sr : fam.getWifeRefs() ) - if( sr.getRef().equals(p2.getId()) ) - sr.setRef( idNuovo ); - for( ChildRef cr : fam.getChildRefs() ) - if( cr.getRef().equals(p2.getId()) ) - cr.setRef( idNuovo ); - } - p2.setId( idNuovo ); - break; - case 7: // Family - idNuovo = idMassimo( Family.class ); - Family f2 = (Family) fronte.oggetto2; - for( Person per : Global.gc2.getPeople() ) { - for( ParentFamilyRef pfr : per.getParentFamilyRefs() ) - if( pfr.getRef().equals(f2.getId()) ) - pfr.setRef( idNuovo ); - for( SpouseFamilyRef sfr : per.getSpouseFamilyRefs() ) - if( sfr.getRef().equals(f2.getId()) ) - sfr.setRef( idNuovo ); - } - f2.setId( idNuovo ); - } - } - } - if( fattoQualcosa ) - U.saveJson( Global.gc2, Global.treeId2); - - // La regolare aggiunta/sostituzione/eliminazione dei record da albero2 ad albero - for( Confronto.Fronte fronte : Confronto.getLista() ) { - switch( fronte.tipo ) { - case 1: // Nota - if( fronte.destino > 1 ) - Global.gc.getNotes().remove( fronte.oggetto ); - if( fronte.destino > 0 && fronte.destino < 3 ) { - Global.gc.addNote( (Note) fronte.oggetto2 ); - copiaTuttiFile( fronte.oggetto2 ); - } - break; - case 2: // Submitter - if( fronte.destino > 1 ) - Global.gc.getSubmitters().remove( fronte.oggetto ); - if( fronte.destino > 0 && fronte.destino < 3 ) - Global.gc.addSubmitter( (Submitter) fronte.oggetto2 ); - break; - case 3: // Repository - if( fronte.destino > 1 ) - Global.gc.getRepositories().remove( fronte.oggetto ); - if( fronte.destino > 0 && fronte.destino < 3 ) { - Global.gc.addRepository( (Repository) fronte.oggetto2 ); - copiaTuttiFile( fronte.oggetto2 ); - } - break; - case 4: // Media - if( fronte.destino > 1 ) - Global.gc.getMedia().remove( fronte.oggetto ); - if( fronte.destino > 0 && fronte.destino < 3 ) { - Global.gc.addMedia( (Media) fronte.oggetto2 ); - vediSeCopiareFile( (Media)fronte.oggetto2 ); - } - break; - case 5: // Source - if( fronte.destino > 1 ) - Global.gc.getSources().remove( fronte.oggetto ); - if( fronte.destino > 0 && fronte.destino < 3 ) { - Global.gc.addSource( (Source) fronte.oggetto2 ); - copiaTuttiFile( fronte.oggetto2 ); - } - break; - case 6: // Person - if( fronte.destino > 1 ) - Global.gc.getPeople().remove( fronte.oggetto ); - if( fronte.destino > 0 && fronte.destino < 3 ) { - Global.gc.addPerson( (Person) fronte.oggetto2 ); - copiaTuttiFile( fronte.oggetto2 ); - } - break; - case 7: // Family - if( fronte.destino > 1 ) - Global.gc.getFamilies().remove( fronte.oggetto ); - if( fronte.destino > 0 && fronte.destino < 3 ) { - Global.gc.addFamily( (Family) fronte.oggetto2 ); - copiaTuttiFile( fronte.oggetto2 ); - } - } - } - U.saveJson( Global.gc, Global.settings.openTree); - - // Se ha fatto tutto propone di eliminare l'albero importato - boolean tuttiOk = true; - for( Confronto.Fronte fron : Confronto.getLista() ) - if( fron.destino == 0 ) { - tuttiOk = false; - break; - } - if( tuttiOk ) { - Global.settings.getTree( Global.treeId2).grade = 30; - Global.settings.save(); - new AlertDialog.Builder( Conferma.this ) - .setMessage( R.string.all_imported_delete ) - .setPositiveButton( android.R.string.ok, (d, i) -> { - Alberi.deleteTree( this, Global.treeId2); - concludi(); - }).setNegativeButton( R.string.no, (d, i) -> concludi() ) - .setOnCancelListener( dialog -> concludi() ).show(); - } else - concludi(); - }); - } else onBackPressed(); - } - - // Apre l'elenco degli alberi - void concludi() { - Confronto.reset(); - startActivity( new Intent( this, Alberi.class ) ); - } - - // Calcola l'id più alto per una certa classe confrontando albero nuovo e vecchio - String idMassimo( Class classe ) { - String id = U.nuovoId( Global.gc, classe ); // id nuovo rispetto ai record dell'albero vecchio - String id2 = U.nuovoId( Global.gc2, classe ); // e dell'albero nuovo - if( Integer.valueOf( id.substring(1) ) > Integer.valueOf( id2.substring(1) ) ) // toglie la lettera iniziale - return id; - else - return id2; - } - - // Se un oggetto nuovo ha dei media, valuta se copiare i file nella cartella immagini dell'albero vecchio - // comunque aggiorna il collegamento nel Media - void copiaTuttiFile( Object oggetto ) { - ListaMedia cercaMedia = new ListaMedia( Global.gc2, 2 ); - ((Visitable)oggetto).accept( cercaMedia ); - for( Media media : cercaMedia.lista ) { - vediSeCopiareFile( media ); - } - } - void vediSeCopiareFile( Media media ) { - String origine = F.percorsoMedia( Global.treeId2, media ); - if( origine != null ) { - File fileOrigine = new File( origine ); - File dirMemoria = getExternalFilesDir( String.valueOf(Global.settings.openTree) ); // dovrebbe stare fuori dal loop ma vabè - String nomeFile = origine.substring( origine.lastIndexOf('/') + 1 ); - File fileGemello = new File( dirMemoria.getAbsolutePath(), nomeFile ); - if( fileGemello.isFile() // se il file corrispondente esiste già - && fileGemello.lastModified() == fileOrigine.lastModified() // e hanno la stessa data - && fileGemello.length() == fileOrigine.length() ) { // e la stessa dimensione - // Allora utilizza il file già esistente - media.setFile( fileGemello.getAbsolutePath() ); - } else { // Altrimenti copia il file nuovo - File fileDestinazione = F.fileNomeProgressivo( dirMemoria.getAbsolutePath(), nomeFile ); - try { - FileUtils.copyFile( fileOrigine, fileDestinazione ); - } catch( IOException e ) { - e.printStackTrace(); - } - media.setFile( fileDestinazione.getAbsolutePath() ); - } - } - } - - @Override - public boolean onOptionsItemSelected( MenuItem i ) { - onBackPressed(); - return true; - } -} \ No newline at end of file diff --git a/app/src/main/java/app/familygem/ConfirmationActivity.java b/app/src/main/java/app/familygem/ConfirmationActivity.java new file mode 100644 index 00000000..760aaf31 --- /dev/null +++ b/app/src/main/java/app/familygem/ConfirmationActivity.java @@ -0,0 +1,291 @@ +package app.familygem; + +import android.content.Intent; +import android.os.Bundle; +import android.view.MenuItem; +import android.view.View; +import android.widget.TextView; +import androidx.appcompat.app.AlertDialog; +import androidx.cardview.widget.CardView; +import org.apache.commons.io.FileUtils; +import org.folg.gedcom.model.ChildRef; +import org.folg.gedcom.model.Family; +import org.folg.gedcom.model.Media; +import org.folg.gedcom.model.Note; +import org.folg.gedcom.model.ParentFamilyRef; +import org.folg.gedcom.model.Person; +import org.folg.gedcom.model.Repository; +import org.folg.gedcom.model.Source; +import org.folg.gedcom.model.SpouseFamilyRef; +import org.folg.gedcom.model.SpouseRef; +import org.folg.gedcom.model.Submitter; +import org.folg.gedcom.model.Visitable; +import java.io.File; +import java.io.IOException; +import app.familygem.visitor.MediaContainers; +import app.familygem.visitor.NoteContainers; +import app.familygem.visitor.ListOfSourceCitations; +import app.familygem.visitor.MediaList; + +/** + * Final activity when importing news in an existing tree + * */ +public class ConfirmationActivity extends BaseActivity { + + @Override + protected void onCreate( Bundle bundle ) { + super.onCreate( bundle ); + setContentView( R.layout.conferma ); + if( !Comparison.getList().isEmpty() ) { + + // Old tree + CardView card = findViewById( R.id.conferma_vecchio ); + Settings.Tree tree = Global.settings.getTree( Global.settings.openTree); + ((TextView)card.findViewById(R.id.confronto_titolo )).setText( tree.title); + String txt = TreesActivity.writeData( this, tree); + ((TextView)card.findViewById(R.id.confronto_testo )).setText( txt ); + card.findViewById( R.id.confronto_data ).setVisibility( View.GONE ); + + int add = 0; + int replace = 0; + int delete = 0; + for( Comparison.Front front : Comparison.getList() ) { + switch( front.destiny) { + case 1: add++; + break; + case 2: replace++; + break; + case 3: delete++; + } + } + String text = getString( R.string.accepted_news, add+replace+delete, add, replace, delete ); + ((TextView)findViewById(R.id.conferma_testo )).setText( text ); + + findViewById(R.id.conferma_annulla ).setOnClickListener( v -> { + Comparison.reset(); + startActivity( new Intent( ConfirmationActivity.this, TreesActivity.class ) ); + }); + + findViewById(R.id.conferma_ok ).setOnClickListener( v -> { + //Change the id and all refs to objects with canBothAddAndReplace and destiny to add // Modifica l'id e tutti i ref agli oggetti con doppiaOpzione e destino da aggiungere + boolean changed = false; + for( Comparison.Front front : Comparison.getList() ) { + if( front.canBothAddAndReplace && front.destiny == 1 ) { + String newID; + changed = true; + switch( front.type) { + case 1: // Note + newID = maxID( Note.class ); + Note n2 = (Note) front.object2; + new NoteContainers( Global.gc2, n2, newID ); // updates all refs to the note + n2.setId( newID ); // then update the note id + break; + case 2: // Submitter + newID = maxID( Submitter.class ); + ((Submitter)front.object2).setId( newID ); + break; + case 3: // Repository + newID = maxID( Repository.class ); + Repository repo2 = (Repository)front.object2; + for( Source fon : Global.gc2.getSources() ) + if( fon.getRepositoryRef() != null && fon.getRepositoryRef().getRef().equals(repo2.getId()) ) + fon.getRepositoryRef().setRef( newID ); + repo2.setId( newID ); + break; + case 4: // Media + newID = maxID( Media.class ); + Media m2 = (Media) front.object2; + new MediaContainers( Global.gc2, m2, newID ); + m2.setId( newID ); + break; + case 5: // Source + newID = maxID( Source.class ); + Source s2 = (Source) front.object2; + ListOfSourceCitations sourceCitations = new ListOfSourceCitations( Global.gc2, s2.getId() ); + for( ListOfSourceCitations.Triplet tri : sourceCitations.list) + tri.citation.setRef( newID ); + s2.setId( newID ); + break; + case 6: // Person + newID = maxID( Person.class ); + Person p2 = (Person) front.object2; + for( Family fam : Global.gc2.getFamilies() ) { + for( SpouseRef sr : fam.getHusbandRefs() ) + if( sr.getRef().equals(p2.getId()) ) + sr.setRef( newID ); + for( SpouseRef sr : fam.getWifeRefs() ) + if( sr.getRef().equals(p2.getId()) ) + sr.setRef( newID ); + for( ChildRef cr : fam.getChildRefs() ) + if( cr.getRef().equals(p2.getId()) ) + cr.setRef( newID ); + } + p2.setId( newID ); + break; + case 7: // Family + newID = maxID( Family.class ); + Family f2 = (Family) front.object2; + for( Person per : Global.gc2.getPeople() ) { + for( ParentFamilyRef pfr : per.getParentFamilyRefs() ) + if( pfr.getRef().equals(f2.getId()) ) + pfr.setRef( newID ); + for( SpouseFamilyRef sfr : per.getSpouseFamilyRefs() ) + if( sfr.getRef().equals(f2.getId()) ) + sfr.setRef( newID ); + } + f2.setId( newID ); + } + } + } + if( changed ) + U.saveJson( Global.gc2, Global.treeId2); + + // Regular addition / replacement / deletion of records from tree2 to tree + for( Comparison.Front front : Comparison.getList() ) { + switch( front.type) { + case 1: // Note + if( front.destiny > 1 ) + Global.gc.getNotes().remove( front.object ); + if( front.destiny > 0 && front.destiny < 3 ) { + Global.gc.addNote( (Note) front.object2 ); + copyAllFiles( front.object2 ); + } + break; + case 2: // Submitter + if( front.destiny > 1 ) + Global.gc.getSubmitters().remove( front.object ); + if( front.destiny > 0 && front.destiny < 3 ) + Global.gc.addSubmitter( (Submitter) front.object2 ); + break; + case 3: // Repository + if( front.destiny > 1 ) + Global.gc.getRepositories().remove( front.object ); + if( front.destiny > 0 && front.destiny < 3 ) { + Global.gc.addRepository( (Repository) front.object2 ); + copyAllFiles( front.object2 ); + } + break; + case 4: // Media + if( front.destiny > 1 ) + Global.gc.getMedia().remove( front.object ); + if( front.destiny > 0 && front.destiny < 3 ) { + Global.gc.addMedia( (Media) front.object2 ); + checkIfShouldCopyFiles( (Media)front.object2 ); + } + break; + case 5: // Source + if( front.destiny > 1 ) + Global.gc.getSources().remove( front.object ); + if( front.destiny > 0 && front.destiny < 3 ) { + Global.gc.addSource( (Source) front.object2 ); + copyAllFiles( front.object2 ); + } + break; + case 6: // Person + if( front.destiny > 1 ) + Global.gc.getPeople().remove( front.object ); + if( front.destiny > 0 && front.destiny < 3 ) { + Global.gc.addPerson( (Person) front.object2 ); + copyAllFiles( front.object2 ); + } + break; + case 7: // Family + if( front.destiny > 1 ) + Global.gc.getFamilies().remove( front.object ); + if( front.destiny > 0 && front.destiny < 3 ) { + Global.gc.addFamily( (Family) front.object2 ); + copyAllFiles( front.object2 ); + } + } + } + U.saveJson( Global.gc, Global.settings.openTree); + + // If he has done everything he proposes to delete the imported tree (?)//Se ha fatto tutto propone di eliminare l'albero importato + boolean allOK = true; + for( Comparison.Front front : Comparison.getList() ) + if( front.destiny == 0 ) { + allOK = false; + break; + } + if( allOK ) { + Global.settings.getTree( Global.treeId2).grade = 30; + Global.settings.save(); + new AlertDialog.Builder( ConfirmationActivity.this ) + .setMessage( R.string.all_imported_delete ) + .setPositiveButton( android.R.string.ok, (d, i) -> { + TreesActivity.deleteTree( this, Global.treeId2); + done(); + }).setNegativeButton( R.string.no, (d, i) -> done() ) + .setOnCancelListener( dialog -> done() ).show(); + } else + done(); + }); + } else onBackPressed(); + } + + /** + * Opens the tree list + * */ + void done() { + Comparison.reset(); + startActivity( new Intent( this, TreesActivity.class ) ); + } + + /** + * Calculate the highest id for a certain class by comparing new and old tree + * Calcola l'id più alto per una certa classe confrontando albero nuovo e vecchio + * */ + String maxID(Class classe ) { + String id = U.newID( Global.gc, classe ); // new id against old tree records + String id2 = U.newID( Global.gc2, classe ); // and of the new tree + if( Integer.parseInt( id.substring(1) ) > Integer.parseInt( id2.substring(1) ) ) // removes the initial letter + return id; + else + return id2; + } + + /** + * If a new object has media, consider copying the files to the old tree image folder + * still update the link in the Media + * + * Se un object nuovo ha dei media, valuta se copiare i file nella cartella immagini dell'albero vecchio + * comunque + * aggiorna il collegamento nel Media + * */ + void copyAllFiles(Object object ) { + MediaList searchMedia = new MediaList( Global.gc2, 2 ); + ((Visitable)object).accept( searchMedia ); + for( Media media : searchMedia.list) { + checkIfShouldCopyFiles( media ); + } + } + void checkIfShouldCopyFiles(Media media ) { + String path = F.mediaPath( Global.treeId2, media ); + if( path != null ) { + File filePath = new File( path ); + File memoryDir = getExternalFilesDir( String.valueOf(Global.settings.openTree) ); // it should stay out of the loop but oh well //dovrebbe stare fuori dal loop ma vabè + String nameFile = path.substring( path.lastIndexOf('/') + 1 ); + File twinFile = new File( memoryDir.getAbsolutePath(), nameFile ); + if( twinFile.isFile() // if the corresponding file already exists + && twinFile.lastModified() == filePath.lastModified() // and have the same date + && twinFile.length() == filePath.length() ) { // and the same size + // Then use the already existing file + media.setFile( twinFile.getAbsolutePath() ); + } else { // Otherwise copy the new file + File destinationFile = F.nextAvailableFileName( memoryDir.getAbsolutePath(), nameFile ); + try { + FileUtils.copyFile( filePath, destinationFile ); + } catch( IOException e ) { + e.printStackTrace(); + } + media.setFile( destinationFile.getAbsolutePath() ); + } + } + } + + @Override + public boolean onOptionsItemSelected( MenuItem i ) { + onBackPressed(); + return true; + } +} \ No newline at end of file diff --git a/app/src/main/java/app/familygem/Confrontatore.java b/app/src/main/java/app/familygem/Confrontatore.java deleted file mode 100644 index 908e7a36..00000000 --- a/app/src/main/java/app/familygem/Confrontatore.java +++ /dev/null @@ -1,247 +0,0 @@ -// Activity per valutare un record dell'albero importato, con eventuale confronto col corrispondente record dell'albero vecchio - -package app.familygem; - -import android.content.Intent; -import android.os.Bundle; -import android.util.TypedValue; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Button; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.ProgressBar; -import android.widget.TextView; -import androidx.appcompat.content.res.AppCompatResources; -import androidx.cardview.widget.CardView; -import org.folg.gedcom.model.Change; -import org.folg.gedcom.model.Family; -import org.folg.gedcom.model.Gedcom; -import org.folg.gedcom.model.Media; -import org.folg.gedcom.model.Note; -import org.folg.gedcom.model.Person; -import org.folg.gedcom.model.Repository; -import org.folg.gedcom.model.Source; -import org.folg.gedcom.model.Submitter; - -public class Confrontatore extends BaseActivity { - - Class classe; // la classe dominante dell'attività - int destino; - - @Override - protected void onCreate( Bundle bandolo ) { - super.onCreate( bandolo ); - setContentView( R.layout.confronto ); - - if( Confronto.getLista().size() > 0 ) { - - int max; - int posizione; - if( Confronto.get().autoProsegui ) { - max = Confronto.get().quanteScelte; - posizione = Confronto.get().scelteFatte; - } else { - max = Confronto.getLista().size(); - posizione = getIntent().getIntExtra("posizione",0); - } - ProgressBar barra = findViewById( R.id.confronto_progresso ); - barra.setMax( max ); - barra.setProgress( posizione ); - ((TextView)findViewById( R.id.confronto_stato )).setText( posizione+"/"+max ); - - final Object o = Confronto.getFronte(this).oggetto; - final Object o2 = Confronto.getFronte(this).oggetto2; - if( o != null ) classe = o.getClass(); - else classe = o2.getClass(); - arredaScheda( Global.gc, R.id.confronto_vecchio, o ); - arredaScheda( Global.gc2, R.id.confronto_nuovo, o2 ); - - destino = 2; - - Button bottoneOk = findViewById(R.id.confronto_bottone_ok); - bottoneOk.setBackground( AppCompatResources.getDrawable(getApplicationContext(),R.drawable.frecciona) ); - if( o == null ) { - destino = 1; - bottoneOk.setText( R.string.add ); - bottoneOk.setBackgroundColor( 0xff00dd00 ); // getResources().getColor(R.color.evidenzia) - bottoneOk.setHeight( 30 ); // inefficace - } else if( o2 == null ) { - destino = 3; - bottoneOk.setText( R.string.delete ); - bottoneOk.setBackgroundColor( 0xffff0000 ); - } else if( Confronto.getFronte(this).doppiaOpzione ) { - // Altro bottone Aggiungi - Button bottoneAggiungi = new Button( this ); - bottoneAggiungi.setTextSize( TypedValue.COMPLEX_UNIT_SP,16 ); - bottoneAggiungi.setTextColor( 0xFFFFFFFF ); - LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( LinearLayout.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT ); - params.rightMargin = 15; - params.weight = 3; - bottoneAggiungi.setLayoutParams( params ); - bottoneAggiungi.setText( R.string.add ); - bottoneAggiungi.setBackgroundColor( 0xff00dd00 ); - bottoneAggiungi.setOnClickListener( v -> { - Confronto.getFronte(this).destino = 1; - vaiAvanti(); - }); - (( LinearLayout)findViewById( R.id.confronto_bottoni )).addView( bottoneAggiungi, 1 ); - } - - // Prosegue in automatico se non c'è una doppia azione da scegliere - if( Confronto.get().autoProsegui && !Confronto.getFronte(this).doppiaOpzione ) { - Confronto.getFronte(this).destino = destino; - vaiAvanti(); - } - - // Bottone per accettare la novità - bottoneOk.setOnClickListener( vista -> { - Confronto.getFronte(this).destino = destino; - vaiAvanti(); - }); - - findViewById(R.id.confronto_bottone_ignora ).setOnClickListener( v -> { - Confronto.getFronte(this).destino = 0; - vaiAvanti(); - }); - } else - onBackPressed(); // Ritorna a Compara - } - - void arredaScheda( Gedcom gc, int idScheda, Object o ) { - String tit = ""; - String txt = ""; - String data = ""; - CardView carta = findViewById(idScheda); - ImageView vistaFoto = carta.findViewById( R.id.confronto_foto ); - if( o instanceof Note ) { - tipoRecord( R.string.shared_note ); - Note n = (Note) o; - txt = n.getValue(); - data = dataOra( n.getChange() ); - } - else if( o instanceof Submitter ) { - tipoRecord( R.string.submitter ); - Submitter s = (Submitter) o; - tit = s.getName(); - if( s.getEmail() != null ) txt += s.getEmail() + "\n"; - if( s.getAddress() != null ) txt += Dettaglio.writeAddress(s.getAddress(), true); - data = dataOra(s.getChange()); - } - else if( o instanceof Repository ) { - tipoRecord( R.string.repository ); - Repository r = (Repository) o; - tit = r.getName(); - if( r.getAddress() != null ) txt += Dettaglio.writeAddress(r.getAddress(), true) + "\n"; - if( r.getEmail() != null ) txt += r.getEmail(); - data = dataOra(r.getChange()); - } - else if( o instanceof Media ) { - tipoRecord( R.string.shared_media ); - Media m = (Media) o; - if(m.getTitle()!=null) tit = m.getTitle(); - txt = m.getFile(); - data = dataOra( m.getChange() ); - vistaFoto.setVisibility( View.VISIBLE ); - F.dipingiMedia( m, vistaFoto, null ); - } - else if( o instanceof Source ) { - tipoRecord( R.string.source ); - Source f = (Source) o; - if(f.getTitle()!=null) tit = f.getTitle(); - else if(f.getAbbreviation()!=null) tit = f.getAbbreviation(); - if(f.getAuthor()!=null) txt = f.getAuthor()+"\n"; - if(f.getPublicationFacts()!=null) txt += f.getPublicationFacts()+"\n"; - if(f.getText()!=null) txt += f.getText(); - data = dataOra( f.getChange() ); - } - else if( o instanceof Person ) { - tipoRecord( R.string.person ); - Person p = (Person) o; - tit = U.epiteto( p ); - txt = U.details( p, null ); - data = dataOra( p.getChange() ); - vistaFoto.setVisibility( View.VISIBLE ); - F.unaFoto( gc, p, vistaFoto ); - } - else if( o instanceof Family ) { - tipoRecord( R.string.family ); - Family f = (Family) o; - txt = U.testoFamiglia( this, gc, f, false ); - data = dataOra( f.getChange() ); - } - TextView testoTitolo = carta.findViewById( R.id.confronto_titolo ); - if( tit == null || tit.isEmpty() ) - testoTitolo.setVisibility( View.GONE ); - else - testoTitolo.setText( tit ); - - TextView testoTesto = carta.findViewById( R.id.confronto_testo ); - if( txt.isEmpty() ) - testoTesto.setVisibility( View.GONE ); - else { - if( txt.endsWith( "\n" ) ) - txt = txt.substring( 0, txt.length() - 1 ); - testoTesto.setText( txt ); - } - - View vistaCambi = carta.findViewById(R.id.confronto_data); - if( data.isEmpty() ) - vistaCambi.setVisibility(View.GONE); - else - ((TextView)vistaCambi.findViewById(R.id.cambi_testo)).setText(data); - - if( idScheda == R.id.confronto_nuovo ) { - carta.setCardBackgroundColor(getResources().getColor(R.color.evidenziaMedio)); - } - - if( tit.isEmpty() && txt.isEmpty() && data.isEmpty() ) // todo intendi oggetto null? - carta.setVisibility( View.GONE ); - } - - // Titolo della pagina - void tipoRecord( int string ) { - TextView testoTipo = findViewById( R.id.confronto_tipo ); - testoTipo.setText( getString(string) ); - } - - String dataOra( Change cambi ) { - String dataOra = ""; - if( cambi != null ) - dataOra = cambi.getDateTime().getValue() + " - " + cambi.getDateTime().getTime(); - return dataOra; - } - - void vaiAvanti() { - Intent intent = new Intent(); - if( getIntent().getIntExtra("posizione",0) == Confronto.getLista().size() ) { - // Terminati i confronti - intent.setClass( this, Conferma.class ); - } else { - // Prossimo confronto - intent.setClass( this, Confrontatore.class ); - intent.putExtra( "posizione", getIntent().getIntExtra("posizione",0) + 1 ); - } - if( Confronto.get().autoProsegui ) { - if( Confronto.getFronte(this).doppiaOpzione ) - Confronto.get().scelteFatte++; - else - finish(); // rimuove il fronte attuale dallo stack - } - startActivity( intent ); - } - - @Override - public boolean onOptionsItemSelected( MenuItem i ) { - onBackPressed(); - return true; - } - - @Override - public void onBackPressed() { - super.onBackPressed(); - if( Confronto.get().autoProsegui ) - Confronto.get().scelteFatte--; - } -} \ No newline at end of file diff --git a/app/src/main/java/app/familygem/Confronto.java b/app/src/main/java/app/familygem/Confronto.java deleted file mode 100644 index 0b76e16c..00000000 --- a/app/src/main/java/app/familygem/Confronto.java +++ /dev/null @@ -1,58 +0,0 @@ -// Singleton che gestisce gli oggetti dei 2 Gedcom durante l'importazione degli aggiornamenti - -package app.familygem; - -import android.app.Activity; -import java.util.ArrayList; -import java.util.List; - -public class Confronto { - - private static final Confronto confronto = new Confronto(); - private List lista = new ArrayList<>(); - boolean autoProsegui; // stabilisce se accettare automaticamente tutti gli aggiornamenti - int quanteScelte; // Scelte totali in caso di autoProsegui - int scelteFatte; // Posizione in caso di autoProsegui - - static Confronto get() { - return confronto; - } - - public static List getLista() { - return get().lista; - } - - static Fronte addFronte( Object oggetto, Object oggetto2, int tipo ) { - Fronte fronte = new Fronte(); - fronte.oggetto = oggetto; - fronte.oggetto2 = oggetto2; - fronte.tipo = tipo; - getLista().add( fronte ); - return fronte; - } - - // Restituisce il fronte attualmente attivo - static Fronte getFronte(Activity attivita) { - return getLista().get( attivita.getIntent().getIntExtra("posizione",0) - 1 ); - } - - // Da chiamare quando si esce dal processo di confronto - static void reset() { - getLista().clear(); - get().autoProsegui = false; - } - - static class Fronte { - Object oggetto; - Object oggetto2; - int tipo; // numero da 1 a 7 che definisce il tipo: 1 Nota -> 7 Famiglia - boolean doppiaOpzione; // ha la possibilità di aggiungi + sostituisci - /* - che fare di questa coppia di oggetti: - 0 niente - 1 oggetto2 viene aggiunto ad albero - 2 oggetto2 sostituisce oggetto - 3 oggetto viene eliminato */ - int destino; - } -} \ No newline at end of file diff --git a/app/src/main/java/app/familygem/Dettaglio.java b/app/src/main/java/app/familygem/DetailActivity.java similarity index 54% rename from app/src/main/java/app/familygem/Dettaglio.java rename to app/src/main/java/app/familygem/DetailActivity.java index 773d5f9c..1cfc9dac 100644 --- a/app/src/main/java/app/familygem/Dettaglio.java +++ b/app/src/main/java/app/familygem/DetailActivity.java @@ -7,6 +7,8 @@ import android.os.Bundle; import com.google.android.flexbox.FlexboxLayout; import com.google.android.material.floatingactionbutton.FloatingActionButton; + +import androidx.annotation.NonNull; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; @@ -56,42 +58,42 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; -import app.familygem.detail.CitazioneFonte; -import app.familygem.detail.Estensione; -import app.familygem.detail.Evento; -import app.familygem.detail.Famiglia; -import app.familygem.detail.Immagine; -import app.familygem.detail.Indirizzo; -import app.familygem.detail.Nota; -import app.familygem.visitor.TrovaPila; +import app.familygem.detail.SourceCitationActivity; +import app.familygem.detail.ExtensionActivity; +import app.familygem.detail.EventActivity; +import app.familygem.detail.FamilyActivity; +import app.familygem.detail.ImageActivity; +import app.familygem.detail.AddressActivity; +import app.familygem.detail.NoteActivity; +import app.familygem.visitor.FindStack; import static app.familygem.Global.gc; -public class Dettaglio extends AppCompatActivity { +public class DetailActivity extends AppCompatActivity { public LinearLayout box; public Object object; // Name Media SourceCitation ecc. List eggs = new ArrayList<>(); // List of all the possible editable pieces - List> otherEvents; // Eventi per il FAB di Famiglia - public Person unRappresentanteDellaFamiglia; // una Person della Famiglia per nascondere nel FAB 'Collega persona' - EditoreData editoreData; + List> otherEvents; // Events for the Family FAB + public Person aRepresentativeOfTheFamily; // a family Person to hide in the FAB 'Colleague person' TODO what? Original: una Persona della Famiglia per nascondere nel FAB 'Collega persona' + PublisherDateLinearLayout publisherDateLinearLayout; FloatingActionButton fab; ActionBar actionBar; @Override protected void onCreate(Bundle bundle) { super.onCreate(bundle); - setContentView(R.layout.dettaglio); + setContentView(R.layout.activity_detail); getSupportActionBar().setDisplayHomeAsUpEnabled(true); - box = findViewById(R.id.dettaglio_scatola); + box = findViewById(R.id.detail_box); fab = findViewById(R.id.fab); actionBar = getSupportActionBar(); - U.gedcomSicuro(gc); + U.ensureGlobalGedcomNotNull(gc); - object = Memoria.getOggetto(); + object = Memory.getObject(); if( object == null ) { - onBackPressed(); // salta tutti gli altri dettagli senza oggetto + onBackPressed(); // skip all other details without object } else - impagina(); + format(); // List of other events String[] otherEventTags = {"ANUL", "CENS", "DIVF", "ENGA", "MARB", "MARC", "MARL", "MARS", "RESI", "EVEN", "NCHI"}; @@ -111,108 +113,108 @@ protected void onCreate(Bundle bundle) { PopupMenu popup = menuFAB(view); popup.show(); popup.setOnMenuItemClickListener(item -> { - // FAB + mette un nuovo uovo e lo rende subito editabile + // FAB + puts a new egg and makes it immediately editable int id = item.getItemId(); boolean toBeSaved = false; if( id < 100 ) { - Object coso = eggs.get(id).yolk; - if( coso instanceof Address ) { // coso è un new Address() + Object thing = eggs.get(id).yolk; + if( thing instanceof Address ) { // thing is a new Address() if( object instanceof EventFact ) - ((EventFact)object).setAddress( (Address)coso ); + ((EventFact)object).setAddress( (Address)thing ); else if( object instanceof Submitter ) - ((Submitter)object).setAddress( (Address)coso ); + ((Submitter)object).setAddress( (Address)thing ); else if( object instanceof Repository ) - ((Repository)object).setAddress( (Address)coso ); + ((Repository)object).setAddress( (Address)thing ); } - // Tag necessari per poi esportare in Gedcom - if( object instanceof Name && coso.equals("Type") ) { + // Tags needed to then export to Gedcom + if( object instanceof Name && thing.equals("Type") ) { ((Name)object).setTypeTag("TYPE"); } else if( object instanceof Repository ) { - if( coso.equals("Www") ) + if( thing.equals("Www") ) ((Repository)object).setWwwTag("WWW"); - if( coso.equals("Email") ) + if( thing.equals("Email") ) ((Repository)object).setEmailTag("EMAIL"); } else if( object instanceof Submitter ) { - if( coso.equals("Www") ) + if( thing.equals("Www") ) ((Submitter)object).setWwwTag("WWW"); - if( coso.equals("Email") ) + if( thing.equals("Email") ) ((Submitter)object).setEmailTag("EMAIL"); } - View pezzo = placePiece(eggs.get(id).title, "", coso, eggs.get(id).multiLine); - if( coso instanceof String ) - edit(pezzo); - // todo : aprire Address nuovo per editarlo - } else if( id == 101 ) { - Magazzino.newRepository(this, (Source)object); + View piece = placePiece(eggs.get(id).title, "", thing, eggs.get(id).multiLine); + if( thing instanceof String ) + edit(piece); + // TODO open new Address ("to edit it"?)["for editing"?] //aprire Address nuovo per editarlo + } else if( id == 101 ) {//TODO code smell: use of magic numbers + RepositoriesFragment.newRepository(this, (Source)object); } else if( id == 102 ) { - Intent intento = new Intent(this, Principal.class); - intento.putExtra("magazzinoScegliArchivio", true); - startActivityForResult(intento, 4562); - } else if( id == 103 ) { // Nuova nota + Intent intent = new Intent(this, Principal.class); + intent.putExtra("magazzinoScegliArchivio", true); //TODO translate + startActivityForResult(intent, 4562); + } else if( id == 103 ) { // New note Note note = new Note(); note.setValue(""); ((NoteContainer)object).addNote(note); - Memoria.aggiungi(note); - startActivity(new Intent(this, Nota.class)); + Memory.add(note); + startActivity(new Intent(this, NoteActivity.class)); toBeSaved = true; - } else if( id == 104 ) { // Nuova nota condivisa - Quaderno.newNote(this, object); - } else if( id == 105 ) { // Collega nota condivisa - Intent intento = new Intent(this, Principal.class); - intento.putExtra("quadernoScegliNota", true); - startActivityForResult(intento, 7074); - } else if( id == 106 ) { // Cerca media locale - F.appAcquisizioneImmagine(this, null, 4173, (MediaContainer)object); - } else if( id == 107 ) { // Cerca media condiviso - F.appAcquisizioneImmagine(this, null, 4174, (MediaContainer)object); - } else if( id == 108 ) { // Collega media condiviso - Intent inten = new Intent(this, Principal.class); - inten.putExtra("galleriaScegliMedia", true); - startActivityForResult(inten, 43616); - } else if( id == 109 ) { // Nuova fonte-nota - SourceCitation citaz = new SourceCitation(); - citaz.setValue(""); - if( object instanceof Note ) ((Note)object).addSourceCitation(citaz); - else ((SourceCitationContainer)object).addSourceCitation(citaz); - Memoria.aggiungi(citaz); - startActivity(new Intent(this, CitazioneFonte.class)); + } else if( id == 104 ) { // New shared note + NotebookFragment.newNote(this, object); + } else if( id == 105 ) { // Link shared note + Intent intent = new Intent(this, Principal.class); + intent.putExtra("quadernoScegliNota", true); //TODO translate + startActivityForResult(intent, 7074); + } else if( id == 106 ) { // Search for local media + F.displayImageCaptureDialog(this, null, 4173, (MediaContainer)object); + } else if( id == 107 ) { // Search for shared media + F.displayImageCaptureDialog(this, null, 4174, (MediaContainer)object); + } else if( id == 108 ) { // Link shared media + Intent intent = new Intent(this, Principal.class); + intent.putExtra("galleriaScegliMedia", true); //TODO translate + startActivityForResult(intent, 43616); + } else if( id == 109 ) { // New source citation //Nuova fonte-nota + SourceCitation citation = new SourceCitation(); + citation.setValue(""); + if( object instanceof Note ) ((Note)object).addSourceCitation(citation); + else ((SourceCitationContainer)object).addSourceCitation(citation); + Memory.add(citation); + startActivity(new Intent(this, SourceCitationActivity.class)); toBeSaved = true; - } else if( id == 110 ) { // Nuova fonte - Biblioteca.nuovaFonte(this, object); - } else if( id == 111 ) { // Collega fonte + } else if( id == 110 ) { // New source + LibraryFragment.newSource(this, object); + } else if( id == 111 ) { // Link source Intent intent = new Intent(this, Principal.class); - intent.putExtra("bibliotecaScegliFonte", true); + intent.putExtra("bibliotecaScegliFonte", true); //TODO translate startActivityForResult(intent, 5065); - } else if( id == 120 || id == 121 ) { // Crea nuovo familiare - Intent intent = new Intent(this, EditaIndividuo.class); - intent.putExtra("idIndividuo", "TIZIO_NUOVO"); - intent.putExtra("idFamiglia", ((Family)object).getId()); - intent.putExtra("relazione", id - 115); + } else if( id == 120 || id == 121 ) { // Create new family member + Intent intent = new Intent(this, IndividualEditorActivity.class); + intent.putExtra("idIndividuo", "TIZIO_NUOVO"); //TODO translate + intent.putExtra("idFamiglia", ((Family)object).getId()); //TODO translate + intent.putExtra("relazione", id - 115); //TODO translate startActivity(intent); - } else if( id == 122 || id == 123 ) { // Collega persona esistente + } else if( id == 122 || id == 123 ) { // Link existing person Intent intent = new Intent(this, Principal.class); - intent.putExtra("anagrafeScegliParente", true); + intent.putExtra("anagrafeScegliParente", true); //TODO translate intent.putExtra("relazione", id - 117); startActivityForResult(intent, 34417); - } else if( id == 124 ) { // Metti matrimonio + } else if( id == 124 ) { // Put marriage - Metti matrimonio EventFact marriage = new EventFact(); marriage.setTag("MARR"); marriage.setDate(""); marriage.setPlace(""); marriage.setType(""); ((Family)object).addEventFact(marriage); - Memoria.aggiungi(marriage); - startActivity(new Intent(this, Evento.class)); + Memory.add(marriage); + startActivity(new Intent(this, EventActivity.class)); toBeSaved = true; - } else if( id == 125 ) { // Metti divorzio + } else if( id == 125 ) { // Put divorce - Metti divorzio EventFact divorce = new EventFact(); divorce.setTag("DIV"); divorce.setDate(""); ((Family)object).addEventFact(divorce); - Memoria.aggiungi(divorce); - startActivity(new Intent(this, Evento.class)); + Memory.add(divorce); + startActivity(new Intent(this, EventActivity.class)); toBeSaved = true; - } else if( id >= 200 ) { // Metti altro evento + } else if( id >= 200 ) { // Put another event - Metti altro evento EventFact event = new EventFact(); event.setTag(otherEvents.get(id - 200).first); ((Family)object).addEventFact(event); @@ -224,44 +226,46 @@ else if( object instanceof Repository ) return true; }); }); - // Prova del menu: se è vuoto nasconde il fab + // Menu test: if it is empty it hides the fab if( !menuFAB(null).getMenu().hasVisibleItems() ) // todo ok? fab.hide(); } - // Menu del FAB: solo coi metodi che non sono già presenti in box - PopupMenu menuFAB(View vista) { - PopupMenu popup = new PopupMenu(this, vista); + /** + * FAB menu: only with methods that are not already present in the box + * */ + PopupMenu menuFAB(View view) { + PopupMenu popup = new PopupMenu(this, view); Menu menu = popup.getMenu(); - String[] conIndirizzo = {"Www", "Email", "Phone", "Fax"}; // questi oggetti compaiono nel FAB di Evento se esiste un Indirizzo - int u = 0; + String[] withAddress = {"Www", "Email", "Phone", "Fax"}; // these objects appear in the Event FAB if an Address exists + int counter = 0; for( Egg egg : eggs ) { - boolean giaMesso = false; - boolean indirizzoPresente = false; + boolean alreadyPut = false; + boolean addressPresent = false; for( int i = 0; i < box.getChildCount(); i++ ) { - Object ogg = box.getChildAt(i).getTag(R.id.tag_oggetto); - if( ogg != null && ogg.equals(egg.yolk) ) - giaMesso = true; - if( ogg instanceof Address ) - indirizzoPresente = true; + Object object = box.getChildAt(i).getTag(R.id.tag_object); + if( object != null && object.equals(egg.yolk) ) + alreadyPut = true; + if( object instanceof Address ) + addressPresent = true; } - if( !giaMesso ) { - if( egg.common || (indirizzoPresente && Arrays.asList(conIndirizzo).contains(egg.yolk)) ) - menu.add(0, u, 0, egg.title); + if( !alreadyPut ) { + if( egg.common || (addressPresent && Arrays.asList(withAddress).contains(egg.yolk)) ) + menu.add(0, counter, 0, egg.title); } - u++; + counter++; } if( object instanceof Family ) { - boolean ciSonoFigli = !((Family)object).getChildRefs().isEmpty(); + boolean hasChildren = !((Family)object).getChildRefs().isEmpty(); SubMenu newMemberMenu = menu.addSubMenu(0, 100, 0, R.string.new_relative); - // Not-expert can add maximum two parents // todo: expert too?? + // Non-expert can add maximum two parents // todo: expert too?? if( !(!Global.settings.expert && ((Family)object).getHusbandRefs().size() + ((Family)object).getWifeRefs().size() >= 2) ) - newMemberMenu.add(0, 120, 0, ciSonoFigli ? R.string.parent : R.string.partner); + newMemberMenu.add(0, 120, 0, hasChildren ? R.string.parent : R.string.partner); newMemberMenu.add(0, 121, 0, R.string.child); - if( U.ciSonoIndividuiCollegabili(unRappresentanteDellaFamiglia) ) { + if( U.containsConnectableIndividuals(aRepresentativeOfTheFamily) ) { SubMenu linkMemberMenu = menu.addSubMenu(0, 100, 0, R.string.link_person); if( !(!Global.settings.expert && ((Family)object).getHusbandRefs().size() + ((Family)object).getWifeRefs().size() >= 2) ) - linkMemberMenu.add(0, 122, 0, ciSonoFigli ? R.string.parent : R.string.partner); + linkMemberMenu.add(0, 122, 0, hasChildren ? R.string.parent : R.string.partner); linkMemberMenu.add(0, 123, 0, R.string.child); } SubMenu eventSubMenu = menu.addSubMenu(0, 100, 0, R.string.event); @@ -274,26 +278,26 @@ PopupMenu menuFAB(View vista) { eventSubMenu.add(0, 124, 0, marriageLabel); eventSubMenu.add(0, 125, 0, divorceLabel); - // Gli altri eventi che si possono inserire - SubMenu subAltri = eventSubMenu.addSubMenu(0, 100, 0, R.string.other); + // The other events that can be entered + SubMenu otherSubMenu = eventSubMenu.addSubMenu(0, 100, 0, R.string.other); int i = 0; for( Pair event : otherEvents ) { - subAltri.add(0, 200 + i, 0, event.second); + otherSubMenu.add(0, 200 + i, 0, event.second); i++; } } - if( object instanceof Source && findViewById(R.id.citazione_fonte) == null ) { // todo dubbio: non dovrebbe essere citazione_ARCHIVIO ? - SubMenu subArchivio = menu.addSubMenu(0, 100, 0, R.string.repository); - subArchivio.add(0, 101, 0, R.string.new_repository); + if( object instanceof Source && findViewById(R.id.citazione_fonte) == null ) { // todo doubt: shouldn't it be citation_REPOSITORY? + SubMenu subRepository = menu.addSubMenu(0, 100, 0, R.string.repository); + subRepository.add(0, 101, 0, R.string.new_repository); if( !gc.getRepositories().isEmpty() ) - subArchivio.add(0, 102, 0, R.string.link_repository); + subRepository.add(0, 102, 0, R.string.link_repository); } if( object instanceof NoteContainer ) { - SubMenu subNota = menu.addSubMenu(0, 100, 0, R.string.note); - subNota.add(0, 103, 0, R.string.new_note); - subNota.add(0, 104, 0, R.string.new_shared_note); + SubMenu subNote = menu.addSubMenu(0, 100, 0, R.string.note); + subNote.add(0, 103, 0, R.string.new_note); + subNote.add(0, 104, 0, R.string.new_shared_note); if( !gc.getNotes().isEmpty() ) - subNota.add(0, 105, 0, R.string.link_shared_note); + subNote.add(0, 105, 0, R.string.link_shared_note); } if( object instanceof MediaContainer ) { SubMenu subMedia = menu.addSubMenu(0, 100, 0, R.string.media); @@ -303,93 +307,100 @@ PopupMenu menuFAB(View vista) { subMedia.add(0, 108, 0, R.string.link_shared_media); } if( (object instanceof SourceCitationContainer || object instanceof Note) && Global.settings.expert ) { - SubMenu subFonte = menu.addSubMenu(0, 100, 0, R.string.source); - subFonte.add(0, 109, 0, R.string.new_source_note); - subFonte.add(0, 110, 0, R.string.new_source); + SubMenu subSource = menu.addSubMenu(0, 100, 0, R.string.source); + subSource.add(0, 109, 0, R.string.new_source_note); + subSource.add(0, 110, 0, R.string.new_source); if( !gc.getSources().isEmpty() ) - subFonte.add(0, 111, 0, R.string.link_source); + subSource.add(0, 111, 0, R.string.link_source); } return popup; } - // Imposta ciò che è stato scelto nelle liste + /** + * Set what has been chosen in the lists + * */ @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if( resultCode == RESULT_OK ) { - // Dal submenu 'Collega...' in FAB - if( requestCode == 34417 ) { // Familiare scelto in Anagrafe - Person aggiungendo = gc.getPerson(data.getStringExtra("idParente")); - Famiglia.aggrega(aggiungendo, (Family)object, data.getIntExtra("relazione", 0)); - U.save(true, Memoria.oggettoCapo()); + // From the 'Connect/Link ...' submenu in FAB + if( requestCode == 34417 ) { // Family member chosen in the Registry + Person personToBeAdded = gc.getPerson(data.getStringExtra("idParente")); //TODO translate + FamilyActivity.connect(personToBeAdded, (Family)object, data.getIntExtra("relazione", 0)); + U.save(true, Memory.firstObject()); return; - } else if( requestCode == 5065 ) { // Fonte scelta in Biblioteca - SourceCitation citaFonte = new SourceCitation(); - citaFonte.setRef(data.getStringExtra("idFonte")); - if( object instanceof Note ) ((Note)object).addSourceCitation(citaFonte); - else ((SourceCitationContainer)object).addSourceCitation(citaFonte); - } else if( requestCode == 7074 ) { // Nota condivisa - NoteRef rifNota = new NoteRef(); - rifNota.setRef(data.getStringExtra("idNota")); - ((NoteContainer)object).addNoteRef(rifNota); - } else if( requestCode == 4173 ) { // File preso dal file manager o altra app diventa media locale + } else if( requestCode == 5065 ) { // Source chosen in the Library + SourceCitation sourceCitation = new SourceCitation(); + sourceCitation.setRef(data.getStringExtra("idFonte")); //TODO translate + if( object instanceof Note ) ((Note)object).addSourceCitation(sourceCitation); + else ((SourceCitationContainer)object).addSourceCitation(sourceCitation); + } else if( requestCode == 7074 ) { // Shared note + NoteRef noteRef = new NoteRef(); + noteRef.setRef(data.getStringExtra("idNota")); //TODO translate + ((NoteContainer)object).addNoteRef(noteRef); + } else if( requestCode == 4173 ) { // File taken from file manager or other app becomes local media //File preso dal file manager o altra app diventa media locale Media media = new Media(); media.setFileTag("FILE"); ((MediaContainer)object).addMedia(media); - if( F.proponiRitaglio(this, null, data, media) ) { - U.save(false, Memoria.oggettoCapo()); + if( F.proposeCropping(this, null, data, media) ) { + U.save(false, Memory.firstObject()); return; } - } else if( requestCode == 4174 ) { // File preso dal file manager diventa media condiviso - Media media = Galleria.nuovoMedia(object); - if( F.proponiRitaglio(this, null, data, media) ) { - U.save(false, media, Memoria.oggettoCapo()); + } else if( requestCode == 4174 ) { // File taken from the file manager becomes shared media + Media media = GalleryFragment.newMedia(object); + if( F.proposeCropping(this, null, data, media) ) { + U.save(false, media, Memory.firstObject()); return; } - } else if( requestCode == 43616 ) { // Media da Galleria - MediaRef rifMedia = new MediaRef(); - rifMedia.setRef(data.getStringExtra("idMedia")); - ((MediaContainer)object).addMediaRef(rifMedia); - } else if( requestCode == 4562 ) { // Archivio scelto in Magazzino da Fonte + } else if( requestCode == 43616 ) { // Media from the Gallery + MediaRef mediaRef = new MediaRef(); + mediaRef.setRef(data.getStringExtra("idMedia")); + ((MediaContainer)object).addMediaRef(mediaRef); + } else if( requestCode == 4562 ) { // Repository selected in database (? lit. "Warehouse") from source //Archivio scelto in Magazzino da Fonte RepositoryRef archRef = new RepositoryRef(); archRef.setRef(data.getStringExtra("idArchivio")); ((Source)object).setRepositoryRef(archRef); - } else if( requestCode == 5173 ) { // Salva in Media un file scelto con le app da Immagine - if( F.proponiRitaglio(this, null, data, (Media)object) ) { - U.save(false, Memoria.oggettoCapo()); + } else if( requestCode == 5173 ) { // Save in Media a file chosen with the apps from Image // Salva in Media un file scelto con le app da Immagine + if( F.proposeCropping(this, null, data, (Media)object) ) { + U.save(false, Memory.firstObject()); return; } } else if( requestCode == CropImage.CROP_IMAGE_ACTIVITY_REQUEST_CODE ) { - F.fineRitaglioImmagine(data); + F.endImageCropping(data); } - // da menu contestuale 'Scegli...' - if( requestCode == 5390 ) { // Imposta l'archivio che è stato scelto in Magazzino da ArchivioRef + // from the context menu 'Choose ...' + if( requestCode == 5390 ) { // Sets the archive that has been chosen in database (? lit. "Warehouse") from Repository Ref //Imposta l'archivio che è stato scelto in Magazzino da ArchivioRef ((RepositoryRef)object).setRef(data.getStringExtra("idArchivio")); - } else if( requestCode == 7047 ) { // Imposta la fonte che è stata scelta in Biblioteca da CitazioneFonte + } else if( requestCode == 7047 ) { // Set the source that has been chosen in the Library by SourceCitation ((SourceCitation)object).setRef(data.getStringExtra("idFonte")); } - U.save(true, Memoria.oggettoCapo()); - // 'true' indica di ricaricare sia questo Dettaglio grazie al seguente onRestart(), sia Individuo o Famiglia + U.save(true, Memory.firstObject()); + // 'true' indicates to reload both this Detail thanks to the following onRestart (), and Individual or Family //'true' indica di ricaricare sia questo Dettaglio grazie al seguente onRestart(), sia Individuo o Famiglia } else if( requestCode == CropImage.CROP_IMAGE_ACTIVITY_REQUEST_CODE ) Global.edited = true; } - // Aggiorna i contenuti quando si torna indietro con backPressed() + /** + * Update contents when back with backPressed() + * Aggiorna i contenuti quando si torna indietro con backPressed() + * */ @Override public void onRestart() { super.onRestart(); - if( Global.edited ) { // rinfresca il dettaglio + if( Global.edited ) { // refresh the detail refresh(); } } - public void impagina() {} + public void format() {} //original: impagina() - // Ricarica i contenuti del dettaglio, inclusa la data di modifica + /** + * Reload the contents of the detail, including the modification date + * */ public void refresh() { box.removeAllViews(); eggs.clear(); - impagina(); + format(); } // Place the tags slug @@ -401,14 +412,14 @@ public void placeSlug(String tag, String id) { FlexboxLayout slugLayout = findViewById(R.id.dettaglio_bava); if( Global.settings.expert ) { slugLayout.removeAllViews(); - for( final Memoria.Passo step : Memoria.getPila() ) { + for( final Memory.Step step : Memory.getStepStack() ) { View stepView = LayoutInflater.from(this).inflate(R.layout.pezzo_bava, box, false); TextView stepText = stepView.findViewById(R.id.bava_goccia); - if( Memoria.getPila().indexOf(step) < Memoria.getPila().size() - 1 ) { - if( step.oggetto instanceof Visitable ) // le estensioni GedcomTag non sono Visitable ed è impossibile trovargli la pila + if( Memory.getStepStack().indexOf(step) < Memory.getStepStack().size() - 1 ) { + if( step.object instanceof Visitable ) // GedcomTag extensions are not Visitable and it is impossible to find the stack of them //le estensioni GedcomTag non sono Visitable ed è impossibile trovargli la pila stepView.setOnClickListener(v -> { - new TrovaPila(gc, step.oggetto); - startActivity(new Intent(this, Memoria.classi.get(step.oggetto.getClass()))); + new FindStack(gc, step.object); + startActivity(new Intent(this, Memory.classes.get(step.object.getClass()))); }); } else { step.tag = tag; @@ -436,7 +447,7 @@ private void concludeOtherPiece() { EditText editText = otherPiece.findViewById(R.id.fatto_edita); if( editText != null && editText.isShown() ) { TextView textView = otherPiece.findViewById(R.id.fatto_testo); - if( !editText.getText().toString().equals(textView.getText().toString()) ) // se c'è stata editazione + if( !editText.getText().toString().equals(textView.getText().toString()) ) // if there has been editing save(otherPiece); else restore(otherPiece); @@ -444,12 +455,15 @@ private void concludeOtherPiece() { } } - // Return 'object' casted in the required class, - // or a new instance of the class, but in this case it immediately goes back + /** + * Return 'object' casted in the required class, + * or a new instance of the class, but in this case it immediately goes back + * TODO code smell: no type safety and reflection creating new classes. + * */ public Object cast(Class aClass) { Object casted = null; try { - // male che vada gli passa una nuova istanza della classe, giusto per non inchiodare il dettaglio + // Google translate badly he passes a new instance of the class, just so as not to nail the detail // male che vada gli passa una nuova istanza della classe, giusto per non inchiodare il dettaglio if( aClass.equals(GedcomTag.class) ) casted = new GedcomTag(null, null, null); else @@ -461,36 +475,42 @@ public Object cast(Class aClass) { return casted; } - // An item of all the possible that can be displayed on a 'dettaglio' activity + /** + * A wrapper for every possible widget that can be displayed on a 'Details...' activity + * */ class Egg { String title; Object yolk; // Can be a method string ("Value", "Date", "Type"...) or an Address - boolean common; // indica se farlo comparire nel menu del FAB per inserire il pezzo + boolean common; // indicates whether to make it appear in the FAB menu to insert the piece boolean multiLine; Egg(String title, Object yolk, boolean common, boolean multiLine) { this.title = title; this.yolk = yolk; this.common = common; this.multiLine = multiLine; - eggs.add(this); + eggs.add(this); //TODO this is bad form: it relies on the side effect of creating an object, which should naturally be stateless, and makes it unclear from reading the code what should happen, besides for the compiler not necessarily knowing that anything was done with the object, which I would imagine makes garbage collection slower - besides for being bad form. } } - // Overloading of the following method + /** + * This is an overload of the next method + * */ public void place(String title, String method) { place(title, method, true, false); } - // Try to put a basic editable text piece + /** + * Attempt to put a basic editable text piece in the layout + * */ public void place(String title, String method, boolean common, boolean multiLine) { new Egg(title, method, common, multiLine); String text; try { - text = (String)object.getClass().getMethod("get" + method).invoke(object); + text = (String)object.getClass().getMethod("get" + method).invoke(object); //TODO this reflection is bad performance } catch( Exception e ) { text = "ERROR: " + e.getMessage(); } - // Value 'Y' is hidden for not-experts + // Value 'Y' is hidden for non-experts if( !Global.settings.expert && object instanceof EventFact && method.equals("Value") && text != null && text.equals("Y") ) { String tag = ((EventFact)object).getTag(); @@ -501,14 +521,19 @@ public void place(String title, String method, boolean common, boolean multiLine placePiece(title, text, method, multiLine); } - // Diverse firme per intercettare i vari tipi di oggetto + /** + * Places this address in the layout. [place] is implemented with different signatures to + * accommodate various types of objects being placed. + * */ public void place(String title, Address address) { Address addressNotNull = address == null ? new Address() : address; new Egg(title, addressNotNull, true, false); placePiece(title, writeAddress(address, false), addressNotNull, false); } - // Events of Famiglia + /** + * @param event Events of [{@link FamilyActivity}] + * */ public void place(String title, EventFact event) { EventFact eventNotNull = event == null ? new EventFact() : event; placePiece(title, writeEvent(event), eventNotNull, false); @@ -526,50 +551,50 @@ public View placePiece(String title, String text, Object object, boolean multiLi editText.setVerticalScrollBarEnabled(true); } View.OnClickListener click = null; - if( object instanceof Integer ) { // Nome e cognome in modalità inesperto + if( object instanceof Integer ) { //Full name in inexperienced mode click = this::edit; } else if( object instanceof String ) { // Method click = this::edit; - // Se si tratta di una data + // If it is a date if( object.equals("Date") ) { - editoreData = pieceView.findViewById(R.id.fatto_data); - editoreData.inizia(editText); + publisherDateLinearLayout = pieceView.findViewById(R.id.fatto_data); + publisherDateLinearLayout.initialize(editText); } - } else if( object instanceof Address ) { // Indirizzo + } else if( object instanceof Address ) { // Address click = v -> { - Memoria.aggiungi(object); - startActivity(new Intent(this, Indirizzo.class)); + Memory.add(object); + startActivity(new Intent(this, AddressActivity.class)); }; - } else if( object instanceof EventFact ) { // Evento + } else if( object instanceof EventFact ) { // Event click = v -> { - Memoria.aggiungi(object); - startActivity(new Intent(this, Evento.class)); + Memory.add(object); + startActivity(new Intent(this, EventActivity.class)); }; - // Gli EventFact della famiglia possono avere delle note e dei media + // Family EventFacts can have notes and media LinearLayout scatolaNote = pieceView.findViewById(R.id.fatto_note); U.placeNotes(scatolaNote, object, false); U.placeMedia(scatolaNote, object, false); - } else if( object instanceof GedcomTag ) { // Estensione + } else if( object instanceof GedcomTag ) { // Extension click = v -> { - Memoria.aggiungi(object); - startActivity(new Intent(this, Estensione.class)); + Memory.add(object); + startActivity(new Intent(this, ExtensionActivity.class)); }; } pieceView.setOnClickListener(click); registerForContextMenu(pieceView); - pieceView.setTag(R.id.tag_oggetto, object); // Serve a vari processi per riconoscere il pezzo + pieceView.setTag(R.id.tag_object, object); // It serves various processes to recognize the piece //Serve a vari processi per riconoscere il pezzo return pieceView; } - public void placeExtensions(ExtensionContainer contenitore) { - for( app.familygem.Estensione est : U.trovaEstensioni(contenitore) ) { - placePiece(est.nome, est.testo, est.gedcomTag, false); + public void placeExtensions(ExtensionContainer container) { + for( Extension ext : U.findExtensions(container) ) { + placePiece(ext.name, ext.text, ext.gedcomTag, false); } } public static String writeAddress(Address adr, boolean oneLine) { if( adr == null ) return null; - String txt = ""; + String txt = ""; //TODO use StringBuilder String br = oneLine ? ", " : "\n"; if( adr.getValue() != null ) txt = adr.getValue() + br; @@ -591,17 +616,21 @@ public static String writeAddress(Address adr, boolean oneLine) { return txt; } - // Elimina un indirizzo dai 3 possibili contenitori - public void eliminaIndirizzo( Object contenitore ) { - if( contenitore instanceof EventFact ) - ((EventFact)contenitore).setAddress( null ); - else if( contenitore instanceof Repository ) - ((Repository)contenitore).setAddress( null ); - else if( contenitore instanceof Submitter ) - ((Submitter)contenitore).setAddress( null ); + /** + * Delete an address from the 3 possible containers + * */ + public void deleteAddress(Object container ) { + if( container instanceof EventFact ) + ((EventFact)container).setAddress( null ); + else if( container instanceof Repository ) + ((Repository)container).setAddress( null ); + else if( container instanceof Submitter ) + ((Submitter)container).setAddress( null ); } - // Compose the title of family events + /** + * Compose the title of family events + * */ public String writeEventTitle(Family family, EventFact event) { String tit; switch( event.getTag() ) { @@ -625,10 +654,13 @@ public String writeEventTitle(Family family, EventFact event) { return tit; } - // Compone il testo dell'evento in Famiglia + /** + * He composes the text of the event in FamilyActivity + * // Compone il testo dell'evento in Famiglia + * */ public static String writeEvent(EventFact ef) { if( ef == null ) return null; - String txt = ""; + String txt = ""; //TODO use StringBuilder if( ef.getValue() != null ) { if( ef.getValue().equals("Y") && ef.getTag() != null && (ef.getTag().equals("MARR") || ef.getTag().equals("DIV")) ) @@ -638,7 +670,7 @@ public static String writeEvent(EventFact ef) { txt += "\n"; } if( ef.getDate() != null ) - txt += new Datatore(ef.getDate()).writeDateLong() + "\n"; + txt += new GedcomDateConverter(ef.getDate()).writeDateLong() + "\n"; if( ef.getPlace() != null ) txt += ef.getPlace() + "\n"; Address indirizzo = ef.getAddress(); @@ -652,22 +684,22 @@ public static String writeEvent(EventFact ef) { EditText editText; void edit(View pieceView) { concludeOtherPiece(); - // Poi rende editabile questo pezzo + // Then make this piece editable //Poi rende editabile questo pezzo TextView textView = pieceView.findViewById(R.id.fatto_testo); textView.setVisibility(View.GONE); fab.hide(); - Object pieceObject = pieceView.getTag(R.id.tag_oggetto); + Object pieceObject = pieceView.getTag(R.id.tag_object); boolean showInput = false; editText = pieceView.findViewById(R.id.fatto_edita); - // Luogo + // Place if( pieceObject.equals("Place") ) { showInput = true; - // Se non l'ha già fatto, sostituisce vistaEdita con TrovaLuogo - if( !(editText instanceof TrovaLuogo) ) { - ViewGroup parent = (ViewGroup)pieceView; // todo: si potrebbe usare direttamente vistaPezzo se fosse un ViewGroup o LinearLayout anzicé View + // If it hasn't already done so, it replaces EditText with PlaceFinderTextView + if( !(editText instanceof PlaceFinderTextView) ) { + ViewGroup parent = (ViewGroup)pieceView; // todo: you could use Partview (vistaPezzo) directly if it were a ViewGroup or LinearLayout instead of View int index = parent.indexOfChild(editText); parent.removeView(editText); - editText = new TrovaLuogo(editText.getContext(), null); + editText = new PlaceFinderTextView(editText.getContext(), null); editText.setId(R.id.fatto_edita); parent.addView(editText, index); } else @@ -693,7 +725,7 @@ else if( object instanceof EventFact && pieceObject.equals("Type") && ((EventFac } // Data else if( pieceObject.equals("Date") ) { editText.setVisibility(View.VISIBLE); - } // Tutti gli altri casi normali di editazione + } // All other normal editing cases else { showInput = true; editText.setVisibility(View.VISIBLE); @@ -707,7 +739,7 @@ else if( pieceObject.equals("Date") ) { editText.requestFocus(); editText.setSelection(text.length()); // Cursor at the end - // Intercetta il 'Done' e 'Next' sulla tastiera + // Intercept the 'Done' and 'Next' on the keyboard editText.setOnEditorActionListener((vista, actionId, keyEvent) -> { if( actionId == EditorInfo.IME_ACTION_DONE ) save(pieceView); @@ -717,15 +749,15 @@ else if( actionId == EditorInfo.IME_ACTION_NEXT ) { else restore(pieceView); View nextPiece = box.getChildAt(box.indexOfChild(pieceView) + 1); - if( nextPiece != null && nextPiece.getTag(R.id.tag_oggetto) instanceof String ) + if( nextPiece != null && nextPiece.getTag(R.id.tag_object) instanceof String ) edit(nextPiece); } return false; }); - // ActionBar personalizzata - actionBar.setDisplayHomeAsUpEnabled(false); // nasconde freccia <- - qualeMenu = 0; + // Custom ActionBar + actionBar.setDisplayHomeAsUpEnabled(false); // hides arrow <- + whichMenu = 0; invalidateOptionsMenu(); View editBar = getLayoutInflater().inflate(R.layout.barra_edita, new LinearLayout(box.getContext()), false); editBar.findViewById(R.id.edita_annulla).setOnClickListener(v -> { @@ -738,56 +770,61 @@ else if( actionId == EditorInfo.IME_ACTION_NEXT ) { } void save(View pieceView) { - if( editoreData != null ) editoreData.chiudi(); // In sostanza solo per aggiungere le parentesi alla data frase + if( publisherDateLinearLayout != null ) publisherDateLinearLayout.encloseInParentheses(); // Basically just to add parentheses to the given sentence String text = editText.getText().toString().trim(); - Object pieceObject = pieceView.getTag(R.id.tag_oggetto); - if( pieceObject instanceof Integer ) { // Salva nome e cognome per inesperti - String nome = ((EditText)box.getChildAt(0).findViewById(R.id.fatto_edita)).getText().toString(); - String cognome = ((EditText)box.getChildAt(1).findViewById(R.id.fatto_edita)).getText().toString(); - ((Name)object).setValue( nome + " /" + cognome + "/" ); - } else try { // Tutti gli altri normali metodi - object.getClass().getMethod("set" + pieceObject, String.class).invoke(object, text); + Object pieceObject = pieceView.getTag(R.id.tag_object); + if( pieceObject instanceof Integer ) { // Save first and last name for inexperienced (non-expert mode?) + String firstName = ((EditText)box.getChildAt(0).findViewById(R.id.fatto_edita)).getText().toString(); + String lastName = ((EditText)box.getChildAt(1).findViewById(R.id.fatto_edita)).getText().toString(); + ((Name)object).setValue( firstName + " /" + lastName + "/" ); + } else try { // All other normal methods + object.getClass().getMethod("set" + pieceObject, String.class).invoke(object, text); //TODO reflection } catch( Exception e ) { Toast.makeText(box.getContext(), e.getLocalizedMessage(), Toast.LENGTH_LONG).show(); - return; // in caso di errore rimane in modalità editore + return; // in case of error it remains in editor mode } ((TextView)pieceView.findViewById(R.id.fatto_testo)).setText(text); restore(pieceView); - U.save(true, Memoria.oggettoCapo()); - /*if( Memoria.getPila().size() == 1 ) { - ricrea(); // Todo Bisognerebbe aggiornare la data Cambiamento del record, però magari senza ricaricare tutto. + U.save(true, Memory.firstObject()); + /*if( Memory.getStepStack().size() == 1 ) { + ricrea(); // Todo The record change date should be updated, but perhaps without reloading everything. }*/ - // In immagine modificato il percorso aggiorna l'immagine - if( this instanceof Immagine && pieceObject.equals("File") ) - ((Immagine)this).aggiornaImmagine(); - // Se ha modificato un autore chiede di referenziarlo in header + // In modified image the path updates the image (?) //In immagine modificato il percorso aggiorna l'immagine + if( this instanceof ImageActivity && pieceObject.equals("File") ) + ((ImageActivity)this).updateImage(); + // If he has modified an author, he asks to refer to him in the header //Se ha modificato un autore chiede di referenziarlo in header else if( object instanceof Submitter ) - U.autorePrincipale(this, ((Submitter)object).getId()); - else if( this instanceof Evento ) + U.mainAuthor(this, ((Submitter)object).getId()); + else if( this instanceof EventActivity) refresh(); // To update the title bar } - // Operazioni comuni a Salva e Annulla - void restore(View vistaPezzo) { + /** + * Operations common to Save and Cancel + * */ + void restore(View viewPiece) { editText.setVisibility(View.GONE); - vistaPezzo.findViewById(R.id.fatto_data).setVisibility(View.GONE); - vistaPezzo.findViewById(R.id.fatto_testo).setVisibility(View.VISIBLE); - actionBar.setDisplayShowCustomEnabled(false); // nasconde barra personalizzata + viewPiece.findViewById(R.id.fatto_data).setVisibility(View.GONE); + viewPiece.findViewById(R.id.fatto_testo).setVisibility(View.VISIBLE); + actionBar.setDisplayShowCustomEnabled(false); // hides custom bar// nasconde barra personalizzata actionBar.setDisplayHomeAsUpEnabled(true); - qualeMenu = 1; + whichMenu = 1; invalidateOptionsMenu(); InputMethodManager imm = (InputMethodManager)getSystemService(getApplicationContext().INPUT_METHOD_SERVICE); - imm.hideSoftInputFromWindow(vistaPezzo.getWindowToken(), 0); - if( !(object instanceof Note && !Global.settings.expert) ) // Le note in modalità inesperto non hanno fab + imm.hideSoftInputFromWindow(viewPiece.getWindowToken(), 0); + if( !(object instanceof Note && !Global.settings.expert) ) // Notes in inexperienced mode have no fab //Le note in modalità inesperto non hanno fab fab.show(); } - // Menu opzioni - int qualeMenu = 1; // serve per nasconderlo quando si entra in modalità editore + /** + * Options menu + * serves to hide it when entering editor mode + * */ + int whichMenu = 1; @Override public boolean onCreateOptionsMenu( Menu menu ) { - if( qualeMenu == 1 ) { // Menu standard della barra - if( object instanceof Submitter && (gc.getHeader() == null || // Autore non principale + if( whichMenu == 1 ) { // Standard bar menu//Menu standard della barra + if( object instanceof Submitter && (gc.getHeader() == null || // Non-principal(/main?) author gc.getHeader().getSubmitter(gc) == null || !gc.getHeader().getSubmitter(gc).equals(object)) ) menu.add(0, 1, 0, R.string.make_default); if( object instanceof Media ) { @@ -797,36 +834,39 @@ public boolean onCreateOptionsMenu( Menu menu ) { } if( object instanceof Family ) menu.add(0, 4, 0, R.string.delete); - else if( !(object instanceof Submitter && U.autoreHaCondiviso((Submitter)object)) ) // autore che ha condiviso non può essere eliminato + else if( !(object instanceof Submitter && U.submitterHasShared((Submitter)object)) ) // the author who shared cannot be deleted menu.add(0, 5, 0, R.string.delete); } return true; } - @Override // è evocato quando viene scelta una voce del menu E cliccando freccia indietro + /** + * is invoked when a menu item is chosen AND by clicking the back arrow + * */ + @Override public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); - if( id == 1 ) { // Autore principale - Podio.autorePrincipale((Submitter)object); - } else if( id == 2 ) { // Immagine: ritaglia - croppaImmagine(box); - } else if( id == 3 ) { // Immagine: scegli - F.appAcquisizioneImmagine(this, null, 5173, null); - } else if( id == 4 ) { // Famiglia + if( id == 1 ) { // Main author //TODO code smell : magic number + ListOfAuthorsFragment.mainAuthor((Submitter)object); + } else if( id == 2 ) { // Image: crop + cropImage(box); + } else if( id == 3 ) { // Image: choose + F.displayImageCaptureDialog(this, null, 5173, null); + } else if( id == 4 ) { // Family Family fam = (Family)object; if( fam.getHusbandRefs().size() + fam.getWifeRefs().size() + fam.getChildRefs().size() > 0 ) { new AlertDialog.Builder(this).setMessage(R.string.really_delete_family) .setPositiveButton(android.R.string.yes, (dialog, i) -> { - Chiesa.deleteFamily(fam); + ChurchFragment.deleteFamily(fam); onBackPressed(); }).setNeutralButton(android.R.string.cancel, null).show(); } else { - Chiesa.deleteFamily(fam); + ChurchFragment.deleteFamily(fam); onBackPressed(); } - } else if( id == 5 ) { // Tutti gli altri - // todo: conferma eliminazione di tutti gli oggetti.. - elimina(); - U.save(true); // l'aggiornamento delle date avviene negli Override di elimina() + } else if( id == 5 ) { // All the others + // todo: confirm deletion of all objects.. + delete(); + U.save (true); // the update of the dates takes place in the Overrides of delete() onBackPressed(); } else if( id == android.R.id.home ) { onBackPressed(); @@ -838,43 +878,43 @@ public boolean onOptionsItemSelected(MenuItem item) { public void onBackPressed() { super.onBackPressed(); if( object instanceof EventFact ) - Evento.ripulisciTag((EventFact)object); - Memoria.arretra(); + EventActivity.cleanUpTag((EventFact)object); + Memory.clearStackAndRemove(); } - public void elimina() {} + public void delete() {} - // Menu contestuale - View pieceView; // testo editabile, note, citazioni, media... + // Contextual menu + View pieceView; // editable text, notes, quotes, media ... Object pieceObject; - Person person; // siccome usato molto ne facciamo un pieceObject a sè stante + Person person; // as it is used a lot, we make it a pieceObject in its own right @Override public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo info) { // info is null - if( qualeMenu != 0 ) { // Se siamo in modalità edita mostra i menu editore + if( whichMenu != 0 ) { // If we are in edit mode show the editor menus pieceView = view; - pieceObject = view.getTag(R.id.tag_oggetto); + pieceObject = view.getTag(R.id.tag_object); if( pieceObject instanceof Person ) { person = (Person)pieceObject; Family fam = (Family)object; - // Genera le etichette per le voci 'famiglia' (come figlio e come coniuge) - String[] etichetteFam = {null, null}; + // Generate labels for 'family' entries (such as child and spouse) + String[] famLabels = {null, null}; if( person.getParentFamilies(gc).size() > 1 && person.getSpouseFamilies(gc).size() > 1 ) { - etichetteFam[0] = getString(R.string.family_as_child); - etichetteFam[1] = getString(R.string.family_as_spouse); + famLabels[0] = getString(R.string.family_as_child); + famLabels[1] = getString(R.string.family_as_spouse); } menu.add(0, 10, 0, R.string.diagram); menu.add(0, 11, 0, R.string.card); - if( etichetteFam[0] != null ) - menu.add(0, 12, 0, etichetteFam[0]); - if( etichetteFam[1] != null ) - menu.add(0, 13, 0, etichetteFam[1]); + if( famLabels[0] != null ) + menu.add(0, 12, 0, famLabels[0]); + if( famLabels[1] != null ) + menu.add(0, 13, 0, famLabels[1]); if( fam.getChildren(gc).indexOf(person) > 0 ) menu.add(0, 14, 0, R.string.move_before); if( fam.getChildren(gc).indexOf(person) < fam.getChildren(gc).size() - 1 && fam.getChildren(gc).contains(person) ) - // così esclude i genitori il cui indice è -1 + // thus excludes parents whose index is -1 menu.add(0, 15, 0, R.string.move_after); menu.add(0, 16, 0, R.string.modify); - if( Famiglia.findParentFamilyRef(person, fam) != null ) + if( FamilyActivity.findParentFamilyRef(person, fam) != null ) menu.add(0, 17, 0, R.string.lineage); menu.add(0, 18, 0, R.string.unlink); menu.add(0, 19, 0, R.string.delete); @@ -915,12 +955,12 @@ public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.Context menu.add(0, 90, 0, R.string.copy); menu.add(0, 91, 0, R.string.choose_repository); } else if( pieceObject instanceof Integer ) { - if( pieceObject.equals(43614) ) { // Immaginona - // è un'immagine ritagliabile + if( pieceObject.equals(43614) ) { //Google translate: "Imagine it", probably Image// Immaginona + // it is a croppable image if( pieceView.findViewById(R.id.immagine_foto).getTag(R.id.tag_tipo_file).equals(1) ) menu.add(0, 100, 0, R.string.crop); menu.add(0, 101, 0, R.string.choose_file); - } else if( pieceObject.equals(4043) || pieceObject.equals(6064) ) // Nome e cognome per inesperti + } else if( pieceObject.equals(4043) || pieceObject.equals(6064) ) // Name and surname for inexperienced menu.add(0, 0, 0, R.string.copy); } else if( pieceObject instanceof String ) { if( ((TextView)view.findViewById(R.id.fatto_testo)).getText().length() > 0 ) @@ -932,16 +972,16 @@ public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.Context @Override public boolean onContextItemSelected( MenuItem item ) { switch( item.getItemId() ) { - // TODo tutti gli elimina necessitano di conferma eliminazione - // Copia - case 0: // Pezzo editabile + // TODo all deletes require deletion confirmation + // Copy + case 0: // Editable piece case 50: // Address - case 55: // Evento - case 60: // Estensione - U.copiaNegliAppunti(((TextView)pieceView.findViewById(R.id.fatto_titolo)).getText(), + case 55: // Event + case 60: // Extension + U.copyToClipboard(((TextView)pieceView.findViewById(R.id.fatto_titolo)).getText(), ((TextView)pieceView.findViewById(R.id.fatto_testo)).getText()); return true; - case 1: // Elimina pezzo editabile + case 1: // Delete editable piece try { object.getClass().getMethod("set" + pieceObject, String.class).invoke(object, (Object)null); } catch( Exception e ) { @@ -949,87 +989,87 @@ public boolean onContextItemSelected( MenuItem item ) { break; } break; - case 10: // Diagramma - U.qualiGenitoriMostrare(this, person, 1); + case 10: // Diagram + U.askWhichParentsToShow(this, person, 1); return true; - case 11: // Scheda persona - Memoria.setPrimo(person); - startActivity(new Intent(this, Individuo.class)); + case 11: // Person card? - Scheda persona + Memory.setFirst(person); + startActivity(new Intent(this, IndividualPersonActivity.class)); return true; - case 12: // Famiglia come figlio - U.qualiGenitoriMostrare(this, person, 2); + case 12: // Family as? with? a son - Famiglia come figlio + U.askWhichParentsToShow(this, person, 2); return true; - case 13: // Famiglia come coniuge - U.qualiConiugiMostrare(this, person, null); + case 13: // Family as a spouse? - Famiglia come coniuge + U.askWhichSpouseToShow(this, person, null); return true; - case 14: // Figlio sposta su + case 14: // Son moves on? - Figlio sposta su Family fa = (Family)object; - ChildRef refBimbo = fa.getChildRefs().get(fa.getChildren(gc).indexOf(person)); - fa.getChildRefs().add(fa.getChildRefs().indexOf(refBimbo) - 1, refBimbo); - fa.getChildRefs().remove(fa.getChildRefs().lastIndexOf(refBimbo)); + ChildRef childRef1 = fa.getChildRefs().get(fa.getChildren(gc).indexOf(person)); + fa.getChildRefs().add(fa.getChildRefs().indexOf(childRef1) - 1, childRef1); + fa.getChildRefs().remove(fa.getChildRefs().lastIndexOf(childRef1)); break; - case 15: // Figlio sposta giù + case 15: // Son moves down? - Figlio sposta giù Family f = (Family)object; ChildRef childRef = f.getChildRefs().get(f.getChildren(gc).indexOf(person)); f.getChildRefs().add(f.getChildRefs().indexOf(childRef) + 2, childRef); - f.getChildRefs().remove(f.getChildRefs().indexOf(childRef)); + f.getChildRefs().remove(f.getChildRefs().indexOf(childRef)); //TODO can this be optimizeed to use removal by object? break; - case 16: // Modifica - Intent i = new Intent(this, EditaIndividuo.class); + case 16: // Modification + Intent i = new Intent(this, IndividualEditorActivity.class); i.putExtra("idIndividuo", person.getId()); startActivity(i); return true; case 17: // Lineage - Famiglia.chooseLineage(this, person, (Family)object); + FamilyActivity.chooseLineage(this, person, (Family)object); break; - case 18: // Scollega - Famiglia.scollega((SpouseFamilyRef)pieceView.getTag(R.id.tag_spouse_family_ref), + case 18: // Disconnect + FamilyActivity.disconnect((SpouseFamilyRef)pieceView.getTag(R.id.tag_spouse_family_ref), (SpouseRef)pieceView.getTag(R.id.tag_spouse_ref)); U.updateChangeDate(person); - trovaUnAltroRappresentanteDellaFamiglia(person); + findAnotherRepresentativeOfTheFamily(person); break; - case 19: // Elimina membro + case 19: // Delete member new AlertDialog.Builder(this).setMessage(R.string.really_delete_person) .setPositiveButton(R.string.delete, (dialog, id) -> { - Anagrafe.deletePerson(this, person.getId()); + ListOfPeopleFragment.deletePerson(this, person.getId()); box.removeView(pieceView); - trovaUnAltroRappresentanteDellaFamiglia(person); + findAnotherRepresentativeOfTheFamily(person); }).setNeutralButton(R.string.cancel, null).show(); return true; case 20: // Nota - U.copiaNegliAppunti(getText(R.string.note), ((TextView)pieceView.findViewById(R.id.nota_testo)).getText()); + U.copyToClipboard(getText(R.string.note), ((TextView)pieceView.findViewById(R.id.nota_testo)).getText()); return true; case 21: - U.scollegaNota((Note)pieceObject, object, null); + U.disconnectNote((Note)pieceObject, object, null); break; case 22: - Object[] capi = U.eliminaNota((Note)pieceObject, pieceView); - U.save(true, capi); + Object[] heads /*capi*/ = U.deleteNote((Note)pieceObject, pieceView); + U.save(true, heads); return true; - case 30: // Citazione fonte - U.copiaNegliAppunti(getText(R.string.source_citation), + case 30: // Source citation + U.copyToClipboard(getText(R.string.source_citation), ((TextView)pieceView.findViewById(R.id.fonte_testo)).getText() + "\n" + ((TextView)pieceView.findViewById(R.id.citazione_testo)).getText()); return true; case 31: - if( object instanceof Note ) // Note non estende SourceCitationContainer + if( object instanceof Note ) // Notes does not extend SourceCitationContainer ((Note)object).getSourceCitations().remove(pieceObject); else ((SourceCitationContainer)object).getSourceCitations().remove(pieceObject); - Memoria.annullaIstanze(pieceObject); + Memory.setInstanceAndAllSubsequentToNull(pieceObject); break; case 40: // Media - Galleria.scollegaMedia(((Media)pieceObject).getId(), (MediaContainer)object); + GalleryFragment.disconnectMedia(((Media)pieceObject).getId(), (MediaContainer)object); break; case 41: - Object[] capiMedia = Galleria.eliminaMedia((Media)pieceObject, null); - U.save(true, capiMedia); // un media condiviso può dover aggiornare le date di più capi + Object[] mediaHeads = GalleryFragment.deleteMedia((Media)pieceObject, null); + U.save(true, mediaHeads); // a shared media may need to update the dates of multiple heads refresh(); return true; case 51: - eliminaIndirizzo(object); + deleteAddress(object); break; - case 56: // Evento di Famiglia + case 56: // Family Event int index1 = ((Family)object).getEventsFacts().indexOf(pieceObject); Collections.swap(((Family)object).getEventsFacts(), index1, index1 - 1); break; @@ -1039,85 +1079,91 @@ public boolean onContextItemSelected( MenuItem item ) { break; case 58: ((Family)object).getEventsFacts().remove(pieceObject); - Memoria.annullaIstanze(pieceObject); + Memory.setInstanceAndAllSubsequentToNull(pieceObject); break; - case 61: // Estensione - U.eliminaEstensione((GedcomTag)pieceObject, object, null); + case 61: // Extension + U.deleteExtension((GedcomTag)pieceObject, object, null); break; - // Fonte - case 70: // Copia - U.copiaNegliAppunti(getText(R.string.source), ((TextView)pieceView.findViewById(R.id.fonte_testo)).getText()); + // Source + case 70: // Copy + U.copyToClipboard(getText(R.string.source), ((TextView)pieceView.findViewById(R.id.fonte_testo)).getText()); return true; - case 71: // Scegli in Biblioteca + case 71: // Choose in the Library Intent inte = new Intent(this, Principal.class); inte.putExtra("bibliotecaScegliFonte", true); startActivityForResult(inte, 7047); return true; - // Citazione archivio - case 80: // Copia - U.copiaNegliAppunti(getText(R.string.repository_citation), + // Citation repository + case 80: // Copy + U.copyToClipboard(getText(R.string.repository_citation), ((TextView)pieceView.findViewById(R.id.fonte_testo)).getText() + "\n" + ((TextView)pieceView.findViewById(R.id.citazione_testo)).getText()); return true; - case 81: // Elimina + case 81: // Delete ((Source)object).setRepositoryRef(null); - Memoria.annullaIstanze(pieceObject); + Memory.setInstanceAndAllSubsequentToNull(pieceObject); break; - // Archivio - case 90: // Copia - U.copiaNegliAppunti(getText(R.string.repository), ((TextView)pieceView.findViewById(R.id.fonte_testo)).getText()); + // Repository + case 90: // Copy + U.copyToClipboard(getText(R.string.repository), ((TextView)pieceView.findViewById(R.id.fonte_testo)).getText()); return true; - case 91: // Scegli in Magazzino + case 91: // Choose in magazine? Intent intn = new Intent(this, Principal.class); intn.putExtra("magazzinoScegliArchivio", true); startActivityForResult(intn, 5390); return true; - case 100: // Immaginona ritaglia - croppaImmagine(pieceView); + case 100: // Crop image + cropImage(pieceView); return true; - case 101: // scegli immagine - F.appAcquisizioneImmagine(this, null, 5173, null); + case 101: // Choose image + F.displayImageCaptureDialog(this, null, 5173, null); return true; default: return false; } - // Prima ricrea la pagina e poi salva, che per alberi grossi può metterci alcuni secondi - //closeContextMenu(); // Inutile. La chiusura del menu aspetta la fine del salvataggio, - // a meno di mettere salvaJson() dentro un postDelayed() di almeno 500 ms - U.updateChangeDate(Memoria.oggettoCapo()); + // First recreate the page and then save, which for large trees can take a few seconds + // closeContextMenu(); // Useless. Closing the menu waits for the end of saving, + // unless you put saveJson () inside a postDelayed () of at least 500 ms + U.updateChangeDate(Memory.firstObject()); refresh(); U.save(true, (Object[])null); return true; } - // Corregge unRappresentanteDellaFamiglia per far comparire correttamente "Collega persona esistente" nel menu - private void trovaUnAltroRappresentanteDellaFamiglia(Person p) { - if( unRappresentanteDellaFamiglia.equals(p) ) { + /** + * Fix a Family Representative to correctly show "Link Existing Person" in the menu + * */ + private void findAnotherRepresentativeOfTheFamily(Person p) { + if( aRepresentativeOfTheFamily.equals(p) ) { Family fam = (Family)object; if( !fam.getHusbands(gc).isEmpty() ) - unRappresentanteDellaFamiglia = fam.getHusbands(gc).get(0); + aRepresentativeOfTheFamily = fam.getHusbands(gc).get(0); else if( !fam.getWives(gc).isEmpty() ) - unRappresentanteDellaFamiglia = fam.getWives(gc).get(0); + aRepresentativeOfTheFamily = fam.getWives(gc).get(0); else if( !fam.getChildren(gc).isEmpty() ) - unRappresentanteDellaFamiglia = fam.getChildren(gc).get(0); + aRepresentativeOfTheFamily = fam.getChildren(gc).get(0); else - unRappresentanteDellaFamiglia = null; + aRepresentativeOfTheFamily = null; } } - // Riceve una View in cui c'è l'immagine da ritagliare e avvia il ritaglio - private void croppaImmagine(View vista) { - ImageView vistaImg = vista.findViewById(R.id.immagine_foto); - File fileMedia = null; - String percorso = (String)vistaImg.getTag(R.id.tag_percorso); - if( percorso != null ) - fileMedia = new File(percorso); - Uri uriMedia = (Uri)vistaImg.getTag(R.id.tag_uri); - Global.mediaCroppato = (Media)object; - F.tagliaImmagine(this, fileMedia, uriMedia, null); + /** + * Receives a View in which there is the image to be cropped and starts cropping + * */ + private void cropImage(View view) { + ImageView imageView = view.findViewById(R.id.immagine_foto); + File mediaFile = null; + String path = (String)imageView.getTag(R.id.tag_percorso); + if( path != null ) + mediaFile = new File(path); + Uri mediaUri = (Uri)imageView.getTag(R.id.tag_uri); + Global.croppedMedia = (Media)object; + F.cropImage(this, mediaFile, mediaUri, null); } - // Chiude tastiera eventualmente visibile + /** + * Closes the keyboard that may be visible + * */ @Override protected void onPause() { super.onPause(); @@ -1128,10 +1174,10 @@ protected void onPause() { } @Override - public void onRequestPermissionsResult(int codice, String[] permessi, int[] accordi) { - super.onRequestPermissionsResult(codice, permessi, accordi); - F.risultatoPermessi(this, null, codice, permessi, accordi, + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + F.permissionsResult(this, null, requestCode, permissions, grantResults, object instanceof MediaContainer ? (MediaContainer)object : null); - // Immagine ha 'object' instance di Media, non di MediaContainer + // Image has Media 'object' instance, not MediaContainer } } diff --git a/app/src/main/java/app/familygem/Diagram.java b/app/src/main/java/app/familygem/Diagram.java index 867f1466..88467790 100644 --- a/app/src/main/java/app/familygem/Diagram.java +++ b/app/src/main/java/app/familygem/Diagram.java @@ -44,7 +44,7 @@ import java.util.Set; import app.familygem.constant.Gender; import app.familygem.constant.Relation; -import app.familygem.detail.Famiglia; +import app.familygem.detail.FamilyActivity; import graph.gedcom.Bond; import graph.gedcom.CurveLine; import graph.gedcom.FamilyNode; @@ -83,26 +83,26 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle st DASH = toPx(4); GLOW_SPACE = toPx(35); - getActivity().findViewById(R.id.toolbar).setVisibility(View.GONE); // Necessario in caso di backPressed dopo onActivityresult + getActivity().findViewById(R.id.toolbar).setVisibility(View.GONE); // Necessary in case of backPressed after onActivityresult final View view = inflater.inflate(R.layout.diagram, container, false); view.findViewById(R.id.diagram_hamburger).setOnClickListener(v -> { - DrawerLayout scatolissima = getActivity().findViewById(R.id.scatolissima); - scatolissima.openDrawer(GravityCompat.START); + DrawerLayout drawer = getActivity().findViewById(R.id.scatolissima); + drawer.openDrawer(GravityCompat.START); }); view.findViewById(R.id.diagram_options).setOnClickListener(vista -> { - PopupMenu opzioni = new PopupMenu(getContext(), vista); - Menu menu = opzioni.getMenu(); + PopupMenu options = new PopupMenu(getContext(), vista); + Menu menu = options.getMenu(); menu.add(0, 0, 0, R.string.diagram_settings); if( gc.getPeople().size() > 0 ) menu.add(0, 1, 0, R.string.export_pdf); - opzioni.show(); - opzioni.setOnMenuItemClickListener(item -> { + options.show(); + options.setOnMenuItemClickListener(item -> { switch( item.getItemId() ) { case 0: // Diagram settings startActivity(new Intent(getContext(), DiagramSettings.class)); break; case 1: // Export PDF - F.salvaDocumento(null, this, Global.settings.openTree, "application/pdf", "pdf", 903); + F.saveDocument(null, this, Global.settings.openTree, "application/pdf", "pdf", 903); break; default: return false; @@ -128,18 +128,21 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle st return view; } - // Individua il fulcro da cui partire, mostra eventuale bottone 'Crea la prima persona' oppure avvia il diagramma + /** + * Identify the hub to start from, show any button 'Create the first person' or start the diagram + * Individua il fulcro da cui partire, mostra eventuale bottone 'Crea la prima persona' oppure avvia il diagramma + * */ @Override public void onStart() { super.onStart(); - // Ragioni per cui bisogna proseguire, in particolare cose che sono cambiate - if( forceDraw || (fulcrum != null && !fulcrum.getId().equals(Global.indi)) // TODO andrebbe testato + // Reasons why we must continue, especially things that have changed// Ragioni per cui bisogna proseguire, in particolare cose che sono cambiate + if( forceDraw || (fulcrum != null && !fulcrum.getId().equals(Global.indi)) // TODO should be tested || (graph != null && graph.whichFamily != Global.familyNum) ) { forceDraw = false; box.removeAllViews(); box.setAlpha(0); - String[] ids = {Global.indi, Global.settings.getCurrentTree().root, U.trovaRadice(gc)}; + String[] ids = {Global.indi, Global.settings.getCurrentTree().root, U.findRoot(gc)}; for( String id : ids ) { fulcrum = gc.getPerson(id); if( fulcrum != null ) @@ -149,7 +152,7 @@ public void onStart() { if( fulcrum == null ) { View button = LayoutInflater.from(getContext()).inflate(R.layout.diagram_button, null); button.findViewById(R.id.diagram_new).setOnClickListener(v -> - startActivity(new Intent(getContext(), EditaIndividuo.class) + startActivity(new Intent(getContext(), IndividualEditorActivity.class) .putExtra("idIndividuo", "TIZIO_NUOVO") ) ); @@ -157,7 +160,7 @@ public void onStart() { if( !Global.settings.expert ) ((View)moveLayout.getParent()).findViewById(R.id.diagram_options).setVisibility(View.GONE); } else { - Global.indi = fulcrum.getId(); // Casomai lo ribadisce + Global.indi = fulcrum.getId(); // If anything, he reiterates it //Casomai lo ribadisce graph.maxAncestors(Global.settings.diagram.ancestors) .maxGreatUncles(Global.settings.diagram.uncles) .displaySpouses(Global.settings.diagram.spouses) @@ -171,7 +174,9 @@ public void onStart() { } } - // Put a view under the suggestion balloon + /** + * Put a view under the suggestion balloon + * */ class SuggestionBalloon extends ConstraintLayout { SuggestionBalloon(Context context, View childView, int suggestion) { super(context); @@ -187,6 +192,7 @@ class SuggestionBalloon extends ConstraintLayout { ((TextView)popup.findViewById(R.id.popup_testo)).setText(suggestion); popup.setVisibility(INVISIBLE); popup.setOnTouchListener((v, e) -> { + //v.performClick(); //TODO Android Studio says to call this if( e.getAction() == MotionEvent.ACTION_DOWN ) { v.setVisibility(INVISIBLE); return true; @@ -210,7 +216,9 @@ public void invalidate() { } } - // Diagram initialized the first time and clicking on a card + /** + * Diagram initialized the first time and clicking on a card + * */ void drawDiagram() { // Place various type of graphic nodes in the box taking them from the list of nodes @@ -255,7 +263,7 @@ else if( personNode.mini ) View nodeView = box.getChildAt(i); if( nodeView instanceof GraphicMetric ) { // To avoid ClassCastException that mysteriously happens sometimes GraphicMetric graphic = (GraphicMetric)nodeView; - // GraphicPerson can be larger because of VistaTesto, the child has the correct width + // GraphicPerson can be larger because of TextView, the child has the correct width graphic.metric.width = toDp(graphic.getChildAt(0).getWidth()); graphic.metric.height = toDp(graphic.getChildAt(0).getHeight()); } @@ -294,7 +302,9 @@ private Context context() { return getContext() != null ? getContext() : Global.context; } - // Update visible position of nodes and lines + /** + * Update visible position of nodes and lines + * */ void displaceDiagram() { if( moveLayout.scaleDetector.isInProgress() ) return; @@ -330,7 +340,9 @@ void displaceDiagram() { (int)(toPx(fulcrumView.metric.centerY()) * scale - moveLayout.height / 2 + padding)); } - // The glow around fulcrum card + /** + * The glow around fulcrum card + * */ class FulcrumGlow extends View { Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); BlurMaskFilter bmf = new BlurMaskFilter(toPx(25), BlurMaskFilter.Blur.NORMAL); @@ -356,7 +368,9 @@ public void invalidate() { } } - // Node with one person or one bond + /** + * Node with one person or one bond + * */ abstract class GraphicMetric extends RelativeLayout { Metric metric; GraphicMetric(Context context, Metric metric) { @@ -366,7 +380,9 @@ abstract class GraphicMetric extends RelativeLayout { } } - // Card of a person + /** + * Card of a person + * */ class GraphicPerson extends GraphicMetric { ImageView background; GraphicPerson(Context context, PersonNode personNode) { @@ -385,14 +401,14 @@ else if( Gender.isFemale(person) ) } else if( personNode.acquired ) { background.setBackgroundResource(R.drawable.casella_sfondo_sposo); } - F.unaFoto(Global.gc, person, view.findViewById(R.id.card_photo)); + F.showMainImageForPerson(Global.gc, person, view.findViewById(R.id.card_photo)); TextView vistaNome = view.findViewById(R.id.card_name); - String nome = U.epiteto(person, true); + String nome = U.properName(person, true); if( nome.isEmpty() && view.findViewById(R.id.card_photo).getVisibility()==View.VISIBLE ) vistaNome.setVisibility( View.GONE ); else vistaNome.setText( nome ); TextView vistaTitolo = view.findViewById(R.id.card_title); - String titolo = U.titolo( person ); + String titolo = U.title( person ); if( titolo.isEmpty() ) vistaTitolo.setVisibility(View.GONE); else vistaTitolo.setText(titolo); TextView vistaDati = view.findViewById(R.id.card_data); @@ -404,8 +420,8 @@ else if( Gender.isFemale(person) ) registerForContextMenu(this); setOnClickListener( v -> { if( person.getId().equals(Global.indi) ) { - Memoria.setPrimo( person ); - startActivity( new Intent(getContext(), Individuo.class) ); + Memory.setFirst( person ); + startActivity( new Intent(getContext(), IndividualPersonActivity.class) ); } else { clickCard( person ); } @@ -420,7 +436,9 @@ public void invalidate() { } } - // Marriage with eventual year and vertical line + /** + * Marriage with eventual year and vertical line + * */ class GraphicBond extends GraphicMetric { View hearth; GraphicBond(Context context, Bond bond) { @@ -441,15 +459,15 @@ class GraphicBond extends GraphicMetric { TextView year = new TextView( context ); year.setBackgroundResource(R.drawable.diagram_year_oval); year.setGravity(Gravity.CENTER); - year.setText(new Datatore(bond.marriageDate).writeDate(true)); + year.setText(new GedcomDateConverter(bond.marriageDate).writeDate(true)); year.setTextSize(13f); LayoutParams yearParams = new LayoutParams(LayoutParams.MATCH_PARENT, toPx(MARRIAGE_HEIGHT)); yearParams.topMargin = toPx(bond.centerRelY() - MARRIAGE_HEIGHT / 2); bondLayout.addView(year, yearParams); } setOnClickListener( view -> { - Memoria.setPrimo( familyNode.spouseFamily ); - startActivity( new Intent( context, Famiglia.class ) ); + Memory.setFirst( familyNode.spouseFamily ); + startActivity( new Intent( context, FamilyActivity.class ) ); }); } @Override @@ -460,7 +478,9 @@ public void invalidate() { } } - // Little ancestry or progeny card + /** + * Little ancestry or progeny card + * */ class GraphicMiniCard extends GraphicMetric { RelativeLayout layout; GraphicMiniCard(Context context, PersonNode personNode) { @@ -487,20 +507,24 @@ public void invalidate() { } } - // Replacement for another person who is actually fulcrum + /** + * Replacement for another person who is actually fulcrum + * */ class Asterisk extends GraphicMetric { Asterisk(Context context, PersonNode personNode) { super(context, personNode); getLayoutInflater().inflate(R.layout.diagram_asterisk, this, true); registerForContextMenu(this); setOnClickListener( v -> { - Memoria.setPrimo(personNode.person); - startActivity(new Intent(getContext(), Individuo.class)); + Memory.setFirst(personNode.person); + startActivity(new Intent(getContext(), IndividualPersonActivity.class)); }); } } - // Generate the view of lines connecting the cards + /** + * Generate the view of lines connecting the cards + * */ class Lines extends View { List> lineGroups; boolean dashed; @@ -588,19 +612,23 @@ private void clickCard(Person person) { selectParentFamily(person); } - // Ask which family to display in the diagram if fulcrum has many parent families + /** + * Ask which family to display in the diagram if fulcrum has many parent families + * */ private void selectParentFamily(Person fulcrum) { List families = fulcrum.getParentFamilies(gc); if( families.size() > 1 ) { new AlertDialog.Builder(getContext()).setTitle(R.string.which_family) - .setItems(U.elencoFamiglie(families), (dialog, which) -> { + .setItems(U.listFamilies(families), (dialog, which) -> { completeSelect(fulcrum, which); }).show(); } else { completeSelect(fulcrum, 0); } } - // Complete above function + /** + * Complete above function + * */ private void completeSelect(Person fulcrum, int whichFamily) { Global.indi = fulcrum.getId(); Global.familyNum = whichFamily; @@ -619,19 +647,21 @@ private int toPx(float dips) { return (int) (dips * density + 0.5f); } - // Generate the 2 family (as child and as partner) labels for contextual menu + /** + * Generate the 2 family (as child and as partner) labels for contextual menu + * */ static String[] getFamilyLabels(Context context, Person person, Family family) { String[] labels = { null, null }; List parentFams = person.getParentFamilies(gc); List spouseFams = person.getSpouseFamilies(gc); if( parentFams.size() > 0 ) labels[0] = spouseFams.isEmpty() ? context.getString(R.string.family) - : context.getString(R.string.family_as, Famiglia.getRole(person, null, Relation.CHILD, true).toLowerCase()); + : context.getString(R.string.family_as, FamilyActivity.getRole(person, null, Relation.CHILD, true).toLowerCase()); if( family == null && spouseFams.size() == 1 ) family = spouseFams.get(0); if( spouseFams.size() > 0 ) labels[1] = parentFams.isEmpty() ? context.getString(R.string.family) - : context.getString(R.string.family_as, Famiglia.getRole(person, family, Relation.PARTNER, true).toLowerCase()); + : context.getString(R.string.family_as, FamilyActivity.getRole(person, family, Relation.PARTNER, true).toLowerCase()); return labels; } @@ -662,7 +692,7 @@ else if( vista instanceof Asterisk ) if( familyLabels[1] != null ) menu.add(0, 2, 0, familyLabels[1]); menu.add(0, 3, 0, R.string.new_relative); - if( U.ciSonoIndividuiCollegabili(pers) ) + if( U.containsConnectableIndividuals(pers) ) menu.add(0, 4, 0, R.string.link_person); menu.add(0, 5, 0, R.string.modify); if( !pers.getParentFamilies(gc).isEmpty() || !pers.getSpouseFamilies(gc).isEmpty() ) @@ -683,73 +713,73 @@ public boolean onContextItemSelected(MenuItem item) { else // Due famiglie completeSelect(pers, Global.familyNum == 0 ? 1 : 0); } else if( id == 0 ) { // Apri scheda individuo - Memoria.setPrimo(pers); - startActivity(new Intent(getContext(), Individuo.class)); + Memory.setFirst(pers); + startActivity(new Intent(getContext(), IndividualPersonActivity.class)); } else if( id == 1 ) { // Famiglia come figlio if( idPersona.equals(Global.indi) ) { // Se è fulcro apre direttamente la famiglia - Memoria.setPrimo(parentFam); - startActivity(new Intent(getContext(), Famiglia.class)); + Memory.setFirst(parentFam); + startActivity(new Intent(getContext(), FamilyActivity.class)); } else - U.qualiGenitoriMostrare(getContext(), pers, 2); + U.askWhichParentsToShow(getContext(), pers, 2); } else if( id == 2 ) { // Famiglia come coniuge - U.qualiConiugiMostrare(getContext(), pers, null); + U.askWhichSpouseToShow(getContext(), pers, null); } else if( id == 3 ) { // Collega persona nuova if( Global.settings.expert ) { - DialogFragment dialog = new NuovoParente(pers, parentFam, spouseFam, true, null); + DialogFragment dialog = new NewRelativeDialog(pers, parentFam, spouseFam, true, null); dialog.show(getActivity().getSupportFragmentManager(), "scegli"); } else { new AlertDialog.Builder(getContext()).setItems(parenti, (dialog, quale) -> { - Intent intento = new Intent(getContext(), EditaIndividuo.class); - intento.putExtra("idIndividuo", idPersona); - intento.putExtra("relazione", quale + 1); - if( U.controllaMultiMatrimoni(intento, getContext(), null) ) // aggiunge 'idFamiglia' o 'collocazione' + Intent intent = new Intent(getContext(), IndividualEditorActivity.class); + intent.putExtra("idIndividuo", idPersona); + intent.putExtra("relazione", quale + 1); + if( U.checkMultipleMarriages(intent, getContext(), null) ) // aggiunge 'idFamiglia' o 'collocazione' return; // se perno è sposo in più famiglie, chiede a chi aggiungere un coniuge o un figlio - startActivity(intento); + startActivity(intent); }).show(); } } else if( id == 4 ) { // Collega persona esistente if( Global.settings.expert ) { - DialogFragment dialog = new NuovoParente(pers, parentFam, spouseFam, false, Diagram.this); + DialogFragment dialog = new NewRelativeDialog(pers, parentFam, spouseFam, false, Diagram.this); dialog.show(getActivity().getSupportFragmentManager(), "scegli"); } else { new AlertDialog.Builder(getContext()).setItems(parenti, (dialog, quale) -> { - Intent intento = new Intent(getContext(), Principal.class); - intento.putExtra("idIndividuo", idPersona); - intento.putExtra("anagrafeScegliParente", true); - intento.putExtra("relazione", quale + 1); - if( U.controllaMultiMatrimoni(intento, getContext(), Diagram.this) ) + Intent intent = new Intent(getContext(), Principal.class); + intent.putExtra("idIndividuo", idPersona); + intent.putExtra("anagrafeScegliParente", true); + intent.putExtra("relazione", quale + 1); + if( U.checkMultipleMarriages(intent, getContext(), Diagram.this) ) return; - startActivityForResult(intento, 1401); + startActivityForResult(intent, 1401); }).show(); } } else if( id == 5 ) { // Modifica - Intent intento = new Intent(getContext(), EditaIndividuo.class); - intento.putExtra("idIndividuo", idPersona); - startActivity(intento); + Intent intent = new Intent(getContext(), IndividualEditorActivity.class); + intent.putExtra("idIndividuo", idPersona); + startActivity(intent); } else if( id == 6 ) { // Scollega /* Todo ad esser precisi bisognerebbe usare Famiglia.scollega( sfr, sr ) che rimuove esattamente il singolo link anziché tutti i link se una persona è linkata + volte nella stessa famiglia */ List modificate = new ArrayList<>(); if( parentFam != null ) { - Famiglia.scollega(idPersona, parentFam); + FamilyActivity.disconnect(idPersona, parentFam); modificate.add(parentFam); } if( spouseFam != null ) { - Famiglia.scollega(idPersona, spouseFam); + FamilyActivity.disconnect(idPersona, spouseFam); modificate.add(spouseFam); } ripristina(); Family[] modificateArr = modificate.toArray(new Family[0]); - U.controllaFamiglieVuote(getContext(), this::ripristina, false, modificateArr); + U.checkFamilyItem(getContext(), this::ripristina, false, modificateArr); U.updateChangeDate(pers); U.save(true, (Object[])modificateArr); } else if( id == 7 ) { // Elimina new AlertDialog.Builder(getContext()).setMessage(R.string.really_delete_person) .setPositiveButton(R.string.delete, (dialog, i) -> { - Family[] famiglie = Anagrafe.deletePerson(getContext(), idPersona); + Family[] famiglie = ListOfPeopleFragment.deletePerson(getContext(), idPersona); ripristina(); - U.controllaFamiglieVuote(getContext(), this::ripristina, false, famiglie); + U.checkFamilyItem(getContext(), this::ripristina, false, famiglie); }).setNeutralButton(R.string.cancel, null).show(); } else return false; @@ -766,7 +796,7 @@ public void onActivityResult( int requestCode, int resultCode, Intent data ) { if( resultCode == AppCompatActivity.RESULT_OK ) { // Aggiunge il parente che è stata scelto in Anagrafe if( requestCode == 1401 ) { - Object[] modificati = EditaIndividuo.aggiungiParente( + Object[] modificati = IndividualEditorActivity.addParent( data.getStringExtra("idIndividuo"), // corrisponde a 'idPersona', il quale però si annulla in caso di cambio di configurazione data.getStringExtra("idParente"), data.getStringExtra("idFamiglia"), diff --git a/app/src/main/java/app/familygem/DiagramSettings.java b/app/src/main/java/app/familygem/DiagramSettings.java index d9273e11..57b1432e 100644 --- a/app/src/main/java/app/familygem/DiagramSettings.java +++ b/app/src/main/java/app/familygem/DiagramSettings.java @@ -19,24 +19,24 @@ public class DiagramSettings extends BaseActivity { private SeekBar siblings; private SeekBar cousins; private LinearLayout indicator; - private AnimatorSet anima; + private AnimatorSet anim; private final boolean leftToRight = TextUtilsCompat.getLayoutDirectionFromLocale(Locale.getDefault()) == ViewCompat.LAYOUT_DIRECTION_LTR; @Override - protected void onCreate( Bundle bandolo ) { - super.onCreate( bandolo ); + protected void onCreate( Bundle bundle ) { + super.onCreate( bundle ); setContentView( R.layout.diagram_settings ); indicator = findViewById( R.id.settings_indicator ); // Number of ancestors ancestors = findViewById(R.id.settings_ancestors); - ancestors.setProgress(decodifica(Global.settings.diagram.ancestors)); + ancestors.setProgress(decode(Global.settings.diagram.ancestors)); ancestors.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int i, boolean b) { if( i < uncles.getProgress() ) { uncles.setProgress(i); - Global.settings.diagram.uncles = converti(i); + Global.settings.diagram.uncles = convert(i); } if( i == 0 && siblings.getProgress() > 0 ) { siblings.setProgress(0); @@ -52,20 +52,20 @@ public void onProgressChanged(SeekBar seekBar, int i, boolean b) { public void onStartTrackingTouch(SeekBar seekBar) {} @Override public void onStopTrackingTouch(SeekBar seekBar) { - Global.settings.diagram.ancestors = converti(seekBar.getProgress()); - salva(); + Global.settings.diagram.ancestors = convert(seekBar.getProgress()); + save(); } }); // Number of uncles, linked to ancestors uncles = findViewById(R.id.settings_great_uncles); - uncles.setProgress(decodifica(Global.settings.diagram.uncles)); + uncles.setProgress(decode(Global.settings.diagram.uncles)); uncles.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int i, boolean b) { if( i > ancestors.getProgress() ) { ancestors.setProgress(i); - Global.settings.diagram.ancestors = converti(i); + Global.settings.diagram.ancestors = convert(i); } indicator(seekBar); } @@ -73,8 +73,8 @@ public void onProgressChanged(SeekBar seekBar, int i, boolean b) { public void onStartTrackingTouch(SeekBar seekBar) {} @Override public void onStopTrackingTouch(SeekBar seekBar) { - Global.settings.diagram.uncles = converti(seekBar.getProgress()); - salva(); + Global.settings.diagram.uncles = convert(seekBar.getProgress()); + save(); } }); @@ -83,12 +83,12 @@ public void onStopTrackingTouch(SeekBar seekBar) { spouses.setChecked(Global.settings.diagram.spouses); spouses.setOnCheckedChangeListener((button, active) -> { Global.settings.diagram.spouses = active; - salva(); + save(); }); // Number of descendants SeekBar descendants = findViewById(R.id.settings_descendants); - descendants.setProgress(decodifica(Global.settings.diagram.descendants)); + descendants.setProgress(decode(Global.settings.diagram.descendants)); descendants.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int i, boolean b) { @@ -98,14 +98,14 @@ public void onProgressChanged(SeekBar seekBar, int i, boolean b) { public void onStartTrackingTouch(SeekBar seekBar) {} @Override public void onStopTrackingTouch(SeekBar seekBar) { - Global.settings.diagram.descendants = converti(seekBar.getProgress()); - salva(); + Global.settings.diagram.descendants = convert(seekBar.getProgress()); + save(); } }); // Number of siblings and nephews siblings = findViewById(R.id.settings_siblings_nephews); - siblings.setProgress(decodifica(Global.settings.diagram.siblings)); + siblings.setProgress(decode(Global.settings.diagram.siblings)); siblings.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int i, boolean b) { @@ -119,14 +119,14 @@ public void onProgressChanged(SeekBar seekBar, int i, boolean b) { public void onStartTrackingTouch(SeekBar seekBar) {} @Override public void onStopTrackingTouch(SeekBar seekBar) { - Global.settings.diagram.siblings = converti(seekBar.getProgress()); - salva(); + Global.settings.diagram.siblings = convert(seekBar.getProgress()); + save(); } }); // Number of uncles and cousins, linked to ancestors cousins = findViewById(R.id.settings_uncles_cousins); - cousins.setProgress(decodifica(Global.settings.diagram.cousins)); + cousins.setProgress(decode(Global.settings.diagram.cousins)); cousins.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int i, boolean b) { @@ -140,8 +140,8 @@ public void onProgressChanged(SeekBar seekBar, int i, boolean b) { public void onStartTrackingTouch(SeekBar seekBar) {} @Override public void onStopTrackingTouch(SeekBar seekBar) { - Global.settings.diagram.cousins = converti(seekBar.getProgress()); - salva(); + Global.settings.diagram.cousins = convert(seekBar.getProgress()); + save(); } }); @@ -150,15 +150,15 @@ public void onStopTrackingTouch(SeekBar seekBar) { ObjectAnimator alphaOut = ObjectAnimator.ofFloat(indicator, View.ALPHA, 1, 0); alphaOut.setStartDelay(2000); alphaOut.setDuration(500); - anima = new AnimatorSet(); - anima.play(alphaIn); - anima.play(alphaOut).after(alphaIn); + anim = new AnimatorSet(); + anim.play(alphaIn); + anim.play(alphaOut).after(alphaIn); indicator.setAlpha(0); } private void indicator(SeekBar seekBar) { int i = seekBar.getProgress(); - ((TextView)indicator.findViewById(R.id.settings_indicator_text)).setText(String.valueOf(converti(i))); + ((TextView)indicator.findViewById(R.id.settings_indicator_text)).setText(String.valueOf(convert(i))); int width = seekBar.getWidth() - seekBar.getPaddingLeft() - seekBar.getPaddingRight(); float x; if( leftToRight ) @@ -167,12 +167,14 @@ private void indicator(SeekBar seekBar) { x = seekBar.getX() + seekBar.getWidth() + seekBar.getPaddingRight() - width / 9f * (i + 1) - indicator.getWidth() / 2f; indicator.setX(x); indicator.setY(seekBar.getY() - indicator.getHeight()); - anima.cancel(); - anima.start(); + anim.cancel(); + anim.start(); } - // Valore dalle preferenze (1 2 3 4 5 10 20 50 100) alla scala lineare (1 2 3 4 5 6 7 8 9) - private int decodifica( int i ) { + /** + * Value from preferences (1 2 3 4 5 10 20 50 100) to linear scale (1 2 3 4 5 6 7 8 9) + * */ + private int decode(int i ) { if( i == 100 ) return 9; else if( i == 50 ) return 8; else if( i == 20 ) return 7; @@ -180,8 +182,10 @@ private int decodifica( int i ) { else return i; } - // Valore delle scala lineare a quella esagerata - private int converti( int i ) { + /** + * Linear scale value to exaggerated (scaled?) one + * */ + private int convert(int i ) { if( i == 6 ) return 10; else if( i == 7 ) return 20; else if( i == 8 ) return 50; @@ -189,7 +193,7 @@ private int converti( int i ) { else return i; } - private void salva() { + private void save() { Global.settings.save(); Global.edited = true; } diff --git a/app/src/main/java/app/familygem/EditaIndividuo.java b/app/src/main/java/app/familygem/EditaIndividuo.java deleted file mode 100644 index 13fad117..00000000 --- a/app/src/main/java/app/familygem/EditaIndividuo.java +++ /dev/null @@ -1,429 +0,0 @@ -package app.familygem; - -import android.os.Bundle; -import androidx.appcompat.widget.SwitchCompat; -import androidx.appcompat.app.ActionBar; -import androidx.appcompat.app.AppCompatActivity; -import android.view.View; -import android.view.inputmethod.EditorInfo; -import android.widget.EditText; -import android.widget.LinearLayout; -import android.widget.RadioButton; -import android.widget.RadioGroup; -import org.folg.gedcom.model.ChildRef; -import org.folg.gedcom.model.EventFact; -import org.folg.gedcom.model.Family; -import org.folg.gedcom.model.Name; -import org.folg.gedcom.model.ParentFamilyRef; -import org.folg.gedcom.model.Person; -import org.folg.gedcom.model.SpouseFamilyRef; -import org.folg.gedcom.model.SpouseRef; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import app.familygem.constant.Gender; -import app.familygem.detail.Evento; -import app.familygem.detail.Famiglia; -import static app.familygem.Global.gc; - -public class EditaIndividuo extends AppCompatActivity { - - Person p; - String idIndi; - String idFamiglia; - int relazione; - RadioButton sexMale; - RadioButton sexFemale; - RadioButton sexUnknown; - int lastChecked; - EditText dataNascita; - EditoreData editoreDataNascita; - EditText luogoNascita; - SwitchCompat bottonMorte; - EditText dataMorte; - EditoreData editoreDataMorte; - EditText luogoMorte; - boolean nomeDaPieces; // Se il nome/cognome vengono dai pieces Given e Surname, lì devono tornare - - @Override - protected void onCreate(Bundle bandolo) { - super.onCreate(bandolo); - U.gedcomSicuro(gc); - setContentView( R.layout.edita_individuo ); - Bundle bundle = getIntent().getExtras(); - idIndi = bundle.getString("idIndividuo"); - idFamiglia = bundle.getString("idFamiglia"); - relazione = bundle.getInt("relazione", 0 ); - - sexMale = findViewById(R.id.sesso1); - sexFemale = findViewById(R.id.sesso2); - sexUnknown = findViewById(R.id.sesso3); - dataNascita = findViewById( R.id.data_nascita ); - editoreDataNascita = findViewById(R.id.editore_data_nascita); - luogoNascita = findViewById(R.id.luogo_nascita); - bottonMorte = findViewById( R.id.defunto ); - dataMorte = findViewById( R.id.data_morte ); - editoreDataMorte = findViewById( R.id.editore_data_morte ); - luogoMorte = findViewById( R.id.luogo_morte ); - - // Toggle sex radio buttons - RadioGroup radioGroup = findViewById(R.id.radioGroup); - View.OnClickListener radioClick = radioButton -> { - if( radioButton.getId() == lastChecked ) { - radioGroup.clearCheck(); - } - }; - sexMale.setOnClickListener(radioClick); - sexFemale.setOnClickListener(radioClick); - sexUnknown.setOnClickListener(radioClick); - radioGroup.setOnCheckedChangeListener((group, checked) -> { - group.post(() -> { - lastChecked = checked; - }); - }); - - disattivaMorte(); - - // Nuovo individuo in relazione di parentela - if( relazione > 0 ) { - p = new Person(); - Person pivot = gc.getPerson(idIndi); - String surname = null; - // Cognome del fratello - if( relazione == 2 ) { // = fratello - surname = U.surname(pivot); - // Cognome del padre - } else if( relazione == 4 ) { // = figlio da Diagramma o Individuo - if( Gender.isMale(pivot) ) - surname = U.surname(pivot); - else if( idFamiglia != null ) { - Family fam = gc.getFamily(idFamiglia); - if( fam != null && !fam.getHusbands(gc).isEmpty() ) - surname = U.surname(fam.getHusbands(gc).get(0)); - } - } else if( relazione == 6 ) { // = figlio da Famiglia - Family fam = gc.getFamily(idFamiglia); - if( !fam.getHusbands(gc).isEmpty() ) - surname = U.surname(fam.getHusbands(gc).get(0)); - else if( !fam.getChildren(gc).isEmpty() ) - surname = U.surname(fam.getChildren(gc).get(0)); - } - ((EditText)findViewById(R.id.cognome)).setText(surname); - // Nuovo individuo scollegato - } else if( idIndi.equals("TIZIO_NUOVO") ) { - p = new Person(); - // Carica i dati di un individuo esistente da modificare - } else { - p = gc.getPerson(idIndi); - // Nome e cognome - if( !p.getNames().isEmpty() ) { - String nome = ""; - String cognome = ""; - Name n = p.getNames().get(0); - String epiteto = n.getValue(); - if( epiteto != null ) { - nome = epiteto.replaceAll( "/.*?/", "" ).trim(); // rimuove il cognome '/.../' - if( epiteto.indexOf('/') < epiteto.lastIndexOf('/') ) - cognome = epiteto.substring( epiteto.indexOf('/') + 1, epiteto.lastIndexOf('/') ).trim(); - } else { - if( n.getGiven() != null ) { - nome = n.getGiven(); - nomeDaPieces = true; - } - if( n.getSurname() != null ) { - cognome = n.getSurname(); - nomeDaPieces = true; - } - } - ((EditText)findViewById( R.id.nome )).setText( nome ); - ((EditText)findViewById( R.id.cognome )).setText( cognome ); - } - // Sex - switch( Gender.getGender(p) ) { - case MALE: - sexMale.setChecked(true); - break; - case FEMALE: - sexFemale.setChecked(true); - break; - case UNKNOWN: - sexUnknown.setChecked(true); - } - lastChecked = radioGroup.getCheckedRadioButtonId(); - // Nascita e morte - for( EventFact fatto : p.getEventsFacts() ) { - if( fatto.getTag().equals("BIRT") ) { - if( fatto.getDate() != null ) - dataNascita.setText( fatto.getDate().trim() ); - if( fatto.getPlace() != null ) - luogoNascita.setText(fatto.getPlace().trim()); - } - if( fatto.getTag().equals("DEAT") ) { - bottonMorte.setChecked(true); - attivaMorte(); - if( fatto.getDate() != null ) - dataMorte.setText( fatto.getDate().trim() ); - if( fatto.getPlace() != null ) - luogoMorte.setText(fatto.getPlace().trim()); - } - } - } - editoreDataNascita.inizia( dataNascita ); - bottonMorte.setOnCheckedChangeListener( (coso, attivo) -> { - if (attivo) - attivaMorte(); - else - disattivaMorte(); - }); - editoreDataMorte.inizia( dataMorte ); - luogoMorte.setOnEditorActionListener( (vista, actionId, keyEvent) -> { - if( actionId == EditorInfo.IME_ACTION_DONE ) - salva(); - return false; - }); - - // Barra - ActionBar barra = getSupportActionBar(); - View barraAzione = getLayoutInflater().inflate( R.layout.barra_edita, new LinearLayout(getApplicationContext()), false); - barraAzione.findViewById( R.id.edita_annulla ).setOnClickListener( v -> onBackPressed() ); - barraAzione.findViewById(R.id.edita_salva).setOnClickListener( v -> salva() ); - barra.setCustomView( barraAzione ); - barra.setDisplayShowCustomEnabled( true ); - } - - void disattivaMorte() { - findViewById(R.id.morte).setVisibility(View.GONE); - luogoNascita.setImeOptions(EditorInfo.IME_ACTION_DONE); - luogoNascita.setNextFocusForwardId(0); - // Intercetta il 'Done' sulla tastiera - luogoNascita.setOnEditorActionListener((view, action, event) -> { - if( action == EditorInfo.IME_ACTION_DONE ) - salva(); - return false; - }); - } - - void attivaMorte() { - luogoNascita.setImeOptions(EditorInfo.IME_ACTION_NEXT); - luogoNascita.setNextFocusForwardId(R.id.data_morte); - luogoNascita.setOnEditorActionListener(null); - findViewById(R.id.morte).setVisibility(View.VISIBLE); - } - - void salva() { - U.gedcomSicuro(gc); // È capitato un crash perché qui gc era null - - // Nome - String nome = ((EditText)findViewById(R.id.nome)).getText().toString().trim(); - String cognome = ((EditText)findViewById(R.id.cognome)).getText().toString().trim(); - Name name; - if( p.getNames().isEmpty() ) { - List nomi = new ArrayList<>(); - name = new Name(); - nomi.add(name); - p.setNames(nomi); - } else - name = p.getNames().get(0); - - if( nomeDaPieces ) { - name.setGiven(nome); - name.setSurname(cognome); - } else { - name.setValue(nome + " /" + cognome + "/".trim()); - } - - // Sesso - String sessoScelto = null; - if( sexMale.isChecked() ) - sessoScelto = "M"; - else if( sexFemale.isChecked() ) - sessoScelto = "F"; - else if( sexUnknown.isChecked() ) - sessoScelto = "U"; - if( sessoScelto != null ) { - boolean mancaSesso = true; - for( EventFact fact : p.getEventsFacts() ) { - if( fact.getTag().equals("SEX") ) { - fact.setValue(sessoScelto); - mancaSesso = false; - } - } - if( mancaSesso ) { - EventFact sesso = new EventFact(); - sesso.setTag("SEX"); - sesso.setValue(sessoScelto); - p.addEventFact(sesso); - } - IndividuoEventi.aggiornaRuoliConiugali(p); - } else { // Remove existing sex tag - for( EventFact fact : p.getEventsFacts() ) { - if( fact.getTag().equals("SEX") ) { - p.getEventsFacts().remove(fact); - break; - } - } - } - - // Nascita - editoreDataNascita.chiudi(); - String data = dataNascita.getText().toString().trim(); - String luogo = luogoNascita.getText().toString().trim(); - boolean trovato = false; - for (EventFact fatto : p.getEventsFacts()) { - if( fatto.getTag().equals("BIRT") ) { - /* TODO: if( data.isEmpty() && luogo.isEmpty() && tagTuttoVuoto(fatto) ) - p.getEventsFacts().remove(fatto); - più in generale, eliminare un tag quando è vuoto */ - fatto.setDate( data ); - fatto.setPlace( luogo ); - Evento.ripulisciTag( fatto ); - trovato = true; - } - } - // Se c'è qualche dato da salvare crea il tag - if( !trovato && ( !data.isEmpty() || !luogo.isEmpty() ) ) { - EventFact nascita = new EventFact(); - nascita.setTag( "BIRT" ); - nascita.setDate( data ); - nascita.setPlace( luogo ); - Evento.ripulisciTag( nascita ); - p.addEventFact( nascita ); - } - - // Morte - editoreDataMorte.chiudi(); - data = dataMorte.getText().toString().trim(); - luogo = luogoMorte.getText().toString().trim(); - trovato = false; - for( EventFact fatto : p.getEventsFacts() ) { - if( fatto.getTag().equals("DEAT") ) { - if( !bottonMorte.isChecked() ) { - p.getEventsFacts().remove(fatto); - } else { - fatto.setDate( data ); - fatto.setPlace( luogo ); - Evento.ripulisciTag( fatto ); - } - trovato = true; - break; - } - } - if( !trovato && bottonMorte.isChecked() ) { - EventFact morte = new EventFact(); - morte.setTag( "DEAT" ); - morte.setDate( data ); - morte.setPlace( luogo ); - Evento.ripulisciTag( morte ); - p.addEventFact( morte ); - } - - // Finalizzazione individuo nuovo - Object[] modificati = { p, null }; // il null serve per accogliere una eventuale Family - if( idIndi.equals("TIZIO_NUOVO") || relazione > 0 ) { - String nuovoId = U.nuovoId( gc, Person.class ); - p.setId( nuovoId ); - gc.addPerson( p ); - if( Global.settings.getCurrentTree().root == null ) - Global.settings.getCurrentTree().root = nuovoId; - Global.settings.save(); - if( relazione >= 5 ) { // viene da Famiglia - Famiglia.aggrega( p, gc.getFamily(idFamiglia), relazione ); - modificati[1] = gc.getFamily(idFamiglia); - } else if( relazione > 0 ) // viene da Diagramma o IndividuoFamiliari - modificati = aggiungiParente( idIndi, nuovoId, idFamiglia, relazione, getIntent().getStringExtra("collocazione") ); - } else - Global.indi = p.getId(); // per mostrarlo orgogliosi in Diagramma - U.save(true, modificati); - onBackPressed(); - } - - /** Aggiunge un nuovo individuo in relazione di parentela con 'perno', eventualmente all'interno della famiglia fornita. - * @param idFamiglia Id della famiglia di destinazione. Se è null si crea una nuova famiglia - * @param collocazione Sintetizza come è stata individuata la famiglia e quindi cosa fare delle persone coinvolte - */ - static Object[] aggiungiParente(String idPerno, String nuovoId, String idFamiglia, int relazione, String collocazione) { - Global.indi = idPerno; - Person nuovo = gc.getPerson( nuovoId ); - // Si crea una nuova famiglia in cui finiscono sia Perno che Nuovo - if( collocazione != null && collocazione.startsWith("NUOVA_FAMIGLIA_DI") ) { // Contiene l'id del genitore di cui creare una nuova famiglia - idPerno = collocazione.substring(17); // il genitore diventa effettivamente il perno - relazione = relazione == 2 ? 4 : relazione; // anziché un fratello a perno, è come se mettessimo un figlio al genitore - } - // In Anagrafe è stata individuata la famiglia in cui finirà perno - else if( collocazione != null && collocazione.equals("FAMIGLIA_ESISTENTE") ) { - nuovoId = null; - nuovo = null; - } - // Nuovo è accolto nella famiglia di Perno - else if( idFamiglia != null ) { - idPerno = null; // perno è già presente nella sua famiglia e non va riaggiunto - } - Family famiglia = idFamiglia != null ? gc.getFamily(idFamiglia) : Chiesa.nuovaFamiglia(true);; - Person perno = gc.getPerson( idPerno ); - SpouseRef refSposo1 = new SpouseRef(), refSposo2 = new SpouseRef(); - ChildRef refFiglio1 = new ChildRef(), refFiglio2 = new ChildRef(); - ParentFamilyRef refFamGenitori = new ParentFamilyRef(); - SpouseFamilyRef refFamSposi = new SpouseFamilyRef(); - refFamGenitori.setRef( famiglia.getId() ); - refFamSposi.setRef( famiglia.getId() ); - - // Popolamento dei ref - switch (relazione) { - case 1: // Genitore - refSposo1.setRef(nuovoId); - refFiglio1.setRef(idPerno); - if (nuovo != null) nuovo.addSpouseFamilyRef( refFamSposi ); - if (perno != null) perno.addParentFamilyRef( refFamGenitori ); - break; - case 2: // Fratello - refFiglio1.setRef(idPerno); - refFiglio2.setRef(nuovoId); - if (perno != null) perno.addParentFamilyRef( refFamGenitori ); - if (nuovo != null) nuovo.addParentFamilyRef( refFamGenitori ); - break; - case 3: // Compagno - refSposo1.setRef(idPerno); - refSposo2.setRef(nuovoId); - if (perno != null) perno.addSpouseFamilyRef( refFamSposi ); - if (nuovo != null) nuovo.addSpouseFamilyRef( refFamSposi ); - break; - case 4: // Figlio - refSposo1.setRef(idPerno); - refFiglio1.setRef(nuovoId); - if (perno != null) perno.addSpouseFamilyRef( refFamSposi ); - if (nuovo != null) nuovo.addParentFamilyRef( refFamGenitori ); - } - - if( refSposo1.getRef() != null ) - aggiungiConiuge(famiglia, refSposo1); - if( refSposo2.getRef() != null ) - aggiungiConiuge(famiglia, refSposo2); - if( refFiglio1.getRef() != null ) - famiglia.addChild(refFiglio1); - if( refFiglio2.getRef() != null ) - famiglia.addChild(refFiglio2); - - if( relazione == 1 || relazione == 2 ) // Farà comparire la famiglia selezionata - Global.familyNum = gc.getPerson(Global.indi).getParentFamilies(gc).indexOf(famiglia); - else - Global.familyNum = 0; // eventuale reset - - Set cambiati = new HashSet<>(); - if( perno != null && nuovo != null ) - Collections.addAll(cambiati, famiglia, perno, nuovo); - else if( perno != null ) - Collections.addAll(cambiati, famiglia, perno); - else if( nuovo != null ) - Collections.addAll(cambiati, famiglia, nuovo); - return cambiati.toArray(); - } - - // Aggiunge il coniuge in una famiglia: sempre e solo in base al sesso - public static void aggiungiConiuge(Family family, SpouseRef sr) { - Person person = Global.gc.getPerson(sr.getRef()); - if( Gender.isFemale(person) ) family.addWife(sr); - else family.addHusband(sr); - } -} \ No newline at end of file diff --git a/app/src/main/java/app/familygem/EditoreData.java b/app/src/main/java/app/familygem/EditoreData.java deleted file mode 100644 index 48cdd936..00000000 --- a/app/src/main/java/app/familygem/EditoreData.java +++ /dev/null @@ -1,373 +0,0 @@ -package app.familygem; - -import android.content.Context; -import android.text.Editable; -import android.text.InputType; -import android.text.TextWatcher; -import android.util.AttributeSet; -import android.view.Menu; -import android.view.MotionEvent; -import android.view.View; -import android.view.inputmethod.InputMethodManager; -import android.widget.CheckBox; -import android.widget.CompoundButton; -import android.widget.EditText; -import android.widget.LinearLayout; -import android.widget.NumberPicker; -import android.widget.TextView; -import androidx.appcompat.widget.PopupMenu; -import java.lang.reflect.Field; -import java.util.Calendar; -import java.util.Date; -import java.util.GregorianCalendar; -import java.util.Locale; -import app.familygem.constant.Format; -import app.familygem.constant.Kind; - -public class EditoreData extends LinearLayout { - - Datatore datatore; - Datatore.Data data1; - Datatore.Data data2; - EditText editaTesto; - String[] giorniRuota = { "-","1","2","3","4","5","6","7","8","9","10","11","12","13","14","15", - "16","17","18","19","20","21","22","23","24","25","26","27","28","29","30","31" }; - String[] mesiRuota = { "-", s(R.string.january), s(R.string.february), s(R.string.march), s(R.string.april), s(R.string.may), s(R.string.june), - s(R.string.july), s(R.string.august), s(R.string.september), s(R.string.october), s(R.string.november), s(R.string.december) }; - String[] anniRuota = new String[101]; - int[] dateKinds = { R.string.exact, R.string.approximate, R.string.calculated, R.string.estimated, - R.string.after, R.string.before, R.string.between_and, - R.string.from, R.string.to, R.string.from_to, R.string.date_phrase }; - Calendar calenda = GregorianCalendar.getInstance(); - boolean veroImputTesto; // stabilisce se l'utente sta effettivamente digitando sulla tastiera virtuale o se il testo viene cambiato in altro modo - InputMethodManager tastiera; - boolean tastieraVisibile; - - public EditoreData( Context contesto, AttributeSet as ) { - super( contesto, as ); - } - - // Azioni da fare una sola volta all'inizio - void inizia( final EditText editaTesto ) { - - addView( inflate( getContext(), R.layout.editore_data, null ), this.getLayoutParams() ); - this.editaTesto = editaTesto; - - for( int i = 0; i < anniRuota.length - 1; i++ ) - anniRuota[i] = i < 10 ? "0" + i : "" + i; - anniRuota[100] = "-"; - - datatore = new Datatore( editaTesto.getText().toString() ); - data1 = datatore.data1; - data2 = datatore.data2; - - // Arreda l'editore data - if( Global.settings.expert ) { - final TextView elencoTipi = findViewById(R.id.editadata_tipi); - elencoTipi.setOnClickListener( vista -> { - PopupMenu popup = new PopupMenu(getContext(), vista); - Menu menu = popup.getMenu(); - for( int i = 0; i < dateKinds.length - 1; i++ ) - menu.add(0, i, 0, dateKinds[i]); - popup.show(); - popup.setOnMenuItemClickListener(item -> { - datatore.kind = Kind.values()[item.getItemId()]; - // Se eventualmente invisibile - findViewById(R.id.editadata_prima).setVisibility(View.VISIBLE); - if( data1.date == null ) // micro settaggio del carro - ((NumberPicker)findViewById(R.id.prima_anno)).setValue(100); - if( datatore.kind == Kind.BETWEEN_AND || datatore.kind == Kind.FROM_TO ) { - findViewById(R.id.editadata_seconda_avanzate).setVisibility(VISIBLE); - findViewById(R.id.editadata_seconda).setVisibility(VISIBLE); - if( data2.date == null ) - ((NumberPicker)findViewById(R.id.seconda_anno)).setValue(100); - } else { - findViewById(R.id.editadata_seconda_avanzate).setVisibility(GONE); - findViewById(R.id.editadata_seconda).setVisibility(GONE); - } - elencoTipi.setText(dateKinds[item.getItemId()]); - veroImputTesto = false; - genera(); - return true; - }); - }); - findViewById(R.id.editadata_negativa1).setOnClickListener(vista -> { - data1.negativa = ((CompoundButton)vista).isChecked(); - veroImputTesto = false; - genera(); - }); - findViewById(R.id.editadata_doppia1).setOnClickListener(vista -> { - data1.doppia = ((CompoundButton)vista).isChecked(); - veroImputTesto = false; - genera(); - }); - findViewById(R.id.editadata_negativa2).setOnClickListener(vista -> { - data2.negativa = ((CompoundButton)vista).isChecked(); - veroImputTesto = false; - genera(); - }); - findViewById(R.id.editadata_doppia2).setOnClickListener(vista -> { - data2.doppia = ((CompoundButton)vista).isChecked(); - veroImputTesto = false; - genera(); - }); - findViewById(R.id.editadata_circa).setVisibility(GONE); - } else { - findViewById(R.id.editadata_circa).setOnClickListener(vista -> { - findViewById(R.id.editadata_seconda).setVisibility(GONE); // casomai fosse visibile per tipi 6 o 9 - datatore.kind = ((CompoundButton)vista).isChecked() ? Kind.APPROXIMATE : Kind.EXACT; - veroImputTesto = false; - genera(); - }); - findViewById(R.id.editadata_avanzate).setVisibility(GONE); - } - - arredaCarro(1, findViewById(R.id.prima_giorno), findViewById(R.id.prima_mese), - findViewById(R.id.prima_secolo), findViewById(R.id.prima_anno)); - - arredaCarro(2, findViewById(R.id.seconda_giorno), findViewById(R.id.seconda_mese), - findViewById(R.id.seconda_secolo), findViewById(R.id.seconda_anno)); - - // Al primo focus mostra sè stesso (EditoreData) nascondendo la tastiera - tastiera = (InputMethodManager)getContext().getSystemService(Context.INPUT_METHOD_SERVICE); - editaTesto.setOnFocusChangeListener((v, ciapaFocus) -> { - if( ciapaFocus ) { - if( datatore.kind == Kind.PHRASE ) { - //genera(); // Toglie le parentesi alla frase - editaTesto.setText(datatore.frase); - } else { - tastieraVisibile = tastiera.hideSoftInputFromWindow( editaTesto.getWindowToken(), 0 ); // ok nasconde tastiera - /*Window finestra = ((Activity)getContext()).getWindow(); non aiuta la scomparsa della tastiera - finestra.setSoftInputMode( WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN );*/ - editaTesto.setInputType( InputType.TYPE_NULL ); // disabilita input testo con tastiera - // necessario in versioni recenti di android in cui la tastiera ricompare - } - datatore.data1.date = null; // un resettino - impostaTutto(); - setVisibility(View.VISIBLE); - } else - setVisibility(View.GONE); - } ); - - // Al secondo tocco fa comparire la tastiera - editaTesto.setOnTouchListener((vista, event) -> { - if( event.getAction() == MotionEvent.ACTION_DOWN ) { - editaTesto.setInputType(InputType.TYPE_CLASS_TEXT); // riabilita l'input - } else if( event.getAction() == MotionEvent.ACTION_UP ) { - tastieraVisibile = tastiera.showSoftInput(editaTesto, 0); // fa ricomparire la tastiera - //veroImputTesto = true; - //vista.performClick(); non ne vedo l'utilità - } - return false; - }); - // Imposta l'editore data in base a quanto scritto - editaTesto.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence testo, int i, int i1, int i2) {} - @Override - public void onTextChanged(CharSequence testo, int i, int i1, int i2) {} - @Override - public void afterTextChanged(Editable testo) { - // non so perché ma in android 5 alla prima editazione viene chiamato 2 volte, che comunque non è un problema - if( veroImputTesto ) - impostaTutto(); - veroImputTesto = true; - } - }); - } - - // Prepara le quattro ruote di un carro con le impostazioni iniziali - void arredaCarro( final int quale, final NumberPicker ruotaGiorno, final NumberPicker ruotaMese, final NumberPicker ruotaSecolo, final NumberPicker ruotaAnno ) { - ruotaGiorno.setMinValue(0); - ruotaGiorno.setMaxValue(31); - ruotaGiorno.setDisplayedValues( giorniRuota ); - stilizza(ruotaGiorno); - ruotaGiorno.setOnValueChangedListener( (picker, vecchio, nuovo) -> - aggiorna( quale == 1 ? data1 : data2, ruotaGiorno, ruotaMese, ruotaSecolo, ruotaAnno ) - ); - ruotaMese.setMinValue(0); - ruotaMese.setMaxValue(12); - ruotaMese.setDisplayedValues( mesiRuota ); - stilizza(ruotaMese); - ruotaMese.setOnValueChangedListener( (picker, vecchio, nuovo) -> - aggiorna( quale == 1 ? data1 : data2, ruotaGiorno, ruotaMese, ruotaSecolo, ruotaAnno ) - ); - ruotaSecolo.setMinValue(0); - ruotaSecolo.setMaxValue(20); - stilizza(ruotaSecolo); - ruotaSecolo.setOnValueChangedListener( (picker, vecchio, nuovo) -> - aggiorna( quale == 1 ? data1 : data2, ruotaGiorno, ruotaMese, ruotaSecolo, ruotaAnno ) - ); - ruotaAnno.setMinValue(0); - ruotaAnno.setMaxValue(100); - ruotaAnno.setDisplayedValues( anniRuota ); - stilizza(ruotaAnno); - ruotaAnno.setOnValueChangedListener( ( picker, vecchio, nuovo ) -> - aggiorna( quale == 1 ? data1 : data2, ruotaGiorno, ruotaMese, ruotaSecolo, ruotaAnno ) - ); - } - - void stilizza( NumberPicker ruota ) { - // Toglie le famigerate linee divisorie azzurre - try { - Field campo = NumberPicker.class.getDeclaredField( "mSelectionDivider" ); - campo.setAccessible( true ); - campo.set( ruota, null ); - } catch( Exception e ) {} - // Risolve il bug https://issuetracker.google.com/issues/37055335 - ruota.setSaveFromParentEnabled(false); - } - - // Prende la stringa data, aggiorna le Date e ci modifica tutto l'editore data - // Chiamato quando clicco sul campo editabile, e dopo ogni editazione del testo - void impostaTutto() { - datatore.analizza( editaTesto.getText().toString() ); - ((CheckBox)findViewById( R.id.editadata_circa )).setChecked( datatore.kind == Kind.APPROXIMATE ); - ((TextView)findViewById( R.id.editadata_tipi )).setText( dateKinds[datatore.kind.ordinal()] ); - - // Primo carro - impostaCarro( data1, findViewById( R.id.prima_giorno ), findViewById( R.id.prima_mese ), - findViewById( R.id.prima_secolo ), findViewById( R.id.prima_anno ) ); - if( Global.settings.expert ) - impostaCecchi( data1 ); - - // Secondo carro - if( datatore.kind == Kind.BETWEEN_AND || datatore.kind == Kind.FROM_TO ) { - impostaCarro( data2, findViewById( R.id.seconda_giorno ), findViewById( R.id.seconda_mese ), - findViewById( R.id.seconda_secolo ), findViewById( R.id.seconda_anno ) ); - if( Global.settings.expert ) { - findViewById( R.id.editadata_seconda_avanzate ).setVisibility( VISIBLE ); - impostaCecchi( data2 ); - } - findViewById( R.id.editadata_seconda ).setVisibility( VISIBLE ); - } else { - findViewById( R.id.editadata_seconda_avanzate ).setVisibility( GONE ); - findViewById( R.id.editadata_seconda ).setVisibility( GONE ); - } - } - - // Gira le ruote di un carro in base a una data - void impostaCarro(Datatore.Data data, NumberPicker ruotaGiorno, NumberPicker ruotaMese, NumberPicker ruotaSecolo, NumberPicker ruotaAnno) { - calenda.clear(); - if( data.date != null ) - calenda.setTime(data.date); - ruotaGiorno.setMaxValue(calenda.getActualMaximum(Calendar.DAY_OF_MONTH)); - if( data.date != null && (data.isFormat(Format.D_M_Y) || data.isFormat(Format.D_M)) ) - ruotaGiorno.setValue(data.date.getDate()); - else - ruotaGiorno.setValue(0); - if( data.date == null || data.isFormat(Format.Y) ) - ruotaMese.setValue(0); - else - ruotaMese.setValue(data.date.getMonth() + 1); - if( data.date == null || data.isFormat(Format.D_M) ) - ruotaSecolo.setValue(0); - else - ruotaSecolo.setValue((data.date.getYear() + 1900) / 100); - if( data.date == null || data.isFormat(Format.D_M) ) - ruotaAnno.setValue(100); - else - ruotaAnno.setValue((data.date.getYear() + 1900) % 100); - } - - // Imposta i Checkbox per una data che può essere negativa e doppia - void impostaCecchi(Datatore.Data data) { - CheckBox ceccoBC, ceccoDoppia; - if( data.equals(data1) ) { - ceccoBC = findViewById(R.id.editadata_negativa1); - ceccoDoppia = findViewById(R.id.editadata_doppia1); - } else { - ceccoBC = findViewById(R.id.editadata_negativa2); - ceccoDoppia = findViewById(R.id.editadata_doppia2); - } - if( data.date == null || data.isFormat(Format.EMPTY) || data.isFormat(Format.D_M) ) { // date senza anno - ceccoBC.setVisibility(INVISIBLE); - ceccoDoppia.setVisibility(INVISIBLE); - } else { - ceccoBC.setChecked(data.negativa); - ceccoBC.setVisibility(VISIBLE); - ceccoDoppia.setChecked(data.doppia); - ceccoDoppia.setVisibility(VISIBLE); - } - } - - // Aggiorna una Data coi nuovi valori presi dalle ruote - void aggiorna( Datatore.Data data, NumberPicker ruotaGiorno, NumberPicker ruotaMese, NumberPicker ruotaSecolo, NumberPicker ruotaAnno ) { - if( tastieraVisibile ) { // Nasconde eventuale tastiera visibile - tastieraVisibile = tastiera.hideSoftInputFromWindow( editaTesto.getWindowToken(), 0 ); - // Nasconde subito la tastiera, ma ha bisogno di un secondo tentativo per restituire false. Comunque non è un problema - } - int giorno = ruotaGiorno.getValue(); - int mese = ruotaMese.getValue(); - int secolo = ruotaSecolo.getValue(); - int anno = ruotaAnno.getValue(); - // Imposta i giorni del mese in ruotaGiorno - calenda.set( secolo*100+anno, mese-1, 1 ); - ruotaGiorno.setMaxValue( calenda.getActualMaximum(Calendar.DAY_OF_MONTH) ); - if( data.date == null ) data.date = new Date(); - data.date.setDate( giorno == 0 ? 1 : giorno ); // altrimenti la data M_A arretra di un mese - data.date.setMonth( mese == 0 ? 0 : mese - 1 ); - data.date.setYear( anno == 100 ? -1899 : secolo*100 + anno - 1900 ); - if( giorno != 0 && mese != 0 && anno != 100 ) - data.format.applyPattern(Format.D_M_Y); - else if( giorno != 0 && mese != 0 ) - data.format.applyPattern(Format.D_M); - else if( mese != 0 && anno != 100 ) - data.format.applyPattern(Format.M_Y); - else if( anno != 100 ) - data.format.applyPattern(Format.Y); - else - data.format.applyPattern(Format.EMPTY); - impostaCecchi( data ); - veroImputTesto = false; - genera(); - } - - // Ricostruisce la stringa con la data finale e la mette in editaTesto - void genera() { - String rifatta; - if( datatore.kind == Kind.EXACT ) - rifatta = rifai(data1); - else if( datatore.kind == Kind.BETWEEN_AND ) - rifatta = "BET " + rifai(data1) + " AND " + rifai(data2); - else if( datatore.kind == Kind.FROM_TO ) - rifatta = "FROM " + rifai(data1) + " TO " + rifai(data2); - else if( datatore.kind == Kind.PHRASE ) { - // La frase viene sostituita da data esatta - datatore.kind = Kind.EXACT; - ((TextView)findViewById(R.id.editadata_tipi)).setText(dateKinds[0]); - rifatta = rifai(data1); - } else - rifatta = datatore.kind.prefix + " " + rifai(data1); - editaTesto.setText(rifatta); - } - - // Scrive la singola data in base al formato - String rifai( Datatore.Data data ) { - String fatta = ""; - if( data.date != null ) { - // Date con l'anno doppio - if( data.doppia && !(data.isFormat(Format.EMPTY) || data.isFormat(Format.D_M)) ) { - Date unAnnoDopo = new Date(); - unAnnoDopo.setYear( data.date.getYear() + 1 ); - String secondoAnno = String.format( Locale.ENGLISH, "%tY", unAnnoDopo ); - fatta = data.format.format( data.date ) +"/"+ secondoAnno.substring( 2 ); - } else // Le altre date normali - fatta = data.format.format( data.date ); - } - if( data.negativa ) - fatta += " B.C."; - return fatta; - } - - // Chiamato dall'esterno in sostanza solo per aggiungere le parentesi alla data-frase - void chiudi() { - if( datatore.kind == Kind.PHRASE ) { - editaTesto.setText("(" + editaTesto.getText() + ")"); - } - } - - String s(int id) { - return Global.context.getString(id); - } -} \ No newline at end of file diff --git a/app/src/main/java/app/familygem/Esportatore.java b/app/src/main/java/app/familygem/Esportatore.java deleted file mode 100644 index bd3e993d..00000000 --- a/app/src/main/java/app/familygem/Esportatore.java +++ /dev/null @@ -1,268 +0,0 @@ -package app.familygem; - -import android.content.Context; -import android.content.Intent; -import android.database.Cursor; -import android.net.Uri; -import android.provider.OpenableColumns; -import androidx.documentfile.provider.DocumentFile; -import org.apache.commons.io.FileUtils; -import org.folg.gedcom.model.Gedcom; -import org.folg.gedcom.model.Header; -import org.folg.gedcom.model.Media; -import org.folg.gedcom.model.Name; -import org.folg.gedcom.model.Person; -import org.folg.gedcom.visitors.GedcomWriter; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.zip.ZipEntry; -import java.util.zip.ZipOutputStream; -import app.familygem.visitor.ListaMedia; - -public class Esportatore { - - private final Context contesto; - private int idAlbero; - private Gedcom gc; - private Uri targetUri; - public String messaggioErrore; // Messaggio di eventuale errore - public String messaggioSuccesso; // Messaggio del risultato ottenuto - - Esportatore(Context context) { - this.contesto = context; - } - - // Apre l'albero Json e restituisce true se c'è riuscito - public boolean apriAlbero(int idAlbero) { - this.idAlbero = idAlbero; - gc = Alberi.apriGedcomTemporaneo(idAlbero, true); - if( gc == null ) { - return errore(R.string.no_useful_data); - } - return true; - } - - // Scrive il solo GEDCOM nell'URI - public boolean esportaGedcom(Uri targetUri) { - this.targetUri = targetUri; - aggiornaTestata(estraiNome(targetUri)); - ottimizzaGedcom(); - GedcomWriter scrittore = new GedcomWriter(); - File fileGc = new File(contesto.getCacheDir(), "temp.ged"); - try { - scrittore.write(gc, fileGc); - OutputStream out = contesto.getContentResolver().openOutputStream(targetUri); - FileUtils.copyFile(fileGc, out); - out.flush(); - out.close(); - } catch( Exception e ) { - return errore(e.getLocalizedMessage()); - } - // Rende il file visibile da Windows - // Ma pare inefficace in KitKat in cui il file rimane invisibile - contesto.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, targetUri)); - Global.gc = Alberi.readJson(idAlbero); // Resetta le modifiche - return successo(R.string.gedcom_exported_ok); - } - - // Scrive il GEDCOM con i media in un file ZIP - public boolean esportaGedcomZippato(Uri targetUri) { - this.targetUri = targetUri; - // Crea il file GEDCOM - String titolo = Global.settings.getTree(idAlbero).title; - String nomeFileGedcom = titolo.replaceAll("[\\\\/:*?\"<>|'$]", "_") + ".ged"; - aggiornaTestata(nomeFileGedcom); - ottimizzaGedcom(); - GedcomWriter scrittore = new GedcomWriter(); - File fileGc = new File(contesto.getCacheDir(), nomeFileGedcom); - try { - scrittore.write(gc, fileGc); - } catch( Exception e ) { - return errore(e.getLocalizedMessage()); - } - DocumentFile gedcomDocument = DocumentFile.fromFile(fileGc); - // Aggiunge il GEDCOM alla raccolta di file media - Map raccolta = raccogliMedia(); - raccolta.put(gedcomDocument, 0); - if( !creaFileZip(raccolta) ) - return false; - contesto.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, targetUri)); - Global.gc = Alberi.readJson(idAlbero); - return successo(R.string.zip_exported_ok); - } - - // Crea un file zippato con l'albero, i settaggi e i media - public boolean esportaBackupZip(String radice, int grado, Uri targetUri) { - this.targetUri = targetUri; - // Media - Map files = raccogliMedia(); - // Json dell'albero - File fileTree = new File(contesto.getFilesDir(), idAlbero + ".json"); - files.put(DocumentFile.fromFile(fileTree), 1); - // Json delle preferenze - Settings.Tree tree = Global.settings.getTree(idAlbero); - if( radice == null ) radice = tree.root; - if( grado < 0 ) grado = tree.grade; - // String titoloAlbero, String radice, int grado possono arrivare diversi da Condividi - Settings.ZippedTree settaggi = new Settings.ZippedTree( - tree.title, tree.persons, tree.generations, radice, tree.shares, grado); - File fileSettings = settaggi.salva(); - files.put(DocumentFile.fromFile(fileSettings), 0); - if( !creaFileZip(files) ) - return false; - contesto.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, targetUri)); - return successo(R.string.zip_exported_ok); - } - - // Restituisce il numero di file media da allegare - public int quantiFileMedia() { - ListaMedia visitaMedia = new ListaMedia( gc, 0 ); - gc.accept( visitaMedia ); - int quantiFile = 0; - for( Media med : visitaMedia.lista ) { - if( F.percorsoMedia(idAlbero, med) != null || F.uriMedia( idAlbero, med ) != null ) - quantiFile++; - } - return quantiFile; - } - - // Riceve l'id di un albero e restituisce una Map di DocumentFile dei media che riesce a rastrellare - private Map raccogliMedia() { - ListaMedia visitaMedia = new ListaMedia( gc, 0 ); - gc.accept( visitaMedia ); - /* Capita che diversi Media puntino allo stesso file. - * E potrebbe anche capitare che diversi percorsi finiscano con nomi di file uguali, - * ad es. 'percorsoA/img.jpg' 'percorsoB/img.jpg' - * Bisogna evitare che nei media dello ZIP finiscano file con lo stesso nome. - * Questo loop crea una lista di percorsi con nome file univoci */ - Set paths = new HashSet<>(); - Set onlyFileNames = new HashSet<>(); // Nomi file di controllo - for( Media med : visitaMedia.lista ) { - String path = med.getFile(); - if( path != null && !path.isEmpty() ) { - String fileName = path.replace('\\', '/'); - if( fileName.lastIndexOf('/') > -1 ) - fileName = fileName.substring(fileName.lastIndexOf('/') + 1); - if( !onlyFileNames.contains(fileName) ) - paths.add(path); - onlyFileNames.add(fileName); - } - } - Map collezione = new HashMap<>(); - for( String path : paths ) { - Media med = new Media(); - med.setFile(path); - // Paths - String percorsoMedia = F.percorsoMedia(idAlbero, med); - if( percorsoMedia != null ) - collezione.put(DocumentFile.fromFile(new File(percorsoMedia)), 2); // todo canRead() ? - else { // URIs - Uri uriMedia = F.uriMedia(idAlbero, med); - if( uriMedia != null ) - collezione.put(DocumentFile.fromSingleUri(contesto, uriMedia), 2); - } - } - return collezione; - } - - private void aggiornaTestata(String nomeFileGedcom) { - Header testa = gc.getHeader(); - if( testa == null ) - gc.setHeader(AlberoNuovo.creaTestata(nomeFileGedcom)); - else { - testa.setFile(nomeFileGedcom); - testa.setDateTime(U.actualDateTime()); - } - } - - // Migliora il GEDCOM per l'esportazione - void ottimizzaGedcom() { - // Value dei nomi da given e surname - for( Person pers : gc.getPeople() ) { - for( Name n : pers.getNames() ) - if( n.getValue() == null && (n.getPrefix() != null || n.getGiven() != null - || n.getSurname() != null || n.getSuffix() != null) ) { - String epiteto = ""; - if( n.getPrefix() != null ) - epiteto = n.getPrefix(); - if( n.getGiven() != null ) - epiteto += " " + n.getGiven(); - if( n.getSurname() != null ) - epiteto += " /" + n.getSurname() + "/"; - if( n.getSuffix() != null ) - epiteto += " " + n.getSuffix(); - n.setValue( epiteto.trim() ); - } - } - } - - // Estrae solo il nome del file da un URI - private String estraiNome( Uri uri ) { - // file:// - if( uri.getScheme() != null && uri.getScheme().equalsIgnoreCase("file") ) { - return uri.getLastPathSegment(); - } - // Cursore (di solito funziona questo) - Cursor cursore = contesto.getContentResolver().query( uri, null, null, null, null); - if( cursore != null && cursore.moveToFirst() ) { - int indice = cursore.getColumnIndex( OpenableColumns.DISPLAY_NAME ); - String nomeFile = cursore.getString( indice ); - cursore.close(); - if( nomeFile != null ) return nomeFile; - } - // DocumentFile - DocumentFile document = DocumentFile.fromSingleUri( contesto, targetUri ); - String nomeFile = document.getName(); - if( nomeFile != null ) return nomeFile; - // Alla frutta - return "tree.ged"; - } - - // Riceve la lista di DocumentFile e li mette in un file ZIP scritto nel targetUri - // Restiuisce messaggio di errore o null se tutto a posto - boolean creaFileZip(Map files) { - byte[] buffer = new byte[128]; - try { - ZipOutputStream zos = new ZipOutputStream(contesto.getContentResolver().openOutputStream(targetUri)); - for( Map.Entry fileTipo : files.entrySet() ) { - DocumentFile file = fileTipo.getKey(); - InputStream input = contesto.getContentResolver().openInputStream(file.getUri()); - String nomeFile = file.getName(); // File che non vengono rinominati ('settings.json', 'famiglia.ged') - if( fileTipo.getValue() == 1 ) - nomeFile = "tree.json"; - else if( fileTipo.getValue() == 2 ) - nomeFile = "media/" + file.getName(); - zos.putNextEntry(new ZipEntry(nomeFile)); - int read; - while( (read = input.read(buffer)) != -1 ) { - zos.write(buffer, 0, read); - } - zos.closeEntry(); - input.close(); - } - zos.close(); - } catch( IOException e ) { - return errore(e.getLocalizedMessage()); - } - return true; - } - - public boolean successo( int messaggio ) { - messaggioSuccesso = contesto.getString( messaggio ); - return true; - } - - public boolean errore(int error) { - return errore(contesto.getString(error)); - } - public boolean errore(String error) { - messaggioErrore = error; - return false; - } -} diff --git a/app/src/main/java/app/familygem/Estensione.java b/app/src/main/java/app/familygem/Estensione.java deleted file mode 100644 index 01f652c5..00000000 --- a/app/src/main/java/app/familygem/Estensione.java +++ /dev/null @@ -1,14 +0,0 @@ -package app.familygem; - -import org.folg.gedcom.model.GedcomTag; - -public class Estensione { - String nome; - String testo; - GedcomTag gedcomTag; - public Estensione( String nome, String testo, GedcomTag gedcomTag ) { - this.nome = nome; - this.testo = testo; - this.gedcomTag = gedcomTag; - } -} diff --git a/app/src/main/java/app/familygem/Exporter.java b/app/src/main/java/app/familygem/Exporter.java new file mode 100644 index 00000000..6b4fe5f5 --- /dev/null +++ b/app/src/main/java/app/familygem/Exporter.java @@ -0,0 +1,303 @@ +package app.familygem; + +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.net.Uri; +import android.provider.OpenableColumns; + +import androidx.documentfile.provider.DocumentFile; + +import org.apache.commons.io.FileUtils; +import org.folg.gedcom.model.Gedcom; +import org.folg.gedcom.model.Header; +import org.folg.gedcom.model.Media; +import org.folg.gedcom.model.Name; +import org.folg.gedcom.model.Person; +import org.folg.gedcom.visitors.GedcomWriter; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +import app.familygem.visitor.MediaList; + +public class Exporter { + + private final Context context; + private int treeId; + private Gedcom gc; + private Uri targetUri; + public String errorMessage; // Message of any error + public String successMessage;// Message of the result obtained + + Exporter(Context context) { + this.context = context; + } + + /** + * Opens the Json tree and returns true if successful + */ + public boolean openTree(int treeId) { + this.treeId = treeId; + gc = TreesActivity.openGedcomTemporarily(treeId, true); + if (gc == null) { + return error(R.string.no_useful_data); + } + return true; + } + + /** + * Writes only GEDCOM in the URI + * Scrive il solo GEDCOM nell'URI + */ + public boolean exportGedcom(Uri targetUri) { + this.targetUri = targetUri; + updateHeader(extractFilename(targetUri)); + optimizeGedcom(); + GedcomWriter writer = new GedcomWriter(); + File fileGc = new File(context.getCacheDir(), "temp.ged"); + try { + writer.write(gc, fileGc); + OutputStream out = context.getContentResolver().openOutputStream(targetUri); + FileUtils.copyFile(fileGc, out); + out.flush(); + out.close(); + } catch (Exception e) { + return error(e.getLocalizedMessage()); + } + + // Make the file visible from Windows // Rende il file visibile da Windows + // But it seems ineffective in KitKat where the file remains invisible // Ma pare inefficace in KitKat in cui il file rimane invisibile + context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, targetUri)); + Global.gc = TreesActivity.readJson(treeId); // Reset the changes + return success(R.string.gedcom_exported_ok); + } + + /** + * Writes the GEDCOM with the media in a ZIP file + */ + public boolean exportGedcomToZip(Uri targetUri) { + this.targetUri = targetUri; + // Create the GEDCOM file + String title = Global.settings.getTree(treeId).title; + String filename = title.replaceAll("[\\\\/:*?\"<>|'$]", "_") + ".ged"; + updateHeader(filename); + optimizeGedcom(); + GedcomWriter writer = new GedcomWriter(); + File fileGc = new File(context.getCacheDir(), filename); + try { + writer.write(gc, fileGc); + } catch (Exception e) { + return error(e.getLocalizedMessage()); + } + DocumentFile gedcomDocument = DocumentFile.fromFile(fileGc); + // Add the GEDCOM to the media file collection + Map collection = collectMedia(); + collection.put(gedcomDocument, 0); + if (!createZipFile(collection)) + return false; + context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, targetUri)); + Global.gc = TreesActivity.readJson(treeId); + return success(R.string.zip_exported_ok); + } + + /** + * Create a zipped file with the tree, settings and media + */ + public boolean exportBackupZip(String root, int grade, Uri targetUri) { + this.targetUri = targetUri; + // Media + Map files = collectMedia(); + // Tree's json + File fileTree = new File(context.getFilesDir(), treeId + ".json"); + files.put(DocumentFile.fromFile(fileTree), 1); + // Preference's json + Settings.Tree tree = Global.settings.getTree(treeId); + if (root == null) root = tree.root; + if (grade < 0) grade = tree.grade; + // String titleTree, String root, int degree can arrive other than Share // String titoloAlbero, String radice, int grado possono arrivare diversi da Condividi + Settings.ZippedTree settings = new Settings.ZippedTree( + tree.title, tree.persons, tree.generations, root, tree.shares, grade); + File fileSettings = settings.save(); + files.put(DocumentFile.fromFile(fileSettings), 0); + if (!createZipFile(files)) + return false; + context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, targetUri)); + return success(R.string.zip_exported_ok); + } + + /** + * Returns the number of media files to attach + */ + public int numMediaFilesToAttach() { + MediaList mediaList = new MediaList(gc, 0); + gc.accept(mediaList); + int numFiles = 0; + for (Media med : mediaList.list) { + if (F.mediaPath(treeId, med) != null || F.mediaUri(treeId, med) != null) + numFiles++; + } + return numFiles; + } + + /** + * Receives the id of a tree and gets a DocumentFile Map of the media that it manages to find + *

+ * Riceve l'id di un albero e arriva una Map di DocumentFile dei media che riesce a rastrellare + */ + private Map collectMedia() { + MediaList mediaList = new MediaList(gc, 0); + gc.accept(mediaList); + + /* It happens that different Media point to the same file. + * And it could also happen that different paths end up with the same filenames, + * eg. 'pathA / img.jpg' 'pathB / img.jpg' + * You must avoid that files with the same name end up in the ZIP media. + * This loop creates a list of paths with unique filenames */ + + /* Capita che diversi Media puntino allo stesso file. + * E potrebbe anche capitare che diversi percorsi finiscano con nomi di file uguali, + * ad es. 'percorsoA/img.jpg' 'percorsoB/img.jpg' + * Bisogna evitare che nei media dello ZIP finiscano file con lo stesso nome. + * Questo loop crea una lista di percorsi con nome file univoci */ + + Set paths = new HashSet<>(); + Set onlyFileNames = new HashSet<>(); // Control file names //Nomi file di controllo + for (Media med : mediaList.list) { + String path = med.getFile(); + if (path != null && !path.isEmpty()) { + String fileName = path.replace('\\', '/'); + if (fileName.lastIndexOf('/') > -1) + fileName = fileName.substring(fileName.lastIndexOf('/') + 1); + if (!onlyFileNames.contains(fileName)) + paths.add(path); + onlyFileNames.add(fileName); + } + } + Map collection = new HashMap<>(); + for (String path : paths) { + Media med = new Media(); + med.setFile(path); + // Paths + String mediaPath = F.mediaPath(treeId, med); + if (mediaPath != null) + collection.put(DocumentFile.fromFile(new File(mediaPath)), 2); // todo canRead() ? + else { // URIs + Uri uriMedia = F.mediaUri(treeId, med); + if (uriMedia != null) + collection.put(DocumentFile.fromSingleUri(context, uriMedia), 2); + } + } + return collection; + } + + private void updateHeader(String gedcomFilename) { + Header header = gc.getHeader(); + if (header == null) + gc.setHeader(NewTree.createHeader(gedcomFilename)); + else { + header.setFile(gedcomFilename); + header.setDateTime(U.actualDateTime()); + } + } + + /** + * Enhance GEDCOM for export + * */ + void optimizeGedcom() { + // Value of names from given and surname + for (Person pers : gc.getPeople()) { + for (Name n : pers.getNames()) + if (n.getValue() == null && (n.getPrefix() != null || n.getGiven() != null + || n.getSurname() != null || n.getSuffix() != null)) { + String epiteto = ""; //TODO replace with stringbuilder + if (n.getPrefix() != null) + epiteto = n.getPrefix(); + if (n.getGiven() != null) + epiteto += " " + n.getGiven(); + if (n.getSurname() != null) + epiteto += " /" + n.getSurname() + "/"; + if (n.getSuffix() != null) + epiteto += " " + n.getSuffix(); + n.setValue(epiteto.trim()); + } + } + } + + /** + * Extracts only the filename from a URI + * */ + private String extractFilename(Uri uri) { + // file:// + if (uri.getScheme() != null && uri.getScheme().equalsIgnoreCase("file")) { + return uri.getLastPathSegment(); + } + // Cursor (this usually works) + Cursor cursor = context.getContentResolver().query(uri, null, null, null, null); + if (cursor != null && cursor.moveToFirst()) { + int index = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); + String filename = cursor.getString(index); + cursor.close(); + if (filename != null) return filename; + } + // DocumentFile + DocumentFile document = DocumentFile.fromSingleUri(context, targetUri); + String filename = document.getName(); + if (filename != null) return filename; + // Not much else to do + return "tree.ged"; + } + + /** + * Get the list of DocumentFiles and put them in a ZIP file written to the targetUri + * Return error message or null if all is well + * */ + boolean createZipFile(Map files) { + byte[] buffer = new byte[128]; + try { + ZipOutputStream zos = new ZipOutputStream(context.getContentResolver().openOutputStream(targetUri)); + for (Map.Entry fileType : files.entrySet()) { + DocumentFile file = fileType.getKey(); + InputStream input = context.getContentResolver().openInputStream(file.getUri()); + String filename = file.getName(); //Files that are not renamed ('settings.json', 'family.ged') // File che non vengono rinominati ('settings.json', 'famiglia.ged') + if (fileType.getValue() == 1) + filename = "tree.json"; + else if (fileType.getValue() == 2) + filename = "media/" + file.getName(); + zos.putNextEntry(new ZipEntry(filename)); + int read; + while ((read = input.read(buffer)) != -1) { + zos.write(buffer, 0, read); + } + zos.closeEntry(); + input.close(); + } + zos.close(); + } catch (IOException e) { + return error(e.getLocalizedMessage()); + } + return true; + } + + public boolean success(int message) { + successMessage = context.getString(message); + return true; + } + + public boolean error(int error) { + return error(context.getString(error)); + } + + public boolean error(String error) { + errorMessage = error; + return false; + } +} diff --git a/app/src/main/java/app/familygem/Extension.java b/app/src/main/java/app/familygem/Extension.java new file mode 100644 index 00000000..cb233804 --- /dev/null +++ b/app/src/main/java/app/familygem/Extension.java @@ -0,0 +1,14 @@ +package app.familygem; + +import org.folg.gedcom.model.GedcomTag; + +public class Extension { + String name; + String text; + GedcomTag gedcomTag; + public Extension(String name, String text, GedcomTag gedcomTag ) { + this.name = name; + this.text = text; + this.gedcomTag = gedcomTag; + } +} diff --git a/app/src/main/java/app/familygem/F.java b/app/src/main/java/app/familygem/F.java index 910bcbd8..676cf620 100644 --- a/app/src/main/java/app/familygem/F.java +++ b/app/src/main/java/app/familygem/F.java @@ -1,5 +1,3 @@ -// Funzioni statiche per gestire file e media - package app.familygem; import android.Manifest; @@ -33,6 +31,7 @@ import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.Toast; + import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; @@ -40,12 +39,14 @@ import androidx.core.content.FileProvider; import androidx.documentfile.provider.DocumentFile; import androidx.fragment.app.Fragment; + import com.google.gson.JsonPrimitive; import com.squareup.picasso.Callback; import com.squareup.picasso.Picasso; import com.squareup.picasso.RequestCreator; import com.theartofdev.edmodo.cropper.CropImage; import com.theartofdev.edmodo.cropper.CropImageView; + import org.apache.commons.io.FileUtils; import org.apache.commons.io.FilenameUtils; import org.folg.gedcom.model.Gedcom; @@ -56,756 +57,830 @@ import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; + import java.io.File; import java.io.InputStream; import java.net.URL; import java.net.URLConnection; import java.util.ArrayList; import java.util.List; -import app.familygem.detail.Immagine; -import app.familygem.visitor.ListaMedia; +import app.familygem.detail.ImageActivity; +import app.familygem.visitor.MediaList; + +/** + * Static functions to manage files and media + */ public class F { - // Impacchettamento per ricavare una cartella in KitKat - static String uriPercorsoCartellaKitKat( Context contesto, Uri uri ) { - String percorso = uriPercorsoFile( uri ); - if( percorso != null && percorso.lastIndexOf('/') > 0 ) { - return percorso.substring( 0, percorso.lastIndexOf('/') ); - } else { - Toast.makeText(contesto, "Could not get this position.", Toast.LENGTH_SHORT).show(); - return null; - } - } - - // Riceve un Uri e cerca di restituire il percorso del file - // Versione commentata in lab - static String uriPercorsoFile( Uri uri ) { - if( uri == null ) return null; - if( uri.getScheme() != null && uri.getScheme().equalsIgnoreCase("file") ) { - // Toglie 'file://' - return uri.getPath(); - } - switch( uri.getAuthority() ) { - case "com.android.externalstorage.documents": // memoria interna e scheda SD - String[] split = uri.getLastPathSegment().split(":"); - if( split[0].equalsIgnoreCase("primary")) { - // Storage principale - String percorso = Environment.getExternalStorageDirectory() + "/" + split[1]; - if( new File(percorso).canRead() ) - return percorso; - } else if( split[0].equalsIgnoreCase("home") ) { - // Cartella 'Documents' in Android 9 e 10 - return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS) + "/" + split[1]; - } else { - // Tutti gli altri casi, tra cui schede SD - File[] luoghi = Global.context.getExternalFilesDirs(null); - for( File luogo : luoghi ) { - if( luogo.getAbsolutePath().indexOf("/Android") > 0 ) { - String dir = luogo.getAbsolutePath().substring(0, luogo.getAbsolutePath().indexOf("/Android")); - File trovando = new File( dir, split[1] ); - if( trovando.canRead() ) - return trovando.getAbsolutePath(); - } - } - } - break; - case "com.android.providers.downloads.documents": // file dalla cartella Download - String id = uri.getLastPathSegment(); - if( id.startsWith( "raw:/" ) ) - return id.replaceFirst("raw:", ""); - if( id.matches("\\d+") ) { - String[] contentUriPrefixesToTry = new String[] { - "content://downloads/public_downloads", - "content://downloads/my_downloads" - }; - for( String contentUriPrefix : contentUriPrefixesToTry ) { - Uri uriRicostruito = ContentUris.withAppendedId( Uri.parse(contentUriPrefix), Long.parseLong(id) ); - try { - String nomeFile = trovaNomeFile( uriRicostruito ); - if( nomeFile != null ) - return nomeFile; - } catch(Exception e) {} - } - } - } - return trovaNomeFile( uri ); - } - - // Riceve l'URI (eventualmente ricostruito) di un file preso con SAF - // Se riesce restituisce il percorso completo, altrimenti il singolo nome del file - private static String trovaNomeFile( Uri uri ) { - Cursor cursore = Global.context.getContentResolver().query( uri, null, null, null, null); - if( cursore != null && cursore.moveToFirst() ) { - int indice = cursore.getColumnIndex( MediaStore.Files.FileColumns.DATA ); - if( indice < 0 ) - indice = cursore.getColumnIndex( OpenableColumns.DISPLAY_NAME ); - String nomeFile = cursore.getString( indice ); - cursore.close(); - return nomeFile; - } - return null; - } - - // Riceve un tree Uri ricavato con ACTION_OPEN_DOCUMENT_TREE e cerca di restituire il percorso della cartella - // altrimenti tranquillamente restituisce null - public static String uriPercorsoCartella( Uri uri ) { - if( uri == null ) return null; - if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ) { - String treeDocId = DocumentsContract.getTreeDocumentId( uri ); - switch( uri.getAuthority() ) { - case "com.android.externalstorage.documents": // memoria interna e scheda SD - String[] split = treeDocId.split(":"); - String percorso = null; - // Storage principale - if( split[0].equalsIgnoreCase("primary") ) { - percorso = Environment.getExternalStorageDirectory().getAbsolutePath(); - } - // Documents in Android 9 e 10 - else if( split[0].equalsIgnoreCase("home") ) { - percorso = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS).getAbsolutePath(); - } - // Tutti gli altri, tipo la scheda SD - else { - File[] filesDirs = Global.context.getExternalFilesDirs(null); - for( File dir : filesDirs ) { - String altro = dir.getAbsolutePath(); - if( altro.contains(split[0])) { - percorso = altro.substring( 0, altro.indexOf("/Android") ); - break; - } - } - } - if( percorso != null ) { - if( split.length > 1 && !split[1].isEmpty() ) - percorso += "/" + split[1]; - return percorso; - } - break; - case "com.android.providers.downloads.documents": // provider Downloads e sottocartelle - if( treeDocId.equals("downloads") ) - return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath(); - if( treeDocId.startsWith("raw:/") ) - return treeDocId.replaceFirst("raw:", ""); - } - } - return null; - } - - // Fa salvare un documento (PDF, GEDCOM, ZIP) con SAF - static void salvaDocumento( Activity attivita, Fragment frammento, int idAlbero, String mime, String ext, int requestCode ) { - String nome = Global.settings.getTree(idAlbero).title; - // GEDCOM deve esplicitare l'estensione, gli altri la mettono in base al mime type - ext = ext.equals("ged") ? ".ged" : ""; - // rimpiazza caratteri pericolosi per il filesystem di Android che non vengono ripiazzati da Android stesso - nome = nome.replaceAll( "[$']", "_" ); - Intent intent = new Intent( Intent.ACTION_CREATE_DOCUMENT ) - .addCategory( Intent.CATEGORY_OPENABLE ) - .setType( mime ) - .putExtra( Intent.EXTRA_TITLE, nome + ext ); - if( attivita != null ) - attivita.startActivityForResult( intent, requestCode ); - else - frammento.startActivityForResult( intent, requestCode ); - } - - // Metodi per mostrare immagini: - - // Riceve una Person e sceglie il Media principale da cui ricavare l'immagine - static void unaFoto( Gedcom gc, Person p, ImageView img ) { - ListaMedia visita = new ListaMedia( gc, 0 ); - p.accept( visita ); - boolean trovatoQualcosa = false; - for( Media med : visita.lista ) { // Cerca un media contrassegnato Primario Y - if( med.getPrimary() != null && med.getPrimary().equals("Y") ) { - dipingiMedia( med, img, null ); - trovatoQualcosa = true; - break; - } - } - if( !trovatoQualcosa ) { // In alternativa restituisce il primo che trova - for( Media med : visita.lista ) { - dipingiMedia( med, img, null ); - trovatoQualcosa = true; - break; - } - } - img.setVisibility( trovatoQualcosa ? View.VISIBLE : View.GONE ); - } - - // Mostra le immagini con Picasso - public static void dipingiMedia( Media media, ImageView imageView, ProgressBar circo ) { - int idAlbero; - // Confrontatore ha bisogno dell'id dell'albero nuovo per cercare nella sua cartella - View probabile = null; - if( imageView.getParent() != null && imageView.getParent().getParent() != null ) - probabile = (View) imageView.getParent().getParent().getParent(); - if( probabile != null && probabile.getId() == R.id.confronto_nuovo ) - idAlbero = Global.treeId2; - else idAlbero = Global.settings.openTree; - String percorso = percorsoMedia(idAlbero, media); - Uri[] uri = new Uri[1]; - if( percorso == null ) - uri[0] = uriMedia(idAlbero, media); - if( circo != null ) circo.setVisibility(View.VISIBLE); - imageView.setTag(R.id.tag_tipo_file, 0); - if( percorso != null || uri[0] != null ) { - RequestCreator creator; - if( percorso != null ) - creator = Picasso.get().load("file://" + percorso); - else - creator = Picasso.get().load(uri[0]); - creator.placeholder(R.drawable.image) - .fit() - .centerInside() - .into(imageView, new Callback() { - @Override - public void onSuccess() { - if( circo != null ) circo.setVisibility(View.GONE); - imageView.setTag(R.id.tag_tipo_file, 1); - imageView.setTag(R.id.tag_percorso, percorso); // 'percorso' o 'uri' uno dei 2 è valido, l'altro è null - imageView.setTag(R.id.tag_uri, uri[0]); - // Nella pagina Dettaglio Immagine ricarica il menu opzioni per mostrare il comando Crop - if( imageView.getId() == R.id.immagine_foto ) { - if( imageView.getContext() instanceof Activity ) // In KitKat è instance di TintContextWrapper - ((Activity)imageView.getContext()).invalidateOptionsMenu(); - } - } - @Override - public void onError( Exception e ) { - // Magari è un video da cui ricavare una thumbnail - Bitmap bitmap = null; - try { // Ultimamente questi generatori di thumbnail inchiodano, quindi meglio pararsi il culo - bitmap = ThumbnailUtils.createVideoThumbnail( percorso, MediaStore.Video.Thumbnails.MINI_KIND ); - // Tramite l'URI - if( bitmap == null && uri[0] != null ) { - MediaMetadataRetriever mMR = new MediaMetadataRetriever(); - mMR.setDataSource( Global.context, uri[0] ); - bitmap = mMR.getFrameAtTime(); - } - } catch( Exception excpt ) {} - imageView.setTag(R.id.tag_tipo_file, 2); - if( bitmap == null ) { - // un File locale senza anteprima - String formato = media.getFormat(); - if( formato == null ) - formato = percorso != null ? MimeTypeMap.getFileExtensionFromUrl(percorso.replaceAll("[^a-zA-Z0-9./]", "_")) : ""; - // Rimuove gli spazi bianchi che non fanno trovare l'estensione - if( formato.isEmpty() && uri[0] != null ) - formato = MimeTypeMap.getFileExtensionFromUrl( uri[0].getLastPathSegment() ); - bitmap = generaIcona( imageView, R.layout.media_file, formato ); - imageView.setScaleType( ImageView.ScaleType.FIT_CENTER ); - if( imageView.getParent() instanceof RelativeLayout && // brutto ma efficace - ((RelativeLayout)imageView.getParent()).findViewById(R.id.media_testo) != null ) { - RelativeLayout.LayoutParams parami = new RelativeLayout.LayoutParams( - RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT ); - parami.addRule( RelativeLayout.ABOVE, R.id.media_testo ); - imageView.setLayoutParams( parami ); - } - imageView.setTag( R.id.tag_tipo_file, 3 ); - } - imageView.setImageBitmap(bitmap); - imageView.setTag( R.id.tag_percorso, percorso ); - imageView.setTag( R.id.tag_uri, uri[0] ); - if( circo!=null ) circo.setVisibility( View.GONE ); - } - }); - } else if( media.getFile() != null && !media.getFile().isEmpty() ) { // magari è un'immagine in internet - String percorsoFile = media.getFile(); - Picasso.get().load(percorsoFile).fit() - .placeholder(R.drawable.image).centerInside() - .into(imageView, new Callback() { - @Override - public void onSuccess() { - if( circo != null ) circo.setVisibility(View.GONE); - imageView.setTag(R.id.tag_tipo_file, 1); - try { - new ImboscaImmagine(media).execute(new URL(percorsoFile)); - } catch( Exception e ) {} - } - @Override - public void onError( Exception e ) { - // Proviamo con una pagina web - new ZuppaMedia(imageView, circo, media).execute(percorsoFile); - } - }); - } else { // Media privo di collegamento a un file - if( circo != null ) circo.setVisibility(View.GONE); - imageView.setImageResource(R.drawable.image); - } - } - - // Riceve un Media, cerca il file in locale con diverse combinazioni di percorso e restituisce l'indirizzo - public static String percorsoMedia( int idAlbero, Media m ) { - String file = m.getFile(); - if( file != null && !file.isEmpty() ) { - String nome = file.replace("\\", "/"); - // Percorso FILE (quello nel gedcom) - if( new File(nome).canRead() ) - return nome; - for( String dir : Global.settings.getTree( idAlbero ).dirs ) { - // Cartella media + percorso FILE - String percorso = dir + '/' + nome; - File prova = new File(percorso); - /* Todo Talvolta File.isFile() produce un ANR, tipo https://stackoverflow.com/questions/224756 - Ho provato con vari percorsi inesistenti, tipo la scheda SD rimossa, o con caratteri assurdi, - ma tutti restituiscono semplicemente false. - Probabilmente l'ANR è quando il percorso punta a una risorsa esistente che però attende per tempo indefinito. */ - if( prova.isFile() && prova.canRead() ) - return percorso; - // Cartella media + nome del FILE - percorso = dir + '/' + new File(nome).getName(); - prova = new File(percorso); - if( prova.isFile() && prova.canRead() ) - return percorso; - } - Object stringa = m.getExtension("cache"); - // A volte è String a volte JsonPrimitive, non ho capito bene perché - if( stringa != null ) { - String percorsoCache; - if( stringa instanceof String ) - percorsoCache = (String) stringa; - else - percorsoCache = ((JsonPrimitive)stringa).getAsString(); - if( new File(percorsoCache).isFile() ) - return percorsoCache; - } - } - return null; - } - - // Riceve un Media, cerca il file in locale negli eventuali tree-URI e restituisce l'URI - public static Uri uriMedia( int idAlbero, Media m ) { - String file = m.getFile(); - if( file != null && !file.isEmpty() ) { - // OBJE.FILE non è mai un Uri, sempre un percorso (Windows o Android) - String nomeFile = new File(file.replace("\\", "/")).getName(); - for( String uri : Global.settings.getTree(idAlbero).uris ) { - DocumentFile documentDir = DocumentFile.fromTreeUri( Global.context, Uri.parse(uri) ); - DocumentFile docFile = documentDir.findFile( nomeFile ); - if( docFile != null && docFile.isFile() ) - return docFile.getUri(); - } - } - return null; - } - - static Bitmap generaIcona( ImageView vista, int icona, String testo ) { - LayoutInflater inflater = (LayoutInflater) vista.getContext().getSystemService( Context.LAYOUT_INFLATER_SERVICE ); - View inflated = inflater.inflate( icona, null ); - RelativeLayout frameLayout = inflated.findViewById( R.id.icona ); - ((TextView)frameLayout.findViewById( R.id.icona_testo ) ).setText( testo ); - frameLayout.setDrawingCacheEnabled( true ); - frameLayout.measure( View.MeasureSpec.makeMeasureSpec( 0, View.MeasureSpec.UNSPECIFIED ), - View.MeasureSpec.makeMeasureSpec( 0, View.MeasureSpec.UNSPECIFIED ) ); - frameLayout.layout( 0, 0, frameLayout.getMeasuredWidth(), frameLayout.getMeasuredHeight() ); - frameLayout.buildDrawingCache( true ); - return frameLayout.getDrawingCache(); - } - - // Salva in cache un'immagine trovabile in internet per poi riusarla - // todo? forse potrebbe anche non essere un task asincrono ma una semplice funzione - static class ImboscaImmagine extends AsyncTask { - Media media; - ImboscaImmagine( Media media ) { - this.media = media; - } - protected String doInBackground( URL... url ) { - try { - File cartellaCache = new File( Global.context.getCacheDir().getPath() + "/" + Global.settings.openTree); - if( !cartellaCache.exists() ) { - // Elimina extension "cache" da tutti i Media - ListaMedia visitaMedia = new ListaMedia( Global.gc, 0 ); - Global.gc.accept( visitaMedia ); - for( Media media : visitaMedia.lista ) - if( media.getExtension("cache") != null ) - media.putExtension( "cache", null ); - cartellaCache.mkdir(); - } - String estensione = FilenameUtils.getName( url[0].getPath() ); - if( estensione.lastIndexOf('.') > 0 ) - estensione = estensione.substring( estensione.lastIndexOf('.')+1 ); - String ext; - switch( estensione ) { - case "png": - ext = "png"; - break; - case "gif": - ext = "gif"; - break; - case "bmp": - ext = "bmp"; - break; - case "jpg": - case "jpeg": - default: - ext = "jpg"; - } - File cache = fileNomeProgressivo( cartellaCache.getPath(), "img." + ext ); - FileUtils.copyURLToFile( url[0], cache ); - return cache.getPath(); - } catch( Exception e ) { - e.printStackTrace(); - } - return null; - } - protected void onPostExecute( String percorso) { - if( percorso != null ) - media.putExtension( "cache", percorso ); - } - } - - // Scarica asincronicamente un'immagine da una pagina internet - static class ZuppaMedia extends AsyncTask { - ImageView vistaImmagine; - ProgressBar circo; - Media media; - URL url; - int tagTipoFile = 0; // setTag deve stare nel thread principale, non nel doInBackground - int vistaImmagineWidth; // idem - ZuppaMedia( ImageView vistaImmagine, ProgressBar circo, Media media ) { - this.vistaImmagine = vistaImmagine; - this.circo = circo; - this.media = media; - vistaImmagineWidth = vistaImmagine.getWidth(); - } - @Override - protected Bitmap doInBackground(String... parametri) { - Bitmap bitmap; - try { - Connection connessione = Jsoup.connect(parametri[0]); - //if (connessione.equals(bitmap)) { // TODO: verifica che un address sia associato all'hostname - Document doc = connessione.get(); - List lista = doc.select("img"); - if( lista.isEmpty() ) { // Pagina web trovata ma senza immagini - tagTipoFile = 3; - url = new URL( parametri[0] ); - return generaIcona( vistaImmagine, R.layout.media_mondo, url.getProtocol() ); // ritorna una bitmap - } - int maxDimensioniConAlt = 1; - int maxDimensioni = 1; - int maxLunghezzaAlt = 0; - int maxLunghezzaSrc = 0; - Element imgGrandeConAlt = null; - Element imgGrande = null; - Element imgAltLungo = null; - Element imgSrcLungo = null; - for( Element img : lista ) { - int larga, alta; - if (img.attr("width").isEmpty()) larga = 1; - else larga = Integer.parseInt(img.attr("width")); - if (img.attr("height").isEmpty()) alta = 1; - else alta = Integer.parseInt(img.attr("height")); - if( larga * alta > maxDimensioniConAlt && !img.attr("alt").isEmpty() ) { // la più grande con alt - imgGrandeConAlt = img; - maxDimensioniConAlt = larga * alta; - } - if( larga * alta > maxDimensioni ) { // la più grande anche senza alt - imgGrande = img; - maxDimensioni = larga * alta; - } - if( img.attr("alt").length() > maxLunghezzaAlt ) { // quella con l'alt più lungo - imgAltLungo = img; - maxLunghezzaAlt = img.attr( "alt" ).length(); - } - if( img.attr("src").length() > maxLunghezzaSrc ) { // quella col src più lungo - imgSrcLungo = img; - maxLunghezzaSrc = img.attr("src").length(); - } - } - String percorso = null; - if( imgGrandeConAlt != null ) - percorso = imgGrandeConAlt.absUrl( "src" ); //absolute URL on src - else if( imgGrande != null ) - percorso = imgGrande.absUrl( "src" ); - else if( imgAltLungo != null ) - percorso = imgAltLungo.absUrl( "src" ); - else if( imgSrcLungo != null ) - percorso = imgSrcLungo.absUrl( "src" ); - url = new URL(percorso); - InputStream inputStream = url.openConnection().getInputStream(); - BitmapFactory.Options opzioni = new BitmapFactory.Options(); - opzioni.inJustDecodeBounds = true; // prende solo le info dell'immagine senza scaricarla - BitmapFactory.decodeStream(inputStream, null, opzioni); - // Infine cerca di caricare l'immagine vera e propria ridimensionandola - if( opzioni.outWidth > vistaImmagineWidth ) - opzioni.inSampleSize = opzioni.outWidth / (vistaImmagineWidth+1); - inputStream = url.openConnection().getInputStream(); - opzioni.inJustDecodeBounds = false; // Scarica l'immagine - bitmap = BitmapFactory.decodeStream( inputStream, null, opzioni ); - tagTipoFile = 1; - } catch( Exception e ) { - return null; - } - return bitmap; - } - @Override - protected void onPostExecute(Bitmap bitmap) { - vistaImmagine.setTag(R.id.tag_tipo_file, tagTipoFile); - if( bitmap != null ) { - vistaImmagine.setImageBitmap(bitmap); - vistaImmagine.setTag(R.id.tag_percorso, url.toString()); // usato da Immagine - if( tagTipoFile == 1 ) - new ImboscaImmagine(media).execute(url); - } - if( circo != null ) // può arrivare molto in ritardo quando la pagina non esiste più - circo.setVisibility(View.GONE); - } - } - - // Metodi per acquisizione immagini: - - // Propone una bella lista di app per acquisire immagini - public static void appAcquisizioneImmagine(Context contesto, Fragment frammento, int codice, MediaContainer contenitore) { - // Richiesta permesso accesso memoria device - int perm = ContextCompat.checkSelfPermission(contesto, Manifest.permission.READ_EXTERNAL_STORAGE); - if( perm == PackageManager.PERMISSION_DENIED ) { - if( frammento != null ) { // Galleria - frammento.requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, codice); - } else - ActivityCompat.requestPermissions((AppCompatActivity)contesto, - new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, codice); - return; - } - // Colleziona gli intenti utili per acquisire immagini - List listaRisolvi = new ArrayList<>(); - final List listaIntenti = new ArrayList<>(); - // Camere - Intent intentoCamera = new Intent( MediaStore.ACTION_IMAGE_CAPTURE ); - for( ResolveInfo info : contesto.getPackageManager().queryIntentActivities(intentoCamera,0) ) { - Intent finalIntent = new Intent( intentoCamera ); - finalIntent.setComponent( new ComponentName(info.activityInfo.packageName, info.activityInfo.name) ); - listaIntenti.add(finalIntent); - listaRisolvi.add( info ); - } - // Gallerie - Intent intentoGalleria = new Intent( Intent.ACTION_GET_CONTENT ); - intentoGalleria.setType("image/*"); - String[] mimeTypes = { "image/*", "audio/*", "video/*", "application/*", "text/*" }; - if( Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT ) - mimeTypes[0] = "*/*"; // Altrimenti KitKat non vede gli 'application/*' in Downloads - intentoGalleria.putExtra( Intent.EXTRA_MIME_TYPES, mimeTypes ); - for( ResolveInfo info : contesto.getPackageManager().queryIntentActivities(intentoGalleria,0) ) { - Intent finalIntent = new Intent( intentoGalleria ); - finalIntent.setComponent(new ComponentName(info.activityInfo.packageName, info.activityInfo.name)); - listaIntenti.add( finalIntent ); - listaRisolvi.add( info ); - } - // Media vuoto - if( Global.settings.expert && codice != 5173 ) { // tranne che per la scelta di file in Immagine - Intent intento = new Intent( contesto, Immagine.class ); - ResolveInfo info = contesto.getPackageManager().resolveActivity( intento, 0 ); - intento.setComponent(new ComponentName(info.activityInfo.packageName,info.activityInfo.name)); - listaIntenti.add( intento ); - listaRisolvi.add( info ); - } - new AlertDialog.Builder( contesto ).setAdapter( faiAdattatore( contesto, listaRisolvi ), - (dialog, id) -> { - Intent intento = listaIntenti.get(id); - // Predispone un Uri in cui mettere la foto scattata dall'app fotocamera - if( intento.getAction() != null && intento.getAction().equals(MediaStore.ACTION_IMAGE_CAPTURE) ) { - File dir = contesto.getExternalFilesDir( String.valueOf(Global.settings.openTree) ); - if( !dir.exists() ) - dir.mkdir(); - File fotoFile = fileNomeProgressivo( dir.getAbsolutePath(), "image.jpg" ); - Global.fotoCamera = fotoFile.getAbsolutePath(); // Lo salva per riprenderlo dopo che la foto è stata scattata - Uri fotoUri; - if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ) - fotoUri = FileProvider.getUriForFile( contesto, BuildConfig.APPLICATION_ID + ".provider", fotoFile ); - else // KitKat - fotoUri = Uri.fromFile( fotoFile ); - intento.putExtra( MediaStore.EXTRA_OUTPUT, fotoUri ); - } - if( intento.getComponent().getPackageName().equals("app.familygem") ) { - // Crea un Media vuoto - Media med; - if( codice==4173 || codice==2173 ) { // Media semplice - med = new Media(); - med.setFileTag( "FILE" ); - contenitore.addMedia( med ); - Memoria.aggiungi( med ); - } else { // Media condiviso - med = Galleria.nuovoMedia( contenitore ); - Memoria.setPrimo( med ); - } - med.setFile( "" ); - contesto.startActivity( intento ); - U.save( true, Memoria.oggettoCapo() ); - } else if( frammento != null ) - frammento.startActivityForResult( intento, codice ); // Così il risultato ritorna al frammento - else - ((AppCompatActivity)contesto).startActivityForResult( intento, codice ); - }).show(); - } - // Strettamente legato a quello qui sopra - private static ArrayAdapter faiAdattatore(final Context contesto, final List listaRisolvi) { - return new ArrayAdapter(contesto, R.layout.pezzo_app, R.id.intento_titolo, listaRisolvi) { - @Override - public View getView(int posizione, View vista, ViewGroup genitore) { - View view = super.getView(posizione, vista, genitore); - ResolveInfo info = listaRisolvi.get(posizione); - ImageView image = view.findViewById(R.id.intento_icona); - TextView textview = view.findViewById(R.id.intento_titolo); - if( info.activityInfo.packageName.equals("app.familygem") ) { - image.setImageResource(R.drawable.image); - textview.setText(R.string.empty_media); - } else { - image.setImageDrawable(info.loadIcon(contesto.getPackageManager())); - textview.setText(info.loadLabel(contesto.getPackageManager()).toString()); - } - return view; - } - }; - } - - // Salva il file acquisito e propone di ritagliarlo se è un'immagine - // ritorna true se apre il dialogo e quindi bisogna bloccare l'aggiornamento dell'attività - static boolean proponiRitaglio( Context contesto, Fragment frammento, Intent data, Media media ) { - // Trova il percorso dell'immagine - Uri uri = null; - String percorso; - // Contenuto preso con SAF - if( data != null && data.getData() != null ) { - uri = data.getData(); - percorso = uriPercorsoFile( uri ); - } // Foto da app camera - else if( Global.fotoCamera != null ) { - percorso = Global.fotoCamera; - Global.fotoCamera = null; // lo resetta - } // Niente di utilizzabile - else { - Toast.makeText( contesto, R.string.something_wrong, Toast.LENGTH_SHORT ).show(); - return false; - } - - // Crea il file - File[] fileMedia = new File[1]; // perché occorre final - if( percorso != null && percorso.lastIndexOf('/') > 0 ) { // se è un percorso completo del file - // Punta direttamente il file - fileMedia[0] = new File( percorso ); - } else { // È solo il nome del file 'mioFile.ext' o più raramente null - // Memoria esterna dell'app: /storage/emulated/0/Android/data/app.familygem/files/12 - File dirMemoria = contesto.getExternalFilesDir( String.valueOf(Global.settings.openTree) ); - try { // Usiamo l'URI - InputStream input = contesto.getContentResolver().openInputStream( uri ); - // Todo se il file esiste già identico non duplicarlo ma riutilizzarlo: come in Conferma.vediSeCopiareFile() - if( percorso == null ) { // Nome del file null, va inventato - String type = contesto.getContentResolver().getType(uri); - percorso = type.substring(0, type.indexOf('/')) + "." - + MimeTypeMap.getSingleton().getExtensionFromMimeType(type); - } - fileMedia[0] = fileNomeProgressivo( dirMemoria.getAbsolutePath(), percorso ); - FileUtils.copyInputStreamToFile( input, fileMedia[0] ); // Crea la cartella se non esiste - } catch( Exception e ) { - String msg = e.getLocalizedMessage() != null ? e.getLocalizedMessage() : contesto.getString(R.string.something_wrong); - Toast.makeText( contesto, msg, Toast.LENGTH_LONG ).show(); - } - } - // Aggiunge il percorso della cartella nel Tree in preferenze - if( Global.settings.getCurrentTree().dirs.add( fileMedia[0].getParent() ) ) // true se ha aggiunto la cartella - Global.settings.save(); - // Imposta nel Media il percorso trovato - media.setFile( fileMedia[0].getAbsolutePath() ); - - // Se si tratta di un'immagine apre il diaogo di proposta ritaglio - String tipoMime = URLConnection.guessContentTypeFromName( fileMedia[0].getName() ); - if( tipoMime != null && tipoMime.startsWith("image/") ) { - ImageView vistaImmagine = new ImageView( contesto ); - dipingiMedia( media, vistaImmagine, null ); - Global.mediaCroppato = media; // Media parcheggiato in attesa di essere aggiornato col nuovo percorso file - Global.edited = false; // per non innescare il recreate() che negli Android nuovi non fa comparire l'AlertDialog - new AlertDialog.Builder( contesto ) - .setView(vistaImmagine) - .setMessage( R.string.want_crop_image ) - .setPositiveButton( R.string.yes, (dialog, id) -> tagliaImmagine( contesto, fileMedia[0], null, frammento ) ) - .setNeutralButton( R.string.no, (dialog, which) -> { - concludiProponiRitaglio( contesto, frammento ); - }).setOnCancelListener( dialog -> { // click fuori dal dialogo - concludiProponiRitaglio( contesto, frammento ); - }).show(); - FrameLayout.LayoutParams params = new FrameLayout.LayoutParams( FrameLayout.LayoutParams.MATCH_PARENT, U.dpToPx(320) ); - vistaImmagine.setLayoutParams( params ); // l'assegnazione delle dimensioni deve venire DOPO la creazione del dialogo - return true; - } - return false; - } - // Conclusione negativa della proposta di ritaglio dell'immagine: aggiorna semplicemente la pagina per mostrare l'immagine - static void concludiProponiRitaglio( Context contesto, Fragment frammento ) { - if( frammento instanceof Galleria ) - ((Galleria)frammento).ricrea(); - else if( contesto instanceof Dettaglio ) - ((Dettaglio)contesto).refresh(); - else if( contesto instanceof Individuo ) { - IndividuoMedia indiMedia = (IndividuoMedia) ((AppCompatActivity)contesto).getSupportFragmentManager() - .findFragmentByTag( "android:switcher:" + R.id.schede_persona + ":0" ); - indiMedia.refresh(); - } - Global.edited = true; // per rinfrescare le pagine precedenti - } - - // Avvia il ritaglio di un'immagine con CropImage - // 'fileMedia' e 'uriMedia': uno dei due è valido, l'altro è null - static void tagliaImmagine( Context contesto, File fileMedia, Uri uriMedia, Fragment frammento ) { - // Partenza - if( uriMedia == null ) - uriMedia = Uri.fromFile(fileMedia); - // Destinazione - File dirMemoria = contesto.getExternalFilesDir( String.valueOf(Global.settings.openTree) ); - if( !dirMemoria.exists() ) - dirMemoria.mkdir(); - File fileDestinazione; - if( fileMedia != null && fileMedia.getAbsolutePath().startsWith(dirMemoria.getAbsolutePath()) ) - fileDestinazione = fileMedia; // File già nella cartella memoria vengono sovrascritti - else { - String nome; - if( fileMedia != null ) - nome = fileMedia.getName(); - else // Uri - nome = DocumentFile.fromSingleUri( contesto, uriMedia ).getName(); - fileDestinazione = fileNomeProgressivo( dirMemoria.getAbsolutePath(), nome ); - } - Intent intento = CropImage.activity( uriMedia ) - .setOutputUri( Uri.fromFile(fileDestinazione) ) // cartella in memoria esterna - .setGuidelines( CropImageView.Guidelines.OFF ) - .setBorderLineThickness( 1 ) - .setBorderCornerThickness( 6 ) - .setBorderCornerOffset( -3 ) - .setCropMenuCropButtonTitle( contesto.getText(R.string.done) ) - .getIntent( contesto ); - if( frammento != null ) - frammento.startActivityForResult( intento, CropImage.CROP_IMAGE_ACTIVITY_REQUEST_CODE ); - else - ((AppCompatActivity)contesto).startActivityForResult( intento, CropImage.CROP_IMAGE_ACTIVITY_REQUEST_CODE ); - } - - // Se in quella cartella esiste già un file con quel nome lo incrementa con 1 2 3... - static File fileNomeProgressivo( String dir, String nome ) { - File file = new File( dir, nome ); - int incremento = 0; - while( file.exists() ) { - incremento++; - file = new File( dir, nome.substring(0,nome.lastIndexOf('.')) - + incremento + nome.substring(nome.lastIndexOf('.')) ); - } - return file; - } - - // Conclude la procedura di ritaglio di un'immagine - static void fineRitaglioImmagine( Intent data ) { - CropImage.ActivityResult risultato = CropImage.getActivityResult(data); - Uri uri = risultato.getUri(); // ad es. 'file:///storage/emulated/0/Android/data/app.familygem/files/5/anna.webp' - Picasso.get().invalidate( uri ); // cancella dalla cache l'eventuale immagine prima del ritaglio che ha lo stesso percorso - String percorso = uriPercorsoFile( uri ); - Global.mediaCroppato.setFile( percorso ); - } - - // Risposta a tutte le richieste di permessi per Android 6+ - static void risultatoPermessi( Context contesto, Fragment frammento, int codice, String[] permessi, int[] accordi, MediaContainer contenitore ) { - if( accordi.length > 0 && accordi[0] == PackageManager.PERMISSION_GRANTED ) { - appAcquisizioneImmagine( contesto, frammento, codice, contenitore ); - } else { - String permesso = permessi[0].substring(permessi[0].lastIndexOf('.') + 1); - Toast.makeText( contesto, contesto.getString(R.string.not_granted,permesso), Toast.LENGTH_LONG ).show(); - } - } + /** + * Packaging to get a folder in KitKat + * Impacchettamento per ricavare una cartella in KitKat + */ + static String uriPathFolderKitKat(Context context, Uri uri) { + String path = uriFilePath(uri); + if (path != null && path.lastIndexOf('/') > 0) { + return path.substring(0, path.lastIndexOf('/')); + } else { + Toast.makeText(context, "Could not get this position.", Toast.LENGTH_SHORT).show(); + return null; + } + } + + /** + * It receives a Uri and tries to return the path to the file + * Version commented in lab + *

+ * Riceve un Uri e cerca di restituire il percorso del file + * Versione commentata in lab + */ + static String uriFilePath(Uri uri) { + if (uri == null) return null; + if (uri.getScheme() != null && uri.getScheme().equalsIgnoreCase("file")) { + // Remove 'file://' + return uri.getPath(); + } + switch (uri.getAuthority()) { + case "com.android.externalstorage.documents": // internal memory and SD card + String[] split = uri.getLastPathSegment().split(":"); + if (split[0].equalsIgnoreCase("primary")) { + // Main storage + String path = Environment.getExternalStorageDirectory() + "/" + split[1]; + if (new File(path).canRead()) + return path; + } else if (split[0].equalsIgnoreCase("home")) { + // 'Documents' folder in Android 9 and 10 + return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS) + "/" + split[1]; + } else { + // All other cases including SD cards + File[] places = Global.context.getExternalFilesDirs(null); + for (File file : places) { + if (file.getAbsolutePath().indexOf("/Android") > 0) { + String dir = file.getAbsolutePath().substring(0, file.getAbsolutePath().indexOf("/Android")); + File found = new File(dir, split[1]); + if (found.canRead()) + return found.getAbsolutePath(); + } + } + } + break; + case "com.android.providers.downloads.documents": //files from the Downloads folder + String id = uri.getLastPathSegment(); + if (id.startsWith("raw:/")) + return id.replaceFirst("raw:", ""); + if (id.matches("\\d+")) { + String[] contentUriPrefixesToTry = new String[]{ + "content://downloads/public_downloads", + "content://downloads/my_downloads" + }; + for (String contentUriPrefix : contentUriPrefixesToTry) { + Uri rebuilt = ContentUris.withAppendedId(Uri.parse(contentUriPrefix), Long.parseLong(id)); + try { + String filename = findFilename(rebuilt); + if (filename != null) + return filename; + } catch (Exception e) { + } + } + } + } + return findFilename(uri); + } + + /** + * // Get the URI (possibly reconstructed) of a file taken with SAF + * // If successful, return the full path, otherwise the single file name + *

+ * // Riceve l'URI (eventualmente ricostruito) di un file preso con SAF + * // Se riesce restituisce il percorso completo, altrimenti il singolo nome del file + */ + private static String findFilename(Uri uri) { + Cursor cursor = Global.context.getContentResolver().query(uri, null, null, null, null); + if (cursor != null && cursor.moveToFirst()) { + int index = cursor.getColumnIndex(MediaStore.Files.FileColumns.DATA); + if (index < 0) + index = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); + String filename = cursor.getString(index); + cursor.close(); + return filename; + } + return null; + } + + /** + * // Receive a Uri tree obtained with ACTION_OPEN_DOCUMENT_TREE and try to return the path of the folder + * // otherwise it quietly returns null + *

+ * // Riceve un tree Uri ricavato con ACTION_OPEN_DOCUMENT_TREE e cerca di restituire il percorso della cartella + * // altrimenti tranquillamente restituisce null + */ + public static String uriFolderPath(Uri uri) { + if (uri == null) return null; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + String treeDocId = DocumentsContract.getTreeDocumentId(uri); + switch (uri.getAuthority()) { + case "com.android.externalstorage.documents": // internal memory and SD card + String[] split = treeDocId.split(":"); + String path = null; + // Main storage + if (split[0].equalsIgnoreCase("primary")) { + path = Environment.getExternalStorageDirectory().getAbsolutePath(); + } + // Documents in Android 9 and 10 + else if (split[0].equalsIgnoreCase("home")) { + path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS).getAbsolutePath(); + } + // All the others, like the SD card + else { + File[] filesDirs = Global.context.getExternalFilesDirs(null); + for (File dir : filesDirs) { + String other = dir.getAbsolutePath(); + if (other.contains(split[0])) { + path = other.substring(0, other.indexOf("/Android")); + break; + } + } + } + if (path != null) { + if (split.length > 1 && !split[1].isEmpty()) + path += "/" + split[1]; + return path; + } + break; + case "com.android.providers.downloads.documents": // provider Downloads e sottocartelle + if (treeDocId.equals("downloads")) + return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath(); + if (treeDocId.startsWith("raw:/")) + return treeDocId.replaceFirst("raw:", ""); + } + } + return null; + } + + /** + * Save a document (PDF, GEDCOM, ZIP) with SAF + */ + static void saveDocument(Activity activity, Fragment fragment, int treeId, String mime, String ext, int requestCode) { + String name = Global.settings.getTree(treeId).title; + //GEDCOM must specify the extension, the others put it according to the mime type // GEDCOM deve esplicitare l'estensione, gli altri la mettono in base al mime type + ext = ext.equals("ged") ? ".ged" : ""; + //replaces dangerous characters for the Android filesystem that are not replaced by Android itself // rimpiazza caratteri pericolosi per il filesystem di Android che non vengono ripiazzati da Android stesso + name = name.replaceAll("[$']", "_"); + Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT) + .addCategory(Intent.CATEGORY_OPENABLE) + .setType(mime) + .putExtra(Intent.EXTRA_TITLE, name + ext); + if (activity != null) + activity.startActivityForResult(intent, requestCode); + else + fragment.startActivityForResult(intent, requestCode); + } + + // Methods for displaying images: + + /** + * // Receives a Person and chooses the main Media from which to get the image + * // Riceve una Person e sceglie il Media principale da cui ricavare l'immagine + */ + static void showMainImageForPerson(Gedcom gc, Person p, ImageView img) { + MediaList mediaList = new MediaList(gc, 0); + p.accept(mediaList); + boolean found = false; + for (Media med : mediaList.list) { // Look for a media marked Primary 'Y' + if (med.getPrimary() != null && med.getPrimary().equals("Y")) { + showImage(med, img, null); + found = true; + break; + } + } + if (!found) { // Alternatively, it returns the first one it finds + for (Media med : mediaList.list) { + showImage(med, img, null); + found = true; + break; + } + } + img.setVisibility(found ? View.VISIBLE : View.GONE); + } + + /** + * Show pictures with Picasso + */ + public static void showImage(Media media, ImageView imageView, ProgressBar progressBar) { + int treeId; + // Comparator needs the new tree id to search its folder + View likely = null; + if (imageView.getParent() != null && imageView.getParent().getParent() != null) + likely = (View) imageView.getParent().getParent().getParent(); + if (likely != null && likely.getId() == R.id.confronto_nuovo) + treeId = Global.treeId2; + else treeId = Global.settings.openTree; + String path = mediaPath(treeId, media); + Uri[] uri = new Uri[1]; + if (path == null) + uri[0] = mediaUri(treeId, media); + if (progressBar != null) progressBar.setVisibility(View.VISIBLE); + imageView.setTag(R.id.tag_tipo_file, 0); + if (path != null || uri[0] != null) { + RequestCreator creator; + if (path != null) + creator = Picasso.get().load("file://" + path); + else + creator = Picasso.get().load(uri[0]); + creator.placeholder(R.drawable.image) + .fit() + .centerInside() + .into(imageView, new Callback() { + @Override + public void onSuccess() { + if (progressBar != null) progressBar.setVisibility(View.GONE); + imageView.setTag(R.id.tag_tipo_file, 1); + imageView.setTag(R.id.tag_percorso, path); // 'path' or 'uri' one of the 2 is valid, the other is null + imageView.setTag(R.id.tag_uri, uri[0]); + // On the Image Detail page reload the options menu to show the Crop command + if (imageView.getId() == R.id.immagine_foto) { + if (imageView.getContext() instanceof Activity) // In KitKat it is instance of TintContextWrapper + ((Activity) imageView.getContext()).invalidateOptionsMenu(); + } + } + + @Override + public void onError(Exception e) { + //Maybe it's a video to make a thumbnail from + Bitmap bitmap = null; + try { //These thumbnail generators have been nailing lately, so better cover your ass// Ultimamente questi generatori di thumbnail inchiodano, quindi meglio pararsi il culo + bitmap = ThumbnailUtils.createVideoThumbnail(path, MediaStore.Video.Thumbnails.MINI_KIND); + // Via the URI//Tramite l'URI + if (bitmap == null && uri[0] != null) { + MediaMetadataRetriever mMR = new MediaMetadataRetriever(); + mMR.setDataSource(Global.context, uri[0]); + bitmap = mMR.getFrameAtTime(); + } + } catch (Exception excpt) { + } + imageView.setTag(R.id.tag_tipo_file, 2); + if (bitmap == null) { + // a Local File with no preview + String format = media.getFormat(); + if (format == null) + format = path != null ? MimeTypeMap.getFileExtensionFromUrl(path.replaceAll("[^a-zA-Z0-9./]", "_")) : ""; + // Removes whitespace that does not find the extension + if (format.isEmpty() && uri[0] != null) + format = MimeTypeMap.getFileExtensionFromUrl(uri[0].getLastPathSegment()); + bitmap = generateIcon(imageView, R.layout.media_file, format); + imageView.setScaleType(ImageView.ScaleType.FIT_CENTER); + if (imageView.getParent() instanceof RelativeLayout && // ugly but effective + ((RelativeLayout) imageView.getParent()).findViewById(R.id.media_testo) != null) { + RelativeLayout.LayoutParams param = new RelativeLayout.LayoutParams( + RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT); + param.addRule(RelativeLayout.ABOVE, R.id.media_testo); + imageView.setLayoutParams(param); + } + imageView.setTag(R.id.tag_tipo_file, 3); + } + imageView.setImageBitmap(bitmap); + imageView.setTag(R.id.tag_percorso, path); + imageView.setTag(R.id.tag_uri, uri[0]); + if (progressBar != null) progressBar.setVisibility(View.GONE); + } + }); + } else if (media.getFile() != null && !media.getFile().isEmpty()) { // maybe it's an image on the internet + String filePath = media.getFile(); + Picasso.get().load(filePath).fit() + .placeholder(R.drawable.image).centerInside() + .into(imageView, new Callback() { + @Override + public void onSuccess() { + if (progressBar != null) progressBar.setVisibility(View.GONE); + imageView.setTag(R.id.tag_tipo_file, 1); + try { + new CacheImage(media).execute(new URL(filePath)); + } catch (Exception e) { + } + } + + @Override + public void onError(Exception e) { + // Let's try a web page + new DownloadImage(imageView, progressBar, media).execute(filePath); + } + }); + } else { // Media without a link to a file + if (progressBar != null) progressBar.setVisibility(View.GONE); + imageView.setImageResource(R.drawable.image); + } + } + + /** + * It receives a Media, looks for the file locally with different path combinations and returns the address + */ + public static String mediaPath(int treeId, Media m) { + String file = m.getFile(); + if (file != null && !file.isEmpty()) { + String name = file.replace("\\", "/"); + // FILE path (the one in gedcom) + if (new File(name).canRead()) + return name; + for (String dir : Global.settings.getTree(treeId).dirs) { + // media folder + FILE path + String path = dir + '/' + name; + File test = new File(path); + /* Todo Sometimes File.isFile () produces an ANR, like https://stackoverflow.com/questions/224756 + * I tried with various non-existent paths, such as the removed SD card, or with absurd characters, + * but they all simply return false. + * Probably the ANR is when the path points to an existing resource but it waits indefinitely. */ + if (test.isFile() && test.canRead()) + return path; + // media folder + name of FILE + path = dir + '/' + new File(name).getName(); + test = new File(path); + if (test.isFile() && test.canRead()) + return path; + } + Object string = m.getExtension("cache"); + // Sometimes it is String sometimes JsonPrimitive, I don't quite understand why + if (string != null) { + String cachePath; + if (string instanceof String) + cachePath = (String) string; + else + cachePath = ((JsonPrimitive) string).getAsString(); + if (new File(cachePath).isFile()) + return cachePath; + } + } + return null; + } + + /** + * It receives a Media, looks for the file locally in any tree-URIs and returns the URI + */ + public static Uri mediaUri(int treeId, Media m) { + String file = m.getFile(); + if (file != null && !file.isEmpty()) { + // OBJE.FILE is never a Uri, always a path (Windows or Android) + String filename = new File(file.replace("\\", "/")).getName(); + for (String uri : Global.settings.getTree(treeId).uris) { + DocumentFile documentDir = DocumentFile.fromTreeUri(Global.context, Uri.parse(uri)); + DocumentFile docFile = documentDir.findFile(filename); + if (docFile != null && docFile.isFile()) + return docFile.getUri(); + } + } + return null; + } + + static Bitmap generateIcon(ImageView view, int icona, String testo) { + LayoutInflater inflater = (LayoutInflater) view.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); + View inflated = inflater.inflate(icona, null); + RelativeLayout frameLayout = inflated.findViewById(R.id.icona); + ((TextView) frameLayout.findViewById(R.id.icona_testo)).setText(testo); + frameLayout.setDrawingCacheEnabled(true); + frameLayout.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)); + frameLayout.layout(0, 0, frameLayout.getMeasuredWidth(), frameLayout.getMeasuredHeight()); + frameLayout.buildDrawingCache(true); + return frameLayout.getDrawingCache(); + } + + /** + * Cache an image that can be found on the internet for reuse + * TODO? maybe it might not even be an asynchronous task but a simple function + */ + static class CacheImage extends AsyncTask { + Media media; + + CacheImage(Media media) { + this.media = media; + } + + protected String doInBackground(URL... url) { + try { + File cacheFolder = new File(Global.context.getCacheDir().getPath() + "/" + Global.settings.openTree); + if (!cacheFolder.exists()) { + // Delete "cache" extension from all Media + MediaList mediaList = new MediaList(Global.gc, 0); + Global.gc.accept(mediaList); + for (Media media : mediaList.list) + if (media.getExtension("cache") != null) + media.putExtension("cache", null); + cacheFolder.mkdir(); + } + String extension = FilenameUtils.getName(url[0].getPath()); + if (extension.lastIndexOf('.') > 0) + extension = extension.substring(extension.lastIndexOf('.') + 1); + String ext; + switch (extension) { + case "png": + ext = "png"; + break; + case "gif": + ext = "gif"; + break; + case "bmp": + ext = "bmp"; + break; + case "jpg": + case "jpeg": + default: + ext = "jpg"; + } + File cache = nextAvailableFileName(cacheFolder.getPath(), "img." + ext); + FileUtils.copyURLToFile(url[0], cache); + return cache.getPath(); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + protected void onPostExecute(String path) { + if (path != null) + media.putExtension("cache", path); + } + } + + /** + * Asynchronously downloads an image from an internet page + * */ + static class DownloadImage extends AsyncTask { + ImageView imageView; + ProgressBar progressBar; + Media media; + URL url; + int fileTypeTag = 0; // setTag must be in the main thread, not in the doInBackground. + int imageViewWidth; // ditto + + DownloadImage(ImageView imageView, ProgressBar progressBar, Media media) { + this.imageView = imageView; + this.progressBar = progressBar; + this.media = media; + imageViewWidth = imageView.getWidth(); + } + + @Override + protected Bitmap doInBackground(String... parametri) { + Bitmap bitmap; + try { + Connection connection = Jsoup.connect(parametri[0]); + //if (connection.equals(bitmap)) { // TODO: verify that an address is associated with the hostname + Document doc = connection.get(); + List list = doc.select("img"); + if (list.isEmpty()) { // Web page found but without images + fileTypeTag = 3; + url = new URL(parametri[0]); + return generateIcon(imageView, R.layout.media_mondo, url.getProtocol()); // returns a bitmap + } + int maxDimensionsWithAlt = 1; + int maxDimensions = 1; + int maxLengthAlt = 0; + int maxLengthSrc = 0; + Element imgHeightConAlt = null; + Element imgHeight = null; + Element imgLengthAlt = null; + Element imgLengthSrc = null; + for (Element img : list) { + int width, height; + if (img.attr("width").isEmpty()) width = 1; + else width = Integer.parseInt(img.attr("width")); + if (img.attr("height").isEmpty()) height = 1; + else height = Integer.parseInt(img.attr("height")); + if (width * height > maxDimensionsWithAlt && !img.attr("alt").isEmpty()) { // the largest with alt + imgHeightConAlt = img; + maxDimensionsWithAlt = width * height; + } + if (width * height > maxDimensions) { // the largest even without alt + imgHeight = img; + maxDimensions = width * height; + } + if (img.attr("alt").length() > maxLengthAlt) { //the one with the longest alt + imgLengthAlt = img; + maxLengthAlt = img.attr("alt").length(); + } + if (img.attr("src").length() > maxLengthSrc) { // the one with the longest src + imgLengthSrc = img; + maxLengthSrc = img.attr("src").length(); + } + } + String percorso = null; + if (imgHeightConAlt != null) + percorso = imgHeightConAlt.absUrl("src"); //absolute URL on src + else if (imgHeight != null) + percorso = imgHeight.absUrl("src"); + else if (imgLengthAlt != null) + percorso = imgLengthAlt.absUrl("src"); + else if (imgLengthSrc != null) + percorso = imgLengthSrc.absUrl("src"); + url = new URL(percorso); + InputStream inputStream = url.openConnection().getInputStream(); + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; // it just takes the info of the image without downloading it + BitmapFactory.decodeStream(inputStream, null, options); + // Finally try to load the actual image by resizing it + if (options.outWidth > imageViewWidth) + options.inSampleSize = options.outWidth / (imageViewWidth + 1); + inputStream = url.openConnection().getInputStream(); + options.inJustDecodeBounds = false; // Download the image + bitmap = BitmapFactory.decodeStream(inputStream, null, options); + fileTypeTag = 1; + } catch (Exception e) { + return null; + } + return bitmap; + } + + @Override + protected void onPostExecute(Bitmap bitmap) { + imageView.setTag(R.id.tag_tipo_file, fileTypeTag); + if (bitmap != null) { + imageView.setImageBitmap(bitmap); + imageView.setTag(R.id.tag_percorso, url.toString()); // used by Image + if (fileTypeTag == 1) + new CacheImage(media).execute(url); + } + if (progressBar != null) //it can be very late when the page no longer exists // può arrivare molto in ritardo quando la pagina non esiste più + progressBar.setVisibility(View.GONE); + } + } + + // Methods for image acquisition: + + /** + * Offers a nice list of apps for capturing images + */ + public static void displayImageCaptureDialog(Context context, Fragment fragment, int code, MediaContainer container) { + // Request permission to access device memory + int perm = ContextCompat.checkSelfPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE); + if (perm == PackageManager.PERMISSION_DENIED) { + if (fragment != null) { // Gallery + fragment.requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, code); + } else + ActivityCompat.requestPermissions((AppCompatActivity) context, + new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, code); + return; + } + // Collect useful intents to capture images + List resolveInfos = new ArrayList<>(); + final List intents = new ArrayList<>(); + // Camera + Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); + for (ResolveInfo info : context.getPackageManager().queryIntentActivities(cameraIntent, 0)) { + Intent finalIntent = new Intent(cameraIntent); + finalIntent.setComponent(new ComponentName(info.activityInfo.packageName, info.activityInfo.name)); + intents.add(finalIntent); + resolveInfos.add(info); + } + // Gallery + Intent galleryIntent = new Intent(Intent.ACTION_GET_CONTENT); + galleryIntent.setType("image/*"); + String[] mimeTypes = {"image/*", "audio/*", "video/*", "application/*", "text/*"}; + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) + mimeTypes[0] = "*/*"; // Otherwise KitKat does not see the 'application / *' in Downloads + galleryIntent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes); + for (ResolveInfo info : context.getPackageManager().queryIntentActivities(galleryIntent, 0)) { + Intent finalIntent = new Intent(galleryIntent); + finalIntent.setComponent(new ComponentName(info.activityInfo.packageName, info.activityInfo.name)); + intents.add(finalIntent); + resolveInfos.add(info); + } + // Blank media + if (Global.settings.expert && code != 5173) { //except for choosing files in Image // tranne che per la scelta di file in Immagine + Intent intent = new Intent(context, ImageActivity.class); + ResolveInfo info = context.getPackageManager().resolveActivity(intent, 0); + intent.setComponent(new ComponentName(info.activityInfo.packageName, info.activityInfo.name)); + intents.add(intent); + resolveInfos.add(info); + } + new AlertDialog.Builder(context).setAdapter(createAdapter(context, resolveInfos), + (dialog, id) -> { + Intent intent = intents.get(id); + // Set up a Uri in which to put the photo taken by the camera app + if (intent.getAction() != null && intent.getAction().equals(MediaStore.ACTION_IMAGE_CAPTURE)) { + File dir = context.getExternalFilesDir(String.valueOf(Global.settings.openTree)); + if (!dir.exists()) + dir.mkdir(); + File photoFile = nextAvailableFileName(dir.getAbsolutePath(), "image.jpg"); + Global.pathOfCameraDestination = photoFile.getAbsolutePath(); // This saves it to retake it after the photo is taken + Uri photoUri; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) + photoUri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", photoFile); + else // KitKat + photoUri = Uri.fromFile(photoFile); + intent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri); + } + if (intent.getComponent().getPackageName().equals("app.familygem")) { //TODO extract to build property + // Create a blank media + Media med; + if (code == 4173 || code == 2173) { // Simple media + med = new Media(); + med.setFileTag("FILE"); + container.addMedia(med); + Memory.add(med); + } else { // Shared media + med = GalleryFragment.newMedia(container); + Memory.setFirst(med); + } + med.setFile(""); + context.startActivity(intent); + U.save(true, Memory.firstObject()); + } else if (fragment != null) + fragment.startActivityForResult(intent, code); // Thus the result returns to the fragment + else + ((AppCompatActivity) context).startActivityForResult(intent, code); + }).show(); + } + + /** + * Closely related to the one above + * */ + private static ArrayAdapter createAdapter(final Context context, final List resolveInfos) { + return new ArrayAdapter(context, R.layout.pezzo_app, R.id.intent_titolo, resolveInfos) { + @Override + public View getView(int position, View view1, ViewGroup parent) { + View view = super.getView(position, view1, parent); + ResolveInfo info = resolveInfos.get(position); + ImageView image = view.findViewById(R.id.intent_icona); + TextView textview = view.findViewById(R.id.intent_titolo); + if (info.activityInfo.packageName.equals("app.familygem")) { + image.setImageResource(R.drawable.image); + textview.setText(R.string.empty_media); + } else { + image.setImageDrawable(info.loadIcon(context.getPackageManager())); + textview.setText(info.loadLabel(context.getPackageManager()).toString()); + } + return view; + } + }; + } + + /** + * Save the scanned file and propose to crop it if it is an image + * + * @return true if it opens the dialog and therefore the updating of the activity must be blocked + */ + static boolean proposeCropping(Context context, Fragment fragment, Intent data, Media media) { + // Find the path of the image + Uri uri = null; + String path; + // Content taken with SAF + if (data != null && data.getData() != null) { + uri = data.getData(); + path = uriFilePath(uri); + } // Photo from camera app + else if (Global.pathOfCameraDestination != null) { + path = Global.pathOfCameraDestination; + Global.pathOfCameraDestination = null; // resets it + } // Nothing usable + else { + Toast.makeText(context, R.string.something_wrong, Toast.LENGTH_SHORT).show(); + return false; + } + + // Create the file + File[] fileMedia = new File[1]; //because it is necessary final// perché occorre final + if (path != null && path.lastIndexOf('/') > 0) { // if it is a full path to the file + // Directly point to the file + fileMedia[0] = new File(path); + } else { // It is just the file name 'myFile.ext' or more rarely null + // External app storage: /storage/emulated/0/Android/data/app.familygem/files/12 + File externalFilesDir = context.getExternalFilesDir(String.valueOf(Global.settings.openTree)); + try { // We use the URI + InputStream input = context.getContentResolver().openInputStream(uri); + // Todo if the file already exists, do not duplicate it but reuse it: as in [ConfirmationActivity.checkIfShouldCopyFiles] + if (path == null) { // Null filename, must be created from scratch + String type = context.getContentResolver().getType(uri); + path = type.substring(0, type.indexOf('/')) + "." + + MimeTypeMap.getSingleton().getExtensionFromMimeType(type); + } + fileMedia[0] = nextAvailableFileName(externalFilesDir.getAbsolutePath(), path); + FileUtils.copyInputStreamToFile(input, fileMedia[0]); // Crea la cartella se non esiste + } catch (Exception e) { + String msg = e.getLocalizedMessage() != null ? e.getLocalizedMessage() : context.getString(R.string.something_wrong); + Toast.makeText(context, msg, Toast.LENGTH_LONG).show(); + } + } + //Adds the folder path in the Tree in preferences // Aggiunge il percorso della cartella nel Tree in preferenze + if (Global.settings.getCurrentTree().dirs.add(fileMedia[0].getParent())) // true if it added the folder + Global.settings.save(); + // Set the path found in the Media + media.setFile(fileMedia[0].getAbsolutePath()); + + // If it is an image it opens the cropping proposal dialog + String mimeType = URLConnection.guessContentTypeFromName(fileMedia[0].getName()); + if (mimeType != null && mimeType.startsWith("image/")) { + ImageView imageView = new ImageView(context); + showImage(media, imageView, null); + Global.croppedMedia = media; //Media parked waiting to be updated with new file path // Media parcheggiato in attesa di essere aggiornato col nuovo percorso file + Global.edited = false; //in order not to trigger the recreate () which in new Android does not bring up the AlertDialog // per non innescare il recreate() che negli Android nuovi non fa comparire l'AlertDialog + new AlertDialog.Builder(context) + .setView(imageView) + .setMessage(R.string.want_crop_image) + .setPositiveButton(R.string.yes, (dialog, id) -> cropImage(context, fileMedia[0], null, fragment)) + .setNeutralButton(R.string.no, (dialog, which) -> { + finishProposeCropping(context, fragment); + }).setOnCancelListener(dialog -> { // click out of the dialog + finishProposeCropping(context, fragment); + }).show(); + FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, U.dpToPx(320)); + imageView.setLayoutParams(params); // the size assignment must come AFTER creating the dialog + return true; + } + return false; + } + + /** + * Negative conclusion of the image cropping proposal: simply refresh the page to show the image + * Conclusione negativa della proposta di ritaglio dell'immagine: aggiorna semplicemente la pagina per mostrare l'immagine + * */ + static void finishProposeCropping(Context context, Fragment fragment) { + if (fragment instanceof GalleryFragment) + ((GalleryFragment) fragment).recreate(); + else if (context instanceof DetailActivity) + ((DetailActivity) context).refresh(); + else if (context instanceof IndividualPersonActivity) { + IndividualMediaFragment indiMedia = (IndividualMediaFragment) ((AppCompatActivity) context).getSupportFragmentManager() + .findFragmentByTag("android:switcher:" + R.id.schede_persona + ":0"); + indiMedia.refresh(); + } + Global.edited = true; // to refresh previous pages //per rinfrescare le pagine precedenti + } + + /** + * // Start cropping an image with Crop Image + * // 'file Media' and 'uriMedia': one of the two is valid, the other is null + * + * // Avvia il ritaglio di un'immagine con CropImage + * // 'fileMedia' e 'uriMedia': uno dei due è valido, l'altro è null + * */ + static void cropImage(Context context, File fileMedia, Uri uriMedia, Fragment fragment) { + //Departure // Partenza + if (uriMedia == null) + uriMedia = Uri.fromFile(fileMedia); + // Destination + File externalFilesDir = context.getExternalFilesDir(String.valueOf(Global.settings.openTree)); + if (!externalFilesDir.exists()) + externalFilesDir.mkdir(); + File destinationFile; + if (fileMedia != null && fileMedia.getAbsolutePath().startsWith(externalFilesDir.getAbsolutePath())) + destinationFile = fileMedia; // Files already in the storage folder are overwritten + else { + String name; + if (fileMedia != null) + name = fileMedia.getName(); + else // Uri + name = DocumentFile.fromSingleUri(context, uriMedia).getName(); + destinationFile = nextAvailableFileName(externalFilesDir.getAbsolutePath(), name); + } + Intent intent = CropImage.activity(uriMedia) + .setOutputUri(Uri.fromFile(destinationFile)) // folder in external memory + .setGuidelines(CropImageView.Guidelines.OFF) + .setBorderLineThickness(1) + .setBorderCornerThickness(6) + .setBorderCornerOffset(-3) + .setCropMenuCropButtonTitle(context.getText(R.string.done)) + .getIntent(context); + if (fragment != null) + fragment.startActivityForResult(intent, CropImage.CROP_IMAGE_ACTIVITY_REQUEST_CODE); + else + ((AppCompatActivity) context).startActivityForResult(intent, CropImage.CROP_IMAGE_ACTIVITY_REQUEST_CODE); + } + + /** + * If a file with that name already exists in that folder, increment it with 1 2 3 ... + * Se in quella cartella esiste già un file con quel nome lo incrementa con 1 2 3... + */ + static File nextAvailableFileName(String dir, String name) { + File file = new File(dir, name); + int increment = 0; + while (file.exists()) { + increment++; + file = new File(dir, name.substring(0, name.lastIndexOf('.')) + + increment + name.substring(name.lastIndexOf('.'))); + } + return file; + } + + /** + * Ends the cropping procedure of an image + */ + static void endImageCropping(Intent data) { + CropImage.ActivityResult risultato = CropImage.getActivityResult(data); + Uri uri = risultato.getUri(); // e.g. 'file:///storage/emulated/0/Android/data/app.familygem/files/5/anna.webp' + Picasso.get().invalidate(uri); // clears from the cache any image before clipping that has the same path + String path = uriFilePath(uri); + Global.croppedMedia.setFile(path); + } + + /** + * Answering all permission requests for Android 6+ + * Risposta a tutte le richieste di permessi per Android 6+ + */ + static void permissionsResult(Context context, Fragment fragment, int code, String[] permissions, int[] grantResults, MediaContainer container) { + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + displayImageCaptureDialog(context, fragment, code, container); + } else { + String permission = permissions[0].substring(permissions[0].lastIndexOf('.') + 1); + Toast.makeText(context, context.getString(R.string.not_granted, permission), Toast.LENGTH_LONG).show(); + } + } } \ No newline at end of file diff --git a/app/src/main/java/app/familygem/Fabuloso.java b/app/src/main/java/app/familygem/Fabuloso.java deleted file mode 100644 index 435dd290..00000000 --- a/app/src/main/java/app/familygem/Fabuloso.java +++ /dev/null @@ -1,48 +0,0 @@ -// Fumetto con un suggerimento che compare sopra al FAB - -package app.familygem; - -import android.app.Activity; -import android.content.Context; -import android.os.Handler; -import android.os.Looper; -import android.view.View; -import android.widget.LinearLayout; -import android.widget.TextView; - -public class Fabuloso { - - private final View baloon; - - public Fabuloso(Context context, int textId) { - this(context, context.getString(textId)); - } - - public Fabuloso(Context context, String testo) { - Activity attivita = (Activity)context; - baloon = attivita.getLayoutInflater().inflate(R.layout.fabuloso, null); - baloon.setVisibility(View.INVISIBLE); - ((LinearLayout)attivita.findViewById(R.id.fab_box)).addView(baloon, 0, - new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT)); - ((TextView)baloon.findViewById(R.id.fabuloso_text)).setText(testo); - baloon.setOnTouchListener((vista, evento) -> { - hide(); - return true; - }); - attivita.findViewById(R.id.fab).setOnTouchListener((vista, evento) -> { - hide(); - //vista.performClick(); - return false; // Per eseguire il click dopo - }); - } - - public void show() { - new Handler( Looper.myLooper()).postDelayed( () -> { // compare dopo un secondo - baloon.setVisibility( View.VISIBLE ); - }, 1000); - } - - public void hide() { - baloon.setVisibility( View.INVISIBLE ); - } -} diff --git a/app/src/main/java/app/familygem/FacadeActivity.java b/app/src/main/java/app/familygem/FacadeActivity.java new file mode 100644 index 00000000..079ed1f2 --- /dev/null +++ b/app/src/main/java/app/familygem/FacadeActivity.java @@ -0,0 +1,143 @@ +package app.familygem; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.res.Configuration; +import android.net.Uri; +import android.os.Bundle; +import android.view.View; + +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.app.AppCompatDelegate; + +import org.apache.commons.net.ftp.FTPClient; + +import java.io.FileOutputStream; +import java.io.InputStream; +import java.util.Locale; + +public class FacadeActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle bundle) { + super.onCreate(bundle); + setContentView(R.layout.facciata); + + // Set app locale for application context and resources (localized gedcom.jar library) + Locale locale = AppCompatDelegate.getApplicationLocales().get(0); // Find app locale, or null if not existing + if (locale != null) { + Configuration config = getResources().getConfiguration(); + config.setLocale(locale); + getApplicationContext().getResources().updateConfiguration(config, null); // Change locale both for static methods and jar library + } + + /* + Opening after clicking on various types of links: + https://www.familygem.app/share.php?tree=20190802224208 + Short message + Clicked in Chrome in old Android opens the choice of the app including Family Gem to directly import the tree + Normally opens the site sharing page + intent: //www.familygem.app/condivisi/20200218134922.zip#Intent; scheme = https; end + Official link on the site's sharing page + it is the only one that seems to be sure that it works, in Chrome, in the browser inside Libero, in the L90 Browser + https://www.familygem.app/condivisi/20190802224208.zip + Direct URL to the zip + It works in old android, in new ones simply the file is downloaded + + Apertura in seguito al click su vari tipi di link: + https://www.familygem.app/share.php?tree=20190802224208 + Messaggio breve + Cliccato in Chrome nei vecchi Android apre la scelta dell'app tra cui Family Gem per importare direttamente l'albero + Normalmente apre la pagina di condivisione del sito + intent://www.familygem.app/condivisi/20200218134922.zip#Intent;scheme=https;end + Link ufficiale nella pagina di condivisione del sito + è l'unico che sembra avere certezza di funzionare, in Chrome, nel browser interno a Libero, nel Browser L90 + https://www.familygem.app/condivisi/20190802224208.zip + URL diretto allo zip + Funziona nei vecchi android, nei nuovi semplicemente il file viene scaricato + */ + Intent intent = getIntent(); + Uri uri = intent.getData(); + // By opening the app from Task Manager, avoid re-importing a newly imported shared tree + boolean fromHistory = (intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) == Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY; + if (uri != null && !fromHistory) { + String dataId; + if (uri.getPath().equals("/share.php")) // click on the first message received + dataId = uri.getQueryParameter("tree"); + else if (uri.getLastPathSegment().endsWith(".zip")) // click on the invitation page + dataId = uri.getLastPathSegment().replace(".zip", ""); + else { + U.toast(this, R.string.cant_understand_uri); + return; + } + if (!BuildConfig.utenteAruba.isEmpty()) { + // It does not need to apply for permissions + downloadShared(this, dataId, null); + } + } else { + Intent treesIntent = new Intent(this, TreesActivity.class); + // Open last tree at startup + if (Global.settings.loadTree) { + treesIntent.putExtra("apriAlberoAutomaticamente", true); + treesIntent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); // perhaps ineffective but so be it + } + startActivity(treesIntent); + } + } + + /** + * Connects to the server and downloads the zip file to import it + * */ + static void downloadShared(Context context, String idData, View wheel) { + if (wheel != null) + wheel.setVisibility(View.VISIBLE); + // A new Thread is needed to asynchronously download a file + new Thread(() -> { + try { + FTPClient client = new FTPClient(); //TODO refactor to use Retrofit + client.connect("89.46.104.211"); + client.enterLocalPassiveMode(); + client.login(BuildConfig.utenteAruba, BuildConfig.passwordAruba); + // Todo: Maybe you could use the download manager so that you have the file also listed in 'Downloads' + String zipPath = context.getExternalCacheDir() + "/" + idData + ".zip"; + FileOutputStream fos = new FileOutputStream(zipPath); + String path = "/www.familygem.app/condivisi/" + idData + ".zip"; + InputStream input = client.retrieveFileStream(path); + if (input != null) { + byte[] data = new byte[1024]; + int count; + while ((count = input.read(data)) != -1) { + fos.write(data, 0, count); + } + fos.close(); + if (client.completePendingCommand() && NewTree.unZip(context, zipPath, null)) { + //If the tree was downloaded with the install referrer // Se l'albero è stato scaricato con l'install referrer + if (Global.settings.referrer != null && Global.settings.referrer.equals(idData)) { + Global.settings.referrer = null; + Global.settings.save(); + } + } else { // Failed decompression of downloaded ZIP (e.g. corrupted file) + downloadFailed(context, context.getString(R.string.backup_invalid), wheel); + } + } else // Did not find the file on the server + downloadFailed(context, context.getString(R.string.something_wrong), wheel); + client.logout(); + client.disconnect(); + } catch (Exception e) { + downloadFailed(context, e.getLocalizedMessage(), wheel); + } + }).start(); + } + + /** + * Negative conclusion of the above method + * */ + static void downloadFailed(Context context, String message, View wheel) { + U.toast((Activity) context, message); + if (wheel != null) + ((Activity) context).runOnUiThread(() -> wheel.setVisibility(View.GONE)); + else + context.startActivity(new Intent(context, TreesActivity.class)); + } +} diff --git a/app/src/main/java/app/familygem/Facciata.java b/app/src/main/java/app/familygem/Facciata.java deleted file mode 100644 index 4f0a8b61..00000000 --- a/app/src/main/java/app/familygem/Facciata.java +++ /dev/null @@ -1,123 +0,0 @@ -package app.familygem; - -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.content.res.Configuration; -import android.net.Uri; -import android.os.Bundle; -import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.app.AppCompatDelegate; -import android.view.View; -import org.apache.commons.net.ftp.FTPClient; -import java.io.FileOutputStream; -import java.io.InputStream; -import java.util.Locale; - -public class Facciata extends AppCompatActivity { - - @Override - protected void onCreate(Bundle bundle) { - super.onCreate(bundle); - setContentView(R.layout.facciata); - - // Set app locale for application context and resources (localized gedcom.jar library) - Locale locale = AppCompatDelegate.getApplicationLocales().get(0); // Find app locale, or null if not existing - if( locale != null ) { - Configuration config = getResources().getConfiguration(); - config.setLocale(locale); - getApplicationContext().getResources().updateConfiguration(config, null); // Change locale both for static methods and jar library - } - - /* Apertura in seguito al click su vari tipi di link: - https://www.familygem.app/share.php?tree=20190802224208 - Messaggio breve - Cliccato in Chrome nei vecchi Android apre la scelta dell'app tra cui Family Gem per importare direttamente l'albero - Normalmente apre la pagina di condivisione del sito - intent://www.familygem.app/condivisi/20200218134922.zip#Intent;scheme=https;end - Link ufficiale nella pagina di condivisione del sito - è l'unico che sembra avere certezza di funzionare, in Chrome, nel browser interno a Libero, nel Browser L90 - https://www.familygem.app/condivisi/20190802224208.zip - URL diretto allo zip - Funziona nei vecchi android, nei nuovi semplicemente il file viene scaricato - */ - Intent intent = getIntent(); - Uri uri = intent.getData(); - // Aprendo l'app da Task Manager, evita di re-importare un albero condiviso appena importato - boolean fromHistory = (intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) == Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY; - if( uri != null && !fromHistory ) { - String dataId; - if( uri.getPath().equals( "/share.php" ) ) // click sul primo messaggio ricevuto - dataId = uri.getQueryParameter("tree"); - else if( uri.getLastPathSegment().endsWith( ".zip" ) ) // click sulla pagina di invito - dataId = uri.getLastPathSegment().replace(".zip",""); - else { - U.toast( this, R.string.cant_understand_uri ); - return; - } - if( !BuildConfig.utenteAruba.isEmpty() ) { - // Non ha bisogno di richiedere permessi - scaricaCondiviso( this, dataId, null ); - } - } else { - Intent treesIntent = new Intent(this, Alberi.class); - // Open last tree at startup - if( Global.settings.loadTree ) { - treesIntent.putExtra("apriAlberoAutomaticamente", true); - treesIntent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); // forse inefficace ma tantè - } - startActivity(treesIntent); - } - } - - // Si collega al server e scarica il file zip per importarlo - static void scaricaCondiviso( Context contesto, String idData, View rotella ) { - if( rotella != null ) - rotella.setVisibility( View.VISIBLE ); - // Un nuovo Thread è necessario per scaricare asincronicamente un file - new Thread( () -> { - try { - FTPClient client = new FTPClient(); - client.connect("89.46.104.211"); - client.enterLocalPassiveMode(); - client.login(BuildConfig.utenteAruba, BuildConfig.passwordAruba); - // Todo: Forse si potrebbe usare il download manager così da avere il file anche elencato in 'Downloads' - String percorsoZip = contesto.getExternalCacheDir() + "/" + idData + ".zip"; - FileOutputStream fos = new FileOutputStream(percorsoZip); - String percorso = "/www.familygem.app/condivisi/" + idData + ".zip"; - InputStream input = client.retrieveFileStream(percorso); - if( input != null ) { - byte[] data = new byte[1024]; - int count; - while ((count = input.read(data)) != -1) { - fos.write(data, 0, count); - } - fos.close(); - if( client.completePendingCommand() && AlberoNuovo.unZip(contesto, percorsoZip, null) ) { - // Se l'albero è stato scaricato con l'install referrer - if( Global.settings.referrer != null && Global.settings.referrer.equals(idData) ) { - Global.settings.referrer = null; - Global.settings.save(); - } - } else { // Failed decompression of downloaded ZIP (e.g. corrupted file) - scaricamentoFallito(contesto, contesto.getString(R.string.backup_invalid), rotella); - } - } else // Non ha trovato il file sul server - scaricamentoFallito(contesto, contesto.getString(R.string.something_wrong), rotella); - client.logout(); - client.disconnect(); - } catch( Exception e ) { - scaricamentoFallito(contesto, e.getLocalizedMessage(), rotella); - } - }).start(); - } - - // Conclusione negativa del metodo qui sopra - static void scaricamentoFallito( Context contesto, String messaggio, View rotella ) { - U.toast( (Activity)contesto, messaggio ); - if( rotella != null ) - ((Activity)contesto).runOnUiThread( () -> rotella.setVisibility( View.GONE ) ); - else - contesto.startActivity( new Intent(contesto, Alberi.class) ); - } -} diff --git a/app/src/main/java/app/familygem/Galleria.java b/app/src/main/java/app/familygem/Galleria.java deleted file mode 100644 index c486a1e3..00000000 --- a/app/src/main/java/app/familygem/Galleria.java +++ /dev/null @@ -1,191 +0,0 @@ -// Lista dei Media - -package app.familygem; - -import android.app.Activity; -import android.content.Intent; -import android.os.Bundle; -import androidx.fragment.app.Fragment; -import androidx.appcompat.app.AppCompatActivity; -import androidx.recyclerview.widget.GridLayoutManager; -import androidx.recyclerview.widget.RecyclerView; -import android.view.ContextMenu; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import com.theartofdev.edmodo.cropper.CropImage; -import org.folg.gedcom.model.Media; -import org.folg.gedcom.model.MediaContainer; -import org.folg.gedcom.model.MediaRef; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Set; -import app.familygem.visitor.ListaMediaContenitore; -import app.familygem.visitor.RiferimentiMedia; -import app.familygem.visitor.TrovaPila; -import static app.familygem.Global.gc; - -public class Galleria extends Fragment { - - ListaMediaContenitore visitaMedia; - AdattatoreGalleriaMedia adattatore; - - @Override - public View onCreateView( LayoutInflater inflater, ViewGroup container, Bundle bandolo ) { - setHasOptionsMenu( true ); - View vista = inflater.inflate( R.layout.galleria, container, false ); - RecyclerView griglia = vista.findViewById( R.id.galleria ); - griglia.setHasFixedSize( true ); - if( gc != null ) { - visitaMedia = new ListaMediaContenitore( gc, !getActivity().getIntent().getBooleanExtra("galleriaScegliMedia",false ) ); - gc.accept( visitaMedia ); - arredaBarra(); - RecyclerView.LayoutManager gestoreLayout = new GridLayoutManager( getContext(), 2 ); - griglia.setLayoutManager( gestoreLayout ); - adattatore = new AdattatoreGalleriaMedia( visitaMedia.listaMedia, true ); - griglia.setAdapter( adattatore ); - vista.findViewById( R.id.fab ).setOnClickListener( v -> - F.appAcquisizioneImmagine( getContext(), Galleria.this, 4546, null ) - ); - } - return vista; - } - - // Andandosene dall'attività resetta l'extra se non è stato scelto un media condiviso - @Override - public void onPause() { - super.onPause(); - getActivity().getIntent().removeExtra("galleriaScegliMedia"); - } - - // Scrive il titolo nella barra - void arredaBarra() { - ((AppCompatActivity)getActivity()).getSupportActionBar().setTitle( visitaMedia.listaMedia.size() - + " " + getString(R.string.media).toLowerCase() ); - } - - // Aggiorna i contenuti della galleria - void ricrea() { - visitaMedia.listaMedia.clear(); - gc.accept( visitaMedia ); - adattatore.notifyDataSetChanged(); - arredaBarra(); - } - - // todo bypassabile? - static int popolarita( Media med ) { - RiferimentiMedia riferiMedia = new RiferimentiMedia( gc, med, false ); - return riferiMedia.num; - } - - static Media nuovoMedia( Object contenitore ){ - Media media = new Media(); - media.setId( U.nuovoId(gc,Media.class) ); - media.setFileTag("FILE"); // Necessario per poi esportare il Gedcom - gc.addMedia( media ); - if( contenitore != null ) { - MediaRef rifMed = new MediaRef(); - rifMed.setRef( media.getId() ); - ((MediaContainer)contenitore).addMediaRef( rifMed ); - } - return media; - } - - // Scollega da un contenitore un media condiviso - static void scollegaMedia(String mediaId, MediaContainer container) { - Iterator refs = container.getMediaRefs().iterator(); - while( refs.hasNext() ) { - MediaRef ref = refs.next(); - if( ref.getMedia( Global.gc ) == null // Eventuale ref a un media inesistente - || ref.getRef().equals(mediaId) ) - refs.remove(); - } - if( container.getMediaRefs().isEmpty() ) - container.setMediaRefs( null ); - } - - // Elimina un media condiviso o locale e rimuove i riferimenti nei contenitori - // Restituisce un array con i capostipiti modificati - public static Object[] eliminaMedia(Media media, View vista) { - Set capi; - if( media.getId() != null ) { // media OBJECT - gc.getMedia().remove(media); - // Elimina i riferimenti in tutti i contenitori - RiferimentiMedia eliminaMedia = new RiferimentiMedia(gc, media, true); - capi = eliminaMedia.capostipiti; - } else { // media LOCALE - new TrovaPila(gc, media); // trova temporaneamente la pila del media per individuare il container - MediaContainer container = (MediaContainer)Memoria.oggettoContenitore(); - container.getMedia().remove(media); - if( container.getMedia().isEmpty() ) - container.setMedia(null); - capi = new HashSet<>(); // set con un solo Object capostipite - capi.add( Memoria.oggettoCapo() ); - Memoria.arretra(); // elimina la pila appena creata - } - Memoria.annullaIstanze(media); - if( vista != null ) - vista.setVisibility(View.GONE); - return capi.toArray(new Object[0]); - } - - // Il file pescato dal file manager diventa media condiviso - @Override - public void onActivityResult( int requestCode, int resultCode, Intent data ) { - if( resultCode == Activity.RESULT_OK ) { - if( requestCode == 4546 ) { // File preso da app fornitrice viene salvato in Media ed eventualmente ritagliato - Media media = nuovoMedia(null); - if( F.proponiRitaglio(getContext(), this, data, media) ) { // se è un'immagine (quindi ritagliabile) - U.save(false, media); - // Non deve scattare onRestart() + recreate() perché poi il fragment di arrivo non è più lo stesso - return; - } - } else if( requestCode == CropImage.CROP_IMAGE_ACTIVITY_REQUEST_CODE ) { - F.fineRitaglioImmagine(data); - } - U.save(true, Global.mediaCroppato); - } else if( requestCode == CropImage.CROP_IMAGE_ACTIVITY_REQUEST_CODE ) // se clic su freccia indietro in Crop Image - Global.edited = true; - } - - // Menu contestuale - private Media media; - @Override - public void onCreateContextMenu( ContextMenu menu, View vista, ContextMenu.ContextMenuInfo info ) { - media = (Media) vista.getTag( R.id.tag_oggetto ); - menu.add(0, 0, 0, R.string.delete ); - } - @Override - public boolean onContextItemSelected(MenuItem item) { - if( item.getItemId() == 0 ) { - Object[] modificati = eliminaMedia(media, null); - ricrea(); - U.save(false, modificati); - return true; - } - return false; - } - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - menu.add(0, 0, 0, R.string.media_folders); - } - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if( item.getItemId() == 0 ) { - startActivity(new Intent(getContext(), CartelleMedia.class) - .putExtra("idAlbero", Global.settings.openTree) - ); - return true; - } - return false; - } - - @Override - public void onRequestPermissionsResult(int codice, String[] permessi, int[] accordi) { - F.risultatoPermessi(getContext(), this, codice, permessi, accordi, null); - } -} diff --git a/app/src/main/java/app/familygem/GalleryFragment.java b/app/src/main/java/app/familygem/GalleryFragment.java new file mode 100644 index 00000000..cbbf07e8 --- /dev/null +++ b/app/src/main/java/app/familygem/GalleryFragment.java @@ -0,0 +1,208 @@ +package app.familygem; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; + +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; +import androidx.appcompat.app.AppCompatActivity; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import android.view.ContextMenu; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import com.theartofdev.edmodo.cropper.CropImage; +import org.folg.gedcom.model.Media; +import org.folg.gedcom.model.MediaContainer; +import org.folg.gedcom.model.MediaRef; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import app.familygem.visitor.MediaListContainer; +import app.familygem.visitor.MediaReferences; +import app.familygem.visitor.FindStack; +import static app.familygem.Global.gc; + +/** + * List of Media + * */ +public class GalleryFragment extends Fragment { + + MediaListContainer mediaVisitor; + MediaGalleryAdapter adapter; + + @Override + public View onCreateView( LayoutInflater inflater, ViewGroup container, Bundle bandolo ) { + setHasOptionsMenu( true ); + View view = inflater.inflate( R.layout.galleria, container, false ); + RecyclerView recyclerView = view.findViewById( R.id.galleria ); + recyclerView.setHasFixedSize( true ); + if( gc != null ) { + mediaVisitor = new MediaListContainer( gc, !getActivity().getIntent().getBooleanExtra("galleriaScegliMedia",false ) ); + gc.accept(mediaVisitor); + setToolbarTitle(); + RecyclerView.LayoutManager gestoreLayout = new GridLayoutManager( getContext(), 2 ); + recyclerView.setLayoutManager( gestoreLayout ); + adapter = new MediaGalleryAdapter( mediaVisitor.mediaList, true ); + recyclerView.setAdapter(adapter); + view.findViewById( R.id.fab ).setOnClickListener( v -> + F.displayImageCaptureDialog( getContext(), GalleryFragment.this, 4546, null ) + ); + } + return view; + } + + /** + * Leaving the activity resets the extra if no shared media has been chosen + * // Andandosene dall'attività resetta l'extra se non è stato scelto un media condiviso + * */ + @Override + public void onPause() { + super.onPause(); + getActivity().getIntent().removeExtra("galleriaScegliMedia"); + } + + void setToolbarTitle() { + ((AppCompatActivity)getActivity()).getSupportActionBar().setTitle( mediaVisitor.mediaList.size() + + " " + getString(R.string.media).toLowerCase() ); + } + + /** + * Update the contents of the gallery + * */ + void recreate() { + mediaVisitor.mediaList.clear(); + gc.accept(mediaVisitor); + adapter.notifyDataSetChanged(); + setToolbarTitle(); + } + + // todo bypassabile? + static int popularity(Media med ) { + MediaReferences riferiMedia = new MediaReferences( gc, med, false ); + return riferiMedia.num; + } + + static Media newMedia(Object container ){ + Media media = new Media(); + media.setId( U.newID(gc,Media.class) ); + media.setFileTag("FILE"); // Necessary to then export the Gedcom + gc.addMedia( media ); + if( container != null ) { + MediaRef mediaRef = new MediaRef(); + mediaRef.setRef( media.getId() ); + ((MediaContainer)container).addMediaRef( mediaRef ); + } + return media; + } + + /** + * Detach a shared media from a container + * */ + static void disconnectMedia(String mediaId, MediaContainer container) { + Iterator refs = container.getMediaRefs().iterator(); + while( refs.hasNext() ) { + MediaRef ref = refs.next(); + if( ref.getMedia( Global.gc ) == null // Possible ref to a non-existent media + || ref.getRef().equals(mediaId) ) + refs.remove(); + } + if( container.getMediaRefs().isEmpty() ) + container.setMediaRefs( null ); + } + + /** + * // Delete a shared or local media and remove references in containers + * // Return an array with modified progenitors + * + * // Elimina un media condiviso o locale e rimuove i riferimenti nei contenitori + * // Restituisce un array con i capostipiti modificati + * */ + public static Object[] deleteMedia(Media media, View view) { + Set heads; + if( media.getId() != null ) { // media OBJECT + gc.getMedia().remove(media); + // Delete references in all containers + MediaReferences deleteMedia = new MediaReferences(gc, media, true); + heads = deleteMedia.founders; + } else { // media LOCALE + new FindStack(gc, media); //temporarily find the media stack to locate the container // trova temporaneamente la pila del media per individuare il container + MediaContainer container = (MediaContainer) Memory.getSecondToLastObject(); + container.getMedia().remove(media); + if( container.getMedia().isEmpty() ) + container.setMedia(null); + heads = new HashSet<>(); // set with only one parent Object + heads.add( Memory.firstObject() ); + Memory.clearStackAndRemove(); // delete the stack you just created + } + Memory.setInstanceAndAllSubsequentToNull(media); + if( view != null ) + view.setVisibility(View.GONE); + return heads.toArray(new Object[0]); + } + + /** + * The file fished by the file manager becomes shared media + * // Il file pescato dal file manager diventa media condiviso + * */ + @Override + public void onActivityResult( int requestCode, int resultCode, Intent data ) { + if( resultCode == Activity.RESULT_OK ) { + if( requestCode == 4546 ) { //File taken from the supplier app is saved in Media and possibly cropped // File preso da app fornitrice viene salvato in Media ed eventualmente ritagliato + Media media = newMedia(null); + if( F.proposeCropping(getContext(), this, data, media) ) { // if it is an image (therefore it can be cropped) + U.save(false, media); + //onRestart () + recreate () must not be triggered because then the arrival fragment is no longer the same // Non deve scattare onRestart() + recreate() perché poi il fragment di arrivo non è più lo stesso + return; + } + } else if( requestCode == CropImage.CROP_IMAGE_ACTIVITY_REQUEST_CODE ) { + F.endImageCropping(data); + } + U.save(true, Global.croppedMedia); + } else if( requestCode == CropImage.CROP_IMAGE_ACTIVITY_REQUEST_CODE ) // if you click the back arrow in Crop Image + Global.edited = true; + } + + // contextual Menu + private Media media; + @Override + public void onCreateContextMenu( ContextMenu menu, View view, ContextMenu.ContextMenuInfo info ) { + media = (Media) view.getTag( R.id.tag_object ); + menu.add(0, 0, 0, R.string.delete ); + } + @Override + public boolean onContextItemSelected(MenuItem item) { + if( item.getItemId() == 0 ) { + Object[] modified = deleteMedia(media, null); + recreate(); + U.save(false, modified); + return true; + } + return false; + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + menu.add(0, 0, 0, R.string.media_folders); + } + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if( item.getItemId() == 0 ) { + startActivity(new Intent(getContext(), MediaFoldersActivity.class) + .putExtra("idAlbero", Global.settings.openTree) + ); + return true; + } + return false; + } + + @Override + public void onRequestPermissionsResult(int codice, @NonNull String[] permission, @NonNull int[] grantResults) { + F.permissionsResult(getContext(), this, codice, permission, grantResults, null); + } +} diff --git a/app/src/main/java/app/familygem/Datatore.java b/app/src/main/java/app/familygem/GedcomDateConverter.java similarity index 72% rename from app/src/main/java/app/familygem/Datatore.java rename to app/src/main/java/app/familygem/GedcomDateConverter.java index 0bdfa6b1..741e0ea5 100644 --- a/app/src/main/java/app/familygem/Datatore.java +++ b/app/src/main/java/app/familygem/GedcomDateConverter.java @@ -1,5 +1,3 @@ -// Questa classe riceve una data Gedcom, la analizza e la traduce in una classe Data - package app.familygem; import java.text.DateFormat; @@ -11,24 +9,31 @@ import app.familygem.constant.Format; import app.familygem.constant.Kind; -class Datatore { +/** + * This class receives a Gedcom date, parses it and translates it into a Data class + * */ +class GedcomDateConverter { Data data1; Data data2; - String frase; // Quella che andrà tra parentesi + String phrase; //The one that will go in parentheses // Quella che andrà tra parentesi Kind kind; - static final String[] mesiGedcom = { "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" }; - static final String[] suffissi = { "B.C.", "BC", "BCE" }; + static final String[] gedcomMonths = { "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" }; + static final String[] suffixes = { "B.C.", "BC", "BCE" }; - // With a string date in GEDCOM style - Datatore(String gedcomDate) { + /** + * With a string date in GEDCOM style + * */ + GedcomDateConverter(String gedcomDate) { data1 = new Data(); data2 = new Data(); - analizza(gedcomDate); + analyze(gedcomDate); } - // With one single complete Date - Datatore(Date date) { + /** + * With one single complete Date + * */ + GedcomDateConverter(Date date) { data1 = new Data(); data1.date = date; data1.format.applyPattern(Format.D_M_Y); @@ -38,38 +43,41 @@ class Datatore { class Data { Date date; SimpleDateFormat format; - boolean negativa; - boolean doppia; + boolean negative; + boolean doubleYear; Data() { DateFormatSymbols simboliFormato = new DateFormatSymbols(); - simboliFormato.setShortMonths( mesiGedcom ); + simboliFormato.setShortMonths(gedcomMonths); format = new SimpleDateFormat(); format.setDateFormatSymbols( simboliFormato ); } - // Prende una data Gedcom esatta e ci farcisce gli attributi della classe Data - void scanna( String dataGc ) { + /** + * It takes an exact Gedcom date and stuffs the attributes of the Date class into it + * // Prende una data Gedcom esatta e ci farcisce gli attributi della classe Data + * */ + void scan(String dataGc ) { - // Riconosce se la data è B.C. e rimuove il suffisso - negativa = false; // resetta eventuale true - for( String suffix : suffissi ) { + // Recognize if the date is B.C. and remove the suffix + negative = false; //eventually reset to true // resetta eventuale true + for( String suffix : suffixes) { if( dataGc.endsWith(suffix) ) { - negativa = true; + negative = true; dataGc = dataGc.substring(0, dataGc.indexOf(suffix)).trim(); break; } } dataGc = dataGc.replaceAll("[\\\\_\\-|.,;:?'\"#^&*°+=~()\\[\\]{}]", " "); // tutti tranne '/' - // Distingue una data con anno doppio 1712/1713 da una data tipo 17/12/1713 - doppia = false; // reset + // Distinguishes a date with a double year 1712/1713 from a date type 17/12/1713 + doubleYear = false; // reset if( dataGc.indexOf('/') > 0 ) { String[] tata = dataGc.split("[/ ]"); - if( tata.length > 1 && tata[tata.length-2].length() < 3 && U.soloNumeri( tata[tata.length-2] ) <= 12 ) + if( tata.length > 1 && tata[tata.length-2].length() < 3 && U.extractNum( tata[tata.length-2] ) <= 12 ) dataGc = dataGc.replace( '/', ' ' ); else - doppia = true; + doubleYear = true; } for( String dateFormat : Format.PATTERNS ) { format.applyPattern( dateFormat ); @@ -83,17 +91,19 @@ void scanna( String dataGc ) { if( isFormat(Format.m_Y) ) format.applyPattern(Format.M_Y); - // Rende la data effettivamente negativa (per il calcolo delle età) - if( negativa ) cambiaEra(); + // Makes date effectively negative (for age calculation) + if(negative) changeEra(); } - // Rende la data BC oppure AD coerentemente con il boolean 'negativa' - void cambiaEra() { + /** + * Makes the date BC or AD consistent with the 'negative' boolean + * */ + void changeEra() { if( date != null ) { - // La data viene riparsata cambiandogli l'era + // The date is repaired by changing the era SimpleDateFormat sdf = new SimpleDateFormat(Format.D_M_Y + " G", Locale.US); String data = sdf.format(date); - if( negativa ) + if(negative) data = data.replace("AD", "BC"); else data = data.replace("BC", "AD"); @@ -114,8 +124,10 @@ public String toString() { } } - // Riconosce il tipo di data e crea la classe Data - void analizza(String dataGc) { + /** + * It recognizes the type of data and creates the Data class + * */ + void analyze(String dataGc) { // Reset the important values kind = null; @@ -126,7 +138,7 @@ void analizza(String dataGc) { kind = Kind.EXACT; return; } - // Riconosce i tipi diversi da EXACT e converte la stringa in Data + // It recognizes types other than EXACT and converts the string to Data String dataGcMaiusc = dataGc.toUpperCase(); for( int i = 1; i < Kind.values().length; i++ ) { Kind k = Kind.values()[i]; @@ -134,32 +146,32 @@ void analizza(String dataGc) { kind = k; if( k == Kind.BETWEEN_AND && dataGcMaiusc.contains("AND") ) { if( dataGcMaiusc.indexOf("AND") > dataGcMaiusc.indexOf("BET") + 4 ) - data1.scanna(dataGcMaiusc.substring(4, dataGcMaiusc.indexOf("AND") - 1)); + data1.scan(dataGcMaiusc.substring(4, dataGcMaiusc.indexOf("AND") - 1)); if( dataGcMaiusc.length() > dataGcMaiusc.indexOf("AND") + 3 ) - data2.scanna(dataGcMaiusc.substring(dataGcMaiusc.indexOf("AND") + 4)); + data2.scan(dataGcMaiusc.substring(dataGcMaiusc.indexOf("AND") + 4)); } else if( k == Kind.FROM && dataGcMaiusc.contains("TO") ) { kind = Kind.FROM_TO; if( dataGcMaiusc.indexOf("TO") > dataGcMaiusc.indexOf("FROM") + 5 ) - data1.scanna(dataGcMaiusc.substring(5, dataGcMaiusc.indexOf("TO") - 1)); + data1.scan(dataGcMaiusc.substring(5, dataGcMaiusc.indexOf("TO") - 1)); if( dataGcMaiusc.length() > dataGcMaiusc.indexOf("TO") + 2 ) - data2.scanna(dataGcMaiusc.substring(dataGcMaiusc.indexOf("TO") + 3)); + data2.scan(dataGcMaiusc.substring(dataGcMaiusc.indexOf("TO") + 3)); } else if( k == Kind.PHRASE ) { // Phrase date between parenthesis if( dataGc.endsWith(")") ) - frase = dataGc.substring(1, dataGc.indexOf(")")); + phrase = dataGc.substring(1, dataGc.indexOf(")")); else - frase = dataGc; - } else if( dataGcMaiusc.length() > k.prefix.length() ) // Altri prefissi seguiti da qualcosa - data1.scanna(dataGcMaiusc.substring(k.prefix.length() + 1)); + phrase = dataGc; + } else if( dataGcMaiusc.length() > k.prefix.length() ) // Other prefixes followed by something + data1.scan(dataGcMaiusc.substring(k.prefix.length() + 1)); break; } } - // Rimane da provare il tipo EXACT, altrimenti diventa una frase + //It remains to prove the type EXACT, otherwise it becomes a sentence // Rimane da provare il type EXACT, altrimenti diventa una frase if( kind == null ) { - data1.scanna(dataGc); + data1.scan(dataGc); if( data1.date != null ) { kind = Kind.EXACT; } else { - frase = dataGc; + phrase = dataGc; kind = Kind.PHRASE; } } @@ -175,10 +187,10 @@ public String writeDate(boolean yearOnly) { Locale locale = Locale.getDefault(); DateFormat dateFormat = new SimpleDateFormat(yearOnly ? Format.Y : data1.format.toPattern(), locale); Date dateOne = (Date)data1.date.clone(); // Cloned so the year of a double date can be modified without consequences - if( data1.doppia ) + if( data1.doubleYear) dateOne.setYear(data1.date.getYear() + 1); text = dateFormat.format(dateOne); - if( data1.negativa ) + if( data1.negative) text = "-" + text; if( kind == Kind.APPROXIMATE || kind == Kind.CALCULATED || kind == Kind.ESTIMATED ) text += "?"; @@ -190,14 +202,14 @@ else if( kind == Kind.TO ) text = "→" + text; else if( (kind == Kind.BETWEEN_AND || kind == Kind.FROM_TO) && data2.date != null ) { Date dateTwo = (Date)data2.date.clone(); - if( data2.doppia ) + if( data2.doubleYear) dateTwo.setYear(data2.date.getYear() + 1); dateFormat = new SimpleDateFormat(yearOnly ? Format.Y : data2.format.toPattern(), locale); String second = dateFormat.format(dateTwo); - if( data2.negativa ) + if( data2.negative) second = "-" + second; if( !second.equals(text) ) { - if( !data1.negativa && !data2.negativa ) { + if( !data1.negative && !data2.negative) { if( !yearOnly && data1.isFormat(Format.D_M_Y) && data1.format.equals(data2.format) && dateOne.getMonth() == dateTwo.getMonth() && dateOne.getYear() == dateTwo.getYear() ) { // Same month and year text = text.substring(0, text.indexOf(' ')); @@ -220,7 +232,9 @@ else if( (kind == Kind.BETWEEN_AND || kind == Kind.FROM_TO) && data2.date != nul return text; } - // Plain text of the date in local language + /** + * Plain text of the date in local language + * */ public String writeDateLong() { String txt = ""; int pre = 0; @@ -248,8 +262,8 @@ public String writeDateLong() { if( data2.date != null ) txt += writePiece(data2); } - } else if( frase != null ) { - txt = frase; + } else if( phrase != null ) { + txt = phrase; } return txt.trim(); } @@ -257,19 +271,21 @@ public String writeDateLong() { String writePiece(Data date) { DateFormat dateFormat = new SimpleDateFormat(date.format.toPattern().replace("MMM", "MMMM"), Locale.getDefault()); String txt = " " + dateFormat.format(date.date); - if( date.doppia ) { + if( date.doubleYear) { String year = String.valueOf(date.date.getYear() + 1901); if( year.length() > 1 ) // Two or more digits txt += "/" + year.substring(year.length() - 2); else // One digit txt += "/0" + year; } - if( date.negativa ) + if( date.negative) txt += " B.C."; return txt; } - // Return an integer representing the main date in the format YYYYMMDD, otherwise MAX_VALUE + /** + * Return an integer representing the main date in the format YYYYMMDD, otherwise MAX_VALUE + * */ public int getDateNumber() { if( data1.date != null && !data1.isFormat(Format.D_M) ) { return (data1.date.getYear() + 1900) * 10000 + (data1.date.getMonth() + 1) * 100 + data1.date.getDate(); @@ -277,7 +293,9 @@ public int getDateNumber() { return Integer.MAX_VALUE; } - // Kinds of date that represent a single event in time + /** + * Kinds of date that represent a single event in time + * */ boolean isSingleKind() { return kind == Kind.EXACT || kind == Kind.APPROXIMATE || kind == Kind.CALCULATED || kind == Kind.ESTIMATED; } diff --git a/app/src/main/java/app/familygem/Global.java b/app/src/main/java/app/familygem/Global.java index 257af77f..04b4d3c9 100644 --- a/app/src/main/java/app/familygem/Global.java +++ b/app/src/main/java/app/familygem/Global.java @@ -4,121 +4,134 @@ import android.content.res.Configuration; import android.view.View; import android.widget.Toast; + import androidx.appcompat.app.AppCompatDelegate; import androidx.multidex.MultiDexApplication; + import com.google.gson.Gson; + import org.apache.commons.io.FileUtils; import org.folg.gedcom.model.Gedcom; import org.folg.gedcom.model.Media; + import java.io.File; import java.util.Locale; public class Global extends MultiDexApplication { - public static Gedcom gc; - public static Context context; - public static Settings settings; - public static String indi; // Id of the selected person displayed across the app - public static int familyNum; // Quale famiglia dei genitori mostrare in diagramma, normalmente la 0 - static View principalView; - static int ordineMagazzino; - public static boolean edited; // C'è stata un'editazione in EditaIndividuo o in Dettaglio e quindi il contenuto delle attività precedenti va aggiornato - static boolean daSalvare; // Il contenuto del Gedcom è stato modificato e deve essere salvato - public static String fotoCamera; // percorso in cui l'app fotocamera mette la foto scattata - public static Media mediaCroppato; // parcheggio temporaneo del media in fase di croppaggio - static Gedcom gc2; // per il confronto degli aggiornamenti - static int treeId2; // id dell'albero2 con gli aggiornamenti + public static Gedcom gc; + public static Context context; + public static Settings settings; + public static String indi; // Id of the selected person displayed across the app + public static int familyNum; // Which parents' family to show in the diagram, usually 0 + static View mainView; + static int repositoryOrder; + public static boolean edited; // There has been an editing in IndividualEditorActivity or in DetailActivity and therefore the content of the previous activities must be updated + static boolean shouldSave; // The Gedcom content has been changed and needs to be saved + /** + * path where the camera app puts the photo taken + */ + public static String pathOfCameraDestination; + public static Media croppedMedia; //temporary parking of the media in the cropping phase // parcheggio temporaneo del media in fase di croppaggio + static Gedcom gc2; // for comparison of updates + static int treeId2; // id of tree2 with updates + + /** + * This is called when the application starts, and also when it is restarted + */ + @Override + public void onCreate() { + super.onCreate(); + context = getApplicationContext(); + start(context); + } - // Viene chiamato all'avvio dell'applicazione, e anche quando viene riavviata - @Override - public void onCreate() { - super.onCreate(); - context = getApplicationContext(); - start(context); - } + public static void start(Context context) { + File settingsFile = new File(context.getFilesDir(), "settings.json"); + // Rename "preferenze.json" to "settings.json" (introduced in version 0.8) + File preferencesFile = new File(context.getFilesDir(), "preferenze.json"); + if (preferencesFile.exists() && !settingsFile.exists()) { + if (!preferencesFile.renameTo(settingsFile)) { + Toast.makeText(context, R.string.something_wrong, Toast.LENGTH_LONG).show(); + settingsFile = preferencesFile; + } + } + try { + String jsonString = FileUtils.readFileToString(settingsFile, "UTF-8"); + jsonString = updateSettings(jsonString); + Gson gson = new Gson(); + settings = gson.fromJson(jsonString, Settings.class); + } catch (Exception e) { + // At first boot avoid to show the toast saying that settings.json doesn't exist + if (!(e instanceof java.io.FileNotFoundException)) { + Toast.makeText(context, e.getLocalizedMessage(), Toast.LENGTH_LONG).show(); + } + } + if (settings == null) { + settings = new Settings(); + settings.init(); + // Restore possibly lost trees + for (File file : context.getFilesDir().listFiles()) { + String name = file.getName(); + if (file.isFile() && name.endsWith(".json")) { + try { + int treeId = Integer.parseInt(name.substring(0, name.lastIndexOf(".json"))); + File mediaDir = new File(context.getExternalFilesDir(null), String.valueOf(treeId)); + settings.trees.add(new Settings.Tree(treeId, String.valueOf(treeId), + mediaDir.exists() ? mediaDir.getPath() : null, + 0, 0, null, null, 0)); + } catch (Exception e) { + } + } + } + // Some tree has been restored + if (!settings.trees.isEmpty()) + settings.referrer = null; + settings.save(); + } + // Diagram settings were (probably) introduced in version 0.7.4 + if (settings.diagram == null) { + settings.diagram = new Settings.Diagram().init(); + settings.save(); + } + } - public static void start(Context context) { - File settingsFile = new File(context.getFilesDir(), "settings.json"); - // Rename "preferenze.json" to "settings.json" (introduced in version 0.8) - File preferenzeFile = new File(context.getFilesDir(), "preferenze.json"); - if( preferenzeFile.exists() && !settingsFile.exists()) { - if( !preferenzeFile.renameTo(settingsFile) ) { - Toast.makeText(context, R.string.something_wrong, Toast.LENGTH_LONG).show(); - settingsFile = preferenzeFile; - } - } - try { - String jsonString = FileUtils.readFileToString(settingsFile, "UTF-8"); - jsonString = updateSettings(jsonString); - Gson gson = new Gson(); - settings = gson.fromJson(jsonString, Settings.class); - } catch( Exception e ) { - // At first boot avoid to show the toast saying that settings.json doesn't exist - if( !(e instanceof java.io.FileNotFoundException) ) { - Toast.makeText(context, e.getLocalizedMessage(), Toast.LENGTH_LONG).show(); - } - } - if( settings == null ) { - settings = new Settings(); - settings.init(); - // Restore possibly lost trees - for( File file : context.getFilesDir().listFiles() ) { - String name = file.getName(); - if( file.isFile() && name.endsWith(".json") ) { - try { - int treeId = Integer.parseInt(name.substring(0, name.lastIndexOf(".json"))); - File mediaDir = new File(context.getExternalFilesDir(null), String.valueOf(treeId)); - settings.trees.add(new Settings.Tree(treeId, String.valueOf(treeId), - mediaDir.exists() ? mediaDir.getPath() : null, - 0, 0, null, null, 0)); - } catch( Exception e ) { - } - } - } - // Some tree has been restored - if( !settings.trees.isEmpty() ) - settings.referrer = null; - settings.save(); - } - // Diagram settings were (probably) introduced in version 0.7.4 - if( settings.diagram == null ) { - settings.diagram = new Settings.Diagram().init(); - settings.save(); - } - } + /** + * Modifications to the text coming from files/settings.json + */ + private static String updateSettings(String json) { + // Version 0.8 added new settings for the diagram + return json + .replace("\"siblings\":true", "siblings:2,cousins:2,spouses:true") + .replace("\"siblings\":false", "siblings:0,cousins:0,spouses:true") - // Modifications to the text coming from files/settings.json - private static String updateSettings(String json) { - // Version 0.8 added new settings for the diagram - json = json.replace("\"siblings\":true", "siblings:2,cousins:2,spouses:true"); - json = json.replace("\"siblings\":false", "siblings:0,cousins:0,spouses:true"); - // Italian translated to English (version 0.8) - json = json.replace("\"alberi\":", "\"trees\":"); - json = json.replace("\"idAprendo\":", "\"openTree\":"); - json = json.replace("\"autoSalva\":", "\"autoSave\":"); - json = json.replace("\"caricaAlbero\":", "\"loadTree\":"); - json = json.replace("\"esperto\":", "\"expert\":"); - json = json.replace("\"nome\":", "\"title\":"); - json = json.replace("\"cartelle\":", "\"dirs\":"); - json = json.replace("\"individui\":", "\"persons\":"); - json = json.replace("\"generazioni\":", "\"generations\":"); - json = json.replace("\"radice\":", "\"root\":"); - json = json.replace("\"condivisioni\":", "\"shares\":"); - json = json.replace("\"radiceCondivisione\":", "\"shareRoot\":"); - json = json.replace("\"grado\":", "\"grade\":"); - json = json.replace("\"data\":", "\"dateId\":"); - return json; - } + // Italian translated to English (version 0.8) + .replace("\"alberi\":", "\"trees\":") + .replace("\"alberi\":", "\"trees\":") + .replace("\"idAprendo\":", "\"openTree\":") + .replace("\"autoSalva\":", "\"autoSave\":") + .replace("\"caricaAlbero\":", "\"loadTree\":") + .replace("\"esperto\":", "\"expert\":") + .replace("\"nome\":", "\"title\":") + .replace("\"cartelle\":", "\"dirs\":") + .replace("\"individui\":", "\"persons\":") + .replace("\"generazioni\":", "\"generations\":") + .replace("\"radice\":", "\"root\":") + .replace("\"condivisioni\":", "\"shares\":") + .replace("\"radiceCondivisione\":", "\"shareRoot\":") + .replace("\"grado\":", "\"grade\":") + .replace("\"data\":", "\"dateId\":"); + } - @Override - public void onConfigurationChanged(Configuration newConfig) { - // Keep the app locale if system language is changed while the app is running - Locale appLocale = AppCompatDelegate.getApplicationLocales().get(0); - if( appLocale != null ) { - Locale.setDefault(appLocale); // Keep the gedcom.jar library locale - newConfig.setLocale(appLocale); - getApplicationContext().getResources().updateConfiguration(newConfig, null); // Keep global context - } - super.onConfigurationChanged(newConfig); - } + @Override + public void onConfigurationChanged(Configuration newConfig) { + // Keep the app locale if system language is changed while the app is running + Locale appLocale = AppCompatDelegate.getApplicationLocales().get(0); + if (appLocale != null) { + Locale.setDefault(appLocale); // Keep the gedcom.jar library locale + newConfig.setLocale(appLocale); + getApplicationContext().getResources().updateConfiguration(newConfig, null); // Keep global context + } + super.onConfigurationChanged(newConfig); + } } diff --git a/app/src/main/java/app/familygem/IndividualEditorActivity.java b/app/src/main/java/app/familygem/IndividualEditorActivity.java new file mode 100644 index 00000000..8f0fc579 --- /dev/null +++ b/app/src/main/java/app/familygem/IndividualEditorActivity.java @@ -0,0 +1,442 @@ +package app.familygem; + +import android.os.Bundle; +import androidx.appcompat.widget.SwitchCompat; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; +import android.view.View; +import android.view.inputmethod.EditorInfo; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.RadioButton; +import android.widget.RadioGroup; +import org.folg.gedcom.model.ChildRef; +import org.folg.gedcom.model.EventFact; +import org.folg.gedcom.model.Family; +import org.folg.gedcom.model.Name; +import org.folg.gedcom.model.ParentFamilyRef; +import org.folg.gedcom.model.Person; +import org.folg.gedcom.model.SpouseFamilyRef; +import org.folg.gedcom.model.SpouseRef; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import app.familygem.constant.Gender; +import app.familygem.detail.EventActivity; +import app.familygem.detail.FamilyActivity; +import static app.familygem.Global.gc; + +public class IndividualEditorActivity extends AppCompatActivity { + + Person p; + String idIndi; + String familyId; + int relationship; + RadioButton sexMale; + RadioButton sexFemale; + RadioButton sexUnknown; + int lastChecked; + EditText dateOfBirth; + PublisherDateLinearLayout publisherDateLinearLayoutDOB; //DOB = Date of Birth + EditText birthplaceEditText; + SwitchCompat isDeadSwitch; + EditText dateOfDeath; + PublisherDateLinearLayout publisherDateLinearLayoutDOD; //DOD = Date of Death + EditText deathPlace; + boolean nameFromPieces; //If the name / surname comes from the Given and Surname pieces, they must return there // Se il nome/cognome vengono dai pieces Given e Surname, lì devono tornare + + @Override + protected void onCreate(Bundle bandolo) { + super.onCreate(bandolo); + U.ensureGlobalGedcomNotNull(gc); + setContentView( R.layout.edita_individuo ); + Bundle bundle = getIntent().getExtras(); + idIndi = bundle.getString("idIndividuo"); + familyId = bundle.getString("idFamiglia"); + relationship = bundle.getInt("relazione", 0 ); + + sexMale = findViewById(R.id.sesso1); + sexFemale = findViewById(R.id.sesso2); + sexUnknown = findViewById(R.id.sesso3); + dateOfBirth = findViewById( R.id.data_nascita ); + publisherDateLinearLayoutDOB = findViewById(R.id.editore_data_nascita); + birthplaceEditText = findViewById(R.id.luogo_nascita); + isDeadSwitch = findViewById( R.id.defunto ); + dateOfDeath = findViewById( R.id.data_morte ); + publisherDateLinearLayoutDOD = findViewById( R.id.editore_data_morte ); + deathPlace = findViewById( R.id.luogo_morte ); + + // Toggle sex radio buttons + RadioGroup radioGroup = findViewById(R.id.radioGroup); + View.OnClickListener radioClick = radioButton -> { + if( radioButton.getId() == lastChecked ) { + radioGroup.clearCheck(); + } + }; + sexMale.setOnClickListener(radioClick); + sexFemale.setOnClickListener(radioClick); + sexUnknown.setOnClickListener(radioClick); + radioGroup.setOnCheckedChangeListener((group, checked) -> { + group.post(() -> { + lastChecked = checked; + }); + }); + + disableDeath(); + + // New individual in kinship relationship + if( relationship > 0 ) { + p = new Person(); + Person pivot = gc.getPerson(idIndi); + String surname = null; + // Brother's surname + if( relationship == 2 ) { // = brother + surname = U.surname(pivot); + // Father's surname + } else if( relationship == 4 ) { // = child from Diagram or Individual // = figlio da Diagramma o Individuo + if( Gender.isMale(pivot) ) + surname = U.surname(pivot); + else if( familyId != null ) { + Family fam = gc.getFamily(familyId); + if( fam != null && !fam.getHusbands(gc).isEmpty() ) + surname = U.surname(fam.getHusbands(gc).get(0)); + } + } else if( relationship == 6 ) { // = child of Family(Activity?) // = figlio da Famiglia + Family fam = gc.getFamily(familyId); + if( !fam.getHusbands(gc).isEmpty() ) + surname = U.surname(fam.getHusbands(gc).get(0)); + else if( !fam.getChildren(gc).isEmpty() ) + surname = U.surname(fam.getChildren(gc).get(0)); + } + ((EditText)findViewById(R.id.cognome)).setText(surname); + // New disconnected individual + } else if( idIndi.equals("TIZIO_NUOVO") ) { + p = new Person(); + // Upload the data of an existing individual to modify + } else { + p = gc.getPerson(idIndi); + // Name and surname + if( !p.getNames().isEmpty() ) { + String name = ""; + String surname = ""; + Name n = p.getNames().get(0); + String epithet = n.getValue(); + if( epithet != null ) { + name = epithet.replaceAll( "/.*?/", "" ).trim(); //removes surname '/.../' // rimuove il cognome '/.../' + if( epithet.indexOf('/') < epithet.lastIndexOf('/') ) + surname = epithet.substring( epithet.indexOf('/') + 1, epithet.lastIndexOf('/') ).trim(); + } else { + if( n.getGiven() != null ) { + name = n.getGiven(); + nameFromPieces = true; + } + if( n.getSurname() != null ) { + surname = n.getSurname(); + nameFromPieces = true; + } + } + ((EditText)findViewById( R.id.nome )).setText( name ); + ((EditText)findViewById( R.id.cognome )).setText( surname ); + } + // Sex + switch( Gender.getGender(p) ) { + case MALE: + sexMale.setChecked(true); + break; + case FEMALE: + sexFemale.setChecked(true); + break; + case UNKNOWN: + sexUnknown.setChecked(true); + } + lastChecked = radioGroup.getCheckedRadioButtonId(); + // Birth and death + for( EventFact fact : p.getEventsFacts() ) { + if( fact.getTag().equals("BIRT") ) { + if( fact.getDate() != null ) + dateOfBirth.setText( fact.getDate().trim() ); + if( fact.getPlace() != null ) + birthplaceEditText.setText(fact.getPlace().trim()); + } + if( fact.getTag().equals("DEAT") ) { + isDeadSwitch.setChecked(true); + activateDeathSwitch(); + if( fact.getDate() != null ) + dateOfDeath.setText( fact.getDate().trim() ); + if( fact.getPlace() != null ) + deathPlace.setText(fact.getPlace().trim()); + } + } + } + publisherDateLinearLayoutDOB.initialize(dateOfBirth); + isDeadSwitch.setOnCheckedChangeListener( (button, checked) -> { + if (checked) + activateDeathSwitch(); + else + disableDeath(); + }); + publisherDateLinearLayoutDOD.initialize(dateOfDeath); + deathPlace.setOnEditorActionListener( (vista, actionId, keyEvent) -> { + if( actionId == EditorInfo.IME_ACTION_DONE ) + save(); + return false; + }); + + // Toolbar + ActionBar toolbar = getSupportActionBar(); + View toolbarAction = getLayoutInflater().inflate( R.layout.barra_edita, new LinearLayout(getApplicationContext()), false); + toolbarAction.findViewById( R.id.edita_annulla ).setOnClickListener( v -> onBackPressed() ); + toolbarAction.findViewById(R.id.edita_salva).setOnClickListener( v -> save() ); + toolbar.setCustomView( toolbarAction ); + toolbar.setDisplayShowCustomEnabled( true ); + } + + void disableDeath() { + findViewById(R.id.morte).setVisibility(View.GONE); + birthplaceEditText.setImeOptions(EditorInfo.IME_ACTION_DONE); + birthplaceEditText.setNextFocusForwardId(0); + // Intercept the 'Done' on the keyboard + birthplaceEditText.setOnEditorActionListener((view, action, event) -> { + if( action == EditorInfo.IME_ACTION_DONE ) + save(); + return false; + }); + } + + void activateDeathSwitch() { + birthplaceEditText.setImeOptions(EditorInfo.IME_ACTION_NEXT); + birthplaceEditText.setNextFocusForwardId(R.id.data_morte); + birthplaceEditText.setOnEditorActionListener(null); + findViewById(R.id.morte).setVisibility(View.VISIBLE); + } + + void save() { + U.ensureGlobalGedcomNotNull(gc); //A crash occurred because gc was null here + + // Name + String nameString = ((EditText)findViewById(R.id.nome)).getText().toString().trim(); + String surname = ((EditText)findViewById(R.id.cognome)).getText().toString().trim(); + Name name; + if( p.getNames().isEmpty() ) { + List names = new ArrayList<>(); + name = new Name(); + names.add(name); + p.setNames(names); + } else + name = p.getNames().get(0); + + if(nameFromPieces) { + name.setGiven(nameString); + name.setSurname(surname); + } else { + name.setValue(nameString + " /" + surname + "/".trim()); + } + + // Sex + String chosenGender = null; + if( sexMale.isChecked() ) + chosenGender = "M"; + else if( sexFemale.isChecked() ) + chosenGender = "F"; + else if( sexUnknown.isChecked() ) + chosenGender = "U"; + if( chosenGender != null ) { + boolean missingSex = true; + for( EventFact fact : p.getEventsFacts() ) { + if( fact.getTag().equals("SEX") ) { + fact.setValue(chosenGender); + missingSex = false; + } + } + if( missingSex ) { + EventFact sex = new EventFact(); + sex.setTag("SEX"); + sex.setValue(chosenGender); + p.addEventFact(sex); + } + IndividualEventsFragment.updateMaritalRoles(p); + } else { // Remove existing sex tag + for( EventFact fact : p.getEventsFacts() ) { + if( fact.getTag().equals("SEX") ) { + p.getEventsFacts().remove(fact); + break; + } + } + } + + // Birth + publisherDateLinearLayoutDOB.encloseInParentheses(); + String data = dateOfBirth.getText().toString().trim(); + String location = birthplaceEditText.getText().toString().trim(); + boolean found = false; + for (EventFact fact : p.getEventsFacts()) { + if( fact.getTag().equals("BIRT") ) { + /* TODO: + if (data.isEmpty () && place.isEmpty () && tagAllEmpty (done)) + p.getEventsFacts (). remove (done); + more generally, delete a tag when it is empty + + if( data.isEmpty() && luogo.isEmpty() && tagTuttoVuoto(fatto) ) + p.getEventsFacts().remove(fatto); + più in generale, eliminare un tag quando è vuoto */ + fact.setDate( data ); + fact.setPlace( location ); + EventActivity.cleanUpTag( fact ); + found = true; + } + } + // If there is any data to save, create the tag + if( !found && ( !data.isEmpty() || !location.isEmpty() ) ) { + EventFact birth = new EventFact(); + birth.setTag( "BIRT" ); + birth.setDate( data ); + birth.setPlace( location ); + EventActivity.cleanUpTag( birth ); + p.addEventFact( birth ); + } + + // Death + publisherDateLinearLayoutDOD.encloseInParentheses(); + data = dateOfDeath.getText().toString().trim(); + location = deathPlace.getText().toString().trim(); + found = false; + for( EventFact fact : p.getEventsFacts() ) { + if( fact.getTag().equals("DEAT") ) { + if( !isDeadSwitch.isChecked() ) { + p.getEventsFacts().remove(fact); + } else { + fact.setDate( data ); + fact.setPlace( location ); + EventActivity.cleanUpTag( fact ); + } + found = true; + break; + } + } + if( !found && isDeadSwitch.isChecked() ) { + EventFact morte = new EventFact(); + morte.setTag( "DEAT" ); + morte.setDate( data ); + morte.setPlace( location ); + EventActivity.cleanUpTag( morte ); + p.addEventFact( morte ); + } + + // Finalization of new individual + Object[] modifications = { p, null }; // the null is used to accommodate a possible Family + if( idIndi.equals("TIZIO_NUOVO") || relationship > 0 ) { + String newId = U.newID( gc, Person.class ); + p.setId( newId ); + gc.addPerson( p ); + if( Global.settings.getCurrentTree().root == null ) + Global.settings.getCurrentTree().root = newId; + Global.settings.save(); + if( relationship >= 5 ) { // comes from Family(Activity) + FamilyActivity.connect( p, gc.getFamily(familyId), relationship); + modifications[1] = gc.getFamily(familyId); + } else if( relationship > 0 ) // comes from Family Diagram or Individual + modifications = addParent( idIndi, newId, familyId, relationship, getIntent().getStringExtra("collocazione") ); + } else + Global.indi = p.getId(); //to proudly (prominently?) show it in Diagram // per mostrarlo orgogliosi in Diagramma + U.save(true, modifications); + onBackPressed(); + } + + /** + * Aggiunge un nuovo individuo in relazione di parentela con 'perno', eventualmente all'interno della famiglia fornita. + * @param familyId Id della famiglia di destinazione. Se è null si crea una nuova famiglia + * @param collection Sintetizza come è stata individuata la famiglia e quindi cosa fare delle persone coinvolte + * + * + * Adds a new kinship individual with 'pivot', possibly within the given family. + * @param familyId Id of the target family. If it is null, a new family is created + * @param collection Summarizes how the family was identified and therefore what to do with the people involved + */ + static Object[] addParent(String pivotId, String newId, String familyId, int relationship, String collection) { + Global.indi = pivotId; + Person newPerson = gc.getPerson( newId ); + // A new family is created in which both Pin and New end up + if( collection != null && collection.startsWith("NUOVA_FAMIGLIA_DI") ) { // Contains the id of the parent to create a new family for + pivotId = collection.substring(17); // the parent effectively becomes the pivot + relationship = relationship == 2 ? 4 : relationship; //instead of a pivotal sibling, it is as if we were putting a child to the parent // anziché un fratello a perno, è come se mettessimo un figlio al genitore + } + //The family in which the pivot will end up has been identified in ListOfPeopleActivity // In Anagrafe è stata individuata la famiglia in cui finirà perno + else if( collection != null && collection.equals("FAMIGLIA_ESISTENTE") ) { + newId = null; + newPerson = null; + } + // New is welcomed into the pivot family + else if( familyId != null ) { + pivotId = null; // pivot is already present in his family and should not be added again + } + Family family = familyId != null ? gc.getFamily(familyId) : ChurchFragment.newFamily(true);; + Person pivot = gc.getPerson( pivotId ); + SpouseRef refSpouse1 = new SpouseRef(), refSposo2 = new SpouseRef(); + ChildRef refChild1 = new ChildRef(), refFiglio2 = new ChildRef(); + ParentFamilyRef parentFamilyRef = new ParentFamilyRef(); + SpouseFamilyRef spouseFamilyRef = new SpouseFamilyRef(); + parentFamilyRef.setRef( family.getId() ); + spouseFamilyRef.setRef( family.getId() ); + + // Population of refs + switch (relationship) { + case 1: // Parent + refSpouse1.setRef(newId); + refChild1.setRef(pivotId); + if (newPerson != null) newPerson.addSpouseFamilyRef( spouseFamilyRef ); + if (pivot != null) pivot.addParentFamilyRef( parentFamilyRef ); + break; + case 2: // Brother + refChild1.setRef(pivotId); + refFiglio2.setRef(newId); + if (pivot != null) pivot.addParentFamilyRef( parentFamilyRef ); + if (newPerson != null) newPerson.addParentFamilyRef( parentFamilyRef ); + break; + case 3: // Spouse + refSpouse1.setRef(pivotId); + refSposo2.setRef(newId); + if (pivot != null) pivot.addSpouseFamilyRef( spouseFamilyRef ); + if (newPerson != null) newPerson.addSpouseFamilyRef( spouseFamilyRef ); + break; + case 4: // Child + refSpouse1.setRef(pivotId); + refChild1.setRef(newId); + if (pivot != null) pivot.addSpouseFamilyRef( spouseFamilyRef ); + if (newPerson != null) newPerson.addParentFamilyRef( parentFamilyRef ); + } + + if( refSpouse1.getRef() != null ) + addSpouse(family, refSpouse1); + if( refSposo2.getRef() != null ) + addSpouse(family, refSposo2); + if( refChild1.getRef() != null ) + family.addChild(refChild1); + if( refFiglio2.getRef() != null ) + family.addChild(refFiglio2); + + if( relationship == 1 || relationship == 2 ) // It will bring up the selected family + Global.familyNum = gc.getPerson(Global.indi).getParentFamilies(gc).indexOf(family); + else + Global.familyNum = 0; // eventually reset + + Set transformed = new HashSet<>(); + if( pivot != null && newPerson != null ) + Collections.addAll(transformed, family, pivot, newPerson); + else if( pivot != null ) + Collections.addAll(transformed, family, pivot); + else if( newPerson != null ) + Collections.addAll(transformed, family, newPerson); + return transformed.toArray(); + } + + /** + * Adds the spouse in a family: always and only on the basis of sex + * */ + public static void addSpouse(Family family, SpouseRef sr) { + Person person = Global.gc.getPerson(sr.getRef()); + if( Gender.isFemale(person) ) family.addWife(sr); + else family.addHusband(sr); + } +} \ No newline at end of file diff --git a/app/src/main/java/app/familygem/IndividuoEventi.java b/app/src/main/java/app/familygem/IndividualEventsFragment.java similarity index 64% rename from app/src/main/java/app/familygem/IndividuoEventi.java rename to app/src/main/java/app/familygem/IndividualEventsFragment.java index 763c1a3a..ded3e1c5 100644 --- a/app/src/main/java/app/familygem/IndividuoEventi.java +++ b/app/src/main/java/app/familygem/IndividualEventsFragment.java @@ -30,76 +30,81 @@ import java.util.List; import java.util.Map; import app.familygem.constant.Gender; -import app.familygem.detail.Evento; -import app.familygem.detail.Nome; +import app.familygem.detail.EventActivity; +import app.familygem.detail.ExtensionActivity; +import app.familygem.detail.NameActivity; import static app.familygem.Global.gc; -public class IndividuoEventi extends Fragment { +public class IndividualEventsFragment extends Fragment { Person one; private View changeView; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View vistaEventi = inflater.inflate(R.layout.individuo_scheda, container, false); + View eventsView = inflater.inflate(R.layout.individuo_scheda, container, false); if( gc != null ) { - LinearLayout layout = vistaEventi.findViewById(R.id.contenuto_scheda); + LinearLayout layout = eventsView.findViewById(R.id.contenuto_scheda); one = gc.getPerson(Global.indi); if( one != null ) { - for( Name nome : one.getNames()) { - String tit = getString(R.string.name); - if( nome.getType() != null && !nome.getType().isEmpty() ) { - tit += " (" + TypeView.getTranslatedType(nome.getType(), TypeView.Combo.NAME) + ")"; + for( Name name : one.getNames()) { + String title = getString(R.string.name); + if( name.getType() != null && !name.getType().isEmpty() ) { + title += " (" + TypeView.getTranslatedType(name.getType(), TypeView.Combo.NAME) + ")"; } - placeEvent(layout, tit, U.nomeCognome(nome, " "), nome); + placeEvent(layout, title, U.firstAndLastName(name, " "), name); } - for (EventFact fatto : one.getEventsFacts() ) { + for (EventFact fact : one.getEventsFacts() ) { String txt = ""; - if( fatto.getValue() != null ) { - if( fatto.getValue().equals("Y") && fatto.getTag()!=null && - ( fatto.getTag().equals("BIRT") || fatto.getTag().equals("CHR") || fatto.getTag().equals("DEAT") ) ) + if( fact.getValue() != null ) { + if( fact.getValue().equals("Y") && fact.getTag()!=null && + ( fact.getTag().equals("BIRT") || fact.getTag().equals("CHR") || fact.getTag().equals("DEAT") ) ) txt = getString(R.string.yes); - else txt = fatto.getValue(); + else txt = fact.getValue(); txt += "\n"; } - //if( fatto.getType() != null ) txt += fatto.getType() + "\n"; // Included in event title - if( fatto.getDate() != null ) txt += new Datatore(fatto.getDate()).writeDateLong() + "\n"; - if( fatto.getPlace() != null ) txt += fatto.getPlace() + "\n"; - Address indirizzo = fatto.getAddress(); - if( indirizzo != null ) txt += Dettaglio.writeAddress(indirizzo, true) + "\n"; - if( fatto.getCause() != null ) txt += fatto.getCause(); - if( txt.endsWith("\n") ) txt = txt.substring(0, txt.length() - 1); // Rimuove l'ultimo acapo - placeEvent(layout, writeEventTitle(fatto), txt, fatto); + //if( fact.getType() != null ) txt += fact.getType() + "\n"; // Included in event title + if( fact.getDate() != null ) txt += new GedcomDateConverter(fact.getDate()).writeDateLong() + "\n"; + if( fact.getPlace() != null ) txt += fact.getPlace() + "\n"; + Address address = fact.getAddress(); + if( address != null ) txt += DetailActivity.writeAddress(address, true) + "\n"; + if( fact.getCause() != null ) txt += fact.getCause(); + if( txt.endsWith("\n") ) txt = txt.substring(0, txt.length() - 1); // Remove the last newline + placeEvent(layout, writeEventTitle(fact), txt, fact); } - for( Estensione est : U.trovaEstensioni(one) ) { - placeEvent(layout, est.nome, est.testo, est.gedcomTag); + for( Extension est : U.findExtensions(one) ) { + placeEvent(layout, est.name, est.text, est.gedcomTag); } U.placeNotes(layout, one, true); U.placeSourceCitations(layout, one); changeView = U.placeChangeDate(layout, one.getChange()); } } - return vistaEventi; + return eventsView; } - // Scopre se è un nome con name pieces o un suffisso nel value - boolean nomeComplesso( Name n ) { + /** + * Find out if it's a name with name pieces or a suffix in the value + * */ + boolean complexName(Name n ) { // Name pieces - boolean ricco = n.getGiven() != null || n.getSurname() != null + boolean hasAllFields /*TODO improve translation of ricco*/ = n.getGiven() != null || n.getSurname() != null || n.getPrefix() != null || n.getSurnamePrefix() != null || n.getSuffix() != null || n.getFone() != null || n.getRomn() != null; - // Qualcosa dopo il cognome - String nome = n.getValue(); - boolean suffisso = false; - if( nome != null ) { - nome = nome.trim(); - if( nome.lastIndexOf('/') < nome.length()-1 ) - suffisso = true; + // Something after the surname + String name = n.getValue(); + boolean hasSuffix = false; + if( name != null ) { + name = name.trim(); + if( name.lastIndexOf('/') < name.length()-1 ) + hasSuffix = true; } - return ricco || suffisso; + return hasAllFields || hasSuffix; } - // Compose the title of an event of the person + /** + * Compose the title of an event of the person + * */ public static String writeEventTitle(EventFact event) { int str = 0; switch( event.getTag() ) { @@ -141,26 +146,26 @@ private void placeEvent(LinearLayout layout, String title, String text, Object o LinearLayout otherLayout = eventView.findViewById(R.id.evento_altro); if( object instanceof NoteContainer ) U.placeNotes(otherLayout, object, false); - eventView.setTag(R.id.tag_oggetto, object); + eventView.setTag(R.id.tag_object, object); registerForContextMenu(eventView); if( object instanceof Name ) { U.placeMedia(otherLayout, object, false); eventView.setOnClickListener(v -> { - // Se è un nome complesso propone la modalità esperto - if( !Global.settings.expert && nomeComplesso((Name)object) ) { + // If it is a complex name, it proposes entering expert mode + if( !Global.settings.expert && complexName((Name)object) ) { new AlertDialog.Builder(getContext()).setMessage(R.string.complex_tree_advanced_tools) .setPositiveButton(android.R.string.ok, (dialog, i) -> { Global.settings.expert = true; Global.settings.save(); - Memoria.aggiungi(object); - startActivity(new Intent(getContext(), Nome.class)); + Memory.add(object); + startActivity(new Intent(getContext(), NameActivity.class)); }).setNegativeButton(android.R.string.cancel, (dialog, i) -> { - Memoria.aggiungi(object); - startActivity(new Intent(getContext(), Nome.class)); + Memory.add(object); + startActivity(new Intent(getContext(), NameActivity.class)); }).show(); } else { - Memoria.aggiungi(object); - startActivity(new Intent(getContext(), Nome.class)); + Memory.add(object); + startActivity(new Intent(getContext(), NameActivity.class)); } }); } else if( object instanceof EventFact ) { @@ -183,7 +188,7 @@ private void placeEvent(LinearLayout layout, String title, String text, Object o eventView.setOnClickListener(view -> new AlertDialog.Builder(view.getContext()) .setSingleChoiceItems(sexes.values().toArray(new String[0]), chosenSex, (dialog, item) -> { ((EventFact)object).setValue(new ArrayList<>(sexes.keySet()).get(item)); - aggiornaRuoliConiugali(one); + updateMaritalRoles(one); dialog.dismiss(); refresh(1); U.save(true, one); @@ -191,21 +196,23 @@ private void placeEvent(LinearLayout layout, String title, String text, Object o } else { // All other events U.placeMedia(otherLayout, object, false); eventView.setOnClickListener(v -> { - Memoria.aggiungi(object); - startActivity(new Intent(getContext(), Evento.class)); + Memory.add(object); + startActivity(new Intent(getContext(), EventActivity.class)); }); } } else if( object instanceof GedcomTag ) { eventView.setOnClickListener(v -> { - Memoria.aggiungi(object); - startActivity(new Intent(getContext(), app.familygem.detail.Estensione.class)); + Memory.add(object); + startActivity(new Intent(getContext(), ExtensionActivity.class)); }); } } - // In tutte le famiglie coniugali rimuove gli spouse ref di 'person' e ne aggiunge uno corrispondente al sesso - // Serve soprattutto in caso di esportazione del Gedcom per avere allineati gli HUSB e WIFE con il sesso - static void aggiornaRuoliConiugali(Person person) { + /** + * In all marital families, remove the spouse refs of 'person' and add one corresponding to the gender + * It is especially useful in case of Gedcom export to have the HUSB and WIFE aligned with the sex + * */ + static void updateMaritalRoles(Person person) { SpouseRef spouseRef = new SpouseRef(); spouseRef.setRef(person.getId()); boolean removed = false; @@ -240,14 +247,14 @@ static void aggiornaRuoliConiugali(Person person) { } } - // Menu contestuale + // Contextual menu View pieceView; Object pieceObject; @Override public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo info) { - // menuInfo come al solito è null + // menuInfo as usual is null pieceView = view; - pieceObject = view.getTag(R.id.tag_oggetto); + pieceObject = view.getTag(R.id.tag_object); if( pieceObject instanceof Name ) { menu.add(0, 200, 0, R.string.copy); if( one.getNames().indexOf(pieceObject) > 0 ) @@ -280,95 +287,99 @@ public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.Context @Override public boolean onContextItemSelected(MenuItem item) { - List nomi = one.getNames(); - List fatti = one.getEventsFacts(); - int cosa = 0; // cosa aggiornare dopo la modifica + List names = one.getNames(); + List facts = one.getEventsFacts(); + int toUpdateId = 0; // what to update after the change switch( item.getItemId() ) { // Nome - case 200: // Copia nome - case 210: // Copia evento - case 220: // Copia estensione - U.copiaNegliAppunti(((TextView)pieceView.findViewById(R.id.evento_titolo)).getText(), + case 200: // Copy name + case 210: // Copy event + case 220: // Copy extension + U.copyToClipboard(((TextView)pieceView.findViewById(R.id.evento_titolo)).getText(), ((TextView)pieceView.findViewById(R.id.evento_testo)).getText()); return true; - case 201: // Sposta su - nomi.add(nomi.indexOf(pieceObject) - 1, (Name)pieceObject); - nomi.remove(nomi.lastIndexOf(pieceObject)); - cosa = 2; + case 201: //Move up + names.add(names.indexOf(pieceObject) - 1, (Name)pieceObject); + names.remove(names.lastIndexOf(pieceObject)); + toUpdateId = 2; break; - case 202: // Sposta giù - nomi.add(nomi.indexOf(pieceObject) + 2, (Name)pieceObject); - nomi.remove(nomi.indexOf(pieceObject)); - cosa = 2; + case 202: // Sposta down + names.add(names.indexOf(pieceObject) + 2, (Name)pieceObject); + names.remove(names.indexOf(pieceObject)); + toUpdateId = 2; break; - case 203: // Elimina - if( U.preserva(pieceObject) ) return false; + case 203: // Delete + if( U.preserve(pieceObject) ) return false; one.getNames().remove(pieceObject); - Memoria.annullaIstanze(pieceObject); + Memory.setInstanceAndAllSubsequentToNull(pieceObject); pieceView.setVisibility(View.GONE); - cosa = 2; + toUpdateId = 2; break; - // Evento generico - case 211: // Sposta su - fatti.add(fatti.indexOf(pieceObject) - 1, (EventFact)pieceObject); - fatti.remove(fatti.lastIndexOf(pieceObject)); - cosa = 1; + // Generic Event + case 211: // Move up + facts.add(facts.indexOf(pieceObject) - 1, (EventFact)pieceObject); + facts.remove(facts.lastIndexOf(pieceObject)); + toUpdateId = 1; break; - case 212: // Sposta giu - fatti.add(fatti.indexOf(pieceObject) + 2, (EventFact)pieceObject); - fatti.remove(fatti.indexOf(pieceObject)); - cosa = 1; + case 212: // Move down + facts.add(facts.indexOf(pieceObject) + 2, (EventFact)pieceObject); + facts.remove(facts.indexOf(pieceObject)); + toUpdateId = 1; break; case 213: - // todo Conferma elimina + // todo Confirm delete one.getEventsFacts().remove(pieceObject); - Memoria.annullaIstanze(pieceObject); + Memory.setInstanceAndAllSubsequentToNull(pieceObject); pieceView.setVisibility(View.GONE); break; - // Estensione - case 221: // Elimina - U.eliminaEstensione((GedcomTag)pieceObject, one, pieceView); + // Extension + case 221: // delete + U.deleteExtension((GedcomTag)pieceObject, one, pieceView); break; // Nota - case 225: // Copia - U.copiaNegliAppunti(getText(R.string.note), ((TextView)pieceView.findViewById(R.id.nota_testo)).getText()); + case 225: // Copy + U.copyToClipboard(getText(R.string.note), ((TextView)pieceView.findViewById(R.id.nota_testo)).getText()); return true; - case 226: // Scollega - U.scollegaNota((Note)pieceObject, one, pieceView); + case 226: // disconnect + U.disconnectNote((Note)pieceObject, one, pieceView); break; case 227: - Object[] capi = U.eliminaNota((Note)pieceObject, pieceView); - U.save(true, capi); + Object[] heads = U.deleteNote((Note)pieceObject, pieceView); + U.save(true, heads); refresh(0); return true; - // Citazione fonte - case 230: // Copia - U.copiaNegliAppunti(getText(R.string.source_citation), + // source Citation + case 230: // Copy + U.copyToClipboard(getText(R.string.source_citation), ((TextView)pieceView.findViewById(R.id.fonte_testo)).getText() + "\n" + ((TextView)pieceView.findViewById(R.id.citazione_testo)).getText()); return true; - case 231: // Elimina - // todo conferma : Vuoi eliminare questa citazione della fonte? La fonte continuerà ad esistere. + case 231: // delete + // todo confirm : Do you want to delete this source citation? The source will continue to exist. one.getSourceCitations().remove(pieceObject); - Memoria.annullaIstanze(pieceObject); + Memory.setInstanceAndAllSubsequentToNull(pieceObject); pieceView.setVisibility(View.GONE); break; default: return false; } U.save(true, one); - refresh(cosa); + refresh(toUpdateId); return true; } - // Update person ID in the toolbar and change date + /** + * Update person ID in the toolbar and change date + * */ void refreshId() { TextView idView = getActivity().findViewById(R.id.persona_id); idView.setText("INDI " + one.getId()); refresh(1); } - // Update content of Facts tab + /** + * Update content of Facts tab + * */ void refresh(int what) { if( what == 0 ) { // Only replace change date LinearLayout layout = getActivity().findViewById(R.id.contenuto_scheda); @@ -381,7 +392,7 @@ void refresh(int what) { fragmentManager.beginTransaction().attach(this).commit(); if( what == 2 ) { // Also update person name in toolbar CollapsingToolbarLayout toolbarLayout = requireActivity().findViewById(R.id.toolbar_layout); - toolbarLayout.setTitle(U.epiteto(one)); + toolbarLayout.setTitle(U.properName(one)); } } } diff --git a/app/src/main/java/app/familygem/IndividualFamilyFragment.java b/app/src/main/java/app/familygem/IndividualFamilyFragment.java new file mode 100644 index 00000000..0785f415 --- /dev/null +++ b/app/src/main/java/app/familygem/IndividualFamilyFragment.java @@ -0,0 +1,197 @@ +package app.familygem; + +import android.content.Intent; +import android.os.Bundle; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import android.view.ContextMenu; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import org.folg.gedcom.model.Family; +import org.folg.gedcom.model.Person; +import org.folg.gedcom.model.SpouseFamilyRef; +import java.util.Collections; +import java.util.List; +import app.familygem.constant.Relation; +import app.familygem.detail.FamilyActivity; +import static app.familygem.Global.gc; + +public class IndividualFamilyFragment extends Fragment { + + private View familyView; + Person person1; + + @Override + public View onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState ) { + familyView = inflater.inflate(R.layout.individuo_scheda, container, false); + if( gc != null ) { + person1 = gc.getPerson( Global.indi); + if( person1 != null ) { + /* TODO Show / be able to set the pedigree in the geniotrial families, in particular 'adopted' // Mostrare/poter settare nelle famiglie geniotriali il pedigree, in particolare 'adopted' + LinearLayout container = vistaFamiglia.findViewById( R.id.contenuto_scheda ); + for( ParentFamilyRef pfr : person1.getParentFamilyRefs() ) { + U.place( container, "Ref", pfr.getRef() ); + U.place( container, "Primary", pfr.getPrimary() ); // Custom tag _PRIM _PRIMARY + U.place( container, "Relationship Type", pfr.getRelationshipType() ); // Tag PEDI (pedigree) + for( Extension otherTag : U.findExtensions( pfr ) ) + U.place( container, otherTag.name, otherTag.text ); + } */ + // Families of origin: parents and siblings + List listOfFamilies = person1.getParentFamilies(gc); + for( Family family : listOfFamilies ) { + for( Person father : family.getHusbands(gc) ) + createCard(father, Relation.PARENT, family); + for( Person mother : family.getWives(gc) ) + createCard(mother, Relation.PARENT, family); + for( Person sibling : family.getChildren(gc) ) // only children of the same two parents, not half-siblings + if( !sibling.equals(person1) ) + createCard(sibling, Relation.SIBLING, family); + } + // Step (half?) brothers and sisters + for( Family family : person1.getParentFamilies(gc) ) { + for( Person husband : family.getHusbands(gc) ) { + List fatherFamilies = husband.getSpouseFamilies(gc); + fatherFamilies.removeAll(listOfFamilies); + for( Family fam : fatherFamilies ) + for( Person stepSibling : fam.getChildren(gc) ) + createCard(stepSibling, Relation.HALF_SIBLING, fam); + } + for( Person wife : family.getWives(gc) ) { + List wifeFamilies = wife.getSpouseFamilies(gc); + wifeFamilies.removeAll(listOfFamilies); + for( Family fam : wifeFamilies ) + for( Person stepSibling : fam.getChildren(gc) ) + createCard(stepSibling, Relation.HALF_SIBLING, fam); + } + } + // Spouses and children + for( Family family : person1.getSpouseFamilies(gc) ) { + for( Person husband : family.getHusbands(gc) ) + if( !husband.equals(person1) ) + createCard(husband, Relation.PARTNER, family); + for( Person wife : family.getWives(gc) ) + if( !wife.equals(person1) ) + createCard(wife, Relation.PARTNER, family); + for( Person child : family.getChildren(gc) ) { + createCard(child, Relation.CHILD, family); + } + } + } + } + return familyView; + } + + void createCard(final Person person, Relation relation, Family family) { + LinearLayout container = familyView.findViewById(R.id.contenuto_scheda); + View personView = U.placeIndividual(container, person, + FamilyActivity.getRole(person, family, relation, false) + FamilyActivity.writeLineage(person, family)); + personView.setOnClickListener(v -> { + getActivity().finish(); // Removes the current activity from the stack + Memory.replaceFirst(person); + Intent intent = new Intent(getContext(), IndividualPersonActivity.class); + intent.putExtra("scheda", 2); // apre la scheda famiglia + startActivity(intent); + }); + registerForContextMenu(personView); + + // The main purpose of this tag is to be able to disconnect the individual from the family + // but it is also used below to move multiple marriages: + personView.setTag(R.id.tag_famiglia, family); + } + + private void moveFamilyReference(int direction) { + Collections.swap(person1.getSpouseFamilyRefs(), familyPos, familyPos + direction); + U.save(true, person1); + refresh(); + } + + // context Menu + private String indiId; + private Person person; + private Family family; + private int familyPos; // position of the marital family for those who have more than one + @Override + public void onCreateContextMenu(@NonNull ContextMenu menu, View view, ContextMenu.ContextMenuInfo info ) { + indiId = (String)view.getTag(); + person = gc.getPerson(indiId); + family = (Family)view.getTag(R.id.tag_famiglia); + familyPos = -1; + if( person1.getSpouseFamilyRefs().size() > 1 && !family.getChildren(gc).contains(person) ) { // only spouses, not children + List refs = person1.getSpouseFamilyRefs(); + for( SpouseFamilyRef sfr : refs ) + if( sfr.getRef().equals(family.getId()) ) + familyPos = refs.indexOf(sfr); + } + // Better to use numbers that do not conflict with the context menus of the other individual tabs + menu.add(0, 300, 0, R.string.diagram); + String[] familyLabels = Diagram.getFamilyLabels(getContext(), person, family); + if( familyLabels[0] != null ) + menu.add(0, 301, 0, familyLabels[0]); + if( familyLabels[1] != null ) + menu.add(0, 302, 0, familyLabels[1]); + if( familyPos > 0 ) + menu.add(0, 303, 0, R.string.move_before); + if( familyPos >= 0 && familyPos < person1.getSpouseFamilyRefs().size() - 1 ) + menu.add(0, 304, 0, R.string.move_after); + menu.add(0, 305, 0, R.string.modify); + if( FamilyActivity.findParentFamilyRef(person, family) != null ) + menu.add(0, 306, 0, R.string.lineage); + menu.add(0, 307, 0, R.string.unlink); + if( !person.equals(person1) ) // Here he cannot eliminate himself + menu.add(0, 308, 0, R.string.delete); + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + int id = item.getItemId(); + if( id == 300 ) { // Diagram + U.askWhichParentsToShow(getContext(), person, 1); + } else if( id == 301 ) { // Family as a son + U.askWhichParentsToShow(getContext(), person, 2); + } else if( id == 302 ) { // Family as a spouse + U.askWhichSpouseToShow(getContext(), person, family); + } else if( id == 303 ) { // Move up + moveFamilyReference(-1); + } else if( id == 304 ) { // Move down + moveFamilyReference(1); + } else if( id == 305 ) { // Modify + Intent intent = new Intent(getContext(), IndividualEditorActivity.class); + intent.putExtra("idIndividuo", indiId); + startActivity(intent); + } else if( id == 306 ) { // Lineage + FamilyActivity.chooseLineage(getContext(), person, family); + } else if( id == 307 ) { // Disconnect from this family + FamilyActivity.disconnect(indiId, family); + refresh(); + U.checkFamilyItem(getContext(), this::refresh, false, family); + U.save(true, family, person); + } else if( id == 308 ) { // Delete + new AlertDialog.Builder(getContext()).setMessage(R.string.really_delete_person) + .setPositiveButton(R.string.delete, (dialog, i) -> { + ListOfPeopleFragment.deletePerson(getContext(), indiId); + refresh(); + U.checkFamilyItem(getContext(), this::refresh, false, family); + }).setNeutralButton(R.string.cancel, null).show(); + } else { + return false; + } + return true; + } + + /** + * Refresh the contents of the Family Fragment + * */ + public void refresh() { + FragmentManager fragmentManager = requireActivity().getSupportFragmentManager(); + fragmentManager.beginTransaction().detach(this).commit(); + fragmentManager.beginTransaction().attach(this).commit(); + requireActivity().invalidateOptionsMenu(); + // todo update the change date in the Facts tab + } +} diff --git a/app/src/main/java/app/familygem/IndividualMediaFragment.java b/app/src/main/java/app/familygem/IndividualMediaFragment.java new file mode 100644 index 00000000..8738c328 --- /dev/null +++ b/app/src/main/java/app/familygem/IndividualMediaFragment.java @@ -0,0 +1,106 @@ +package app.familygem; + +import android.os.Bundle; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import android.view.ContextMenu; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import org.folg.gedcom.model.Media; +import org.folg.gedcom.model.MediaContainer; +import org.folg.gedcom.model.Person; +import app.familygem.visitor.MediaListContainer; +import static app.familygem.Global.gc; + +/** + * Photo tab + * */ +public class IndividualMediaFragment extends Fragment { + + Person person; + MediaListContainer mediaListContainer; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View vistaMedia = inflater.inflate(R.layout.individuo_scheda, container, false); + if( gc != null ) { + final LinearLayout layout = vistaMedia.findViewById(R.id.contenuto_scheda); + person = gc.getPerson(Global.indi); + if( person != null ) { + mediaListContainer = new MediaListContainer(gc, true); + person.accept(mediaListContainer); + RecyclerView recyclerView = new RecyclerView(layout.getContext()); + recyclerView.setHasFixedSize(true); + RecyclerView.LayoutManager layoutManager = new GridLayoutManager(getContext(), 2); + recyclerView.setLayoutManager(layoutManager); + MediaGalleryAdapter adapter = new MediaGalleryAdapter(mediaListContainer.mediaList, true); + recyclerView.setAdapter(adapter); + layout.addView(recyclerView); + } + } + return vistaMedia; + } + + // context Menu + Media media; + /** + * The images are not only of {@link #person}, but also of its subordinates {@link org.folg.gedcom.model.EventFact}, {@link org.folg.gedcom.model.SourceCitation} ... + * */ + Object container; + @Override + public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo info) { + media = (Media)view.getTag(R.id.tag_object); + container = view.getTag(R.id.tag_contenitore); + if( mediaListContainer.mediaList.size() > 1 && media.getPrimary() == null ) + menu.add(0, 0, 0, R.string.primary_media); + if( media.getId() != null ) + menu.add(0, 1, 0, R.string.unlink); + menu.add(0, 2, 0, R.string.delete); + } + @Override + public boolean onContextItemSelected( MenuItem item ) { + int id = item.getItemId(); + if( id == 0 ) { // Principal + for( MediaListContainer.MedCont medCont : mediaListContainer.mediaList) // It resets them all then marks this.person + medCont.media.setPrimary(null); + media.setPrimary("Y"); + if( media.getId() != null ) // To update the change date in the Media record rather than in the Person + U.save(true, media); + else + U.save(true, person); + refresh(); + return true; + } else if( id == 1 ) { // Scollega + GalleryFragment.disconnectMedia(media.getId(), (MediaContainer)container); + U.save(true, person); + refresh(); + return true; + } else if( id == 2 ) { // Delete + Object[] capi = GalleryFragment.deleteMedia(media, null); + U.save(true, capi); + refresh(); + return true; + } + return false; + } + + /** + * Refresh the contents of the Media snippet + * */ + void refresh() { + // refill the fragment + FragmentManager fragmentManager = requireActivity().getSupportFragmentManager(); + fragmentManager.beginTransaction().detach(this).commit(); + fragmentManager.beginTransaction().attach(this).commit(); + F.showMainImageForPerson(Global.gc, person, requireActivity().findViewById(R.id.persona_foto)); + F.showMainImageForPerson(Global.gc, person, requireActivity().findViewById(R.id.persona_sfondo)); + // Events tab + IndividualEventsFragment eventsTab = (IndividualEventsFragment)requireActivity().getSupportFragmentManager().findFragmentByTag("android:switcher:" + R.id.schede_persona + ":1"); + eventsTab.refresh(1); + } +} diff --git a/app/src/main/java/app/familygem/Individuo.java b/app/src/main/java/app/familygem/IndividualPersonActivity.java similarity index 50% rename from app/src/main/java/app/familygem/Individuo.java rename to app/src/main/java/app/familygem/IndividualPersonActivity.java index 2c579762..593193f7 100644 --- a/app/src/main/java/app/familygem/Individuo.java +++ b/app/src/main/java/app/familygem/IndividualPersonActivity.java @@ -34,15 +34,15 @@ import java.util.Collections; import java.util.List; import app.familygem.constant.Gender; -import app.familygem.detail.CitazioneFonte; -import app.familygem.detail.Evento; -import app.familygem.detail.Nome; -import app.familygem.detail.Nota; +import app.familygem.detail.SourceCitationActivity; +import app.familygem.detail.EventActivity; +import app.familygem.detail.NameActivity; +import app.familygem.detail.NoteActivity; import static app.familygem.Global.gc; -public class Individuo extends AppCompatActivity { +public class IndividualPersonActivity extends AppCompatActivity { - Person one; + Person thisPerson; TabLayout tabLayout; String[] mainEventTags = {"BIRT", "BAPM", "RESI", "OCCU", "DEAT", "BURI"}; List> otherEvents; // List of tag + label @@ -50,43 +50,43 @@ public class Individuo extends AppCompatActivity { @Override protected void onCreate(Bundle bundle) { super.onCreate(bundle); - U.gedcomSicuro(gc); - one = (Person)Memoria.getOggetto(); - // Se l'app va in background e viene stoppata, 'Memoria' è resettata e quindi 'uno' sarà null - if( one == null && bundle != null ) { - one = gc.getPerson(bundle.getString("idUno")); // In bundle è salvato l'id dell'individuo - Memoria.setPrimo(one); // Altrimenti la memoria è senza una pila + U.ensureGlobalGedcomNotNull(gc); + thisPerson = (Person) Memory.getObject(); + // If the app goes into the background and is stopped, 'Memory' is reset and therefore 'thisPerson' will be null + if( thisPerson == null && bundle != null ) { + thisPerson = gc.getPerson(bundle.getString("idUno")); // The individual's id is saved in the bundle + Memory.setFirst(thisPerson); // Otherwise the memory is without a stack } - if( one == null ) return; // Capita raramente che il bundle non faccia il suo lavoro - Global.indi = one.getId(); + if( thisPerson == null ) return; // Rarely does the bundle not do its job + Global.indi = thisPerson.getId(); setContentView(R.layout.individuo); - // Barra + // Toolbar Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); // fa comparire la freccia indietro e il menu + getSupportActionBar().setDisplayHomeAsUpEnabled(true); // brings up the back arrow and menu - // Assegna alla vista pagina un adapter che gestisce le tre schede + // Give the page view an adapter that manages the three tabs ViewPager viewPager = findViewById(R.id.schede_persona); - ImpaginatoreSezioni impaginatoreSezioni = new ImpaginatoreSezioni(); - viewPager.setAdapter(impaginatoreSezioni); + SectionsPaginator sectionsPaginator = new SectionsPaginator(); + viewPager.setAdapter(sectionsPaginator); - // arricchisce il tablayout + // "enriches"/populates the tablayout tabLayout = findViewById(R.id.tabs); - tabLayout.setupWithViewPager(viewPager); // altrimenti il testo nei TabItem scompare (?!) + tabLayout.setupWithViewPager(viewPager); // otherwise the text in the TabItems disappears (?!) tabLayout.getTabAt(0).setText(R.string.media); tabLayout.getTabAt(1).setText(R.string.events); tabLayout.getTabAt(2).setText(R.string.relatives); tabLayout.getTabAt(getIntent().getIntExtra("scheda", 1)).select(); - // per animare il FAB + // to animate the FAB final FloatingActionButton fab = findViewById(R.id.fab); viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { @Override - public void onPageScrolled( int posizione, // 0 tra la prima e la seconda, 1 tra la seconda e la terza... - float scostamento, // 1->0 a destra, 0->1 a sinistra + public void onPageScrolled( int position, // 0 between first and second, 1 between second and third... + float offset, // 1->0 to the right, 0->1 to the left //delta? direction? int positionOffsetPixels ) { - if( scostamento > 0 ) + if( offset > 0 ) fab.hide(); else fab.show(); @@ -117,25 +117,25 @@ public void onPageScrollStateChanged( int state ) {} Collections.sort(otherEvents, (item1, item2) -> item1.second.compareTo(item2.second)); } - class ImpaginatoreSezioni extends FragmentPagerAdapter { + class SectionsPaginator extends FragmentPagerAdapter { - ImpaginatoreSezioni() { + SectionsPaginator() { super( getSupportFragmentManager() ); } - @Override // in realtà non seleziona ma CREA le tre schede + @Override // it doesn't actually select but CREATE the three tabs public Fragment getItem( int position ) { Fragment scheda = new Fragment(); if( position == 0 ) - scheda = new IndividuoMedia(); + scheda = new IndividualMediaFragment(); else if( position == 1 ) - scheda = new IndividuoEventi(); + scheda = new IndividualEventsFragment(); else if( position == 2 ) - scheda = new IndividuoFamiliari(); + scheda = new IndividualFamilyFragment(); return scheda; } - @Override // necessario + @Override // necessary public int getCount() { return 3; } @@ -144,54 +144,54 @@ public int getCount() { @Override protected void onStart() { super.onStart(); - if( one == null || Global.edited ) - one = gc.getPerson(Global.indi); + if( thisPerson == null || Global.edited ) + thisPerson = gc.getPerson(Global.indi); - if( one == null ) { // ritornando indietro nella Scheda di un individuo che è stato eliminato + if( thisPerson == null ) { // going back to the Record of an individual who has been deleted onBackPressed(); return; } - // Tutto ciò che nella pagina può cambiare + // Everything on the page can change TextView idView = findViewById(R.id.persona_id); if( Global.settings.expert ) { - idView.setText("INDI " + one.getId()); + idView.setText("INDI " + thisPerson.getId()); idView.setOnClickListener(v -> { - U.editId(this, one, ((IndividuoEventi)getTab(1))::refreshId); + U.editId(this, thisPerson, ((IndividualEventsFragment)getTab(1))::refreshId); }); } else idView.setVisibility(View.GONE); - CollapsingToolbarLayout barraCollasso = findViewById(R.id.toolbar_layout); - barraCollasso.setTitle(U.epiteto(one)); // aggiorna il titolo se il nome viene modificato, ma non lo setta se è una stringa vuota - F.unaFoto(Global.gc, one, findViewById(R.id.persona_foto)); - F.unaFoto(Global.gc, one, findViewById(R.id.persona_sfondo)); + CollapsingToolbarLayout collapsingToolbar = findViewById(R.id.toolbar_layout); + collapsingToolbar.setTitle(U.properName(thisPerson)); // updates the title if the name changes, but does not set it if it is an empty string + F.showMainImageForPerson(Global.gc, thisPerson, findViewById(R.id.persona_foto)); + F.showMainImageForPerson(Global.gc, thisPerson, findViewById(R.id.persona_sfondo)); if( Global.edited ) { - // Ricostruisce le tre schede ritornando alla pagina + // Rebuilds the three tabs returning to the page for( int i = 0; i < 3; i++ ) { - Fragment scheda = getTab(i); - if( scheda != null ) { // alla prima creazione dell'activity sono null - getSupportFragmentManager().beginTransaction().detach(scheda).commit(); - getSupportFragmentManager().beginTransaction().attach(scheda).commit(); + Fragment tab = getTab(i); + if( tab != null ) { // at the first creation of the activity they are null + getSupportFragmentManager().beginTransaction().detach(tab).commit(); + getSupportFragmentManager().beginTransaction().attach(tab).commit(); } - // ToDo tornando indietro dopo una editazione non aggiorna la scheda 0 coi media... + // TODO going back after an edit does not update tab 0 with the media... } invalidateOptionsMenu(); } // Menu FAB findViewById(R.id.fab).setOnClickListener(vista -> { - PopupMenu popup = new PopupMenu(Individuo.this, vista); + PopupMenu popup = new PopupMenu(IndividualPersonActivity.this, vista); Menu menu = popup.getMenu(); switch( tabLayout.getSelectedTabPosition() ) { - case 0: // Individuo Media + case 0: // Individual Media menu.add(0, 10, 0, R.string.new_media); menu.add(0, 11, 0, R.string.new_shared_media); if( !gc.getMedia().isEmpty() ) menu.add(0, 12, 0, R.string.link_shared_media); break; - case 1: // Individuo Eventi + case 1: // Individual Events menu.add(0, 20, 0, R.string.name); - // Sesso - if( Gender.getGender(one) == Gender.NONE ) + // Gender + if( Gender.getGender(thisPerson) == Gender.NONE ) menu.add(0, 21, 0, R.string.sex); // Main events SubMenu eventSubMenu = menu.addSubMenu(R.string.event); @@ -210,51 +210,51 @@ protected void onStart() { otherSubMenu.add(0, 50 + i, 0, (String)item.second); i++; } - SubMenu subNota = menu.addSubMenu(R.string.note); - subNota.add(0, 22, 0, R.string.new_note); - subNota.add(0, 23, 0, R.string.new_shared_note); + SubMenu subNote = menu.addSubMenu(R.string.note); + subNote.add(0, 22, 0, R.string.new_note); + subNote.add(0, 23, 0, R.string.new_shared_note); if( !gc.getNotes().isEmpty() ) - subNota.add(0, 24, 0, R.string.link_shared_note); + subNote.add(0, 24, 0, R.string.link_shared_note); if( Global.settings.expert ) { - SubMenu subFonte = menu.addSubMenu(R.string.source); - subFonte.add(0, 25, 0, R.string.new_source_note); - subFonte.add(0, 26, 0, R.string.new_source); + SubMenu subSource = menu.addSubMenu(R.string.source); + subSource.add(0, 25, 0, R.string.new_source_note); + subSource.add(0, 26, 0, R.string.new_source); if( !gc.getSources().isEmpty() ) - subFonte.add(0, 27, 0, R.string.link_source); + subSource.add(0, 27, 0, R.string.link_source); } break; - case 2: // Individuo Familiari + case 2: // Individual family members menu.add(0, 30, 0, R.string.new_relative); - if( U.ciSonoIndividuiCollegabili(one) ) + if( U.containsConnectableIndividuals(thisPerson) ) menu.add(0, 31, 0, R.string.link_person); } popup.show(); popup.setOnMenuItemClickListener(item -> { - CharSequence[] familiari = {getText(R.string.parent), getText(R.string.sibling), getText(R.string.partner), getText(R.string.child)}; - AlertDialog.Builder builder = new AlertDialog.Builder(Individuo.this); + CharSequence[] members = {getText(R.string.parent), getText(R.string.sibling), getText(R.string.partner), getText(R.string.child)}; + AlertDialog.Builder builder = new AlertDialog.Builder(IndividualPersonActivity.this); switch( item.getItemId() ) { - // Scheda Eventi + // Events tab case 0: break; // Media - case 10: // Cerca media locale - F.appAcquisizioneImmagine(Individuo.this, null, 2173, one); + case 10: // Search local media + F.displayImageCaptureDialog(IndividualPersonActivity.this, null, 2173, thisPerson); break; - case 11: // Cerca oggetto media - F.appAcquisizioneImmagine(Individuo.this, null, 2174, one); + case 11: // Search for media objects + F.displayImageCaptureDialog(IndividualPersonActivity.this, null, 2174, thisPerson); break; - case 12: // Collega media in Galleria - Intent inten = new Intent(Individuo.this, Principal.class); - inten.putExtra("galleriaScegliMedia", true); - startActivityForResult(inten, 43614); + case 12: // Link Media in Gallery + Intent principalIntent = new Intent(IndividualPersonActivity.this, Principal.class); + principalIntent.putExtra("galleriaScegliMedia", true); + startActivityForResult(principalIntent, 43614); break; case 20: // Create name Name name = new Name(); name.setValue("//"); - one.addName(name); - Memoria.aggiungi(name); - startActivity(new Intent(Individuo.this, Nome.class)); - U.save(true, one); + thisPerson.addName(name); + Memory.add(name); + startActivity(new Intent(IndividualPersonActivity.this, NameActivity.class)); + U.save(true, thisPerson); break; case 21: // Create sex String[] sexNames = {getString(R.string.male), getString(R.string.female), getString(R.string.unknown)}; @@ -264,76 +264,74 @@ protected void onStart() { gender.setTag("SEX"); String[] sexValues = {"M", "F", "U"}; gender.setValue(sexValues[i]); - one.addEventFact(gender); + thisPerson.addEventFact(gender); dialog.dismiss(); - IndividuoEventi.aggiornaRuoliConiugali(one); - IndividuoEventi factsTab = (IndividuoEventi)getTab(1); + IndividualEventsFragment.updateMaritalRoles(thisPerson); + IndividualEventsFragment factsTab = (IndividualEventsFragment)getTab(1); factsTab.refresh(1); - U.save(true, one); + U.save(true, thisPerson); }).show(); break; case 22: // Create note Note note = new Note(); note.setValue(""); - one.addNote(note); - Memoria.aggiungi(note); - startActivity(new Intent(Individuo.this, Nota.class)); - // todo? Dettaglio.edita(View vistaValore); - U.save(true, one); + thisPerson.addNote(note); + Memory.add(note); + startActivity(new Intent(IndividualPersonActivity.this, NoteActivity.class)); + // todo? DetailActivity.edit(View viewValue); + U.save(true, thisPerson); break; case 23: // Create shared note - Quaderno.newNote(Individuo.this, one); + NotebookFragment.newNote(IndividualPersonActivity.this, thisPerson); break; case 24: // Link shared note - Intent intent = new Intent(Individuo.this, Principal.class); + Intent intent = new Intent(IndividualPersonActivity.this, Principal.class); intent.putExtra("quadernoScegliNota", true); startActivityForResult(intent, 4074); break; - case 25: // Nuova fonte-nota - SourceCitation citaz = new SourceCitation(); - citaz.setValue(""); - one.addSourceCitation(citaz); - Memoria.aggiungi(citaz); - startActivity(new Intent(Individuo.this, CitazioneFonte.class)); - U.save(true, one); + case 25: // New source-note + SourceCitation citation = new SourceCitation(); + citation.setValue(""); + thisPerson.addSourceCitation(citation); + Memory.add(citation); + startActivity(new Intent(IndividualPersonActivity.this, SourceCitationActivity.class)); + U.save(true, thisPerson); break; - case 26: // Nuova fonte - Biblioteca.nuovaFonte(Individuo.this, one); + case 26: // New source + LibraryFragment.newSource(IndividualPersonActivity.this, thisPerson); break; - case 27: // Collega fonte - Intent intento = new Intent(Individuo.this, Principal.class); - intento.putExtra("bibliotecaScegliFonte", true); - startActivityForResult(intento, 50473); + case 27: // Connect Source + startActivityForResult(new Intent(IndividualPersonActivity.this, Principal.class).putExtra("bibliotecaScegliFonte", true), 50473); break; - // Scheda Familiari - case 30:// Collega persona nuova + // Family tab + case 30:// Connect new person if( Global.settings.expert ) { - DialogFragment dialog = new NuovoParente(one, null, null, true, null); + DialogFragment dialog = new NewRelativeDialog(thisPerson, null, null, true, null); dialog.show(getSupportFragmentManager(), "scegli"); } else { - builder.setItems(familiari, (dialog, quale) -> { - Intent intento1 = new Intent(getApplicationContext(), EditaIndividuo.class); - intento1.putExtra("idIndividuo", one.getId()); - intento1.putExtra("relazione", quale + 1); - if( U.controllaMultiMatrimoni(intento1, Individuo.this, null) ) + builder.setItems(members, (dialog, quale) -> { + Intent intent1 = new Intent(getApplicationContext(), IndividualEditorActivity.class); + intent1.putExtra("idIndividuo", thisPerson.getId()); + intent1.putExtra("relazione", quale + 1); + if( U.checkMultipleMarriages(intent1, IndividualPersonActivity.this, null) ) return; - startActivity(intento1); + startActivity(intent1); }).show(); } break; - case 31: // Collega persona esistente + case 31: // Link existing person if( Global.settings.expert ) { - DialogFragment dialog = new NuovoParente(one, null, null, false, null); + DialogFragment dialog = new NewRelativeDialog(thisPerson, null, null, false, null); dialog.show(getSupportFragmentManager(), "scegli"); } else { - builder.setItems(familiari, (dialog, quale) -> { - Intent intento2 = new Intent(getApplication(), Principal.class); - intento2.putExtra("idIndividuo", one.getId()); - intento2.putExtra("anagrafeScegliParente", true); - intento2.putExtra("relazione", quale + 1); - if( U.controllaMultiMatrimoni(intento2, Individuo.this, null) ) + builder.setItems(members, (dialog, which) -> { + Intent intent2 = new Intent(getApplication(), Principal.class); + intent2.putExtra("idIndividuo", thisPerson.getId()); + intent2.putExtra("anagrafeScegliParente", true); + intent2.putExtra("relazione", which + 1); + if( U.checkMultipleMarriages(intent2, IndividualPersonActivity.this, null) ) return; - startActivityForResult(intento2, 1401); + startActivityForResult(intent2, 1401); }).show(); } break; @@ -345,27 +343,27 @@ protected void onStart() { keyTag = mainEventTags[item.getItemId() - 40]; if( keyTag == null ) return false; - EventFact nuovoEvento = new EventFact(); - nuovoEvento.setTag(keyTag); + EventFact newEvent = new EventFact(); + newEvent.setTag(keyTag); switch( keyTag ) { case "OCCU": - nuovoEvento.setValue(""); + newEvent.setValue(""); break; case "RESI": - nuovoEvento.setPlace(""); + newEvent.setPlace(""); break; case "BIRT": case "DEAT": case "CHR": case "BAPM": case "BURI": - nuovoEvento.setPlace(""); - nuovoEvento.setDate(""); + newEvent.setPlace(""); + newEvent.setDate(""); } - one.addEventFact(nuovoEvento); - Memoria.aggiungi(nuovoEvento); - startActivity(new Intent(Individuo.this, Evento.class)); - U.save(true, one); + thisPerson.addEventFact(newEvent); + Memory.add(newEvent); + startActivity(new Intent(IndividualPersonActivity.this, EventActivity.class)); + U.save(true, thisPerson); } return true; }); @@ -380,76 +378,76 @@ private Fragment getTab(int num) { @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); - outState.putString("idUno", one.getId()); + outState.putString("idUno", thisPerson.getId()); } @Override public void onActivityResult( int requestCode, int resultCode, Intent data ) { super.onActivityResult( requestCode, resultCode, data ); if( resultCode == RESULT_OK ) { - if( requestCode == 2173 ) { // File fornito da un'app diventa media locale eventualmente ritagliato con Android Image Cropper + if( requestCode == 2173 ) { // File provided by an app becomes local media possibly cropped with Android Image Cropper Media media = new Media(); media.setFileTag("FILE"); - one.addMedia(media); - if( F.proponiRitaglio(this, null, data, media) ) { // restituisce true se è un'immagine ritagliabile - U.save(true, one); + thisPerson.addMedia(media); + if( F.proposeCropping(this, null, data, media) ) { // returns true if it is a clipable image + U.save(true, thisPerson); return; } - } else if( requestCode == 2174 ) { // File dalle app in nuovo Media condiviso, con proposta di ritagliarlo - Media media = Galleria.nuovoMedia(one); - if( F.proponiRitaglio(this, null, data, media) ) { - U.save(true, media, one); + } else if( requestCode == 2174 ) { // Files from apps in new Shared Media, with proposal to crop it + Media media = GalleryFragment.newMedia(thisPerson); + if( F.proposeCropping(this, null, data, media) ) { + U.save(true, media, thisPerson); return; } } else if( requestCode == CropImage.CROP_IMAGE_ACTIVITY_REQUEST_CODE ) { - // Ottiene l'immagine ritagliata da Android Image Cropper - F.fineRitaglioImmagine(data); - U.save(true); // la data di cambio per i Media condivisi viene già salvata nel passaggio precedente - // todo passargli Global.mediaCroppato ? + // Get the image cropped by Android Image Cropper + F.endImageCropping(data); + U.save(true); // the switch date for Shared Media is already saved in the previous step + // todo pass it Global.mediaCropped ? return; - } else if( requestCode == 43614 ) { // Media da Galleria - MediaRef rifMedia = new MediaRef(); - rifMedia.setRef( data.getStringExtra("idMedia") ); - one.addMediaRef( rifMedia ); - } else if( requestCode == 4074 ) { // Nota - NoteRef rifNota = new NoteRef(); - rifNota.setRef( data.getStringExtra("idNota") ); - one.addNoteRef( rifNota ); - } else if( requestCode == 50473 ) { // Fonte + } else if( requestCode == 43614 ) { // Media from Gallery + MediaRef mediaRef = new MediaRef(); + mediaRef.setRef( data.getStringExtra("idMedia") ); + thisPerson.addMediaRef( mediaRef ); + } else if( requestCode == 4074 ) { // Note + NoteRef noteRef = new NoteRef(); + noteRef.setRef( data.getStringExtra("idNota") ); + thisPerson.addNoteRef( noteRef ); + } else if( requestCode == 50473 ) { // Source SourceCitation citaz = new SourceCitation(); citaz.setRef( data.getStringExtra("idFonte") ); - one.addSourceCitation( citaz ); - } else if( requestCode == 1401 ) { // Parente - Object[] modificati = EditaIndividuo.aggiungiParente( - data.getStringExtra("idIndividuo"), // corrisponde a uno.getId() + thisPerson.addSourceCitation( citaz ); + } else if( requestCode == 1401 ) { // Relative + Object[] modified = IndividualEditorActivity.addParent( + data.getStringExtra("idIndividuo"), // corresponds to thisPerson.getId() data.getStringExtra("idParente"), data.getStringExtra("idFamiglia"), data.getIntExtra("relazione", 0), data.getStringExtra("collocazione") ); - U.save( true, modificati ); + U.save( true, modified ); return; } - U.save(true, one); - } else if( requestCode == CropImage.CROP_IMAGE_ACTIVITY_REQUEST_CODE ) // se clic su freccia indietro in Crop Image + U.save(true, thisPerson); + } else if( requestCode == CropImage.CROP_IMAGE_ACTIVITY_REQUEST_CODE ) // if click back arrow in Crop Image Global.edited = true; } @Override public void onBackPressed() { - Memoria.arretra(); + Memory.clearStackAndRemove(); super.onBackPressed(); } - // Menu Opzioni + // Options Menu @Override public boolean onCreateOptionsMenu(Menu menu) { menu.add(0, 0, 0, R.string.diagram); - String[] familyLabels = Diagram.getFamilyLabels(this, one, null); + String[] familyLabels = Diagram.getFamilyLabels(this, thisPerson, null); if( familyLabels[0] != null ) menu.add(0, 1, 0, familyLabels[0]); if( familyLabels[1] != null ) menu.add(0, 2, 0, familyLabels[1]); - if( Global.settings.getCurrentTree().root == null || !Global.settings.getCurrentTree().root.equals(one.getId()) ) + if( Global.settings.getCurrentTree().root == null || !Global.settings.getCurrentTree().root.equals(thisPerson.getId()) ) menu.add(0, 3, 0, R.string.make_root); menu.add(0, 4, 0, R.string.modify); menu.add(0, 5, 0, R.string.delete); @@ -459,29 +457,29 @@ public boolean onCreateOptionsMenu(Menu menu) { public boolean onOptionsItemSelected( MenuItem item ) { switch ( item.getItemId() ) { case 0: // Diagram - U.qualiGenitoriMostrare(this, one, 1); + U.askWhichParentsToShow(this, thisPerson, 1); return true; case 1: // Family as child - U.qualiGenitoriMostrare(this, one, 2); + U.askWhichParentsToShow(this, thisPerson, 2); return true; case 2: // Family as partner - U.qualiConiugiMostrare(this, one, null); + U.askWhichSpouseToShow(this, thisPerson, null); return true; case 3: // Set as root - Global.settings.getCurrentTree().root = one.getId(); + Global.settings.getCurrentTree().root = thisPerson.getId(); Global.settings.save(); - Toast.makeText(this, getString(R.string.this_is_root, U.epiteto(one)), Toast.LENGTH_LONG).show(); + Toast.makeText(this, getString(R.string.this_is_root, U.properName(thisPerson)), Toast.LENGTH_LONG).show(); return true; case 4: // Edit - Intent intent1 = new Intent(this, EditaIndividuo.class); - intent1.putExtra("idIndividuo", one.getId()); + Intent intent1 = new Intent(this, IndividualEditorActivity.class); + intent1.putExtra("idIndividuo", thisPerson.getId()); startActivity(intent1); return true; case 5: // Delete new AlertDialog.Builder(this).setMessage(R.string.really_delete_person) .setPositiveButton(R.string.delete, (dialog, i) -> { - Family[] famiglie = Anagrafe.deletePerson(this, one.getId()); - if( !U.controllaFamiglieVuote(this, this::onBackPressed, true, famiglie) ) + Family[] families = ListOfPeopleFragment.deletePerson(this, thisPerson.getId()); + if( !U.checkFamilyItem(this, this::onBackPressed, true, families) ) onBackPressed(); }).setNeutralButton(R.string.cancel, null).show(); return true; @@ -494,6 +492,6 @@ public boolean onOptionsItemSelected( MenuItem item ) { @Override public void onRequestPermissionsResult( int codice, String[] permessi, int[] accordi ) { super.onRequestPermissionsResult(codice, permessi, accordi); - F.risultatoPermessi(this, null, codice, permessi, accordi, one); + F.permissionsResult(this, null, codice, permessi, accordi, thisPerson); } } diff --git a/app/src/main/java/app/familygem/IndividuoFamiliari.java b/app/src/main/java/app/familygem/IndividuoFamiliari.java deleted file mode 100644 index b2a04872..00000000 --- a/app/src/main/java/app/familygem/IndividuoFamiliari.java +++ /dev/null @@ -1,191 +0,0 @@ -package app.familygem; - -import android.content.Intent; -import android.os.Bundle; -import androidx.appcompat.app.AlertDialog; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentManager; -import android.view.ContextMenu; -import android.view.LayoutInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.LinearLayout; -import org.folg.gedcom.model.Family; -import org.folg.gedcom.model.Person; -import org.folg.gedcom.model.SpouseFamilyRef; -import java.util.Collections; -import java.util.List; -import app.familygem.constant.Relation; -import app.familygem.detail.Famiglia; -import static app.familygem.Global.gc; - -public class IndividuoFamiliari extends Fragment { - - private View vistaFamiglia; - Person uno; - - @Override - public View onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState ) { - vistaFamiglia = inflater.inflate(R.layout.individuo_scheda, container, false); - if( gc != null ) { - uno = gc.getPerson( Global.indi); - if( uno != null ) { - /* ToDo Mostrare/poter settare nelle famiglie geniotriali il pedigree, in particolare 'adopted' - LinearLayout scatola = vistaFamiglia.findViewById( R.id.contenuto_scheda ); - for( ParentFamilyRef pfr : uno.getParentFamilyRefs() ) { - U.metti( scatola, "Ref", pfr.getRef() ); - U.metti( scatola, "Primary", pfr.getPrimary() ); // Custom tag _PRIM _PRIMARY - U.metti( scatola, "Relationship Type", pfr.getRelationshipType() ); // Tag PEDI (pedigree) - for( Estensione altroTag : U.trovaEstensioni( pfr ) ) - U.metti( scatola, altroTag.nome, altroTag.testo ); - } */ - // Famiglie di origine: genitori e fratelli - List listaFamiglie = uno.getParentFamilies(gc); - for( Family famiglia : listaFamiglie ) { - for( Person padre : famiglia.getHusbands(gc) ) - createCard(padre, Relation.PARENT, famiglia); - for( Person madre : famiglia.getWives(gc) ) - createCard(madre, Relation.PARENT, famiglia); - for( Person fratello : famiglia.getChildren(gc) ) // solo i figli degli stessi due genitori, non i fratellastri - if( !fratello.equals(uno) ) - createCard(fratello, Relation.SIBLING, famiglia); - } - // Fratellastri e sorellastre - for( Family famiglia : uno.getParentFamilies(gc) ) { - for( Person padre : famiglia.getHusbands(gc) ) { - List famigliePadre = padre.getSpouseFamilies(gc); - famigliePadre.removeAll(listaFamiglie); - for( Family fam : famigliePadre ) - for( Person fratellastro : fam.getChildren(gc) ) - createCard(fratellastro, Relation.HALF_SIBLING, fam); - } - for( Person madre : famiglia.getWives(gc) ) { - List famiglieMadre = madre.getSpouseFamilies(gc); - famiglieMadre.removeAll(listaFamiglie); - for( Family fam : famiglieMadre ) - for( Person fratellastro : fam.getChildren(gc) ) - createCard(fratellastro, Relation.HALF_SIBLING, fam); - } - } - // Coniugi e figli - for( Family family : uno.getSpouseFamilies(gc) ) { - for( Person marito : family.getHusbands(gc) ) - if( !marito.equals(uno) ) - createCard(marito, Relation.PARTNER, family); - for( Person moglie : family.getWives(gc) ) - if( !moglie.equals(uno) ) - createCard(moglie, Relation.PARTNER, family); - for( Person figlio : family.getChildren(gc) ) { - createCard(figlio, Relation.CHILD, family); - } - } - } - } - return vistaFamiglia; - } - - void createCard(final Person person, Relation relation, Family family) { - LinearLayout scatola = vistaFamiglia.findViewById(R.id.contenuto_scheda); - View vistaPersona = U.mettiIndividuo(scatola, person, - Famiglia.getRole(person, family, relation, false) + Famiglia.writeLineage(person, family)); - vistaPersona.setOnClickListener(v -> { - getActivity().finish(); // Rimuove l'attività attale dallo stack - Memoria.replacePrimo(person); - Intent intento = new Intent(getContext(), Individuo.class); - intento.putExtra("scheda", 2); // apre la scheda famiglia - startActivity(intento); - }); - registerForContextMenu(vistaPersona); - vistaPersona.setTag(R.id.tag_famiglia, family); // Il principale scopo di questo tag è poter scollegare l'individuo dalla famiglia - // ma è usato anche qui sotto per spostare i molteplici matrimoni - } - - private void spostaRiferimentoFamiglia(int direzione) { - Collections.swap(uno.getSpouseFamilyRefs(), posFam, posFam + direzione); - U.save(true, uno); - refresh(); - } - - // Menu contestuale - private String indiId; - private Person person; - private Family family; - private int posFam; // posizione della famiglia coniugale per chi ne ha più di una - @Override - public void onCreateContextMenu( ContextMenu menu, View vista, ContextMenu.ContextMenuInfo info ) { - indiId = (String)vista.getTag(); - person = gc.getPerson(indiId); - family = (Family)vista.getTag(R.id.tag_famiglia); - posFam = -1; - if( uno.getSpouseFamilyRefs().size() > 1 && !family.getChildren(gc).contains(person) ) { // solo i coniugi, non i figli - List refi = uno.getSpouseFamilyRefs(); - for( SpouseFamilyRef sfr : refi ) - if( sfr.getRef().equals(family.getId()) ) - posFam = refi.indexOf(sfr); - } - // Meglio usare numeri che non confliggano con i menu contestuali delle altre schede individuo - menu.add(0, 300, 0, R.string.diagram); - String[] familyLabels = Diagram.getFamilyLabels(getContext(), person, family); - if( familyLabels[0] != null ) - menu.add(0, 301, 0, familyLabels[0]); - if( familyLabels[1] != null ) - menu.add(0, 302, 0, familyLabels[1]); - if( posFam > 0 ) - menu.add(0, 303, 0, R.string.move_before); - if( posFam >= 0 && posFam < uno.getSpouseFamilyRefs().size() - 1 ) - menu.add(0, 304, 0, R.string.move_after); - menu.add(0, 305, 0, R.string.modify); - if( Famiglia.findParentFamilyRef(person, family) != null ) - menu.add(0, 306, 0, R.string.lineage); - menu.add(0, 307, 0, R.string.unlink); - if( !person.equals(uno) ) // Qui non può eliminare sè stesso - menu.add(0, 308, 0, R.string.delete); - } - - @Override - public boolean onContextItemSelected(MenuItem item) { - int id = item.getItemId(); - if( id == 300 ) { // Diagramma - U.qualiGenitoriMostrare(getContext(), person, 1); - } else if( id == 301 ) { // Famiglia come figlio - U.qualiGenitoriMostrare(getContext(), person, 2); - } else if( id == 302 ) { // Famiglia come coniuge - U.qualiConiugiMostrare(getContext(), person, family); - } else if( id == 303 ) { // Sposta su - spostaRiferimentoFamiglia(-1); - } else if( id == 304 ) { // Sposta giù - spostaRiferimentoFamiglia(1); - } else if( id == 305 ) { // Modifica - Intent intento = new Intent(getContext(), EditaIndividuo.class); - intento.putExtra("idIndividuo", indiId); - startActivity(intento); - } else if( id == 306 ) { // Lineage - Famiglia.chooseLineage(getContext(), person, family); - } else if( id == 307 ) { // Scollega da questa famiglia - Famiglia.scollega(indiId, family); - refresh(); - U.controllaFamiglieVuote(getContext(), this::refresh, false, family); - U.save(true, family, person); - } else if( id == 308 ) { // Elimina - new AlertDialog.Builder(getContext()).setMessage(R.string.really_delete_person) - .setPositiveButton(R.string.delete, (dialog, i) -> { - Anagrafe.deletePerson(getContext(), indiId); - refresh(); - U.controllaFamiglieVuote(getContext(), this::refresh, false, family); - }).setNeutralButton(R.string.cancel, null).show(); - } else { - return false; - } - return true; - } - - // Rinfresca il contenuto del frammento Familiari - public void refresh() { - FragmentManager fragmentManager = requireActivity().getSupportFragmentManager(); - fragmentManager.beginTransaction().detach(this).commit(); - fragmentManager.beginTransaction().attach(this).commit(); - requireActivity().invalidateOptionsMenu(); - // todo aggiorna la data cambiamento nella scheda Fatti - } -} diff --git a/app/src/main/java/app/familygem/IndividuoMedia.java b/app/src/main/java/app/familygem/IndividuoMedia.java deleted file mode 100644 index 9bf1aa80..00000000 --- a/app/src/main/java/app/familygem/IndividuoMedia.java +++ /dev/null @@ -1,99 +0,0 @@ -// Scheda Foto -package app.familygem; - -import android.os.Bundle; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentManager; -import androidx.recyclerview.widget.GridLayoutManager; -import androidx.recyclerview.widget.RecyclerView; -import android.view.ContextMenu; -import android.view.LayoutInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.LinearLayout; -import org.folg.gedcom.model.Media; -import org.folg.gedcom.model.MediaContainer; -import org.folg.gedcom.model.Person; -import app.familygem.visitor.ListaMediaContenitore; -import static app.familygem.Global.gc; - -public class IndividuoMedia extends Fragment { - - Person uno; - ListaMediaContenitore visitaMedia; - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View vistaMedia = inflater.inflate(R.layout.individuo_scheda, container, false); - if( gc != null ) { - final LinearLayout scatola = vistaMedia.findViewById(R.id.contenuto_scheda); - uno = gc.getPerson(Global.indi); - if( uno != null ) { - visitaMedia = new ListaMediaContenitore(gc, true); - uno.accept(visitaMedia); - RecyclerView griglia = new RecyclerView(scatola.getContext()); - griglia.setHasFixedSize(true); - RecyclerView.LayoutManager gestoreLayout = new GridLayoutManager(getContext(), 2); - griglia.setLayoutManager(gestoreLayout); - AdattatoreGalleriaMedia adattatore = new AdattatoreGalleriaMedia(visitaMedia.listaMedia, true); - griglia.setAdapter(adattatore); - scatola.addView(griglia); - } - } - return vistaMedia; - } - - // Menu contestuale - Media media; - Object container; // Le immagini non sono solo di 'uno', ma anche dei suoi subordinati EventFact, SourceCitation... - @Override - public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo info) { - media = (Media)view.getTag(R.id.tag_oggetto); - container = view.getTag(R.id.tag_contenitore); - if( visitaMedia.listaMedia.size() > 1 && media.getPrimary() == null ) - menu.add(0, 0, 0, R.string.primary_media); - if( media.getId() != null ) - menu.add(0, 1, 0, R.string.unlink); - menu.add(0, 2, 0, R.string.delete); - } - @Override - public boolean onContextItemSelected( MenuItem item ) { - int id = item.getItemId(); - if( id == 0 ) { // Principale - for( ListaMediaContenitore.MedCont medCont : visitaMedia.listaMedia ) // Li resetta tutti poi ne contrassegna uno - medCont.media.setPrimary(null); - media.setPrimary("Y"); - if( media.getId() != null ) // Per aggiornare la data cambiamento nel Media record piuttosto che nella Person - U.save(true, media); - else - U.save(true, uno); - refresh(); - return true; - } else if( id == 1 ) { // Scollega - Galleria.scollegaMedia(media.getId(), (MediaContainer)container); - U.save(true, uno); - refresh(); - return true; - } else if( id == 2 ) { // Elimina - Object[] capi = Galleria.eliminaMedia(media, null); - U.save(true, capi); - refresh(); - return true; - } - return false; - } - - // Rinfresca il contenuto del frammento Media - void refresh() { - // ricarica il fragment - FragmentManager fragmentManager = requireActivity().getSupportFragmentManager(); - fragmentManager.beginTransaction().detach(this).commit(); - fragmentManager.beginTransaction().attach(this).commit(); - F.unaFoto(Global.gc, uno, requireActivity().findViewById(R.id.persona_foto)); - F.unaFoto(Global.gc, uno, requireActivity().findViewById(R.id.persona_sfondo)); - // Scheda eventi - IndividuoEventi tabEventi = (IndividuoEventi)requireActivity().getSupportFragmentManager().findFragmentByTag("android:switcher:" + R.id.schede_persona + ":1"); - tabEventi.refresh(1); - } -} diff --git a/app/src/main/java/app/familygem/InfoAlbero.java b/app/src/main/java/app/familygem/InfoAlbero.java deleted file mode 100644 index 3e870b34..00000000 --- a/app/src/main/java/app/familygem/InfoAlbero.java +++ /dev/null @@ -1,323 +0,0 @@ -package app.familygem; - -import android.graphics.Typeface; -import android.os.Bundle; -import android.view.Gravity; -import android.view.MenuItem; -import android.view.View; -import android.widget.Button; -import android.widget.LinearLayout; -import android.widget.TableLayout; -import android.widget.TableRow; -import android.widget.TextView; -import org.folg.gedcom.model.CharacterSet; -import org.folg.gedcom.model.Family; -import org.folg.gedcom.model.Gedcom; -import org.folg.gedcom.model.GedcomVersion; -import org.folg.gedcom.model.Generator; -import org.folg.gedcom.model.Header; -import org.folg.gedcom.model.Person; -import org.folg.gedcom.model.Submitter; -import java.io.File; -import java.util.Locale; -import app.familygem.visitor.ListaMedia; - -public class InfoAlbero extends BaseActivity { - - Gedcom gc; - - @Override - protected void onCreate(Bundle bundle) { - super.onCreate(bundle); - setContentView(R.layout.info_albero); - LinearLayout scatola = findViewById(R.id.info_scatola); - - final int treeId = getIntent().getIntExtra("idAlbero", 1); - final Settings.Tree tree = Global.settings.getTree(treeId); - final File file = new File(getFilesDir(), treeId + ".json"); - String i = getText(R.string.title) + ": " + tree.title; - if( !file.exists() ) { - i += "\n\n" + getText(R.string.item_exists_but_file) + "\n" + file.getAbsolutePath(); - } else { - i += "\n" + getText(R.string.file) + ": " + file.getAbsolutePath(); - gc = Alberi.apriGedcomTemporaneo(treeId, false); - if( gc == null ) - i += "\n\n" + getString(R.string.no_useful_data); - else { - // Aggiornamento dei dati automatico o su richiesta - if( tree.persons < 100 ) { - refreshData(gc, tree); - } else { - Button bottoneAggiorna = findViewById(R.id.info_aggiorna); - bottoneAggiorna.setVisibility(View.VISIBLE); - bottoneAggiorna.setOnClickListener(v -> { - refreshData(gc, tree); - recreate(); - }); - } - i += "\n\n" + getText(R.string.persons) + ": "+ tree.persons - + "\n" + getText(R.string.families) + ": "+ gc.getFamilies().size() - + "\n" + getText(R.string.generations) + ": "+ tree.generations - + "\n" + getText(R.string.media) + ": "+ tree.media - + "\n" + getText(R.string.sources) + ": "+ gc.getSources().size() - + "\n" + getText(R.string.repositories) + ": "+ gc.getRepositories().size(); - if( tree.root != null ) { - i += "\n" + getText(R.string.root) + ": " + U.epiteto(gc.getPerson(tree.root)); - } - if( tree.shares != null && !tree.shares.isEmpty() ) { - i += "\n\n" + getText(R.string.shares) + ":"; - for( Settings.Share share : tree.shares ) { - i += "\n" + dataIdVersoData(share.dateId); - if( gc.getSubmitter(share.submitter) != null ) - i += " - " + nomeAutore( gc.getSubmitter(share.submitter) ); - } - } - } - } - ((TextView)findViewById(R.id.info_statistiche)).setText( i ); - - Button bottoneHeader = scatola.findViewById( R.id.info_gestisci_testata ); - if( gc != null ) { - Header h = gc.getHeader(); - if( h == null) { - bottoneHeader.setText( R.string.create_header ); - bottoneHeader.setOnClickListener( view -> { - gc.setHeader( AlberoNuovo.creaTestata( file.getName() ) ); - U.saveJson(gc, treeId); - recreate(); - }); - } else { - scatola.findViewById( R.id.info_testata ).setVisibility( View.VISIBLE ); - if( h.getFile() != null ) - poni( getText(R.string.file), h.getFile() ); - if( h.getCharacterSet() != null ) { - poni( getText(R.string.characrter_set), h.getCharacterSet().getValue() ); - poni( getText(R.string.version), h.getCharacterSet().getVersion() ); - } - spazio(); // uno spazietto - poni( getText(R.string.language), h.getLanguage() ); - spazio(); - poni( getText(R.string.copyright), h.getCopyright() ); - spazio(); - if (h.getGenerator() != null) { - poni( getText(R.string.software), h.getGenerator().getName() != null ? h.getGenerator().getName() : h.getGenerator().getValue() ); - poni( getText(R.string.version), h.getGenerator().getVersion() ); - if( h.getGenerator().getGeneratorCorporation() != null ) { - poni( getText(R.string.corporation), h.getGenerator().getGeneratorCorporation().getValue() ); - if( h.getGenerator().getGeneratorCorporation().getAddress() != null ) - poni( getText(R.string.address), h.getGenerator().getGeneratorCorporation().getAddress().getDisplayValue() ); // non è male - poni( getText(R.string.telephone), h.getGenerator().getGeneratorCorporation().getPhone() ); - poni( getText(R.string.fax), h.getGenerator().getGeneratorCorporation().getFax() ); - } - spazio(); - if( h.getGenerator().getGeneratorData() != null ) { - poni( getText(R.string.source), h.getGenerator().getGeneratorData().getValue() ); - poni( getText(R.string.date), h.getGenerator().getGeneratorData().getDate() ); - poni( getText(R.string.copyright), h.getGenerator().getGeneratorData().getCopyright() ); - } - } - spazio(); - if( h.getSubmitter(gc) != null ) - poni( getText( R.string.submitter ), nomeAutore(h.getSubmitter(gc)) ); // todo: renderlo cliccabile? - if( gc.getSubmission() != null ) - poni( getText(R.string.submission), gc.getSubmission().getDescription() ); // todo: cliccabile - spazio(); - if( h.getGedcomVersion() != null ) { - poni( getText(R.string.gedcom), h.getGedcomVersion().getVersion() ); - poni( getText(R.string.form), h.getGedcomVersion().getForm() ); - } - poni( getText(R.string.destination), h.getDestination() ); - spazio(); - if( h.getDateTime() != null ) { - poni( getText(R.string.date), h.getDateTime().getValue() ); - poni( getText(R.string.time), h.getDateTime().getTime() ); - } - spazio(); - for( Estensione est : U.trovaEstensioni(h) ) { // ogni estensione nella sua riga - poni( est.nome, est.testo ); - } - spazio(); - if( righetta != null ) - ((TableLayout)findViewById( R.id.info_tabella ) ).removeView( righetta ); - - // Bottone per aggiorna l'header GEDCOM coi parametri di Family Gem - bottoneHeader.setOnClickListener( view -> { - h.setFile(treeId + ".json"); - CharacterSet caratteri = h.getCharacterSet(); - if( caratteri == null ) { - caratteri = new CharacterSet(); - h.setCharacterSet( caratteri ); - } - caratteri.setValue( "UTF-8" ); - caratteri.setVersion( null ); - - Locale loc = new Locale( Locale.getDefault().getLanguage() ); - h.setLanguage( loc.getDisplayLanguage(Locale.ENGLISH) ); - - Generator programma = h.getGenerator(); - if( programma == null ) { - programma = new Generator(); - h.setGenerator( programma ); - } - programma.setValue( "FAMILY_GEM" ); - programma.setName( getString(R.string.app_name) ); - //programma.setVersion( BuildConfig.VERSION_NAME ); // lo farà salvaJson() - programma.setGeneratorCorporation( null ); - - GedcomVersion versioneGc = h.getGedcomVersion(); - if( versioneGc == null ) { - versioneGc = new GedcomVersion(); - h.setGedcomVersion( versioneGc ); - } - versioneGc.setVersion( "5.5.1" ); - versioneGc.setForm( "LINEAGE-LINKED" ); - h.setDestination( null ); - - U.saveJson(gc, treeId); - recreate(); - }); - - U.placeNotes(scatola, h, true); - } - // Estensioni del Gedcom, ovvero tag non standard di livello 0 zero - for( Estensione est : U.trovaEstensioni(gc) ) { - U.metti( scatola, est.nome, est.testo ); - } - } else - bottoneHeader.setVisibility(View.GONE); - } - - String dataIdVersoData(String id) { - if( id == null ) return ""; - return id.substring(0, 4) + "-" + id.substring(4, 6) + "-" + id.substring(6, 8) + " " - + id.substring(8, 10) + ":" + id.substring(10, 12) + ":" + id.substring(12); - } - - static String nomeAutore( Submitter autor ) { - String nome = autor.getName(); - if( nome == null ) - nome = "[" + Global.context.getString(R.string.no_name) + "]"; - else if( nome.isEmpty() ) - nome = "[" + Global.context.getString(R.string.empty_name) + "]"; - return nome; - } - - // Refresh the data displayed below the tree title in Alberi list - static void refreshData(Gedcom gedcom, Settings.Tree treeItem) { - treeItem.persons = gedcom.getPeople().size(); - treeItem.generations = quanteGenerazioni(gedcom, U.getRootId(gedcom, treeItem)); - ListaMedia visitaMedia = new ListaMedia(gedcom, 0); - gedcom.accept(visitaMedia); - treeItem.media = visitaMedia.lista.size(); - Global.settings.save(); - } - - boolean testoMesso; // impedisce di mettere più di uno spazio() consecutivo - void poni(CharSequence title, String text) { - if( text != null ) { - TableRow row = new TableRow(this); - TextView cell1 = new TextView(this); - cell1.setTextSize(14); - cell1.setTypeface(null, Typeface.BOLD); - cell1.setPaddingRelative(0, 0, 10, 0); - cell1.setGravity(Gravity.END); // Does not work on RTL layout - cell1.setText(title); - row.addView(cell1); - TextView cell2 = new TextView(this); - cell2.setTextSize(14); - cell2.setPadding(0, 0, 0, 0); - cell2.setGravity(Gravity.START); - cell2.setText(text); - row.addView(cell2); - ((TableLayout)findViewById(R.id.info_tabella)).addView(row); - testoMesso = true; - } - } - - TableRow righetta; - void spazio() { - if( testoMesso ) { - righetta = new TableRow(getApplicationContext()); - View cella = new View(getApplicationContext()); - cella.setBackgroundResource(R.color.primario); - righetta.addView(cella); - TableRow.LayoutParams param = (TableRow.LayoutParams)cella.getLayoutParams(); - param.weight = 1; - param.span = 2; - param.height = 1; - param.topMargin = 5; - param.bottomMargin = 5; - cella.setLayoutParams(param); - ((TableLayout)findViewById(R.id.info_tabella)).addView(righetta); - testoMesso = false; - } - } - - static int genMin; - static int genMax; - - public static int quanteGenerazioni(Gedcom gc, String radice) { - if( gc.getPeople().isEmpty() ) - return 0; - genMin = 0; - genMax = 0; - risaliGenerazioni(gc.getPerson(radice), gc, 0); - discendiGenerazioni(gc.getPerson(radice), gc, 0); - // Rimuove dalle persone l'estensione 'gen' per permettere successivi conteggi - for( Person person : gc.getPeople() ) { - person.getExtensions().remove("gen"); - if( person.getExtensions().isEmpty() ) - person.setExtensions(null); - } - return 1 - genMin + genMax; - } - - // riceve una Person e trova il numero della generazione di antenati più remota - static void risaliGenerazioni(Person person, Gedcom gc, int gen) { - if( gen < genMin ) - genMin = gen; - // aggiunge l'estensione per indicare che è passato da questa Persona - person.putExtension("gen", gen); - // se è un capostipite va a contare le generazioni di discendenti o risale su eventuali altri matrimoni - if( person.getParentFamilies(gc).isEmpty() ) - discendiGenerazioni(person, gc, gen); - for( Family family : person.getParentFamilies(gc) ) { - // intercetta eventuali fratelli della radice - for( Person sibling : family.getChildren(gc) ) - if( sibling.getExtension("gen") == null ) - discendiGenerazioni(sibling, gc, gen); - for( Person father : family.getHusbands(gc) ) - if( father.getExtension("gen") == null ) - risaliGenerazioni(father, gc, gen - 1); - for( Person mother : family.getWives(gc) ) - if( mother.getExtension("gen") == null ) - risaliGenerazioni(mother, gc, gen - 1); - } - } - - // riceve una Person e trova il numero della generazione più remota di discendenti - static void discendiGenerazioni(Person person, Gedcom gc, int gen) { - if( gen > genMax ) - genMax = gen; - person.putExtension("gen", gen); - for( Family family : person.getSpouseFamilies(gc) ) { - // individua anche la famiglia dei coniugi - for( Person wife : family.getWives(gc) ) - if( wife.getExtension("gen") == null ) - risaliGenerazioni(wife, gc, gen); - for( Person husband : family.getHusbands(gc) ) - if( husband.getExtension("gen") == null ) - risaliGenerazioni(husband, gc, gen); - for( Person child : family.getChildren(gc) ) - if( child.getExtension("gen") == null ) - discendiGenerazioni(child, gc, gen + 1); - } - } - - // freccia indietro nella toolbar come quella hardware - @Override - public boolean onOptionsItemSelected(MenuItem i) { - onBackPressed(); - return true; - } -} diff --git a/app/src/main/java/app/familygem/LibraryFragment.java b/app/src/main/java/app/familygem/LibraryFragment.java new file mode 100644 index 00000000..a813e984 --- /dev/null +++ b/app/src/main/java/app/familygem/LibraryFragment.java @@ -0,0 +1,368 @@ +package app.familygem; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.SearchView; +import androidx.recyclerview.widget.RecyclerView; +import android.view.ContextMenu; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.SubMenu; +import android.view.View; +import android.view.ViewGroup; +import androidx.fragment.app.Fragment; +import android.widget.Filter; +import android.widget.Filterable; +import android.widget.TextView; +import org.folg.gedcom.model.EventFact; +import org.folg.gedcom.model.Family; +import org.folg.gedcom.model.Name; +import org.folg.gedcom.model.Note; +import org.folg.gedcom.model.NoteContainer; +import org.folg.gedcom.model.Person; +import org.folg.gedcom.model.Source; +import org.folg.gedcom.model.SourceCitation; +import org.folg.gedcom.model.SourceCitationContainer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import app.familygem.detail.SourceActivity; +import app.familygem.visitor.ListOfSourceCitations; +import static app.familygem.Global.gc; + +/** + * List of Sources (Sources) + * Unlike {@link ChurchFragment} it uses an adapter for the RecyclerView + * */ +public class LibraryFragment extends Fragment { + + private List listOfSources; + private LibraryAdapter adapter; + private int order; + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle bandolo ) { + listOfSources = gc.getSources(); + ((AppCompatActivity) getActivity()).getSupportActionBar().setTitle( listOfSources.size() + " " + + getString(listOfSources.size()==1 ? R.string.source : R.string.sources).toLowerCase() ); + if( listOfSources.size() > 1 ) + setHasOptionsMenu(true); + View view = inflater.inflate(R.layout.biblioteca, container, false); + RecyclerView sources = view.findViewById( R.id.riciclatore ); + adapter = new LibraryAdapter(); + sources.setAdapter(adapter); + view.findViewById( R.id.fab ).setOnClickListener( v -> newSource( getContext(), null ) ); + return view; + } + + public class LibraryAdapter extends RecyclerView.Adapter implements Filterable { + @Override + public SourceViewHolder onCreateViewHolder(ViewGroup parent, int type ) { + View sourceView = LayoutInflater.from( parent.getContext() ) + .inflate(R.layout.biblioteca_pezzo, parent, false); + registerForContextMenu( sourceView ); + return new SourceViewHolder( sourceView ); + } + @Override + public void onBindViewHolder(SourceViewHolder holder, int position ) { + Source source = listOfSources.get(position); + holder.id.setText( source.getId() ); + holder.id.setVisibility( order == 1 || order == 2 ? View.VISIBLE : View.GONE ); + holder.title.setText( sourceTitle(source) ); + Object times = source.getExtension("citaz"); + // Count citations with my method + if( times == null ) { + times = countCitations( source ); + source.putExtension("citaz", times ); + } + holder.times.setText( String.valueOf(times) ); + } + // Filter source titles based on search words + @Override + public Filter getFilter() { + return new Filter() { + @Override + protected FilterResults performFiltering(CharSequence charSequence) { + String query = charSequence.toString(); + if (query.isEmpty()) { + listOfSources = gc.getSources(); + } else { + List filteredList = new ArrayList<>(); + for (Source source : gc.getSources()) { + if( sourceTitle(source).toLowerCase().contains(query.toLowerCase()) ) { + filteredList.add(source); + } + } + listOfSources = filteredList; + } + sortSources(); // Sorting the query reorders those that appear + FilterResults filterResults = new FilterResults(); + filterResults.values = listOfSources; + return filterResults; + } + @Override + protected void publishResults(CharSequence cs, FilterResults fr) { + notifyDataSetChanged(); + } + }; + } + @Override + public int getItemCount() { + return listOfSources.size(); + } + } + + class SourceViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { + TextView id; + TextView title; + TextView times; + SourceViewHolder(View view ) { + super( view ); + id = view.findViewById( R.id.biblioteca_id ); + title = view.findViewById( R.id.biblioteca_titolo ); + times = view.findViewById( R.id.biblioteca_volte ); + view.setOnClickListener(this); + } + @Override + public void onClick( View v ) { + // Returns the id of a source to IndividualPersonActivity and DetailActivity + if( getActivity().getIntent().getBooleanExtra("bibliotecaScegliFonte",false) ) { + Intent intent = new Intent(); + intent.putExtra("idFonte", id.getText().toString() ); + getActivity().setResult( Activity.RESULT_OK, intent ); + getActivity().finish(); + } else { + Source source = gc.getSource( id.getText().toString() ); + Memory.setFirst( source ); + startActivity( new Intent( getContext(), SourceActivity.class ) ); + } + } + } + + @Override + public void onPause() { + super.onPause(); + getActivity().getIntent().removeExtra("bibliotecaScegliFonte"); + } + + /** + * Sort the sources according to one of the criteria. + * The order then becomes permanent in the Json. + * */ + private void sortSources() { + if( order > 0 ) { + if( order == 5 || order == 6 ) { + for( Source source : listOfSources) { + if( source.getExtension("citaz") == null ) + source.putExtension( "citaz", countCitations(source) ); + } + } + Collections.sort(listOfSources, (f1, f2) -> { + switch(order) { + case 1: // Sort by numeric id + return U.extractNum(f1.getId()) - U.extractNum(f2.getId()); + case 2: + return U.extractNum(f2.getId()) - U.extractNum(f1.getId()); + case 3: // Alphabetical order of titles + return sourceTitle(f1).compareToIgnoreCase( sourceTitle(f2) ); + case 4: + return sourceTitle(f2).compareToIgnoreCase( sourceTitle(f1) ); + case 5: // Sort by number of citations + return U.castJsonInt(f1.getExtension("citaz")) - U.castJsonInt(f2.getExtension("citaz")); + case 6: + return U.castJsonInt(f2.getExtension("citaz")) - U.castJsonInt(f1.getExtension("citaz")); + } + return 0; + }); + } + } + + /** + * Returns the title of the source + * */ + static String sourceTitle(Source fon ) { + String tit = ""; + if( fon != null ) + if( fon.getAbbreviation() != null ) + tit = fon.getAbbreviation(); + else if( fon.getTitle() != null ) + tit = fon.getTitle(); + else if( fon.getText() != null ) { + tit = fon.getText().replaceAll("\n", " "); + //tit = tit.length() > 35 ? tit.substring(0,35)+"…" : tit; + } else if( fon.getPublicationFacts() != null ) { + tit = fon.getPublicationFacts().replaceAll("\n", " "); + } + return tit; + } + + private int count; + /** + * Returns how many times a source is cited in Gedcom + * I tried to rewrite it as Visitor, but it's much slower + * */ + private int countCitations(Source source ) { + count = 0; + for( Person p : Global.gc.getPeople() ) { + countCitations( p, source ); + for( Name n : p.getNames() ) + countCitations( n, source ); + for( EventFact ef : p.getEventsFacts() ) + countCitations( ef, source ); + } + for( Family f : Global.gc.getFamilies() ) { + countCitations( f, source ); + for( EventFact ef : f.getEventsFacts() ) + countCitations( ef, source ); + } + for( Note n : Global.gc.getNotes() ) + countCitations( n, source ); + return count; + } + + /** + * receives an Object (Person, Name, EventFact...) and counts how many times the source is cited + * */ + private void countCitations(Object object, Source source ) { + List sourceCitations; + if( object instanceof Note ) // if it is a Note + sourceCitations = ((Note) object).getSourceCitations(); + else { + for( Note n : ((NoteContainer) object).getNotes() ) + countCitations( n, source ); + sourceCitations = ((SourceCitationContainer) object).getSourceCitations(); + } + for( SourceCitation sc : sourceCitations ) { + if( sc.getRef() != null ) + if( sc.getRef().equals(source.getId()) ) + count++; + } + } + + static void newSource(Context context, Object container ){ + Source source = new Source(); + source.setId( U.newID( gc, Source.class ) ); + source.setTitle( "" ); + gc.addSource( source ); + if( container != null ) { + SourceCitation sourceCitation = new SourceCitation(); + sourceCitation.setRef( source.getId() ); + if( container instanceof Note ) ((Note)container).addSourceCitation( sourceCitation ); + else ((SourceCitationContainer)container).addSourceCitation( sourceCitation ); + } + U.save( true, source ); + Memory.setFirst( source ); + context.startActivity( new Intent( context, SourceActivity.class ) ); + } + + /** + * // Remove the source, the Refs in all SourceCitations pointing to it, and empty SourceCitations + * All citations to the deleted Source become [{@link Source}]s to which a Source should be able to be reattached + * @return an array of modified parents + * */ + public static Object[] deleteSource(Source source ) { + ListOfSourceCitations citations = new ListOfSourceCitations( gc, source.getId() ); + for( ListOfSourceCitations.Triplet citation : citations.list) { + SourceCitation sc = citation.citation; + sc.setRef( null ); + // If the SourceCitation contains nothing else, it can be deleted + boolean deletable = true; + if( sc.getPage()!=null || sc.getDate()!=null || sc.getText()!=null || sc.getQuality()!=null + || !sc.getAllNotes(gc).isEmpty() || !sc.getAllMedia(gc).isEmpty() || !sc.getExtensions().isEmpty() ) + deletable = false; + if( deletable ) { + Object container = citation.container; + List list; + if( container instanceof Note ) + list = ((Note)container).getSourceCitations(); + else + list = ((SourceCitationContainer)container).getSourceCitations(); + list.remove( sc ); + if( list.isEmpty() ) { + if( container instanceof Note ) + ((Note)container).setSourceCitations( null ); + else + ((SourceCitationContainer)container).setSourceCitations( null ); + } + } + } + gc.getSources().remove( source ); + if( gc.getSources().isEmpty() ) + gc.setSources( null ); + gc.createIndexes(); // necessary + Memory.setInstanceAndAllSubsequentToNull( source ); + return citations.getProgenitors(); + } + + // options menu in the toolbar + @Override + public void onCreateOptionsMenu( Menu menu, MenuInflater inflater ) { + SubMenu subMenu = menu.addSubMenu(R.string.order_by); + if( Global.settings.expert ) + subMenu.add(0, 1, 0, R.string.id); + subMenu.add(0, 2, 0, R.string.title); + subMenu.add(0, 3, 0, R.string.citations); + + // Search in the Library + inflater.inflate(R.menu.cerca, menu); + final SearchView searchView = (SearchView)menu.findItem(R.id.ricerca).getActionView(); + searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { + @Override + public boolean onQueryTextChange(String query) { + adapter.getFilter().filter(query); + return true; + } + @Override + public boolean onQueryTextSubmit(String q) { + searchView.clearFocus(); + return false; + } + }); + } + + @Override + public boolean onOptionsItemSelected( MenuItem item ) { + int id = item.getItemId(); + if( id > 0 && id <= 3 ) { + if( order == id*2-1 ) + order++; + else if( order == id*2 ) + order--; + else + order = id*2-1; + sortSources(); + adapter.notifyDataSetChanged(); + return true; + } + return false; + } + + private Source source; + @Override + public void onCreateContextMenu(ContextMenu menu, View vista, ContextMenu.ContextMenuInfo info) { + source = gc.getSource(((TextView)vista.findViewById(R.id.biblioteca_id)).getText().toString()); + if( Global.settings.expert ) + menu.add(0, 0, 0, R.string.edit_id); + menu.add(0, 1, 0, R.string.delete); + } + @Override + public boolean onContextItemSelected(MenuItem item) { + if( item.getItemId() == 0 ) { // Edit source ID + U.editId(getContext(), source, getActivity()::recreate); + } else if( item.getItemId() == 1 ) { // Delete source + Object[] objects = deleteSource(source); + U.save(false, objects); + getActivity().recreate(); + } else { + return false; + } + return true; + } +} \ No newline at end of file diff --git a/app/src/main/java/app/familygem/ListOfAuthorsFragment.java b/app/src/main/java/app/familygem/ListOfAuthorsFragment.java new file mode 100644 index 00000000..92c8656a --- /dev/null +++ b/app/src/main/java/app/familygem/ListOfAuthorsFragment.java @@ -0,0 +1,121 @@ +package app.familygem; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import androidx.fragment.app.Fragment; +import androidx.appcompat.app.AppCompatActivity; +import android.view.ContextMenu; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import android.widget.TextView; +import org.folg.gedcom.model.Header; +import org.folg.gedcom.model.Submitter; +import java.util.List; +import app.familygem.detail.AuthorActivity; +import static app.familygem.Global.gc; + +/** + * List of Submitters (authors) + * */ +public class ListOfAuthorsFragment extends Fragment { + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle stato) { + List authors = gc.getSubmitters(); + ((AppCompatActivity)getActivity()).getSupportActionBar().setTitle(authors.size() + " " + + getString(authors.size() == 1 ? R.string.submitter : R.string.submitters).toLowerCase()); + setHasOptionsMenu(true); + View view = inflater.inflate(R.layout.magazzino, container, false); + LinearLayout layout = view.findViewById(R.id.magazzino_scatola); + for( final Submitter author : authors ) { + View pieceView = inflater.inflate(R.layout.magazzino_pezzo, layout, false); + layout.addView(pieceView); + ((TextView)pieceView.findViewById(R.id.magazzino_nome)).setText(TreeInfoActivity.submitterName(author)); + pieceView.findViewById(R.id.magazzino_archivi).setVisibility(View.GONE); + pieceView.setOnClickListener(v -> { + Memory.setFirst(author); + startActivity(new Intent(getContext(), AuthorActivity.class)); + }); + registerForContextMenu(pieceView); + pieceView.setTag(author); + } + view.findViewById(R.id.fab).setOnClickListener(v -> { + newAuthor(getContext()); + U.save(true); + }); + return view; + } + + /** + * Remove an author + * All I know is that any SubmitterRef should be searched for in all records + * */ + public static void deleteAuthor(Submitter aut) { + Header testa = gc.getHeader(); + if( testa != null && testa.getSubmitterRef() != null + && testa.getSubmitterRef().equals(aut.getId()) ) { + testa.setSubmitterRef(null); + } + gc.getSubmitters().remove(aut); + if( gc.getSubmitters().isEmpty() ) + gc.setSubmitters(null); + Memory.setInstanceAndAllSubsequentToNull(aut); + } + + /** + * Create a new Author, if it receives a context it opens it in editor mode + * */ + static Submitter newAuthor(Context context) { + Submitter submitter = new Submitter(); + submitter.setId(U.newID(gc, Submitter.class)); + submitter.setName(""); + U.updateChangeDate(submitter); + gc.addSubmitter(submitter); + if( context != null ) { + Memory.setFirst(submitter); + context.startActivity(new Intent(context, AuthorActivity.class)); + } + return submitter; + } + + static void mainAuthor(Submitter subm) { + Header testa = gc.getHeader(); + if( testa == null ) { + testa = NewTree.createHeader(Global.settings.openTree + ".json"); + gc.setHeader(testa); + } + testa.setSubmitterRef(subm.getId()); + U.save(false, subm); + } + + // context Menu + Submitter subm; + @Override + public void onCreateContextMenu(ContextMenu menu, View vista, ContextMenu.ContextMenuInfo info ) { + subm = (Submitter)vista.getTag(); + if( gc.getHeader() == null || gc.getHeader().getSubmitter(gc) == null || !gc.getHeader().getSubmitter(gc).equals(subm) ) + menu.add(0, 0, 0, R.string.make_default); + if( !U.submitterHasShared(subm) ) // it can only be deleted if it has never been shared + menu.add(0, 1, 0, R.string.delete); + // todo explain why it can't be deleted? + } + @Override + public boolean onContextItemSelected( MenuItem item ) { + switch( item.getItemId() ) { + case 0: + mainAuthor(subm); + return true; + case 1: + // Todo confirm deletion + deleteAuthor(subm); + U.save(false); + getActivity().recreate(); + return true; + } + return false; + } +} \ No newline at end of file diff --git a/app/src/main/java/app/familygem/Anagrafe.java b/app/src/main/java/app/familygem/ListOfPeopleFragment.java similarity index 55% rename from app/src/main/java/app/familygem/Anagrafe.java rename to app/src/main/java/app/familygem/ListOfPeopleFragment.java index c3eec2d6..2e5d45fc 100644 --- a/app/src/main/java/app/familygem/Anagrafe.java +++ b/app/src/main/java/app/familygem/ListOfPeopleFragment.java @@ -40,12 +40,12 @@ import static app.familygem.Global.gc; import com.lb.fast_scroller_and_recycler_view_fixes_library.FastScrollerEx; -public class Anagrafe extends Fragment { +public class ListOfPeopleFragment extends Fragment { List people; - AdattatoreAnagrafe adapter; + PeopleAdapter adapter; private Order order; - private boolean gliIdsonoNumerici; + private boolean idsAreNumeric; private enum Order { NONE, @@ -67,14 +67,14 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bu View view = inflater.inflate(R.layout.ricicla_vista, container, false); if( gc != null ) { people = gc.getPeople(); - arredaBarra(); - RecyclerView vistaLista = view.findViewById(R.id.riciclatore); - vistaLista.setPadding(12, 12, 12, vistaLista.getPaddingBottom()); - adapter = new AdattatoreAnagrafe(); - vistaLista.setAdapter(adapter); - gliIdsonoNumerici = verificaIdNumerici(); + setupToolbar(); + RecyclerView recyclerView = view.findViewById(R.id.riciclatore); + recyclerView.setPadding(12, 12, 12, recyclerView.getPaddingBottom()); + adapter = new PeopleAdapter(); + recyclerView.setAdapter(adapter); + idsAreNumeric = verifyIdsAreNumeric(); view.findViewById(R.id.fab).setOnClickListener(v -> { - Intent intent = new Intent(getContext(), EditaIndividuo.class); + Intent intent = new Intent(getContext(), IndividualEditorActivity.class); intent.putExtra("idIndividuo", "TIZIO_NUOVO"); startActivity(intent); }); @@ -82,30 +82,30 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bu // Fast scroller StateListDrawable thumbDrawable = (StateListDrawable)ContextCompat.getDrawable(getContext(), R.drawable.scroll_thumb); Drawable lineDrawable = ContextCompat.getDrawable(getContext(), R.drawable.empty); - new FastScrollerEx(vistaLista, thumbDrawable, lineDrawable, thumbDrawable, lineDrawable, + new FastScrollerEx(recyclerView, thumbDrawable, lineDrawable, thumbDrawable, lineDrawable, U.dpToPx(40), U.dpToPx(100), 0, true, U.dpToPx(80)); } return view; } - void arredaBarra() { + void setupToolbar() { ((AppCompatActivity)getActivity()).getSupportActionBar().setTitle(people.size() + " " + getString(people.size() == 1 ? R.string.person : R.string.persons).toLowerCase()); setHasOptionsMenu(people.size() > 1); } - public class AdattatoreAnagrafe extends RecyclerView.Adapter implements Filterable { + public class PeopleAdapter extends RecyclerView.Adapter implements Filterable { @Override - public GestoreIndividuo onCreateViewHolder(ViewGroup parent, int tipo) { - View vistaIndividuo = LayoutInflater.from(parent.getContext()) + public IndividualViewHolder onCreateViewHolder(ViewGroup parent, int type) { + View individualView = LayoutInflater.from(parent.getContext()) .inflate(R.layout.pezzo_individuo, parent, false); - registerForContextMenu(vistaIndividuo); - return new GestoreIndividuo(vistaIndividuo); + registerForContextMenu(individualView); + return new IndividualViewHolder(individualView); } @Override - public void onBindViewHolder(GestoreIndividuo gestore, int posizione) { - Person person = people.get(posizione); - View vistaIndi = gestore.vista; + public void onBindViewHolder(IndividualViewHolder holder, int position) { + Person person = people.get(position); + View indiView = holder.view; String label = null; if( order == Order.ID_ASC || order == Order.ID_DESC ) @@ -114,7 +114,7 @@ else if( order == Order.SURNAME_ASC || order == Order.SURNAME_DESC ) label = U.surname(person); else if( order == Order.KIN_ASC || order == Order.KIN_DESC ) label = String.valueOf(person.getExtension("kin")); - TextView infoView = vistaIndi.findViewById(R.id.indi_ruolo); + TextView infoView = indiView.findViewById(R.id.indi_ruolo); if( label == null ) infoView.setVisibility(View.GONE); else { @@ -123,32 +123,32 @@ else if( order == Order.KIN_ASC || order == Order.KIN_DESC ) infoView.setVisibility(View.VISIBLE); } - TextView vistaNome = vistaIndi.findViewById(R.id.indi_nome); - String nome = U.epiteto(person); - vistaNome.setText(nome); - vistaNome.setVisibility((nome.isEmpty() && label != null) ? View.GONE : View.VISIBLE); + TextView nameView = indiView.findViewById(R.id.indi_nome); + String name = U.properName(person); + nameView.setText(name); + nameView.setVisibility((name.isEmpty() && label != null) ? View.GONE : View.VISIBLE); - TextView vistaTitolo = vistaIndi.findViewById(R.id.indi_titolo); - String titolo = U.titolo(person); - if( titolo.isEmpty() ) - vistaTitolo.setVisibility(View.GONE); + TextView titleView = indiView.findViewById(R.id.indi_titolo); + String title = U.title(person); + if( title.isEmpty() ) + titleView.setVisibility(View.GONE); else { - vistaTitolo.setText(titolo); - vistaTitolo.setVisibility(View.VISIBLE); + titleView.setText(title); + titleView.setVisibility(View.VISIBLE); } - int bordo; + int border; switch( Gender.getGender(person) ) { - case MALE: bordo = R.drawable.casella_bordo_maschio; break; - case FEMALE: bordo = R.drawable.casella_bordo_femmina; break; - default: bordo = R.drawable.casella_bordo_neutro; + case MALE: border = R.drawable.casella_bordo_maschio; break; + case FEMALE: border = R.drawable.casella_bordo_femmina; break; + default: border = R.drawable.casella_bordo_neutro; } - vistaIndi.findViewById(R.id.indi_bordo).setBackgroundResource(bordo); + indiView.findViewById(R.id.indi_bordo).setBackgroundResource(border); - U.details(person, vistaIndi.findViewById(R.id.indi_dettagli)); - F.unaFoto(Global.gc, person, vistaIndi.findViewById(R.id.indi_foto)); - vistaIndi.findViewById(R.id.indi_lutto).setVisibility(U.isDead(person) ? View.VISIBLE : View.GONE); - vistaIndi.setTag(person.getId()); + U.details(person, indiView.findViewById(R.id.indi_dettagli)); + F.showMainImageForPerson(Global.gc, person, indiView.findViewById(R.id.indi_foto)); + indiView.findViewById(R.id.indi_lutto).setVisibility(U.isDead(person) ? View.VISIBLE : View.GONE); + indiView.setTag(person.getId()); } @Override public Filter getFilter() { @@ -160,9 +160,9 @@ protected FilterResults performFiltering(CharSequence charSequence) { people = gc.getPeople(); } else { List filteredList = new ArrayList<>(); - for( Person pers : gc.getPeople() ) { - if( U.epiteto(pers).toLowerCase().contains(query.toLowerCase()) ) { - filteredList.add(pers); + for( Person person : gc.getPeople() ) { + if( U.properName(person).toLowerCase().contains(query.toLowerCase()) ) { + filteredList.add(person); } } people = filteredList; @@ -184,81 +184,85 @@ public int getItemCount() { } } - class GestoreIndividuo extends RecyclerView.ViewHolder implements View.OnClickListener { - View vista; - GestoreIndividuo( View vista ) { - super( vista ); - this.vista = vista; - vista.setOnClickListener(this); + class IndividualViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { + View view; + IndividualViewHolder(View view) { + super(view); + this.view = view; + view.setOnClickListener(this); } @Override public void onClick( View vista ) { - // Anagrafe per scegliere il parente e restituire i valori a Diagramma, Individuo, Famiglia o Condivisione - Person parente = gc.getPerson((String)vista.getTag()); + // Registry to choose the relative and return the values to Diagram, IndividualActivity, FamilyActivity or SharingActivity + Person relative = gc.getPerson((String)vista.getTag()); Intent intent = getActivity().getIntent(); if( intent.getBooleanExtra("anagrafeScegliParente", false) ) { - intent.putExtra( "idParente", parente.getId() ); - // Cerca una eventuale famiglia esistente che possa ospitare perno - String collocazione = intent.getStringExtra("collocazione"); - if( collocazione != null && collocazione.equals("FAMIGLIA_ESISTENTE") ) { - String idFamiglia = null; + intent.putExtra( "idParente", relative.getId() ); + // Look for any existing family that can host the pivot + String placement = intent.getStringExtra("collocazione"); + if( placement != null && placement.equals("FAMIGLIA_ESISTENTE") ) { + String familyId = null; switch( intent.getIntExtra("relazione",0) ) { - case 1: // Genitore - if( parente.getSpouseFamilyRefs().size() > 0 ) - idFamiglia = parente.getSpouseFamilyRefs().get(0).getRef(); + case 1: // Parent + if( relative.getSpouseFamilyRefs().size() > 0 ) + familyId = relative.getSpouseFamilyRefs().get(0).getRef(); break; case 2: - if( parente.getParentFamilyRefs().size() > 0 ) - idFamiglia = parente.getParentFamilyRefs().get(0).getRef(); + if( relative.getParentFamilyRefs().size() > 0 ) + familyId = relative.getParentFamilyRefs().get(0).getRef(); break; case 3: - for( Family fam : parente.getSpouseFamilies(gc) ) { + for( Family fam : relative.getSpouseFamilies(gc) ) { if( fam.getHusbandRefs().isEmpty() || fam.getWifeRefs().isEmpty() ) { - idFamiglia = fam.getId(); + familyId = fam.getId(); break; } } break; case 4: - for( Family fam : parente.getParentFamilies(gc) ) { + for( Family fam : relative.getParentFamilies(gc) ) { if( fam.getHusbandRefs().isEmpty() || fam.getWifeRefs().isEmpty() ) { - idFamiglia = fam.getId(); + familyId = fam.getId(); break; } } break; } - if( idFamiglia != null ) // aggiungiParente() userà la famiglia trovata - intent.putExtra( "idFamiglia", idFamiglia ); - else // aggiungiParente() creerà una nuova famiglia + if( familyId != null ) // addRelative() will use the found family + intent.putExtra( "idFamiglia", familyId ); + else // addRelative() will create a new family intent.removeExtra( "collocazione" ); } getActivity().setResult( AppCompatActivity.RESULT_OK, intent ); getActivity().finish(); - } else { // Normale collegamento alla scheda individuo - // todo Click sulla foto apre la scheda media.. - // intento.putExtra( "scheda", 0 ); - Memoria.setPrimo( parente ); - startActivity( new Intent(getContext(), Individuo.class) ); + } else { // Normal link to the individual file + // todo Click on the photo opens the media tab.. + // intent.putExtra( "scheda", 0 ); + Memory.setFirst( relative ); + startActivity( new Intent(getContext(), IndividualPersonActivity.class) ); } } } - // Andandosene dall'attività senza aver scelto un parente resetta l'extra + /** + * Leaving the activity without having chosen a relative resets the extra + * */ @Override public void onPause() { super.onPause(); getActivity().getIntent().removeExtra("anagrafeScegliParente"); } - // Verifica se tutti gli id delle persone contengono numeri - // Appena un id contiene solo lettere restituisce falso - boolean verificaIdNumerici() { - esterno: + /** + * Check if all people's ids contain numbers + * As soon as an id contains only letters it returns false + * */ + boolean verifyIdsAreNumeric() { + external: for( Person p : gc.getPeople() ) { for( char c : p.getId().toCharArray() ) { if (Character.isDigit(c)) - continue esterno; + continue external; } return false; } @@ -269,28 +273,28 @@ private void sortPeople() { Collections.sort(people, (p1, p2) -> { switch( order ) { case ID_ASC: // Sort for GEDCOM ID - if( gliIdsonoNumerici ) - return U.soloNumeri(p1.getId()) - U.soloNumeri(p2.getId()); + if(idsAreNumeric) + return U.extractNum(p1.getId()) - U.extractNum(p2.getId()); else return p1.getId().compareToIgnoreCase(p2.getId()); case ID_DESC: - if( gliIdsonoNumerici ) - return U.soloNumeri(p2.getId()) - U.soloNumeri(p1.getId()); + if(idsAreNumeric) + return U.extractNum(p2.getId()) - U.extractNum(p1.getId()); else return p2.getId().compareToIgnoreCase(p1.getId()); case SURNAME_ASC: // Sort for surname - if (p1.getNames().size() == 0) // i nomi null vanno in fondo + if (p1.getNames().size() == 0) // null names go to the bottom return (p2.getNames().size() == 0) ? 0 : 1; if (p2.getNames().size() == 0) return -1; Name n1 = p1.getNames().get(0); Name n2 = p2.getNames().get(0); - // anche i nomi con value, given e surname null vanno in fondo + // names with value, given, and surname null also go to the bottom if (n1.getValue() == null && n1.getGiven() == null && n1.getSurname() == null) return (n2.getValue() == null) ? 0 : 1; if (n2.getValue() == null && n2.getGiven() == null && n2.getSurname() == null) return -1; - return cognomeNome(p1).compareToIgnoreCase(cognomeNome(p2)); + return surnameName(p1).compareToIgnoreCase(surnameName(p2)); case SURNAME_DESC: if (p1.getNames().size() == 0) return p2.getNames().size() == 0 ? 0 : 1; @@ -302,7 +306,7 @@ private void sortPeople() { return (n2.getValue() == null) ? 0 : 1; if (n2.getValue() == null && n2.getGiven() == null && n2.getSurname() == null) return -1; - return cognomeNome(p2).compareToIgnoreCase(cognomeNome(p1)); + return surnameName(p2).compareToIgnoreCase(surnameName(p1)); case DATE_ASC: // Sort for person's main year return getDate(p1) - getDate(p2); case DATE_DESC: @@ -332,40 +336,44 @@ private void sortPeople() { }); } - // Restituisce una stringa con cognome e nome attaccati: - // 'SalvadorMichele ' oppure 'ValleFrancesco Maria ' oppure ' Donatella ' - private static String cognomeNome(Person person) { + /** + * Returns a string with surname and firstname attached: + * 'SalvadorMichele ' or 'ValleFrancesco Maria ' or ' Donatella ' + * */ + private static String surnameName(Person person) { Name name = person.getNames().get(0); - String epiteto = name.getValue(); - String nomeDato = ""; - String cognome = " "; // deve esserci uno spazio per ordinare i nomi senza cognome - if( epiteto != null ) { - if( epiteto.indexOf('/') > 0 ) - nomeDato = epiteto.substring( 0, epiteto.indexOf('/') ); // prende il nome prima di '/' - if( epiteto.lastIndexOf('/') - epiteto.indexOf('/') > 1 ) // se c'è un cognome tra i due '/' - cognome = epiteto.substring( epiteto.indexOf('/')+1, epiteto.lastIndexOf("/") ); - String prefix = name.getPrefix(); // Solo il nomeDato proveniente dal value potrebbe avere un prefisso, dal given no perché già di suo è solo il nomeDato - if( prefix != null && nomeDato.startsWith(prefix) ) - nomeDato = nomeDato.substring( prefix.length() ).trim(); + String epithet = name.getValue(); + String givenName = ""; + String surname = " "; // there must be a space to sort first names without last names + if( epithet != null ) { + if( epithet.indexOf('/') > 0 ) + givenName = epithet.substring( 0, epithet.indexOf('/') ); // takes name before '/' + if( epithet.lastIndexOf('/') - epithet.indexOf('/') > 1 ) // if there is a last name between the two '/' + surname = epithet.substring( epithet.indexOf('/')+1, epithet.lastIndexOf("/") ); + String prefix = name.getPrefix(); // Only the givenname coming from value could have a prefix, from given no because it is already only the givenname + if( prefix != null && givenName.startsWith(prefix) ) + givenName = givenName.substring( prefix.length() ).trim(); } else { if( name.getGiven() != null ) - nomeDato = name.getGiven(); + givenName = name.getGiven(); if( name.getSurname() != null ) - cognome = name.getSurname(); + surname = name.getSurname(); } String surPrefix = name.getSurnamePrefix(); - if( surPrefix != null && cognome.startsWith(surPrefix) ) - cognome = cognome.substring( surPrefix.length() ).trim(); - return cognome.concat( nomeDato ); + if( surPrefix != null && surname.startsWith(surPrefix) ) + surname = surname.substring( surPrefix.length() ).trim(); + return surname.concat( givenName ); } - // riceve una Person e restituisce il primo anno della sua esistenza - Datatore datatore = new Datatore(""); + /** + * receives a Person and returns the first year of its existence + * */ + GedcomDateConverter gedcomDateConverter = new GedcomDateConverter(""); private int findDate(Person person) { for( EventFact event : person.getEventsFacts() ) { if( event.getDate() != null ) { - datatore.analizza(event.getDate()); - return datatore.getDateNumber(); + gedcomDateConverter.analyze(event.getDate()); + return gedcomDateConverter.getDateNumber(); } } return Integer.MAX_VALUE; @@ -376,19 +384,21 @@ int getDate(Person person) { return date == null ? Integer.MAX_VALUE : (int)date; } - // Calculate the age of a person in days or MAX_VALUE + /** + * Calculate the age of a person in days or MAX_VALUE + * */ private int calcAge(Person person) { int days = Integer.MAX_VALUE; - Datatore start = null, end = null; + GedcomDateConverter start = null, end = null; for( EventFact event : person.getEventsFacts() ) { if( event.getTag() != null && event.getTag().equals("BIRT") && event.getDate() != null ) { - start = new Datatore(event.getDate()); + start = new GedcomDateConverter(event.getDate()); break; } } for( EventFact event : person.getEventsFacts() ) { if( event.getTag() != null && event.getTag().equals("DEAT") && event.getDate() != null ) { - end = new Datatore(event.getDate()); + end = new GedcomDateConverter(event.getDate()); break; } } @@ -398,7 +408,7 @@ private int calcAge(Person person) { LocalDate now = LocalDate.now(); if( end == null && startDate.isBefore(now) && Years.yearsBetween(startDate, now).getYears() <= 120 && !U.isDead(person) ) { - end = new Datatore(now.toDate()); + end = new GedcomDateConverter(now.toDate()); } if( end != null && end.isSingleKind() && !end.data1.isFormat(Format.D_M) ) { LocalDate endDate = new LocalDate(end.data1.date); @@ -415,7 +425,8 @@ int getAge(Person person) { return age == null ? Integer.MAX_VALUE : (int)age; } - /** Count how many near relatives a person has: parents, siblings, step-siblings, spouses and children. + /** + * Count how many near relatives a person has: parents, siblings, step-siblings, spouses and children. * Save also the result in the 'kin' extension. * @param person The person to start from * @return Number of near relatives (person excluded) @@ -423,43 +434,43 @@ int getAge(Person person) { static int countRelatives(Person person) { int count = 0; if( person != null ) { - // Famiglie di origine: genitori e fratelli - List listaFamiglie = person.getParentFamilies(gc); - for( Family famiglia : listaFamiglie ) { - count += famiglia.getHusbandRefs().size(); - count += famiglia.getWifeRefs().size(); - for( Person fratello : famiglia.getChildren(gc) ) // solo i figli degli stessi due genitori, non i fratellastri - if( !fratello.equals(person) ) + // Families of origin: parents and siblings + List families = person.getParentFamilies(gc); + for( Family family : families ) { + count += family.getHusbandRefs().size(); + count += family.getWifeRefs().size(); + for( Person sibling : family.getChildren(gc) ) // only children of the same two parents, not half-siblings + if( !sibling.equals(person) ) count++; } - // Fratellastri e sorellastre - for( Family famiglia : person.getParentFamilies(gc) ) { - for( Person padre : famiglia.getHusbands(gc) ) { - List famigliePadre = padre.getSpouseFamilies(gc); - famigliePadre.removeAll(listaFamiglie); - for( Family fam : famigliePadre ) + // Stepbrothers and stepsisters + for( Family family : person.getParentFamilies(gc) ) { + for( Person father : family.getHusbands(gc) ) { + List fatherFamily = father.getSpouseFamilies(gc); + fatherFamily.removeAll(families); + for( Family fam : fatherFamily ) count += fam.getChildRefs().size(); } - for( Person madre : famiglia.getWives(gc) ) { - List famiglieMadre = madre.getSpouseFamilies(gc); - famiglieMadre.removeAll(listaFamiglie); - for( Family fam : famiglieMadre ) + for( Person mother : family.getWives(gc) ) { + List motherFamily = mother.getSpouseFamilies(gc); + motherFamily.removeAll(families); + for( Family fam : motherFamily ) count += fam.getChildRefs().size(); } } - // Coniugi e figli - for( Family famiglia : person.getSpouseFamilies(gc) ) { - count += famiglia.getWifeRefs().size(); - count += famiglia.getHusbandRefs().size(); + // Spouses and children + for( Family family : person.getSpouseFamilies(gc) ) { + count += family.getWifeRefs().size(); + count += family.getHusbandRefs().size(); count--; // Minus their self - count += famiglia.getChildRefs().size(); + count += family.getChildRefs().size(); } person.putExtension("kin", count); } return count; } - // menu opzioni nella toolbar + // option menu in the toolbar @Override public void onCreateOptionsMenu( Menu menu, MenuInflater inflater ) { @@ -471,10 +482,10 @@ public void onCreateOptionsMenu( Menu menu, MenuInflater inflater ) { subMenu.add(0, 4, 0, R.string.age); subMenu.add(0, 5, 0, R.string.number_relatives); - // Ricerca nell'Anagrafe - inflater.inflate( R.menu.cerca, menu ); // già questo basta a far comparire la lente con il campo di ricerca - final SearchView vistaCerca = (SearchView) menu.findItem(R.id.ricerca).getActionView(); - vistaCerca.setOnQueryTextListener( new SearchView.OnQueryTextListener() { + //Search in the ListOfPeopleActivity + inflater.inflate( R.menu.cerca, menu ); // this is already enough to bring up the lens with the search field + final SearchView searchView = (SearchView) menu.findItem(R.id.ricerca).getActionView(); + searchView.setOnQueryTextListener( new SearchView.OnQueryTextListener() { @Override public boolean onQueryTextChange( String query ) { adapter.getFilter().filter(query); @@ -482,7 +493,7 @@ public boolean onQueryTextChange( String query ) { } @Override public boolean onQueryTextSubmit( String q ) { - vistaCerca.clearFocus(); + searchView.clearFocus(); return false; } }); @@ -518,17 +529,17 @@ else if( order == Order.values()[id * 2] ) } sortPeople(); adapter.notifyDataSetChanged(); - //U.salvaJson( false ); // dubbio se metterlo per salvare subito il riordino delle persone... + //U.saveJson( false ); // doubt whether to put it to immediately save the tidying up of people... return true; } return false; } - // Menu contestuale + // contextual Menu private String idIndi; @Override - public void onCreateContextMenu( ContextMenu menu, View vista, ContextMenu.ContextMenuInfo info) { - idIndi = (String)vista.getTag(); + public void onCreateContextMenu( ContextMenu menu, View view, ContextMenu.ContextMenuInfo info) { + idIndi = (String)view.getTag(); menu.add(0, 0, 0, R.string.diagram); String[] familyLabels = Diagram.getFamilyLabels(getContext(), gc.getPerson(idIndi), null); if( familyLabels[0] != null ) @@ -543,25 +554,25 @@ public void onCreateContextMenu( ContextMenu menu, View vista, ContextMenu.Conte @Override public boolean onContextItemSelected( MenuItem item ) { int id = item.getItemId(); - if( id == 0 ) { // Apri Diagramma - U.qualiGenitoriMostrare(getContext(), gc.getPerson(idIndi), 1); - } else if( id == 1 ) { // Famiglia come figlio - U.qualiGenitoriMostrare(getContext(), gc.getPerson(idIndi), 2); - } else if( id == 2 ) { // Famiglia come coniuge - U.qualiConiugiMostrare(getContext(), gc.getPerson(idIndi), null); - } else if( id == 3 ) { // Modifica - Intent intento = new Intent(getContext(), EditaIndividuo.class); - intento.putExtra("idIndividuo", idIndi); - startActivity(intento); + if( id == 0 ) { // Open Diagram + U.askWhichParentsToShow(getContext(), gc.getPerson(idIndi), 1); + } else if( id == 1 ) { // Family as child + U.askWhichParentsToShow(getContext(), gc.getPerson(idIndi), 2); + } else if( id == 2 ) { // Family as spouse + U.askWhichSpouseToShow(getContext(), gc.getPerson(idIndi), null); + } else if( id == 3 ) { // Edit + Intent intent = new Intent(getContext(), IndividualEditorActivity.class); + intent.putExtra("idIndividuo", idIndi); + startActivity(intent); } else if( id == 4 ) { // Edit ID U.editId(getContext(), gc.getPerson(idIndi), adapter::notifyDataSetChanged); - } else if( id == 5 ) { // Elimina + } else if( id == 5 ) { // Delete new AlertDialog.Builder(getContext()).setMessage( R.string.really_delete_person ) .setPositiveButton( R.string.delete, (dialog, i) -> { - Family[] famiglie = deletePerson(getContext(), idIndi); + Family[] families = deletePerson(getContext(), idIndi); adapter.notifyDataSetChanged(); - arredaBarra(); - U.controllaFamiglieVuote(getContext(), null, false, famiglie); + setupToolbar(); + U.checkFamilyItem(getContext(), null, false, families); }).setNeutralButton( R.string.cancel, null ).show(); } else { return false; @@ -569,42 +580,45 @@ public boolean onContextItemSelected( MenuItem item ) { return true; } - // Cancella tutti i ref nelle famiglie della tal persona - // Restituisce l'elenco delle famiglie affette - static Family[] scollega( String idScollegando ) { - Person egli = gc.getPerson( idScollegando ); - Set famiglie = new HashSet<>(); - for( Family f : egli.getParentFamilies(gc) ) { // scollega i suoi ref nelle famiglie - f.getChildRefs().remove( f.getChildren(gc).indexOf(egli) ); - famiglie.add( f ); + /** + * Delete all refs in that person's families + * @return the list of affected families + * */ + static Family[] disconnect(String idToDisconnect ) { + Person personToDisconnect = gc.getPerson( idToDisconnect ); + Set families = new HashSet<>(); + for( Family f : personToDisconnect.getParentFamilies(gc) ) { // unlink its refs in families + f.getChildRefs().remove( f.getChildren(gc).indexOf(personToDisconnect) ); + families.add( f ); } - for( Family f : egli.getSpouseFamilies(gc) ) { - if( f.getHusbands(gc).contains(egli) ) { - f.getHusbandRefs().remove( f.getHusbands(gc).indexOf(egli) ); - famiglie.add( f ); + for( Family f : personToDisconnect.getSpouseFamilies(gc) ) { + if( f.getHusbands(gc).contains(personToDisconnect) ) { + f.getHusbandRefs().remove( f.getHusbands(gc).indexOf(personToDisconnect) ); + families.add( f ); } - if( f.getWives(gc).contains(egli) ) { - f.getWifeRefs().remove( f.getWives(gc).indexOf(egli) ); - famiglie.add( f ); + if( f.getWives(gc).contains(personToDisconnect) ) { + f.getWifeRefs().remove( f.getWives(gc).indexOf(personToDisconnect) ); + families.add( f ); } } - egli.setParentFamilyRefs( null ); // nell'indi scollega i ref delle famiglie a cui appartiene - egli.setSpouseFamilyRefs( null ); - return famiglie.toArray( new Family[0] ); + personToDisconnect.setParentFamilyRefs( null ); // in the indi it unlinks the refs of the families it belongs to + personToDisconnect.setSpouseFamilyRefs( null ); + return families.toArray( new Family[0] ); } - /** Delete a person from the tree, possibly find the new root. + /** + * Delete a person from the tree, possibly find the new root. * @param context * @param personId Id of the person to be deleted * @return Array of modified families */ static Family[] deletePerson(Context context, String personId) { - Family[] families = scollega(personId); + Family[] families = disconnect(personId); Person person = gc.getPerson(personId); - Memoria.annullaIstanze(person); + Memory.setInstanceAndAllSubsequentToNull(person); gc.getPeople().remove(person); gc.createIndexes(); // Necessary - String newRootId = U.trovaRadice(gc); // Todo dovrebbe essere: trovaParentePiuProssimo + String newRootId = U.findRoot(gc); // Todo should read: findNearestRelative if( Global.settings.getCurrentTree().root != null && Global.settings.getCurrentTree().root.equals(personId) ) { Global.settings.getCurrentTree().root = newRootId; } diff --git a/app/src/main/java/app/familygem/MediaFoldersActivity.java b/app/src/main/java/app/familygem/MediaFoldersActivity.java new file mode 100644 index 00000000..b79cb44d --- /dev/null +++ b/app/src/main/java/app/familygem/MediaFoldersActivity.java @@ -0,0 +1,210 @@ +package app.familygem; + +import android.Manifest; +import android.app.Activity; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.view.ContextMenu; +import android.view.MenuItem; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; +import androidx.documentfile.provider.DocumentFile; +import java.util.ArrayList; +import java.util.List; + +/** + * Activity where you can see the list of folders, add, delete + * */ +public class MediaFoldersActivity extends BaseActivity { + + int treeId; + List folders; + List uris; + + @Override + protected void onCreate( Bundle bandolo ) { + super.onCreate( bandolo ); + setContentView( R.layout.cartelle_media ); + treeId = getIntent().getIntExtra( "idAlbero", 0 ); + folders = new ArrayList<>( Global.settings.getTree(treeId).dirs); + uris = new ArrayList<>( Global.settings.getTree(treeId).uris ); + updateList(); + getSupportActionBar().setDisplayHomeAsUpEnabled( true ); + findViewById( R.id.fab ).setOnClickListener( v -> { + int perm = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE); + if( perm == PackageManager.PERMISSION_DENIED ) + ActivityCompat.requestPermissions(this, new String[]{ Manifest.permission.READ_EXTERNAL_STORAGE }, 3517); + else if( perm == PackageManager.PERMISSION_GRANTED ) + doChooseFolder(); + }); + if( Global.settings.getTree(treeId).dirs.isEmpty() && Global.settings.getTree(treeId).uris.isEmpty() ) + new SpeechBubble( this, R.string.add_device_folder ).show(); + } + + void doChooseFolder() { + if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ) { + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); + intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + startActivityForResult( intent, 123 ); + } else { + // KitKat uses the selection of a file to find the folder + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.setType( "*/*"); + startActivityForResult( intent, 456 ); + } + } + + void updateList() { + LinearLayout layout = findViewById( R.id.cartelle_scatola ); + layout.removeAllViews(); + for( String cart : folders) { + View folderView = getLayoutInflater().inflate( R.layout.pezzo_cartella, layout, false ); + layout.addView( folderView ); + TextView nameView = folderView.findViewById( R.id.cartella_nome ); + TextView urlView = folderView.findViewById( R.id.cartella_url ); + urlView.setText( cart ); + if( Global.settings.expert ) + urlView.setSingleLine( false ); + View deleteButton = folderView.findViewById( R.id.cartella_elimina ); + // The '/storage/.../Android/data/app.familygem/files/X' folder should be preserved as it is the default copied media + // Also in Android 11 it is no longer reachable by the user with SAF + if( cart.equals(getExternalFilesDir(null) + "/" + treeId) ) { + nameView.setText( R.string.app_storage ); + deleteButton.setVisibility( View.GONE ); + } else { + nameView.setText( folderName(cart) ); + deleteButton.setOnClickListener( v -> { + new AlertDialog.Builder(this).setMessage( R.string.sure_delete ) + .setPositiveButton( R.string.yes, (di,id) -> { + folders.remove( cart ); + save(); + }).setNeutralButton( R.string.cancel, null ).show(); + }); + } + registerForContextMenu( folderView ); + } + for( String stringUri : uris ) { + View uriView = getLayoutInflater().inflate( R.layout.pezzo_cartella, layout, false ); + layout.addView( uriView ); + DocumentFile documentDir = DocumentFile.fromTreeUri( this, Uri.parse(stringUri) ); + String name = null; + if( documentDir != null ) + name = documentDir.getName(); + ((TextView)uriView.findViewById(R.id.cartella_nome)).setText( name ); + TextView urlView = uriView.findViewById( R.id.cartella_url ); + if( Global.settings.expert ) { + urlView.setSingleLine( false ); + urlView.setText( stringUri ); + } else + urlView.setText( Uri.decode(stringUri) ); // lo mostra decodificato cioè un po' più leggibile + uriView.findViewById( R.id.cartella_elimina ).setOnClickListener( v -> { + new AlertDialog.Builder(this).setMessage( R.string.sure_delete ) + .setPositiveButton( R.string.yes, (di,id) -> { + // Revoke permission for this uri, if the uri is not used in any other tree + boolean uriExistsElsewhere = false; + for( Settings.Tree tree : Global.settings.trees ) { + for( String uri : tree.uris ) + if( uri.equals(stringUri) && tree.id != treeId) { + uriExistsElsewhere = true; + break; + } + } + if( !uriExistsElsewhere ) + revokeUriPermission( Uri.parse(stringUri), Intent.FLAG_GRANT_READ_URI_PERMISSION ); + uris.remove( stringUri ); + save(); + }).setNeutralButton( R.string.cancel, null ).show(); + }); + registerForContextMenu( uriView ); + } + } + + String folderName(String url ) { + if( url.lastIndexOf('/') > 0 ) + return url.substring( url.lastIndexOf('/') + 1 ); + return url; + } + + void save() { + Global.settings.getTree(treeId).dirs.clear(); + for( String path : folders) + Global.settings.getTree(treeId).dirs.add( path ); + Global.settings.getTree(treeId).uris.clear(); + for( String uri : uris ) + Global.settings.getTree(treeId).uris.add( uri ); + Global.settings.save(); + updateList(); + } + + @Override + public boolean onOptionsItemSelected( MenuItem item ) { + onBackPressed(); + return true; + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if( resultCode == Activity.RESULT_OK ) { + Uri uri = data.getData(); + if( uri != null ) { + // in KitKat a file has been selected and we get the path to the folder + if( requestCode == 456 ) { + String path = F.uriPathFolderKitKat( this, uri ); + if( path != null ) { + folders.add( path ); + save(); + } + } else if( requestCode == 123 ) { + String path = F.uriFolderPath( uri ); + if( path != null ) { + folders.add( path ); + save(); + } else { + getContentResolver().takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION); + DocumentFile docDir = DocumentFile.fromTreeUri( this, uri ); + if( docDir != null && docDir.canRead() ) { + uris.add( uri.toString() ); + save(); + } else + Toast.makeText( this, "Could not read this position.", Toast.LENGTH_SHORT ).show(); + } + } + } else + Toast.makeText( this, R.string.something_wrong, Toast.LENGTH_SHORT ).show(); + } + } + + View menu; + @Override + public void onCreateContextMenu( ContextMenu menu, View vista, ContextMenu.ContextMenuInfo info ) { + this.menu = vista; + menu.add(0, 0, 0, R.string.copy ); + } + @Override + public boolean onContextItemSelected( MenuItem item ) { + if( item.getItemId() == 0 ) { // Copy + U.copyToClipboard( getText(android.R.string.copyUrl), ((TextView) menu.findViewById(R.id.cartella_url)).getText() ); + return true; + } + return false; + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults ) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED && requestCode == 3517) + doChooseFolder(); + } +} \ No newline at end of file diff --git a/app/src/main/java/app/familygem/MediaGalleryAdapter.java b/app/src/main/java/app/familygem/MediaGalleryAdapter.java new file mode 100644 index 00000000..169311e6 --- /dev/null +++ b/app/src/main/java/app/familygem/MediaGalleryAdapter.java @@ -0,0 +1,183 @@ +package app.familygem; + +import android.app.Activity; +import android.content.Context; +import androidx.appcompat.app.AppCompatActivity; +import androidx.recyclerview.widget.RecyclerView; +import android.content.Intent; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; +import org.folg.gedcom.model.Media; +import org.folg.gedcom.model.Person; +import java.util.List; +import app.familygem.detail.ImageActivity; +import app.familygem.visitor.MediaListContainer; +import app.familygem.visitor.FindStack; + +/** + * Adapter for RecyclerView with media list + * */ +class MediaGalleryAdapter extends RecyclerView.Adapter { + + private List mediaList; + private boolean details; + + MediaGalleryAdapter(List mediaList, boolean details) { + this.mediaList = mediaList; + this.details = details; + } + + @Override + public MediaViewHolder onCreateViewHolder(ViewGroup parent, int type ) { + View view = LayoutInflater.from(parent.getContext()).inflate( R.layout.pezzo_media, parent, false ); + return new MediaViewHolder( view, details); + } + @Override + public void onBindViewHolder(final MediaViewHolder holder, int position ) { + holder.bind( position ); + } + @Override + public int getItemCount() { + return mediaList.size(); + } + @Override + public long getItemId(int position) { + return position; + } + @Override + public int getItemViewType(int position) { + return position; + } + + class MediaViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { + View view; + boolean details; + Media media; + Object container; + ImageView imageView; + TextView textView; + TextView numberView; + MediaViewHolder(View view, boolean details) { + super(view); + this.view = view; + this.details = details; + imageView = view.findViewById( R.id.media_img ); + textView = view.findViewById( R.id.media_testo ); + numberView = view.findViewById( R.id.media_num ); + } + void bind(int position ) { + media = mediaList.get( position ).media; + container = mediaList.get( position ).container; + if(details) { + setupMedia( media, textView, numberView); + view.setOnClickListener( this ); + ((Activity) view.getContext()).registerForContextMenu(view); + view.setTag( R.id.tag_object, media ); + view.setTag( R.id.tag_contenitore, container); + // Register context menu + final AppCompatActivity activity = (AppCompatActivity) view.getContext(); + if( view.getContext() instanceof IndividualPersonActivity) { // IndividualMediaFragment + activity.getSupportFragmentManager() + .findFragmentByTag( "android:switcher:" + R.id.schede_persona + ":0" ) // not guaranteed in the future + .registerForContextMenu(view); + } else if( view.getContext() instanceof Principal ) // GalleryFragment + activity.getSupportFragmentManager().findFragmentById( R.id.contenitore_fragment ).registerForContextMenu(view); + else // in AppCompatActivity + activity.registerForContextMenu(view); + } else { + RecyclerView.LayoutParams params = new RecyclerView.LayoutParams( RecyclerView.LayoutParams.WRAP_CONTENT, U.dpToPx(110) ); + int margin = U.dpToPx(5); + params.setMargins( margin, margin, margin, margin ); + view.setLayoutParams( params ); + textView.setVisibility( View.GONE ); + numberView.setVisibility( View.GONE ); + } + F.showImage( media, imageView, view.findViewById(R.id.media_circolo) ); + } + @Override + public void onClick( View v ) { + AppCompatActivity activity = (AppCompatActivity) v.getContext(); + // Gallery in choose mode of the media object + // Return the id of a media object to IndividualMediaFragment + if( activity.getIntent().getBooleanExtra( "galleriaScegliMedia", false ) ) { + Intent intent = new Intent(); + intent.putExtra( "idMedia", media.getId() ); + activity.setResult( Activity.RESULT_OK, intent ); + activity.finish(); + // Gallery in normal mode opens ImageActivity + } else { + Intent intent = new Intent( v.getContext(), ImageActivity.class ); + if( media.getId() != null ) { // all Media records + Memory.setFirst( media ); + } else if( (activity instanceof IndividualPersonActivity && container instanceof Person) // top tier media in indi + || activity instanceof DetailActivity) { // normal opening in the DetailActivity + Memory.add( media ); + } else { // from Gallery all the simple media, or from IndividualMediaFragment the media under multiple levels + new FindStack( Global.gc, media ); + if( activity instanceof Principal ) // Only in the Gallery + intent.putExtra( "daSolo", true ); // so then ImageActivity shows the pantry (?) + } + v.getContext().startActivity( intent ); + } + } + } + + static void setupMedia(Media media, TextView textView, TextView vistaNumero ) { + String text = ""; + if( media.getTitle() != null ) + text = media.getTitle() + "\n"; + if( Global.settings.expert && media.getFile() != null ) { + String file = media.getFile(); + file = file.replace( '\\', '/' ); + if( file.lastIndexOf('/') > -1 ) { + if( file.length() > 1 && file.endsWith("/") ) // removes the last slash + file = file.substring( 0, file.length()-1 ); + file = file.substring( file.lastIndexOf('/') + 1 ); + } + text += file; + } + if( text.isEmpty() ) + textView.setVisibility( View.GONE ); + else { + if( text.endsWith("\n") ) + text = text.substring( 0, text.length()-1 ); + textView.setText( text ); + } + if( media.getId() != null ) { + vistaNumero.setText( String.valueOf(GalleryFragment.popularity(media)) ); + vistaNumero.setVisibility( View.VISIBLE ); + } else + vistaNumero.setVisibility( View.GONE ); + } + + /** + * This is just to create a RecyclerView with media icons that is transparent to clicks + * TODO prevents scrolling in Detail though + * */ + static class MediaIconsRecyclerView extends RecyclerView { + public MediaIconsRecyclerView(Context context) { + super(context); + } + public MediaIconsRecyclerView(Context context, AttributeSet attrs) { + super(context, attrs); + } + public MediaIconsRecyclerView(Context context,AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + boolean details; + public MediaIconsRecyclerView(Context context, boolean details) { + super(context); + this.details = details; + } + @Override + public boolean onTouchEvent( MotionEvent e ) { + super.onTouchEvent( e ); + return details; // when false the grid does not intercept the click + } + } +} \ No newline at end of file diff --git a/app/src/main/java/app/familygem/Memoria.java b/app/src/main/java/app/familygem/Memoria.java deleted file mode 100644 index 929c9618..00000000 --- a/app/src/main/java/app/familygem/Memoria.java +++ /dev/null @@ -1,187 +0,0 @@ -// Gestisce le pile di oggetti gerarchici per scrivere la bava in Dettaglio - -package app.familygem; - -import org.folg.gedcom.model.Address; -import org.folg.gedcom.model.Change; -import org.folg.gedcom.model.EventFact; -import org.folg.gedcom.model.Family; -import org.folg.gedcom.model.GedcomTag; -import org.folg.gedcom.model.Media; -import org.folg.gedcom.model.Name; -import org.folg.gedcom.model.Note; -import org.folg.gedcom.model.Person; -import org.folg.gedcom.model.Repository; -import org.folg.gedcom.model.RepositoryRef; -import org.folg.gedcom.model.Source; -import org.folg.gedcom.model.SourceCitation; -import org.folg.gedcom.model.Submitter; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Stack; -import app.familygem.detail.Archivio; -import app.familygem.detail.ArchivioRef; -import app.familygem.detail.Autore; -import app.familygem.detail.Cambiamenti; -import app.familygem.detail.CitazioneFonte; -import app.familygem.detail.Estensione; -import app.familygem.detail.Evento; -import app.familygem.detail.Famiglia; -import app.familygem.detail.Fonte; -import app.familygem.detail.Immagine; -import app.familygem.detail.Indirizzo; -import app.familygem.detail.Nome; -import app.familygem.detail.Nota; - -public class Memoria { - - static Map classi = new HashMap<>(); - private static final Memoria memoria = new Memoria(); - List lista = new ArrayList<>(); - - Memoria() { - classi.put( Person.class, Individuo.class ); - classi.put( Repository.class, Archivio.class ); - classi.put( RepositoryRef.class, ArchivioRef.class ); - classi.put( Submitter.class, Autore.class ); - classi.put( Change.class, Cambiamenti.class ); - classi.put( SourceCitation.class, CitazioneFonte.class ); - classi.put( GedcomTag.class, Estensione.class ); - classi.put( EventFact.class, Evento.class ); - classi.put( Family.class, Famiglia.class ); - classi.put( Source.class, Fonte.class ); - classi.put( Media.class, Immagine.class ); - classi.put( Address.class, Indirizzo.class ); - classi.put( Name.class, Nome.class ); - classi.put( Note.class, Nota.class ); - } - - // Restituisce l'ultima pila creata se ce n'è almeno una - // oppure ne restituisce una vuota giusto per non restituire null - static Pila getPila() { - if( memoria.lista.size() > 0 ) - return memoria.lista.get( memoria.lista.size() - 1 ); - else - return new Pila(); // una pila vuota che non viene aggiunta alla lista - } - - public static Pila addPila() { - Pila pila = new Pila(); - memoria.lista.add( pila ); - return pila; - } - - // Aggiunge il primo oggetto in una nuova pila - public static void setPrimo( Object oggetto ) { - setPrimo( oggetto, null ); - } - - public static void setPrimo( Object oggetto, String tag ) { - addPila(); - Passo passo = aggiungi( oggetto ); - if( tag != null ) - passo.tag = tag; - else if( oggetto instanceof Person ) - passo.tag = "INDI"; - //stampa("setPrimo"); - } - - // Aggiunge un oggetto alla fine dell'ultima pila esistente - public static Passo aggiungi( Object oggetto ) { - Passo passo = new Passo(); - passo.oggetto = oggetto; - getPila().add( passo ); - //stampa("aggiungi"); - return passo; - } - - // Mette il primo oggetto se non ci sono pile oppure sostituisce il primo oggetto nell'ultima pila esistente - // In altre parole mette il primo oggetto senza aggiungere ulteriori pile - public static void replacePrimo( Object oggetto ) { - String tag = oggetto instanceof Family ? "FAM" : "INDI"; - if( memoria.lista.size() == 0 ) { - setPrimo( oggetto, tag ); - } else { - getPila().clear(); - Passo passo = aggiungi( oggetto ); - passo.tag = tag; - } - //stampa("replacePrimo"); - } - - // L'oggetto contenuto nel primo passo della pila - public static Object oggettoCapo() { - if( getPila().size() > 0 ) - return getPila().firstElement().oggetto; - else - return null; - } - - // L'oggetto nel passo precedente all'ultimo - public static Object oggettoContenitore() { - if( getPila().size() > 1 ) - return getPila().get( getPila().size() - 2 ).oggetto; - else - return null; - } - - // L'oggetto nell'ultimo passo - public static Object getOggetto() { - if( getPila().size() == 0 ) - return null; - else - return getPila().peek().oggetto; - } - - static void arretra() { - while( getPila().size() > 0 && getPila().lastElement().filotto ) - getPila().pop(); - if( getPila().size() > 0 ) - getPila().pop(); - if( getPila().isEmpty() ) - memoria.lista.remove( getPila() ); - //stampa("arretra"); - } - - // Quando un oggetto viene eliminato, lo rende null in tutti i passi, - // e anche gli oggetti negli eventuali passi seguenti vengono annullati. - public static void annullaIstanze( Object oggio ) { - for( Pila pila : memoria.lista ) { - boolean seguente = false; - for( Passo passo : pila ) { - if( passo.oggetto != null && (passo.oggetto.equals(oggio) || seguente) ) { - passo.oggetto = null; - seguente = true; - } - } - } - } - - public static void stampa( String intro ) { - if( intro != null ) - s.l( intro ); - for( Pila pila : memoria.lista ) { - for( Passo passo : pila ) { - String filotto = passo.filotto ? "< " : ""; - if( passo.tag != null ) - s.p( filotto + passo.tag + " " ); - else if( passo.oggetto != null ) - s.p( filotto + passo.oggetto.getClass().getSimpleName() + " " ); - else - s.p( filotto + "Null" ); // capita in rarissimi casi - } - s.l( "" ); - } - s.l("- - - -"); - } - - static class Pila extends Stack {} - - public static class Passo { - public Object oggetto; - public String tag; - public boolean filotto; // TrovaPila lo setta true quindi onBackPressed la pila va eliminata in blocco - } -} \ No newline at end of file diff --git a/app/src/main/java/app/familygem/Memory.java b/app/src/main/java/app/familygem/Memory.java new file mode 100644 index 00000000..71bc35a7 --- /dev/null +++ b/app/src/main/java/app/familygem/Memory.java @@ -0,0 +1,229 @@ +package app.familygem; + +import org.folg.gedcom.model.Address; +import org.folg.gedcom.model.Change; +import org.folg.gedcom.model.EventFact; +import org.folg.gedcom.model.Family; +import org.folg.gedcom.model.GedcomTag; +import org.folg.gedcom.model.Media; +import org.folg.gedcom.model.Name; +import org.folg.gedcom.model.Note; +import org.folg.gedcom.model.Person; +import org.folg.gedcom.model.Repository; +import org.folg.gedcom.model.RepositoryRef; +import org.folg.gedcom.model.Source; +import org.folg.gedcom.model.SourceCitation; +import org.folg.gedcom.model.Submitter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Stack; +import app.familygem.detail.RepositoryActivity; +import app.familygem.detail.RepositoryRefActivity; +import app.familygem.detail.AuthorActivity; +import app.familygem.detail.ChangesActivity; +import app.familygem.detail.SourceCitationActivity; +import app.familygem.detail.ExtensionActivity; +import app.familygem.detail.EventActivity; +import app.familygem.detail.FamilyActivity; +import app.familygem.detail.SourceActivity; +import app.familygem.detail.ImageActivity; +import app.familygem.detail.AddressActivity; +import app.familygem.detail.NameActivity; +import app.familygem.detail.NoteActivity; + +/** + * Manage stacks of hierarchical objects for writing a breadcrumb trail in {@link DetailActivity} + * */ +public class Memory { + + static Map classes = new HashMap<>(); + private static final Memory memory = new Memory(); + List list = new ArrayList<>(); + + Memory() { + classes.put( Person.class, IndividualPersonActivity.class ); + classes.put( Repository.class, RepositoryActivity.class ); + classes.put( RepositoryRef.class, RepositoryRefActivity.class ); + classes.put( Submitter.class, AuthorActivity.class ); + classes.put( Change.class, ChangesActivity.class ); + classes.put( SourceCitation.class, SourceCitationActivity.class ); + classes.put( GedcomTag.class, ExtensionActivity.class ); + classes.put( EventFact.class, EventActivity.class ); + classes.put( Family.class, FamilyActivity.class ); + classes.put( Source.class, SourceActivity.class ); + classes.put( Media.class, ImageActivity.class ); + classes.put( Address.class, AddressActivity.class ); + classes.put( Name.class, NameActivity.class ); + classes.put( Note.class, NoteActivity.class ); + } + + /** + * Return the last created stack if there is at least one + * or return an empty one just to not return null + * */ + static StepStack getStepStack() { + if( memory.list.size() > 0 ) + return memory.list.get( memory.list.size() - 1 ); + else + return new StepStack(); // an empty stack that is not added to the list + } + + public static StepStack addStack() { + StepStack stepStack = new StepStack(); + memory.list.add(stepStack); + return stepStack; + } + + /** + * Adds the first object to a new stack + * */ + public static void setFirst(Object object ) { + setFirst( object, null ); + } + + public static void setFirst(Object object, String tag ) { + addStack(); + Step step = add( object ); + if( tag != null ) + step.tag = tag; + else if( object instanceof Person ) + step.tag = "INDI"; + //log("setPrimo"); + } + + /** + * Adds an object to the end of the last existing stack + * */ + public static Step add(Object object ) { + Step step = new Step(); + step.object = object; + getStepStack().add(step); + //log("aggiungi"); + return step; + } + + /** + * Put the first item if there are no stacks or replace the first item in the last existing stack. + * In other words, it puts the first object without adding any more stacks + * */ + public static void replaceFirst(Object object ) { + String tag = object instanceof Family ? "FAM" : "INDI"; + if( memory.list.size() == 0 ) { + setFirst( object, tag ); + } else { + getStepStack().clear(); + Step step = add( object ); + step.tag = tag; + } + //log("replacePrimo"); + } + + /** + * The object contained in the first step of the stack + * */ + public static Object firstObject() { + if( getStepStack().size() > 0 ) + return getStepStack().firstElement().object; + else + return null; + } + + /** + * If the stack has more than one object, get the second to last object, otherwise return null + * The object in the previous step to the last - L'object nel passo precedente all'ultimo + * I think it was called containerObject()? + * */ + public static Object getSecondToLastObject() { + StepStack stepStack = getStepStack(); + if( stepStack.size() > 1 ) + return stepStack.get( stepStack.size() - 2 ).object; + else + return null; + } + + /** + * The object in the last step + * */ + public static Object getObject() { + if( getStepStack().size() == 0 ) + return null; + else + return getStepStack().peek().object; + } + + static void clearStackAndRemove() { //lit. retreat + while( getStepStack().size() > 0 && getStepStack().lastElement().clearStackOnBackPressed) + getStepStack().pop(); + if( getStepStack().size() > 0 ) + getStepStack().pop(); + if( getStepStack().isEmpty() ) + memory.list.remove( getStepStack() ); + //log("arretra"); + } + + /** + * When an object is deleted, make it null in all steps, + * and the objects in any subsequent steps are also canceled. + * */ + public static void setInstanceAndAllSubsequentToNull(Object subject ) { + for( StepStack stepStack : memory.list) { + boolean shouldSetSubsequentToNull = false; + /*TODO consider using index instead, to avoid needless reassignment + and boolean expression evaluation ("|| shouldSetSubsequentToNull") + * + * int index = -1; + * for (int i = 0; i < stepStack.size(); i++) { + * if (step.object != null && step.object.equals(subject)) { + * index = i; + * break; + * } + * } + * if(index >= 0) { + * for(Step step : stepStack.subList(index, stepStack.size) { + * step.object = null + * } + * } + * + * in Kotlin this would be: + * val index = stepStack.indexOf { it.object != null && it.object == subject } + * if(index >= 0) for(step in stepStack.subList(index, stepStack.size) { + * step.object = null + * } + * */ + for( Step step : stepStack) { + if( step.object != null && (step.object.equals(subject) || shouldSetSubsequentToNull) ) { + step.object = null; + shouldSetSubsequentToNull = true; + } + } + } + } + + public static void log( String intro ) { + if( intro != null ) + s.l( intro ); + for( StepStack stepStack : memory.list) { + for( Step step : stepStack) { + String triplet = step.clearStackOnBackPressed ? "< " : ""; + if( step.tag != null ) + s.p( triplet + step.tag + " " ); + else if( step.object != null ) + s.p( triplet + step.object.getClass().getSimpleName() + " " ); + else + s.p( triplet + "Null" ); // it happens in very rare cases + } + s.l( "" ); + } + s.l("- - - -"); + } + + static class StepStack extends Stack {} + + public static class Step { + public Object object; + public String tag; + public boolean clearStackOnBackPressed; // FindStack sets it to true then onBackPressed the stack must be deleted in bulk + } +} \ No newline at end of file diff --git a/app/src/main/java/app/familygem/MoveLayout.java b/app/src/main/java/app/familygem/MoveLayout.java index c10ad328..d9fac2c4 100644 --- a/app/src/main/java/app/familygem/MoveLayout.java +++ b/app/src/main/java/app/familygem/MoveLayout.java @@ -77,7 +77,9 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { measureChildren(spec, spec); } - // Intercept motion events also on children with click listener (person cards) + /** + * Intercept motion events also on children with click listener (person cards) + * */ @Override public boolean onInterceptTouchEvent(MotionEvent event) { switch( event.getActionMasked() ) { @@ -156,8 +158,8 @@ else if( scrollY > childHeight - height + overY - mendY ) // Bottom mendY, childHeight - height - mendY, overX, overY); } - postInvalidate(); //invalidate(); superfluo? - //velocityTracker.recycle(); // Provoca IllegalStateException: Already in the pool! + postInvalidate(); //invalidate(); superfluous? + //velocityTracker.recycle(); // throws IllegalStateException: Already in the pool! return false; } return super.onTouchEvent(event); @@ -171,8 +173,10 @@ public void computeScroll() { } } - // Calculate overscroll and mend - // @param centering Add to 'mendX' and to 'mendY' the space to center a small child inside moveLayout + /** + * Calculate overscroll and mend + * @param centering Add to 'mendX' and to 'mendY' the space to center a small child inside moveLayout + * */ void calcOverScroll(boolean centering) { overX = (int)(width / 4 * scale); overY = (int)(height / 4 * scale); @@ -194,10 +198,12 @@ float minimumScale() { return scale; } - // Scroll to x and y + /** + * Scroll to x and y + * */ void panTo(int x, int y) { calcOverScroll(false); - // Remove eccessive space around + // Remove excessive space around if( childHeight * scale - y < height ) // There is space below y = (int)(childHeight * scale - height); if( y < 0 ) // There is space above diff --git a/app/src/main/java/app/familygem/NewRelativeDialog.java b/app/src/main/java/app/familygem/NewRelativeDialog.java new file mode 100644 index 00000000..83294286 --- /dev/null +++ b/app/src/main/java/app/familygem/NewRelativeDialog.java @@ -0,0 +1,259 @@ +package app.familygem; + +import android.app.Dialog; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.view.View; +import android.widget.ArrayAdapter; +import android.widget.RadioButton; +import android.widget.Spinner; +import androidx.annotation.Keep; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.DialogFragment; +import androidx.fragment.app.Fragment; +import org.folg.gedcom.model.Family; +import org.folg.gedcom.model.Person; +import java.util.ArrayList; +import java.util.List; + +/** + * DialogFragment which creates the dialog to connect a relative in expert mode + * */ +public class NewRelativeDialog extends DialogFragment { + + private Person pivot; + private Family childFamPref; // Family as a child to possibly show first in the spinner + private Family spouseFamPref; // Family as a spouse to possibly show first in the spinner + private boolean newRelative; + private Fragment fragment; + private AlertDialog dialog; + private Spinner spinner; + private List items = new ArrayList<>(); + private int relationship; + + public NewRelativeDialog(Person pivot, Family favoriteChild, Family favoriteSpouse, boolean newRelative, Fragment fragment) { + this.pivot = pivot; + childFamPref = favoriteChild; + spouseFamPref = favoriteSpouse; + this.newRelative = newRelative; + this.fragment = fragment; + } + + // Zero-argument constructor: nececessary to re-instantiate this fragment (e.g. rotating the device screen) + @Keep // Request to don't remove when minify + public NewRelativeDialog() {} + + @NonNull + @Override + public Dialog onCreateDialog(Bundle bundle) { + // Recreate dialog + if( bundle != null ) { + pivot = Global.gc.getPerson(bundle.getString("idPerno")); + childFamPref = Global.gc.getFamily(bundle.getString("idFamFiglio")); + spouseFamPref = Global.gc.getFamily(bundle.getString("idFamSposo")); + newRelative = bundle.getBoolean("nuovo"); + fragment = getActivity().getSupportFragmentManager().getFragment(bundle, "frammento"); + } + AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); + //builder.setTitle( newRelative ? R.string.new_relative : R.string.link_person ); + View vista = requireActivity().getLayoutInflater().inflate(R.layout.nuovo_parente, null); + // Spinner to choose the family + spinner = vista.findViewById(R.id.nuovoparente_famiglie); + ArrayAdapter adapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_spinner_item); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + spinner.setAdapter(adapter); + ((View)spinner.getParent()).setVisibility( View.GONE ); //initially the spinner is hidden + + RadioButton radioButton1 = vista.findViewById(R.id.nuovoparente_1); + radioButton1.setOnCheckedChangeListener((r, selected) -> { + if( selected ) populateSpinner(1); + }); + RadioButton radioButton2 = vista.findViewById(R.id.nuovoparente_2); + radioButton2.setOnCheckedChangeListener((r, selected) -> { + if( selected ) populateSpinner(2); + }); + RadioButton radioButton3 = vista.findViewById(R.id.nuovoparente_3); + radioButton3.setOnCheckedChangeListener((r, selected) -> { + if( selected ) populateSpinner(3); + }); + RadioButton radioButton4 = vista.findViewById(R.id.nuovoparente_4); + radioButton4.setOnCheckedChangeListener((r, selected) -> { + if( selected ) populateSpinner(4); + }); + + builder.setView(vista).setPositiveButton(android.R.string.ok, (dialog, id) -> { + // Set some values that will be passed to IndividualEditorActivity or to ListOfPeopleFragment and will arrive at addRelative() + Intent intent = new Intent(); + intent.putExtra("idIndividuo", pivot.getId()); + intent.putExtra("relazione", relationship); + FamilyItem familyItem = (FamilyItem)spinner.getSelectedItem(); + if( familyItem.family != null ) + intent.putExtra("idFamiglia", familyItem.family.getId()); + else if( familyItem.parent != null ) // Using 'location' to convey the id of the parent (the third actor in the scene) + intent.putExtra("collocazione", "NUOVA_FAMIGLIA_DI" + familyItem.parent.getId()); + else if( familyItem.existing) // conveys to the ListOfPeopleFragment the intention to join an existing family + intent.putExtra("collocazione", "FAMIGLIA_ESISTENTE"); + if(newRelative) { // Collega persona nuova + intent.setClass(getContext(), IndividualEditorActivity.class); + startActivity(intent); + } else { // Connect existing person + intent.putExtra("anagrafeScegliParente", true); + intent.setClass(getContext(), Principal.class); + if( fragment != null ) + fragment.startActivityForResult(intent, 1401); + else + getActivity().startActivityForResult(intent, 1401); + } + }).setNeutralButton(R.string.cancel, null); + dialog = builder.create(); + return dialog; + } + + @Override + public void onStart() { + super.onStart(); + dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false); // Initially disabled + } + + @Override + public void onSaveInstanceState(Bundle bandolo) { + bandolo.putString("idPerno", pivot.getId()); + if( childFamPref != null ) + bandolo.putString("idFamFiglio", childFamPref.getId()); + if( spouseFamPref != null ) + bandolo.putString("idFamSposo", spouseFamPref.getId()); + bandolo.putBoolean("nuovo", newRelative); + //Save the fragment's instance + if( fragment != null ) + getActivity().getSupportFragmentManager().putFragment(bandolo, "frammento", fragment); + } + + /** + * Tells if there is empty space in a family to add one of the two parents + * */ + boolean containsSpouseShortage(Family fam) { + return fam.getHusbandRefs().size() + fam.getWifeRefs().size() < 2; + } + + private void populateSpinner(int relationship) { + this.relationship = relationship; + items.clear(); + int select = -1; // Index of the item to select in the spinner. If -1 remains select the first entry of the spinner + switch( relationship ) { + case 1: // Parent + for( Family fam : pivot.getParentFamilies(Global.gc) ) { + items.add( new FamilyItem(getContext(),fam) ); + if( (fam.equals(childFamPref) // Select the preferred family in which he is a child + || select < 0) // or the first one available + && containsSpouseShortage(fam) ) // if they have empty parent space + select = items.size() - 1; + } + items.add( new FamilyItem(getContext(),false) ); + if( select < 0 ) + select = items.size() - 1; // Select "New family" + break; + case 2: // Sibling + for( Family fam : pivot.getParentFamilies(Global.gc) ) { + items.add( new FamilyItem(getContext(),fam) ); + for( Person padre : fam.getHusbands(Global.gc) ) { + for( Family fam2 : padre.getSpouseFamilies(Global.gc) ) + if( !fam2.equals(fam) ) + items.add( new FamilyItem(getContext(),fam2) ); + items.add( new FamilyItem(getContext(),padre) ); + } + for( Person madre : fam.getWives(Global.gc) ) { + for( Family fam2 : madre.getSpouseFamilies(Global.gc) ) + if( !fam2.equals(fam) ) + items.add( new FamilyItem(getContext(),fam2) ); + items.add( new FamilyItem(getContext(),madre) ); + } + } + items.add( new FamilyItem(getContext(),false) ); + // Select the preferred family as a child + select = 0; + for( FamilyItem voce : items) + if( voce.family != null && voce.family.equals(childFamPref) ) { + select = items.indexOf(voce); + break; + } + break; + case 3: // Spouse + case 4: // Child + for( Family fam : pivot.getSpouseFamilies(Global.gc) ) { + items.add( new FamilyItem(getContext(),fam) ); + if( (items.size() > 1 && fam.equals(spouseFamPref)) // Select your favorite family as a spouse (except the first one) + || (containsSpouseShortage(fam) && select < 0) ) // Select the first family where there are no spouses + select = items.size() - 1; + } + items.add( new FamilyItem(getContext(), pivot) ); + if( select < 0 ) + select = items.size() - 1; // Select "New family of..." + // For a child, select the preferred family (if any), otherwise the first + if( relationship == 4 ) { + select = 0; + for( FamilyItem voce : items) + if( voce.family != null && voce.family.equals(spouseFamPref) ) { + select = items.indexOf(voce); + break; + } + } + } + if( !newRelative) { + items.add( new FamilyItem(getContext(), true) ); + } + ArrayAdapter adapter = (ArrayAdapter) spinner.getAdapter(); + adapter.clear(); + adapter.addAll(items); + ((View)spinner.getParent()).setVisibility( View.VISIBLE ); + spinner.setSelection(select); + dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(true); + } + + /** + * Class for family list entries in dialogs "Which family do you want to add...?" + * */ + static class FamilyItem { + Context context; + Family family; + Person parent; + boolean existing; // pivot try to fit into the already existing family + + /** + * Existing family + * */ + FamilyItem(Context context, Family family) { + this.context = context; + this.family = family; + } + + /** + * New family of a parent + * */ + FamilyItem(Context context, Person parent) { + this.context = context; + this.parent = parent; + } + + /** + * Empty new family (false) OR recipient-acquired family (true) + * */ + FamilyItem(Context context, boolean existing) { + this.context = context; + this.existing = existing; + } + + @Override + public String toString() { + if( family != null) + return U.familyText(context, Global.gc, family, true); + else if( parent != null ) + return context.getString(R.string.new_family_of, U.properName(parent)); + else if(existing) + return context.getString(R.string.existing_family); + else + return context.getString(R.string.new_family); + } + } +} diff --git a/app/src/main/java/app/familygem/NewTree.java b/app/src/main/java/app/familygem/NewTree.java new file mode 100644 index 00000000..ffef7b82 --- /dev/null +++ b/app/src/main/java/app/familygem/NewTree.java @@ -0,0 +1,447 @@ +package app.familygem; + +import android.Manifest; +import android.app.Activity; +import android.app.DownloadManager; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.content.res.ColorStateList; +import android.database.Cursor; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodManager; +import android.widget.Button; +import android.widget.EditText; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; + +import com.google.gson.Gson; + +import org.apache.commons.io.FileUtils; +import org.folg.gedcom.model.CharacterSet; +import org.folg.gedcom.model.Gedcom; +import org.folg.gedcom.model.GedcomVersion; +import org.folg.gedcom.model.Generator; +import org.folg.gedcom.model.Header; +import org.folg.gedcom.parser.JsonParser; +import org.folg.gedcom.parser.ModelParser; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.PrintWriter; +import java.util.Locale; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +public class NewTree extends BaseActivity { + + View wheel; + + @Override + protected void onCreate(Bundle bundle) { + super.onCreate(bundle); + setContentView(R.layout.albero_nuovo); + wheel = findViewById(R.id.nuovo_circolo); + String referrer = Global.settings.referrer; // Dataid from a share + boolean existingDataId = referrer != null && referrer.matches("[0-9]{14}"); + + // Download the shared tree + Button downloadShared = findViewById(R.id.bottone_scarica_condiviso); + if (existingDataId) + // It doesn't need any permissions because it only downloads and unpacks to the app's external storage + downloadShared.setOnClickListener(v -> FacadeActivity.downloadShared(this, referrer, wheel)); + else + downloadShared.setVisibility(View.GONE); + + // Create an empty tree + Button emptyTree = findViewById(R.id.bottone_albero_vuoto); + if (existingDataId) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) + emptyTree.setBackgroundTintList(ColorStateList.valueOf(getResources().getColor(R.color.primarioChiaro))); + } + emptyTree.setOnClickListener(v -> { + View messageView = LayoutInflater.from(this).inflate(R.layout.albero_nomina, null); + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setView(messageView).setTitle(R.string.title); + TextView textView = messageView.findViewById(R.id.nuovo_nome_testo); + textView.setText(R.string.modify_later); + textView.setVisibility(View.VISIBLE); + EditText newName = messageView.findViewById(R.id.nuovo_nome_albero); + builder.setPositiveButton(R.string.create, (dialog, id) -> newTree(newName.getText().toString())) + .setNeutralButton(R.string.cancel, null).create().show(); + newName.setOnEditorActionListener((view, action, event) -> { + if (action == EditorInfo.IME_ACTION_DONE) { + newTree(newName.getText().toString()); + return true; // complete save() actions + } + return false; // Any other actions that do not exist + }); + messageView.postDelayed(() -> { + newName.requestFocus(); + InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + inputMethodManager.showSoftInput(newName, InputMethodManager.SHOW_IMPLICIT); + }, 300); + }); + + Button downloadExample = findViewById(R.id.bottone_scarica_esempio); + // It doesn't need permissions + downloadExample.setOnClickListener(v -> downloadExample()); + + Button importGedcom = findViewById(R.id.bottone_importa_gedcom); + importGedcom.setOnClickListener(v -> { + int perm = ContextCompat.checkSelfPermission(v.getContext(), Manifest.permission.READ_EXTERNAL_STORAGE); + if (perm == PackageManager.PERMISSION_DENIED) + ActivityCompat.requestPermissions((AppCompatActivity) v.getContext(), new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 1390); + else if (perm == PackageManager.PERMISSION_GRANTED) + importGedcom(); + }); + + Button fetchBackup = findViewById(R.id.bottone_recupera_backup); + fetchBackup.setOnClickListener(v -> { + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.setType("application/zip"); + startActivityForResult(intent, 219); + }); + } + + /** + * Process response to permit requests + */ + @Override + public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { // If request is cancelled, the result arrays are empty + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + if (requestCode == 1390) { + importGedcom(); + } + } + } + + /** + * Create a brand new tree + */ + void newTree(String title) { + int num = Global.settings.max() + 1; + File jsonFile = new File(getFilesDir(), num + ".json"); + Global.gc = new Gedcom(); + Global.gc.setHeader(createHeader(jsonFile.getName())); + Global.gc.createIndexes(); + JsonParser jp = new JsonParser(); + try { + FileUtils.writeStringToFile(jsonFile, jp.toJson(Global.gc), "UTF-8"); + } catch (Exception e) { + Toast.makeText(this, e.getLocalizedMessage(), Toast.LENGTH_LONG).show(); + return; + } + Global.settings.add(new Settings.Tree(num, title, null, 0, 0, null, null, 0)); + Global.settings.openTree = num; + Global.settings.save(); + onBackPressed(); + Toast.makeText(this, R.string.tree_created, Toast.LENGTH_SHORT).show(); + } + + /** + * Download the Simpsons zip file from Google Drive to the external cache of the app, therefore without the need for permissions + */ + void downloadExample() { + wheel.setVisibility(View.VISIBLE); + DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE); + // Avoid multiple downloads + Cursor cursor = downloadManager.query(new DownloadManager.Query().setFilterByStatus(DownloadManager.STATUS_RUNNING)); + if (cursor.moveToFirst()) { + cursor.close(); + findViewById(R.id.bottone_scarica_esempio).setEnabled(false); + return; + } + String url = "https://drive.google.com/uc?export=download&id=1FT-60avkxrHv6G62pxXs9S6Liv5WkkKf"; + String zipPath = getExternalCacheDir() + "/the_Simpsons.zip"; + File fileZip = new File(zipPath); + if (fileZip.exists()) + fileZip.delete(); + DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url)) + .setTitle(getString(R.string.simpsons_tree)) + .setDescription(getString(R.string.family_gem_example)) + .setMimeType("application/zip") + .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) + .setDestinationUri(Uri.parse("file://" + zipPath)); + downloadManager.enqueue(request); + BroadcastReceiver onComplete = new BroadcastReceiver() { + @Override + public void onReceive(Context contesto, Intent intent) { + unZip(contesto, zipPath, null); + unregisterReceiver(this); + } + }; + registerReceiver(onComplete, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)); + // ACTION_DOWNLOAD_COMPLETE means the completion of ANY download that is in progress, not just this one. + } + + /** + * Unzip a ZIP file in the device storage + * Used equally by: Simpsons example, backup files and shared trees + */ + static boolean unZip(Context context, String zipPath, Uri zipUri) { + int treeNumber = Global.settings.max() + 1; + File mediaDir = context.getExternalFilesDir(String.valueOf(treeNumber)); + String sourceDir = context.getApplicationInfo().sourceDir; + if (!sourceDir.startsWith("/data/")) { // App installed not in internal memory (hopefully moved to SD-card) + File[] externalFilesDirs = context.getExternalFilesDirs(String.valueOf(treeNumber)); + if (externalFilesDirs.length > 1) { + mediaDir = externalFilesDirs[1]; + } + } + try { + InputStream is; + if (zipPath != null) + is = new FileInputStream(zipPath); + else + is = context.getContentResolver().openInputStream(zipUri); + ZipInputStream zis = new ZipInputStream(is); + ZipEntry zipEntry; + int len; + byte[] buffer = new byte[1024]; + File newFile; + while ((zipEntry = zis.getNextEntry()) != null) { + if (zipEntry.getName().equals("tree.json")) + newFile = new File(context.getFilesDir(), treeNumber + ".json"); + else if (zipEntry.getName().equals("settings.json")) + newFile = new File(context.getCacheDir(), "settings.json"); + else // It's a file from the 'media' folder + newFile = new File(mediaDir, zipEntry.getName().replace("media/", "")); + FileOutputStream fos = new FileOutputStream(newFile); + while ((len = zis.read(buffer)) > 0) { + fos.write(buffer, 0, len); + } + fos.close(); + } + zis.closeEntry(); + zis.close(); + // Reads the settings and saves them in the preferences + File settingsFile = new File(context.getCacheDir(), "settings.json"); + String json = FileUtils.readFileToString(settingsFile, "UTF-8"); + json = updateLanguage(json); + Gson gson = new Gson(); + Settings.ZippedTree zipped = gson.fromJson(json, Settings.ZippedTree.class); + Settings.Tree tree = new Settings.Tree(treeNumber, zipped.title, mediaDir.getPath(), + zipped.persons, zipped.generations, zipped.root, zipped.shares, zipped.grade); + Global.settings.add(tree); + settingsFile.delete(); + // Sharing tree intended for comparison + if (zipped.grade == 9 && compare(context, tree, false)) { + tree.grade = 20; // brands it as derivative + } + // The download was done from the referrer dialog in Trees + if (context instanceof TreesActivity) { + TreesActivity treesPage = (TreesActivity) context; + treesPage.runOnUiThread(() -> { + treesPage.wheel.setVisibility(View.GONE); + treesPage.updateList(); + }); + } else // Example tree (Simpson) or backup (from FacadeActivity or NewTree) + context.startActivity(new Intent(context, TreesActivity.class)); + Global.settings.save(); + U.toast((Activity) context, R.string.tree_imported_ok); + return true; + } catch (Exception e) { + U.toast((Activity) context, e.getLocalizedMessage()); + } + return false; + } + + /** + * Replace Italian with English in the Json settings of ZIP backup + * Added in Family Gem 0.8 + */ + static String updateLanguage(String json) { + return json.replace("\"generazioni\":", "\"generations\":") + .replace("\"grado\":", "\"grade\":") + .replace("\"individui\":", "\"persons\":") + .replace("\"radice\":", "\"root\":") + .replace("\"titolo\":", "\"title\":") + .replace("\"condivisioni\":", "\"shares\":") + .replace("\"data\":", "\"dateId\":"); + } + + /** + * Choose a Gedcom file to import + * */ + void importGedcom() { + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + // KitKat disables .ged files in Download folder if type is 'application/*' + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) + intent.setType("*/*"); + else + intent.setType("application/*"); + startActivityForResult(intent, 630); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, final Intent data) { + super.onActivityResult(requestCode, resultCode, data); + // Import a chosen Gedcom file with SAF + if (resultCode == RESULT_OK && requestCode == 630) { + try { + // Read the input + Uri uri = data.getData(); + InputStream input = getContentResolver().openInputStream(uri); + Gedcom gedcom = new ModelParser().parseGedcom(input); + if (gedcom.getHeader() == null) { + Toast.makeText(this, R.string.invalid_gedcom, Toast.LENGTH_LONG).show(); + return; + } + gedcom.createIndexes(); // necessary to then calculate the generations + // Save the json file + int newNumber = Global.settings.max() + 1; + PrintWriter printWriter = new PrintWriter(getFilesDir() + "/" + newNumber + ".json"); + JsonParser jsonParser = new JsonParser(); + printWriter.print(jsonParser.toJson(gedcom)); + printWriter.close(); + // Folder tree name and path + String path = F.uriFilePath(uri); + String treeName; + String folderPath = null; + if (path != null && path.lastIndexOf('/') > 0) { // is a full path to the gedcom file + File fileGedcom = new File(path); + folderPath = fileGedcom.getParent(); + treeName = fileGedcom.getName(); + } else if (path != null) { // It's just the name of the file 'family.ged' + treeName = path; + } else // null path + treeName = getString(R.string.tree) + " " + newNumber; + if (treeName.lastIndexOf('.') > 0) // Remove the extension + treeName = treeName.substring(0, treeName.lastIndexOf('.')); + // Save the settings in preferences + String idRadice = U.findRoot(gedcom); + Global.settings.add(new Settings.Tree(newNumber, treeName, folderPath, + gedcom.getPeople().size(), TreeInfoActivity.countGenerations(gedcom, idRadice), idRadice, null, 0)); + new Notifier(this, gedcom, newNumber, Notifier.What.CREATE); + // If necessary it proposes to show the advanced functions + if (!gedcom.getSources().isEmpty() && !Global.settings.expert) { + new AlertDialog.Builder(this).setMessage(R.string.complex_tree_advanced_tools) + .setPositiveButton(android.R.string.ok, (dialog, i) -> { + Global.settings.expert = true; + Global.settings.save(); + finishImportingGedcom(); + }).setNegativeButton(android.R.string.cancel, (dialog, i) -> finishImportingGedcom()) + .show(); + } else + finishImportingGedcom(); + } catch (Exception e) { + Toast.makeText(this, e.getLocalizedMessage(), Toast.LENGTH_LONG).show(); + } + } + + // Try to unzip the retrieved backup ZIP file + if (resultCode == RESULT_OK && requestCode == 219) { + try { + Uri uri = data.getData(); + boolean settingsFileExists = false; + final ZipInputStream zis = new ZipInputStream(getContentResolver().openInputStream(uri)); + ZipEntry zipEntry; + while ((zipEntry = zis.getNextEntry()) != null) { + if (zipEntry.getName().equals("settings.json")) { + settingsFileExists = true; + break; + } + } + zis.closeEntry(); + zis.close(); + if (settingsFileExists) { + unZip(this, null, uri); + /* todo in the strange case that the same tree suggested by the referrer is imported with the ZIP backup + you should cancel the referrer: + if( decomprimiZip( this, null, uri ) ){ + String idData = Esportatore.estraiNome(uri); // che però non è statico + if( Global.preferenze.referrer.equals(idData) ) { + Global.preferenze.referrer = null; + Global.preferenze.salva(); + }} + */ + } else + Toast.makeText(NewTree.this, R.string.backup_invalid, Toast.LENGTH_LONG).show(); + } catch (Exception e) { + Toast.makeText(NewTree.this, e.getLocalizedMessage(), Toast.LENGTH_LONG).show(); + } + } + } + + void finishImportingGedcom() { + onBackPressed(); + Toast.makeText(this, R.string.tree_imported_ok, Toast.LENGTH_SHORT).show(); + } + + /** + * Compare the posting dates of existing trees + * If it finds at least one original tree among the existing ones, it returns true + * and eventually open the comparator + * */ + static boolean compare(Context context, Settings.Tree tree2, boolean openCompareActivity) { + if (tree2.shares != null) + for (Settings.Tree alb : Global.settings.trees) + if (alb.id != tree2.id && alb.shares != null && alb.grade != 20 && alb.grade != 30) + for (int i = alb.shares.size() - 1; i >= 0; i--) { // The shares from last to first + Settings.Share share = alb.shares.get(i); + for (Settings.Share share2 : tree2.shares) + if (share.dateId != null && share.dateId.equals(share2.dateId)) { + if (openCompareActivity) + context.startActivity(new Intent(context, CompareActivity.class) + .putExtra("idAlbero", alb.id) + .putExtra("idAlbero2", tree2.id) + .putExtra("idData", share.dateId) + ); + return true; + } + } + return false; + } + + /** + * Create the standard header for this app + * */ + public static Header createHeader(String filename) { + Header text = new Header(); + Generator app = new Generator(); + app.setValue("FAMILY_GEM"); + app.setName("Family Gem"); + app.setVersion(BuildConfig.VERSION_NAME); + text.setGenerator(app); + text.setFile(filename); + GedcomVersion version = new GedcomVersion(); + version.setForm("LINEAGE-LINKED"); + version.setVersion("5.5.1"); + text.setGedcomVersion(version); + CharacterSet charSet = new CharacterSet(); + charSet.setValue("UTF-8"); + text.setCharacterSet(charSet); + Locale loc = new Locale(Locale.getDefault().getLanguage()); + // There is also Resources.getSystem().getConfiguration().locale.getLanguage() which returns the same 'it' + text.setLanguage(loc.getDisplayLanguage(Locale.ENGLISH)); // ok takes system language in english, not local language + // in the header there are two data fields: TRANSMISSION DATE a bit forcibly can contain the date of the last modification + text.setDateTime(U.actualDateTime()); + return text; + } + + /** + * Back arrow in the toolbar like the hardware one + * */ + @Override + public boolean onOptionsItemSelected(MenuItem i) { + onBackPressed(); + return true; + } +} \ No newline at end of file diff --git a/app/src/main/java/app/familygem/QuadernoAdapter.java b/app/src/main/java/app/familygem/NotebookAdapter.java similarity index 86% rename from app/src/main/java/app/familygem/QuadernoAdapter.java rename to app/src/main/java/app/familygem/NotebookAdapter.java index 21f78421..46386edf 100644 --- a/app/src/main/java/app/familygem/QuadernoAdapter.java +++ b/app/src/main/java/app/familygem/NotebookAdapter.java @@ -12,9 +12,9 @@ import org.folg.gedcom.model.Note; import java.util.Iterator; import java.util.List; -import app.familygem.visitor.RiferimentiNota; +import app.familygem.visitor.NoteReferences; -public class QuadernoAdapter extends RecyclerView.Adapter implements Filterable { +public class NotebookAdapter extends RecyclerView.Adapter implements Filterable { List noteList; private final LayoutInflater inflater; @@ -22,7 +22,7 @@ public class QuadernoAdapter extends RecyclerView.Adapter data, boolean sharedOnly) { + NotebookAdapter(Context context, List data, boolean sharedOnly) { this.inflater = LayoutInflater.from(context); this.noteList = data; this.sharedOnly = sharedOnly; @@ -41,10 +41,10 @@ public void onBindViewHolder(ViewHolder holder, int position) { holder.countView.setVisibility(View.GONE); else { holder.countView.setVisibility(View.VISIBLE); - RiferimentiNota contaUso = new RiferimentiNota(Global.gc, note.getId(), false); - holder.countView.setText(String.valueOf(contaUso.tot)); + NoteReferences useCount = new NoteReferences(Global.gc, note.getId(), false); + holder.countView.setText(String.valueOf(useCount.count)); } - holder.itemView.setTag(note); // per il menu contestuale Elimina + holder.itemView.setTag(note); // for the Delete context menu holder.textView.setText(note.getValue()); } @@ -59,7 +59,7 @@ public Filter getFilter() { @Override protected FilterResults performFiltering(CharSequence charSequence) { String query = charSequence.toString(); - noteList = Quaderno.getAllNotes(sharedOnly); + noteList = NotebookFragment.getAllNotes(sharedOnly); if( !query.isEmpty() ) { Iterator noteIterator = noteList.iterator(); while( noteIterator.hasNext() ) { diff --git a/app/src/main/java/app/familygem/Quaderno.java b/app/src/main/java/app/familygem/NotebookFragment.java similarity index 71% rename from app/src/main/java/app/familygem/Quaderno.java rename to app/src/main/java/app/familygem/NotebookFragment.java index 9de19559..f0fcd312 100644 --- a/app/src/main/java/app/familygem/Quaderno.java +++ b/app/src/main/java/app/familygem/NotebookFragment.java @@ -20,13 +20,13 @@ import org.folg.gedcom.model.NoteRef; import java.util.ArrayList; import java.util.List; -import app.familygem.detail.Nota; -import app.familygem.visitor.ListaNote; -import app.familygem.visitor.TrovaPila; +import app.familygem.detail.NoteActivity; +import app.familygem.visitor.NoteList; +import app.familygem.visitor.FindStack; -public class Quaderno extends Fragment implements QuadernoAdapter.ItemClickListener { +public class NotebookFragment extends Fragment implements NotebookAdapter.ItemClickListener { - QuadernoAdapter adapter; + NotebookAdapter adapter; public static List getAllNotes(boolean sharedOnly) { // Shared notes @@ -35,9 +35,9 @@ public static List getAllNotes(boolean sharedOnly) { noteList.addAll(sharedNotes); // Inline notes if( !sharedOnly ) { - ListaNote noteVisitor = new ListaNote(); + NoteList noteVisitor = new NoteList(); gc.accept(noteVisitor); - noteList.addAll(noteVisitor.listaNote); + noteList.addAll(noteVisitor.noteList); } return noteList; } @@ -49,7 +49,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle ba recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); boolean sharedOnly = getActivity().getIntent().getBooleanExtra("quadernoScegliNota", false); List allNotes = getAllNotes(sharedOnly); - adapter = new QuadernoAdapter(getContext(), allNotes, sharedOnly); + adapter = new NotebookAdapter(getContext(), allNotes, sharedOnly); adapter.setClickListener(this); recyclerView.setAdapter(adapter); @@ -61,7 +61,9 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle ba return view; } - // Andandosene dall'attività senza aver scelto una nota condivisa resetta l'extra + /** + * Leaving the activity without choosing a shared note resets the extra + * */ @Override public void onPause() { super.onPause(); @@ -71,29 +73,29 @@ public void onPause() { @Override public void onItemClick(View view, int position) { Note note = adapter.getItem(position); - // Restituisce l'id di una nota a Individuo e Dettaglio + // Returns the id of a note to IndividualPersonActivity and DetailActivity if( getActivity().getIntent().getBooleanExtra("quadernoScegliNota", false) ) { - Intent intento = new Intent(); - intento.putExtra("idNota", note.getId()); - getActivity().setResult(AppCompatActivity.RESULT_OK, intento); + Intent intent = new Intent(); + intent.putExtra("idNota", note.getId()); + getActivity().setResult(AppCompatActivity.RESULT_OK, intent); getActivity().finish(); - } else { // Apre il dettaglio della nota - Intent intento = new Intent(getContext(), Nota.class); - if( note.getId() != null ) { // Nota condivisa - Memoria.setPrimo(note); - } else { // Nota semplice - new TrovaPila(gc, note); - intento.putExtra("daQuaderno", true); + } else { // Opens the detail of the note + Intent intent = new Intent(getContext(), NoteActivity.class); + if( note.getId() != null ) { // Shared note + Memory.setFirst(note); + } else { // Simple note + new FindStack(gc, note); + intent.putExtra("daQuaderno", true); } - getContext().startActivity(intento); + getContext().startActivity(intent); } } @Override public boolean onContextItemSelected(MenuItem item) { - if( item.getItemId() == 0 ) { // Elimina - Object[] capi = U.eliminaNota(adapter.selectedNote, null); - U.save(false, capi); + if( item.getItemId() == 0 ) { // Delete + Object[] heads = U.deleteNote(adapter.selectedNote, null); + U.save(false, heads); getActivity().recreate(); } else { return false; @@ -101,7 +103,6 @@ public boolean onContextItemSelected(MenuItem item) { return true; } - // menu opzioni nella toolbar @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { // Search inside notes @@ -121,10 +122,12 @@ public boolean onQueryTextSubmit(String q) { }); } - // Crea una nuova nota condivisa, attaccata a un contenitore oppure slegata + /** + * Create a new shared note, attached to a container or unlinked + * */ static void newNote(Context context, Object container) { Note note = new Note(); - String id = U.nuovoId(gc, Note.class); + String id = U.newID(gc, Note.class); note.setId(id); note.setValue(""); gc.addNote(note); @@ -134,7 +137,7 @@ static void newNote(Context context, Object container) { ((NoteContainer)container).addNoteRef(noteRef); } U.save(true, note); - Memoria.setPrimo(note); - context.startActivity(new Intent(context, Nota.class)); + Memory.setFirst(note); + context.startActivity(new Intent(context, NoteActivity.class)); } } diff --git a/app/src/main/java/app/familygem/Notifier.java b/app/src/main/java/app/familygem/Notifier.java index 0b9d6041..cfd6c543 100644 --- a/app/src/main/java/app/familygem/Notifier.java +++ b/app/src/main/java/app/familygem/Notifier.java @@ -1,5 +1,3 @@ -// Manager of birthday notifications - package app.familygem; import android.app.AlarmManager; @@ -17,6 +15,9 @@ import java.util.HashSet; import app.familygem.constant.Format; +/** + * Manager of birthday notifications + * */ class Notifier { static final String TREE_ID_KEY = "targetTreeId"; static final String INDI_ID_KEY = "targetIndiId"; @@ -60,8 +61,10 @@ enum What {REBOOT, CREATE, DELETE, DEFAULT} } } - // Select people who have to celebrate their birthday and add them to the settings - // Eventually save settings + /** + * Select people who have to celebrate their birthday and add them to the settings + * Eventually save settings + */ void findBirthdays(Gedcom gedcom, Settings.Tree tree) { if( tree.birthdays == null ) tree.birthdays = new HashSet<>(); @@ -73,21 +76,23 @@ void findBirthdays(Gedcom gedcom, Settings.Tree tree) { int years = findAge(birth); if( years >= 0 ) { tree.birthdays.add(new Settings.Birthday(person.getId(), U.givenName(person), - U.epiteto(person), nextBirthday(birth), years)); + U.properName(person), nextBirthday(birth), years)); } } } Global.settings.save(); } - // Possibly find the birth Date of a person + /** + * Possibly find the birth Date of a person + */ private Date findBirth(Person person) { if( !U.isDead(person) ) { for( EventFact event : person.getEventsFacts() ) { if( event.getTag().equals("BIRT") && event.getDate() != null ) { - Datatore datator = new Datatore(event.getDate()); - if( datator.isSingleKind() && datator.data1.isFormat(Format.D_M_Y) ) { - return datator.data1.date; + GedcomDateConverter dateConverter = new GedcomDateConverter(event.getDate()); + if( dateConverter.isSingleKind() && dateConverter.data1.isFormat(Format.D_M_Y) ) { + return dateConverter.data1.date; } } } @@ -95,7 +100,9 @@ private Date findBirth(Person person) { return null; } - // Count the number of years that will be turned on the next birthday + /** + * Count the number of years that will be turned on the next birthday + */ private int findAge(Date birth) { int years = now.getYear() - birth.getYear(); if( birth.getMonth() < now.getMonth() @@ -105,7 +112,9 @@ private int findAge(Date birth) { return years <= 120 ? years : -1; } - // From birth Date find next birthday as long timestamp + /** + * From birth Date find next birthday as long timestamp + */ private long nextBirthday(Date birth) { birth.setYear(now.getYear()); birth.setHours(12); @@ -115,7 +124,9 @@ private long nextBirthday(Date birth) { return birth.getTime(); } - // Generate an alarm from each birthday of the provided tree + /** + * Generate an alarm from each birthday of the provided tree + */ void createAlarms(Context context, Settings.Tree tree) { if( tree.birthdays == null ) return; int eventId = tree.id * FACTOR; // Different for every tree @@ -139,7 +150,9 @@ void createAlarms(Context context, Settings.Tree tree) { } } - // Delete all alarms already set for a tree + /** + * Delete all alarms already set for a tree + */ void deleteAlarms(Context context, Settings.Tree tree) { if( tree.birthdays == null ) return; int eventId = tree.id * FACTOR; diff --git a/app/src/main/java/app/familygem/NotifyReceiver.java b/app/src/main/java/app/familygem/NotifyReceiver.java index 217cbcc2..2c7dfab6 100644 --- a/app/src/main/java/app/familygem/NotifyReceiver.java +++ b/app/src/main/java/app/familygem/NotifyReceiver.java @@ -1,8 +1,3 @@ -/* This BroadcastReceiver has a double function: - - Receive intent from Notifier to create notifications - - Receive ACTION_BOOT_COMPLETED after reboot to restore notifications saved in settings.json -*/ - package app.familygem; import android.app.PendingIntent; @@ -12,6 +7,11 @@ import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationManagerCompat; +/** + * This BroadcastReceiver has a double function: + * - Receive intent from Notifier to create notifications + * - Receive ACTION_BOOT_COMPLETED after reboot to restore notifications saved in settings.json + */ public class NotifyReceiver extends BroadcastReceiver { @Override @@ -23,7 +23,7 @@ public void onReceive(Context context, Intent intent) { } else { // Create notification - Intent notifyIntent = new Intent(context, Alberi.class) + Intent notifyIntent = new Intent(context, TreesActivity.class) .putExtra(Notifier.TREE_ID_KEY, intent.getIntExtra("treeId", 0)) .putExtra(Notifier.INDI_ID_KEY, intent.getStringExtra("indiId")) .putExtra(Notifier.NOTIFY_ID_KEY, intent.getIntExtra("id", 1)); diff --git a/app/src/main/java/app/familygem/NuovoParente.java b/app/src/main/java/app/familygem/NuovoParente.java deleted file mode 100644 index 597ec52c..00000000 --- a/app/src/main/java/app/familygem/NuovoParente.java +++ /dev/null @@ -1,247 +0,0 @@ -// DialogFragment che crea il dialogo per collegare un parente in modalità esperto - -package app.familygem; - -import android.app.Dialog; -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import android.view.View; -import android.widget.ArrayAdapter; -import android.widget.RadioButton; -import android.widget.Spinner; -import androidx.annotation.Keep; -import androidx.appcompat.app.AlertDialog; -import androidx.fragment.app.DialogFragment; -import androidx.fragment.app.Fragment; -import org.folg.gedcom.model.Family; -import org.folg.gedcom.model.Person; -import java.util.ArrayList; -import java.util.List; - -public class NuovoParente extends DialogFragment { - - private Person perno; - private Family famPrefFiglio; // Famiglia come figlio da mostrare eventualmente per prima nello spinner - private Family famPrefSposo; // Famiglia come coniuge da mostrare eventualmente per prima nello spinner - private boolean parenteNuovo; - private Fragment frammento; - private AlertDialog dialog; - private Spinner spinner; - private List voci = new ArrayList<>(); - private int relazione; - - public NuovoParente(Person perno, Family preferitaFiglio, Family preferitaSposo, boolean nuovo, Fragment frammento) { - this.perno = perno; - famPrefFiglio = preferitaFiglio; - famPrefSposo = preferitaSposo; - parenteNuovo = nuovo; - this.frammento = frammento; - } - - // Zero-argument constructor: nececessary to re-instantiate this fragment (e.g. rotating the device screen) - @Keep // Request to don't remove when minify - public NuovoParente() {} - - @Override - public Dialog onCreateDialog(Bundle bundle) { - // Recreate dialog - if( bundle != null ) { - perno = Global.gc.getPerson(bundle.getString("idPerno")); - famPrefFiglio = Global.gc.getFamily(bundle.getString("idFamFiglio")); - famPrefSposo = Global.gc.getFamily(bundle.getString("idFamSposo")); - parenteNuovo = bundle.getBoolean("nuovo"); - frammento = getActivity().getSupportFragmentManager().getFragment(bundle, "frammento"); - } - AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); - //builder.setTitle( nuovo ? R.string.new_relative : R.string.link_person ); - View vista = requireActivity().getLayoutInflater().inflate(R.layout.nuovo_parente, null); - // Spinner per scegliere la famiglia - spinner = vista.findViewById(R.id.nuovoparente_famiglie); - ArrayAdapter adapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_spinner_item); - adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - spinner.setAdapter(adapter); - ((View)spinner.getParent()).setVisibility( View.GONE ); // inizialmente lo spinner è nascosto - - RadioButton ruolo1 = vista.findViewById(R.id.nuovoparente_1); - ruolo1.setOnCheckedChangeListener((r, selected) -> { - if( selected ) popolaSpinner(1); - }); - RadioButton ruolo2 = vista.findViewById(R.id.nuovoparente_2); - ruolo2.setOnCheckedChangeListener((r, selected) -> { - if( selected ) popolaSpinner(2); - }); - RadioButton ruolo3 = vista.findViewById(R.id.nuovoparente_3); - ruolo3.setOnCheckedChangeListener((r, selected) -> { - if( selected ) popolaSpinner(3); - }); - RadioButton ruolo4 = vista.findViewById(R.id.nuovoparente_4); - ruolo4.setOnCheckedChangeListener((r, selected) -> { - if( selected ) popolaSpinner(4); - }); - - builder.setView(vista).setPositiveButton(android.R.string.ok, (dialog, id) -> { - // Setta alcuni valori che verranno passati a EditaIndividuo o ad Anagrafe e arriveranno ad aggiungiParente() - Intent intento = new Intent(); - intento.putExtra("idIndividuo", perno.getId()); - intento.putExtra("relazione", relazione); - VoceFamiglia voceFamiglia = (VoceFamiglia)spinner.getSelectedItem(); - if( voceFamiglia.famiglia != null ) - intento.putExtra("idFamiglia", voceFamiglia.famiglia.getId()); - else if( voceFamiglia.genitore != null ) // Uso 'collocazione' per veicolare l'id del genitore (il terzo attore della scena) - intento.putExtra("collocazione", "NUOVA_FAMIGLIA_DI" + voceFamiglia.genitore.getId()); - else if( voceFamiglia.esistente ) // veicola ad Anagrafe l'intenzione di congiungersi a famiglia esistente - intento.putExtra("collocazione", "FAMIGLIA_ESISTENTE"); - if( parenteNuovo ) { // Collega persona nuova - intento.setClass(getContext(), EditaIndividuo.class); - startActivity(intento); - } else { // Collega persona esistente - intento.putExtra("anagrafeScegliParente", true); - intento.setClass(getContext(), Principal.class); - if( frammento != null ) - frammento.startActivityForResult(intento, 1401); - else - getActivity().startActivityForResult(intento, 1401); - } - }).setNeutralButton(R.string.cancel, null); - dialog = builder.create(); - return dialog; - } - - @Override - public void onStart() { - super.onStart(); - dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false); // Initially disabled - } - - @Override - public void onSaveInstanceState(Bundle bandolo) { - bandolo.putString("idPerno", perno.getId()); - if( famPrefFiglio != null ) - bandolo.putString("idFamFiglio", famPrefFiglio.getId()); - if( famPrefSposo != null ) - bandolo.putString("idFamSposo", famPrefSposo.getId()); - bandolo.putBoolean("nuovo", parenteNuovo); - //Save the fragment's instance - if( frammento != null ) - getActivity().getSupportFragmentManager().putFragment(bandolo, "frammento", frammento); - } - - // Dice se in una famiglia c'è spazio vuoto per aggiungere uno dei due genitori - boolean carenzaConiugi(Family fam) { - return fam.getHusbandRefs().size() + fam.getWifeRefs().size() < 2; - } - - private void popolaSpinner( int relazione) { - this.relazione = relazione; - voci.clear(); - int select = -1; // Indice della voce da selezionare nello spinner - // Se rimane -1 seleziona la prima voce dello spinner - switch( relazione ) { - case 1: // Genitore - for( Family fam : perno.getParentFamilies(Global.gc) ) { - voci.add( new VoceFamiglia(getContext(),fam) ); - if( (fam.equals(famPrefFiglio) // Seleziona la famiglia preferenziale in cui è figlio - || select < 0) // oppure la prima disponibile - && carenzaConiugi(fam) ) // se hanno spazio genitoriale vuoto - select = voci.size() - 1; - } - voci.add( new VoceFamiglia(getContext(),false) ); - if( select < 0 ) - select = voci.size() - 1; // Seleziona "Nuova famiglia" - break; - case 2: // Fratello - for( Family fam : perno.getParentFamilies(Global.gc) ) { - voci.add( new VoceFamiglia(getContext(),fam) ); - for( Person padre : fam.getHusbands(Global.gc) ) { - for( Family fam2 : padre.getSpouseFamilies(Global.gc) ) - if( !fam2.equals(fam) ) - voci.add( new VoceFamiglia(getContext(),fam2) ); - voci.add( new VoceFamiglia(getContext(),padre) ); - } - for( Person madre : fam.getWives(Global.gc) ) { - for( Family fam2 : madre.getSpouseFamilies(Global.gc) ) - if( !fam2.equals(fam) ) - voci.add( new VoceFamiglia(getContext(),fam2) ); - voci.add( new VoceFamiglia(getContext(),madre) ); - } - } - voci.add( new VoceFamiglia(getContext(),false) ); - // Seleziona la famiglia preferenziale come figlio - select = 0; - for( VoceFamiglia voce : voci ) - if( voce.famiglia != null && voce.famiglia.equals(famPrefFiglio) ) { - select = voci.indexOf(voce); - break; - } - break; - case 3: // Coniuge - case 4: // Figlio - for( Family fam : perno.getSpouseFamilies(Global.gc) ) { - voci.add( new VoceFamiglia(getContext(),fam) ); - if( (voci.size() > 1 && fam.equals(famPrefSposo)) // Seleziona la famiglia preferita come coniuge (tranne la prima) - || (carenzaConiugi(fam) && select < 0) ) // Seleziona la prima famiglia dove mancano coniugi - select = voci.size() - 1; - } - voci.add( new VoceFamiglia(getContext(),perno) ); - if( select < 0 ) - select = voci.size() - 1; // Seleziona "Nuova famiglia di..." - // Per un figlio seleziona la famiglia preferenziale (se esiste) altrimenti la prima - if( relazione == 4 ) { - select = 0; - for( VoceFamiglia voce : voci ) - if( voce.famiglia != null && voce.famiglia.equals(famPrefSposo) ) { - select = voci.indexOf(voce); - break; - } - } - } - if( !parenteNuovo ) { - voci.add( new VoceFamiglia(getContext(), true) ); - } - ArrayAdapter adapter = (ArrayAdapter) spinner.getAdapter(); - adapter.clear(); - adapter.addAll(voci); - ((View)spinner.getParent()).setVisibility( View.VISIBLE ); - spinner.setSelection(select); - dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(true); - } - - // Classe per le voci degli elenchi di famiglie nei dialoghi "A quale famiglia vuoi aggiungere...?" - static class VoceFamiglia { - Context contesto; - Family famiglia; - Person genitore; - boolean esistente; // perno cercerà di inseririrsi in famiglia già esistente - - // Famiglia esistente - VoceFamiglia(Context contesto, Family famiglia) { - this.contesto = contesto; - this.famiglia = famiglia; - } - - // Nuova famiglia di un genitore - VoceFamiglia(Context contesto, Person genitore) { - this.contesto = contesto; - this.genitore = genitore; - } - - // Nuova famiglia vuota (false) OPPURE famiglia acquisita dal destinatario (true) - VoceFamiglia(Context contesto, boolean esistente) { - this.contesto = contesto; - this.esistente = esistente; - } - - @Override - public String toString() { - if( famiglia != null) - return U.testoFamiglia(contesto, Global.gc, famiglia, true); - else if( genitore != null ) - return contesto.getString(R.string.new_family_of, U.epiteto(genitore)); - else if( esistente ) - return contesto.getString(R.string.existing_family); - else - return contesto.getString(R.string.new_family); - } - } -} diff --git a/app/src/main/java/app/familygem/Opzioni.java b/app/src/main/java/app/familygem/OptionsActivity.java similarity index 91% rename from app/src/main/java/app/familygem/Opzioni.java rename to app/src/main/java/app/familygem/OptionsActivity.java index e17861e7..1c6dbc52 100644 --- a/app/src/main/java/app/familygem/Opzioni.java +++ b/app/src/main/java/app/familygem/OptionsActivity.java @@ -15,7 +15,7 @@ import java.util.Arrays; import java.util.Locale; -public class Opzioni extends BaseActivity { +public class OptionsActivity extends BaseActivity { Language[] languages = { new Language(null, 0), // System language @@ -52,24 +52,24 @@ protected void onCreate(Bundle bundle) { // Auto save SwitchCompat save = findViewById(R.id.opzioni_salva); save.setChecked(Global.settings.autoSave); - save.setOnCheckedChangeListener((coso, attivo) -> { - Global.settings.autoSave = attivo; + save.setOnCheckedChangeListener((button, isChecked) -> { + Global.settings.autoSave = isChecked; Global.settings.save(); }); // Load tree at startup SwitchCompat load = findViewById(R.id.opzioni_carica); load.setChecked(Global.settings.loadTree); - load.setOnCheckedChangeListener((coso, attivo) -> { - Global.settings.loadTree = attivo; + load.setOnCheckedChangeListener((button, isChecked) -> { + Global.settings.loadTree = isChecked; Global.settings.save(); }); // Expert mode SwitchCompat expert = findViewById(R.id.opzioni_esperto); expert.setChecked(Global.settings.expert); - expert.setOnCheckedChangeListener((coso, attivo) -> { - Global.settings.expert = attivo; + expert.setOnCheckedChangeListener((button, isChecked) -> { + Global.settings.expert = isChecked; Global.settings.save(); }); @@ -122,11 +122,13 @@ protected void onCreate(Bundle bundle) { }).show()); findViewById(R.id.opzioni_lapide).setOnClickListener(view -> startActivity( - new Intent(Opzioni.this, Lapide.class) + new Intent(OptionsActivity.this, TombstoneActivity.class) )); } - // Return the actual Language of the app, otherwise the "system language" + /** + * Return the actual Language of the app, otherwise the "system language" + */ private Language getActualLanguage() { Locale firstLocale = AppCompatDelegate.getApplicationLocales().get(0); if( firstLocale != null ) { diff --git a/app/src/main/java/app/familygem/TrovaLuogo.java b/app/src/main/java/app/familygem/PlaceFinderTextView.java similarity index 70% rename from app/src/main/java/app/familygem/TrovaLuogo.java rename to app/src/main/java/app/familygem/PlaceFinderTextView.java index 90cd2a79..de4695fd 100644 --- a/app/src/main/java/app/familygem/TrovaLuogo.java +++ b/app/src/main/java/app/familygem/PlaceFinderTextView.java @@ -1,5 +1,3 @@ -// Classe di servizio per suggerire i nomi dei luoghi formattati in stile Gedcom grazie a GeoNames - package app.familygem; import android.content.Context; @@ -18,14 +16,17 @@ import java.util.List; import java.util.Locale; -public class TrovaLuogo extends AppCompatAutoCompleteTextView { +/** + * TextView that suggests Gedcom-style place names using GeoNames + * */ +public class PlaceFinderTextView extends AppCompatAutoCompleteTextView { ToponymSearchCriteria searchCriteria; - public TrovaLuogo( Context contesto, AttributeSet as ) { - super( contesto, as ); - AdattatoreLista adattatoreLista = new AdattatoreLista( contesto, android.R.layout.simple_spinner_dropdown_item ); - setAdapter(adattatoreLista); + public PlaceFinderTextView(Context context, AttributeSet as) { + super( context, as ); + ListAdapter listAdapter = new ListAdapter( context, android.R.layout.simple_spinner_dropdown_item ); + setAdapter(listAdapter); setInputType(InputType.TYPE_TEXT_FLAG_CAP_WORDS); //setThreshold(2); @@ -35,15 +36,15 @@ public TrovaLuogo( Context contesto, AttributeSet as ) { searchCriteria.setLanguage(Locale.getDefault().getLanguage()); // en, es, it... searchCriteria.setStyle(Style.FULL); searchCriteria.setMaxRows(3); - //searchCriteria.setFuzzy(0.9); // No con setNameStartsWith - //searchCriteria.setFeatureClass( FeatureClass.A ); // o uno o l'altro + //searchCriteria.setFuzzy(0.9); // Not with setNameStartsWith + //searchCriteria.setFeatureClass( FeatureClass.A ); // either one or the other //searchCriteria.setFeatureClass( FeatureClass.P ); } - class AdattatoreLista extends ArrayAdapter implements Filterable { + class ListAdapter extends ArrayAdapter implements Filterable { List places; - AdattatoreLista( Context contesto, int pezzo ) { - super( contesto, pezzo ); + ListAdapter(Context context, int piece ) { + super( context, piece ); places = new ArrayList<>(); } @Override @@ -52,7 +53,7 @@ public int getCount() { } @Override public String getItem(int index) { - if( places.size() > 0 && index < places.size() ) // Evita IndexOutOfBoundsException + if( places.size() > 0 && index < places.size() ) // Avoid IndexOutOfBoundsException return places.get(index); return ""; } @@ -69,17 +70,17 @@ protected FilterResults performFiltering( CharSequence constraint ) { ToponymSearchResult searchResult = WebService.search(searchCriteria); places.clear(); for( Toponym topo : searchResult.getToponyms() ) { - String str = topo.getName(); // Toponimo + String str = topo.getName(); // Toponym if(topo.getAdminName4() != null && !topo.getAdminName4().equals(str)) - str += ", " + topo.getAdminName4(); // Paese + str += ", " + topo.getAdminName4(); // Village if(topo.getAdminName3() != null && !str.contains(topo.getAdminName3())) - str += ", " + topo.getAdminName3(); // Comune + str += ", " + topo.getAdminName3(); // municipality if(!topo.getAdminName2().isEmpty() && !str.contains(topo.getAdminName2())) - str += ", " + topo.getAdminName2(); // Provincia + str += ", " + topo.getAdminName2(); // Province/district if(!str.contains(topo.getAdminName1())) - str += ", " + topo.getAdminName1(); // Regione + str += ", " + topo.getAdminName1(); // Region if(!str.contains(topo.getCountryName())) - str += ", " + topo.getCountryName(); // Nazione + str += ", " + topo.getCountryName(); // Country if( str != null && !places.contains( str ) ) // Avoid null and duplicates places.add( str ); } diff --git a/app/src/main/java/app/familygem/Podio.java b/app/src/main/java/app/familygem/Podio.java deleted file mode 100644 index f0c9f77a..00000000 --- a/app/src/main/java/app/familygem/Podio.java +++ /dev/null @@ -1,116 +0,0 @@ -// Lista dei Submitter (autori) - -package app.familygem; - -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import androidx.fragment.app.Fragment; -import androidx.appcompat.app.AppCompatActivity; -import android.view.ContextMenu; -import android.view.LayoutInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.LinearLayout; -import android.widget.TextView; -import org.folg.gedcom.model.Header; -import org.folg.gedcom.model.Submitter; -import java.util.List; -import app.familygem.detail.Autore; -import static app.familygem.Global.gc; - -public class Podio extends Fragment { - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle stato) { - List listAutori = gc.getSubmitters(); - ((AppCompatActivity)getActivity()).getSupportActionBar().setTitle(listAutori.size() + " " + - getString(listAutori.size() == 1 ? R.string.submitter : R.string.submitters).toLowerCase()); - setHasOptionsMenu(true); - View vista = inflater.inflate(R.layout.magazzino, container, false); - LinearLayout scatola = vista.findViewById(R.id.magazzino_scatola); - for( final Submitter autor : listAutori ) { - View vistaPezzo = inflater.inflate(R.layout.magazzino_pezzo, scatola, false); - scatola.addView(vistaPezzo); - ((TextView)vistaPezzo.findViewById(R.id.magazzino_nome)).setText(InfoAlbero.nomeAutore(autor)); - vistaPezzo.findViewById(R.id.magazzino_archivi).setVisibility(View.GONE); - vistaPezzo.setOnClickListener(v -> { - Memoria.setPrimo(autor); - startActivity(new Intent(getContext(), Autore.class)); - }); - registerForContextMenu(vistaPezzo); - vistaPezzo.setTag(autor); - } - vista.findViewById(R.id.fab).setOnClickListener(v -> { - nuovoAutore(getContext()); - U.save(true); - }); - return vista; - } - - // Elimina un autore - // Todo mi sa che andrebbe cercato eventuale SubmitterRef in tutti i record - public static void eliminaAutore(Submitter aut) { - Header testa = gc.getHeader(); - if( testa != null && testa.getSubmitterRef() != null - && testa.getSubmitterRef().equals(aut.getId()) ) { - testa.setSubmitterRef(null); - } - gc.getSubmitters().remove(aut); - if( gc.getSubmitters().isEmpty() ) - gc.setSubmitters(null); - Memoria.annullaIstanze(aut); - } - - // Crea un Autore nuovo, se riceve un contesto lo apre in modalità editore - static Submitter nuovoAutore(Context contesto) { - Submitter subm = new Submitter(); - subm.setId(U.nuovoId(gc, Submitter.class)); - subm.setName(""); - U.updateChangeDate(subm); - gc.addSubmitter(subm); - if( contesto != null ) { - Memoria.setPrimo(subm); - contesto.startActivity(new Intent(contesto, Autore.class)); - } - return subm; - } - - static void autorePrincipale(Submitter subm) { - Header testa = gc.getHeader(); - if( testa == null ) { - testa = AlberoNuovo.creaTestata(Global.settings.openTree + ".json"); - gc.setHeader(testa); - } - testa.setSubmitterRef(subm.getId()); - U.save(false, subm); - } - - // Menu contestuale - Submitter subm; - @Override - public void onCreateContextMenu(ContextMenu menu, View vista, ContextMenu.ContextMenuInfo info ) { - subm = (Submitter)vista.getTag(); - if( gc.getHeader() == null || gc.getHeader().getSubmitter(gc) == null || !gc.getHeader().getSubmitter(gc).equals(subm) ) - menu.add(0, 0, 0, R.string.make_default); - if( !U.autoreHaCondiviso(subm) ) // può essere eliminato solo se non ha mai condiviso - menu.add(0, 1, 0, R.string.delete); - // todo spiegare perché non può essere eliminato? - } - @Override - public boolean onContextItemSelected( MenuItem item ) { - switch( item.getItemId() ) { - case 0: - autorePrincipale(subm); - return true; - case 1: - // Todo conferma elimina - eliminaAutore(subm); - U.save(false); - getActivity().recreate(); - return true; - } - return false; - } -} \ No newline at end of file diff --git a/app/src/main/java/app/familygem/Principal.java b/app/src/main/java/app/familygem/Principal.java index 25b1f2d1..c1f21952 100644 --- a/app/src/main/java/app/familygem/Principal.java +++ b/app/src/main/java/app/familygem/Principal.java @@ -24,19 +24,19 @@ import java.util.Arrays; import java.util.List; import java.util.Random; -import app.familygem.visitor.ListaMedia; -import app.familygem.visitor.ListaNote; +import app.familygem.visitor.MediaList; +import app.familygem.visitor.NoteList; import static app.familygem.Global.gc; -public class Principal extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener { +public class Principal /*TODO Main?*/extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener { - DrawerLayout scatolissima; + DrawerLayout drawer; Toolbar toolbar; - NavigationView menuPrincipe; + NavigationView mainMenu; List idMenu = Arrays.asList( R.id.nav_diagramma, R.id.nav_persone, R.id.nav_famiglie, R.id.nav_media, R.id.nav_note, R.id.nav_fonti, R.id.nav_archivi, R.id.nav_autore ); - List frammenti = Arrays.asList( Diagram.class, Anagrafe.class, Chiesa.class, - Galleria.class, Quaderno.class, Biblioteca.class, Magazzino.class, Podio.class ); + List fragments = Arrays.asList( Diagram.class, ListOfPeopleFragment.class, ChurchFragment.class, + GalleryFragment.class, NotebookFragment.class, LibraryFragment.class, RepositoriesFragment.class, ListOfAuthorsFragment.class ); @Override protected void onCreate(Bundle savedInstanceState) { @@ -46,32 +46,32 @@ protected void onCreate(Bundle savedInstanceState) { toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); - scatolissima = findViewById(R.id.scatolissima); + drawer = findViewById(R.id.scatolissima); ActionBarDrawerToggle toggle = new ActionBarDrawerToggle( - this, scatolissima, toolbar, R.string.drawer_open, R.string.drawer_close ); - scatolissima.addDrawerListener(toggle); + this, drawer, toolbar, R.string.drawer_open, R.string.drawer_close ); + drawer.addDrawerListener(toggle); toggle.syncState(); - menuPrincipe = findViewById(R.id.menu); - menuPrincipe.setNavigationItemSelectedListener(this); - Global.principalView = scatolissima; - U.gedcomSicuro( gc ); - furnishMenu(); + mainMenu = findViewById(R.id.menu); + mainMenu.setNavigationItemSelectedListener(this); + Global.mainView = drawer; + U.ensureGlobalGedcomNotNull( gc ); + setupMenu(); - if( savedInstanceState == null ) { // carica la home solo la prima volta, non ruotando lo schermo + if( savedInstanceState == null ) { // loads the home only the first time, not after rotating the screen Fragment fragment; - String backName = null; // Etichetta per individuare diagramma nel backstack dei frammenti + String backName = null; // Label to locate diagram in fragment backstack if( getIntent().getBooleanExtra("anagrafeScegliParente",false) ) - fragment = new Anagrafe(); + fragment = new ListOfPeopleFragment(); else if( getIntent().getBooleanExtra("galleriaScegliMedia",false) ) - fragment = new Galleria(); + fragment = new GalleryFragment(); else if( getIntent().getBooleanExtra("bibliotecaScegliFonte",false) ) - fragment = new Biblioteca(); + fragment = new LibraryFragment(); else if( getIntent().getBooleanExtra("quadernoScegliNota",false) ) - fragment = new Quaderno(); + fragment = new NotebookFragment(); else if( getIntent().getBooleanExtra("magazzinoScegliArchivio",false) ) - fragment = new Magazzino(); - else { // la normale apertura + fragment = new RepositoriesFragment(); + else { // normal opening fragment = new Diagram(); backName = "diagram"; } @@ -79,80 +79,88 @@ else if( getIntent().getBooleanExtra("magazzinoScegliArchivio",false) ) .addToBackStack(backName).commit(); } - menuPrincipe.getHeaderView(0).findViewById(R.id.menu_alberi).setOnClickListener(v -> { - scatolissima.closeDrawer(GravityCompat.START); - startActivity(new Intent(Principal.this, Alberi.class)); + mainMenu.getHeaderView(0).findViewById(R.id.menu_alberi).setOnClickListener(v -> { + drawer.closeDrawer(GravityCompat.START); + startActivity(new Intent(Principal.this, TreesActivity.class)); }); - // Nasconde le voci del menu più ostiche + // Hides difficult menu items if( !Global.settings.expert ) { - Menu menu = menuPrincipe.getMenu(); + Menu menu = mainMenu.getMenu(); menu.findItem(R.id.nav_fonti).setVisible(false); menu.findItem(R.id.nav_archivi).setVisible(false); menu.findItem(R.id.nav_autore).setVisible(false); } } - // Chiamato praticamente sempre tranne che onBackPressed + /** + * Virtually always called except onBackPressed + */ @Override public void onAttachFragment(@NonNull Fragment fragment) { super.onAttachFragment(fragment); - if( !(fragment instanceof NuovoParente) ) - aggiornaInterfaccia(fragment); + if( !(fragment instanceof NewRelativeDialog) ) + updateUI(fragment); } - // Aggiorna i contenuti quando si torna indietro con backPressed() + /** + * Refresh contents when going back with backPressed() + */ @Override public void onRestart() { super.onRestart(); if( Global.edited ) { Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.contenitore_fragment); if( fragment instanceof Diagram ) { - ((Diagram)fragment).forceDraw = true; // Così ridisegna il diagramma - } else if( fragment instanceof Anagrafe ) { + ((Diagram)fragment).forceDraw = true; // So redraw the diagram + } else if( fragment instanceof ListOfPeopleFragment) { // Update persons list - Anagrafe anagrafe = (Anagrafe)fragment; - if( anagrafe.people.size() == 0 ) // Probably it's a Collections.EmptyList - anagrafe.people = gc.getPeople(); // replace it with the real ArrayList - anagrafe.adapter.notifyDataSetChanged(); - anagrafe.arredaBarra(); - } else if( fragment instanceof Chiesa ) { - ((Chiesa)fragment).refresh(Chiesa.What.RELOAD); - } else if( fragment instanceof Galleria ) { - ((Galleria)fragment).ricrea(); - /*} else if( fragment instanceof Quaderno ) { - // Doesn't work to update Quaderno when a note is deleted - ((Quaderno)fragment).adapter.notifyDataSetChanged();*/ + ListOfPeopleFragment listOfPeopleFragment = (ListOfPeopleFragment)fragment; + if( listOfPeopleFragment.people.size() == 0 ) // Probably it's a Collections.EmptyList + listOfPeopleFragment.people = gc.getPeople(); // replace it with the real ArrayList + listOfPeopleFragment.adapter.notifyDataSetChanged(); + listOfPeopleFragment.setupToolbar(); + } else if( fragment instanceof ChurchFragment) { + ((ChurchFragment)fragment).refresh(ChurchFragment.What.RELOAD); + } else if( fragment instanceof GalleryFragment) { + ((GalleryFragment)fragment).recreate(); + /*} else if( fragment instanceof NotebookFragment ) { + // Doesn't work to update NotebookFragment when a note is deleted + ((NotebookFragment)fragment).adapter.notifyDataSetChanged();*/ } else { recreate(); // questo dovrebbe andare a scomparire man mano } Global.edited = false; - furnishMenu(); // praticamente solo per mostrare il bottone Salva + setupMenu(); // basically just to show the save button } } - // Riceve una classe tipo 'Diagram.class' e dice se è il fragment attualmente visibile sulla scena - private boolean frammentoAttuale(Class classe) { - Fragment attuale = getSupportFragmentManager().findFragmentById(R.id.contenitore_fragment); - return classe.isInstance(attuale); + /** + *It receives a class type 'Diagram.class' and tells if it is the fragment currently visible on the scene + */ + private boolean isCurrentFragment(Class classe) { + Fragment current = getSupportFragmentManager().findFragmentById(R.id.contenitore_fragment); + return classe.isInstance(current); } - // Update title, random image, 'Save' button in menu header, and menu items count - void furnishMenu() { - NavigationView navigation = scatolissima.findViewById(R.id.menu); + /** + * Update title, random image, 'Save' button in menu header, and menu items count + */ + void setupMenu() { + NavigationView navigation = drawer.findViewById(R.id.menu); View menuHeader = navigation.getHeaderView(0); ImageView imageView = menuHeader.findViewById( R.id.menu_immagine ); TextView mainTitle = menuHeader.findViewById( R.id.menu_titolo ); imageView.setVisibility( ImageView.GONE ); mainTitle.setText( "" ); if( Global.gc != null ) { - ListaMedia cercaMedia = new ListaMedia( Global.gc, 3 ); - Global.gc.accept( cercaMedia ); - if( cercaMedia.lista.size() > 0 ) { - int caso = new Random().nextInt( cercaMedia.lista.size() ); - for( Media med : cercaMedia.lista ) - if( --caso < 0 ) { // arriva a -1 - F.dipingiMedia( med, imageView, null ); + MediaList searchMedia = new MediaList( Global.gc, 3 ); + Global.gc.accept( searchMedia ); + if( searchMedia.list.size() > 0 ) { + int random = new Random().nextInt( searchMedia.list.size() ); + for( Media med : searchMedia.list) + if( --random < 0 ) { // reaches -1 + F.showImage( med, imageView, null ); imageView.setVisibility( ImageView.VISIBLE ); break; } @@ -171,14 +179,14 @@ void furnishMenu() { case 1: count = gc.getPeople().size(); break; case 2: count = gc.getFamilies().size(); break; case 3: - ListaMedia mediaList = new ListaMedia(gc, 0); + MediaList mediaList = new MediaList(gc, 0); gc.accept(mediaList); - count = mediaList.lista.size(); + count = mediaList.list.size(); break; case 4: - ListaNote notesList = new ListaNote(); + NoteList notesList = new NoteList(); gc.accept(notesList); - count = notesList.listaNote.size() + gc.getNotes().size(); + count = notesList.noteList.size() + gc.getNotes().size(); break; case 5: count = gc.getSources().size(); break; case 6: count = gc.getRepositories().size(); break; @@ -196,8 +204,8 @@ void furnishMenu() { saveButton.setOnClickListener(view -> { view.setVisibility(View.GONE); U.saveJson(Global.gc, Global.settings.openTree); - scatolissima.closeDrawer(GravityCompat.START); - Global.daSalvare = false; + drawer.closeDrawer(GravityCompat.START); + Global.shouldSave = false; Toast.makeText(this, R.string.saved, Toast.LENGTH_SHORT).show(); }); saveButton.setOnLongClickListener( vista -> { @@ -206,28 +214,30 @@ void furnishMenu() { popup.show(); popup.setOnMenuItemClickListener( item -> { if( item.getItemId() == 0 ) { - Alberi.openGedcom(Global.settings.openTree, false); - U.qualiGenitoriMostrare(this, null, 0); // Semplicemente ricarica il diagramma - scatolissima.closeDrawer(GravityCompat.START); + TreesActivity.openGedcom(Global.settings.openTree, false); + U.askWhichParentsToShow(this, null, 0); // Simply reload the diagram + drawer.closeDrawer(GravityCompat.START); saveButton.setVisibility(View.GONE); - Global.daSalvare = false; + Global.shouldSave = false; } return true; }); return true; }); - if( Global.daSalvare ) + if( Global.shouldSave) saveButton.setVisibility( View.VISIBLE ); } - // Evidenzia voce del menu e mostra/nasconde toolbar - void aggiornaInterfaccia(Fragment fragment) { + /** + * Highlight menu item and show/hide toolbar + */ + void updateUI(Fragment fragment) { if( fragment == null ) fragment = getSupportFragmentManager().findFragmentById(R.id.contenitore_fragment); if( fragment != null ) { - int numFram = frammenti.indexOf(fragment.getClass()); - if( menuPrincipe != null ) - menuPrincipe.setCheckedItem(idMenu.get(numFram)); + int numFram = fragments.indexOf(fragment.getClass()); + if( mainMenu != null ) + mainMenu.setCheckedItem(idMenu.get(numFram)); if( toolbar == null ) toolbar = findViewById(R.id.toolbar); if( toolbar != null ) @@ -237,15 +247,15 @@ void aggiornaInterfaccia(Fragment fragment) { @Override public void onBackPressed() { - if( scatolissima.isDrawerOpen(GravityCompat.START) ) { - scatolissima.closeDrawer(GravityCompat.START); + if( drawer.isDrawerOpen(GravityCompat.START) ) { + drawer.closeDrawer(GravityCompat.START); } else { super.onBackPressed(); if( getSupportFragmentManager().getBackStackEntryCount() == 0 ) { - // Fa tornare ad Alberi invece di rivedere il primo diagramma del backstack + // Makes Trees go back instead of reviewing the first backstack diagram super.onBackPressed(); } else - aggiornaInterfaccia(null); + updateUI(null); } } @@ -253,29 +263,31 @@ public void onBackPressed() { public boolean onNavigationItemSelected(MenuItem item) { Fragment fragment = null; try { - fragment = (Fragment) frammenti.get( idMenu.indexOf(item.getItemId()) ).newInstance(); + fragment = (Fragment) fragments.get( idMenu.indexOf(item.getItemId()) ).newInstance(); } catch(Exception e) {} if( fragment != null ) { if( fragment instanceof Diagram ) { - int cosaAprire = 0; // Mostra il diagramma senza chiedere dei molteplici genitori - // Se sono già in diagramma e clicco Diagramma, mostra la persona radice - if( frammentoAttuale(Diagram.class) ) { + int whatToOpen = 0; // Show diagram without asking about multiple parents + // If I'm already in diagram and I click Diagram, show root person + if( isCurrentFragment(Diagram.class) ) { Global.indi = Global.settings.getCurrentTree().root; - cosaAprire = 1; // Eventualmente chiede dei molteplici genitori + whatToOpen = 1; // Possibly ask about multiple parents } - U.qualiGenitoriMostrare( this, Global.gc.getPerson(Global.indi), cosaAprire ); + U.askWhichParentsToShow( this, Global.gc.getPerson(Global.indi), whatToOpen ); } else { FragmentManager fm = getSupportFragmentManager(); - // Rimuove frammento precedente dalla storia se è lo stesso che stiamo per vedere - if( frammentoAttuale(fragment.getClass()) ) fm.popBackStack(); + // Remove previous fragment from history if it is the same one we are about to see + if( isCurrentFragment(fragment.getClass()) ) fm.popBackStack(); fm.beginTransaction().replace( R.id.contenitore_fragment, fragment ).addToBackStack(null).commit(); } } - scatolissima.closeDrawer(GravityCompat.START); + drawer.closeDrawer(GravityCompat.START); return true; } - // Automatically open the 'Sort by' sub-menu + /** + * Automatically open the 'Sort by' sub-menu + */ @Override public boolean onMenuOpened(int featureId, Menu menu) { MenuItem item0 = menu.getItem(0); diff --git a/app/src/main/java/app/familygem/PublisherDateLinearLayout.java b/app/src/main/java/app/familygem/PublisherDateLinearLayout.java new file mode 100644 index 00000000..2361af2a --- /dev/null +++ b/app/src/main/java/app/familygem/PublisherDateLinearLayout.java @@ -0,0 +1,384 @@ +package app.familygem; + +import android.content.Context; +import android.text.Editable; +import android.text.InputType; +import android.text.TextWatcher; +import android.util.AttributeSet; +import android.view.Menu; +import android.view.MotionEvent; +import android.view.View; +import android.view.inputmethod.InputMethodManager; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.NumberPicker; +import android.widget.TextView; +import androidx.appcompat.widget.PopupMenu; + +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.Locale; +import app.familygem.constant.Format; +import app.familygem.constant.Kind; + +public class PublisherDateLinearLayout extends LinearLayout { + + GedcomDateConverter gedcomDateConverter; + GedcomDateConverter.Data data1; + GedcomDateConverter.Data data2; + EditText editText; + String[] daysWheel = { "-","1","2","3","4","5","6","7","8","9","10","11","12","13","14","15", + "16","17","18","19","20","21","22","23","24","25","26","27","28","29","30","31" }; + String[] monthsWheel = { "-", s(R.string.january), s(R.string.february), s(R.string.march), s(R.string.april), s(R.string.may), s(R.string.june), + s(R.string.july), s(R.string.august), s(R.string.september), s(R.string.october), s(R.string.november), s(R.string.december) }; + String[] yearsWheel = new String[101]; + int[] dateKinds = { R.string.exact, R.string.approximate, R.string.calculated, R.string.estimated, + R.string.after, R.string.before, R.string.between_and, + R.string.from, R.string.to, R.string.from_to, R.string.date_phrase }; + Calendar calendar = GregorianCalendar.getInstance(); + boolean userIsTyping; // determines if the user is actually typing on the virtual keyboard or if the text is changed in some other way InputMethodManager tastiera; + InputMethodManager keyboard; + boolean keyboardIsVisible; + + public PublisherDateLinearLayout(Context context, AttributeSet as ) { + super( context, as ); + } + + /** + * Actions to be done only once at the beginning + * */ + void initialize(final EditText editText ) { + + addView( inflate( getContext(), R.layout.editore_data, null ), this.getLayoutParams() ); + this.editText = editText; + + for(int i = 0; i < yearsWheel.length - 1; i++ ) + yearsWheel[i] = i < 10 ? "0" + i : "" + i; + yearsWheel[100] = "-"; + + gedcomDateConverter = new GedcomDateConverter( editText.getText().toString() ); + data1 = gedcomDateConverter.data1; + data2 = gedcomDateConverter.data2; + + // Setup the date editor + if( Global.settings.expert ) { + final TextView listTypes = findViewById(R.id.editadata_tipi); + listTypes.setOnClickListener( vista -> { + PopupMenu popup = new PopupMenu(getContext(), vista); + Menu menu = popup.getMenu(); + for( int i = 0; i < dateKinds.length - 1; i++ ) + menu.add(0, i, 0, dateKinds[i]); + popup.show(); + popup.setOnMenuItemClickListener(item -> { + gedcomDateConverter.kind = Kind.values()[item.getItemId()]; + // If possibly invisible + findViewById(R.id.editadata_prima).setVisibility(View.VISIBLE); + if( data1.date == null ) // wagon micro setting (??) + ((NumberPicker)findViewById(R.id.prima_anno)).setValue(100); + if( gedcomDateConverter.kind == Kind.BETWEEN_AND || gedcomDateConverter.kind == Kind.FROM_TO ) { + findViewById(R.id.editadata_seconda_avanzate).setVisibility(VISIBLE); + findViewById(R.id.editadata_seconda).setVisibility(VISIBLE); + if( data2.date == null ) + ((NumberPicker)findViewById(R.id.seconda_anno)).setValue(100); + } else { + findViewById(R.id.editadata_seconda_avanzate).setVisibility(GONE); + findViewById(R.id.editadata_seconda).setVisibility(GONE); + } + listTypes.setText(dateKinds[item.getItemId()]); + userIsTyping = false; + generate(); + return true; + }); + }); + findViewById(R.id.editadata_negativa1).setOnClickListener(vista -> { + data1.negative = ((CompoundButton)vista).isChecked(); + userIsTyping = false; + generate(); + }); + findViewById(R.id.editadata_doppia1).setOnClickListener(vista -> { + data1.doubleYear = ((CompoundButton)vista).isChecked(); + userIsTyping = false; + generate(); + }); + findViewById(R.id.editadata_negativa2).setOnClickListener(vista -> { + data2.negative = ((CompoundButton)vista).isChecked(); + userIsTyping = false; + generate(); + }); + findViewById(R.id.editadata_doppia2).setOnClickListener(vista -> { + data2.doubleYear = ((CompoundButton)vista).isChecked(); + userIsTyping = false; + generate(); + }); + findViewById(R.id.editadata_circa).setVisibility(GONE); + } else { + findViewById(R.id.editadata_circa).setOnClickListener(vista -> { + findViewById(R.id.editadata_seconda).setVisibility(GONE); // casomai fosse visibile per tipi 6 o 9 + gedcomDateConverter.kind = ((CompoundButton)vista).isChecked() ? Kind.APPROXIMATE : Kind.EXACT; + userIsTyping = false; + generate(); + }); + findViewById(R.id.editadata_avanzate).setVisibility(GONE); + } + + setupWagon(1, findViewById(R.id.prima_giorno), findViewById(R.id.prima_mese), + findViewById(R.id.prima_secolo), findViewById(R.id.prima_anno)); + + setupWagon(2, findViewById(R.id.seconda_giorno), findViewById(R.id.seconda_mese), + findViewById(R.id.seconda_secolo), findViewById(R.id.seconda_anno)); + + // At first focus it shows itself (EditoreData) hiding the keyboard + keyboard = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + editText.setOnFocusChangeListener((v, hasFocus) -> { + if( hasFocus ) { + if( gedcomDateConverter.kind == Kind.PHRASE ) { + //genera(); // Remove the parentheses from the sentence + editText.setText(gedcomDateConverter.phrase); + } else { + keyboardIsVisible = keyboard.hideSoftInputFromWindow( editText.getWindowToken(), 0 ); // ok hide keyboard + /*Window window = ((Activity)getContext()).getWindow(); it doesn't help that the keyboard disappears + window.setSoftInputMode( WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN );*/ + editText.setInputType( InputType.TYPE_NULL ); // disable keyboard text input + //needed in recent versions of android where the keyboard reappears + } + gedcomDateConverter.data1.date = null; // a reset + setAll(); + setVisibility(View.VISIBLE); + } else + setVisibility(View.GONE); + } ); + + // The second touch brings up the keyboard + editText.setOnTouchListener((view, event) -> { + if( event.getAction() == MotionEvent.ACTION_DOWN ) { + editText.setInputType(InputType.TYPE_CLASS_TEXT); // re-enable the input + } else if( event.getAction() == MotionEvent.ACTION_UP ) { + keyboardIsVisible = keyboard.showSoftInput(editText, 0); // makes the keyboard reappear + //userIsTyping = true; + //view.performClick(); non ne vedo l'utilità + } + return false; + }); + // Set the date publisher based on what is written + editText.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence testo, int i, int i1, int i2) {} + @Override + public void onTextChanged(CharSequence testo, int i, int i1, int i2) {} + @Override + public void afterTextChanged(Editable testo) { + // i don't know why but in android 5 on first edition it is called 2 times which is not a problem anyway + if(userIsTyping) + setAll(); + userIsTyping = true; + } + }); + } + + /** + * Prepare the four wheels of a wagon with the initial settings + */ + void setupWagon(final int which, final NumberPicker dayWheel, final NumberPicker monthWheel, final NumberPicker centuryWheel, final NumberPicker yearWheel ) { + dayWheel.setMinValue(0); + dayWheel.setMaxValue(31); + dayWheel.setDisplayedValues(daysWheel); + stylize(dayWheel); + dayWheel.setOnValueChangedListener( (picker, vecchio, nuovo) -> + update( which == 1 ? data1 : data2, dayWheel, monthWheel, centuryWheel, yearWheel ) + ); + monthWheel.setMinValue(0); + monthWheel.setMaxValue(12); + monthWheel.setDisplayedValues(monthsWheel); + stylize(monthWheel); + monthWheel.setOnValueChangedListener( (picker, vecchio, nuovo) -> + update( which == 1 ? data1 : data2, dayWheel, monthWheel, centuryWheel, yearWheel ) + ); + centuryWheel.setMinValue(0); + centuryWheel.setMaxValue(20); + stylize(centuryWheel); + centuryWheel.setOnValueChangedListener( (picker, vecchio, nuovo) -> + update( which == 1 ? data1 : data2, dayWheel, monthWheel, centuryWheel, yearWheel ) + ); + yearWheel.setMinValue(0); + yearWheel.setMaxValue(100); + yearWheel.setDisplayedValues(yearsWheel); + stylize(yearWheel); + yearWheel.setOnValueChangedListener( ( picker, vecchio, nuovo ) -> + update( which == 1 ? data1 : data2, dayWheel, monthWheel, centuryWheel, yearWheel ) + ); + } + + void stylize(NumberPicker wheel) { + wheel.setSaveFromParentEnabled(false); + } + + /** + * Take the date string, update the Dates, and edit all of it in the date editor + * Called when I click on the editable field, and after each text edit + */ + void setAll() { + gedcomDateConverter.analyze( editText.getText().toString() ); + ((CheckBox)findViewById( R.id.editadata_circa )).setChecked( gedcomDateConverter.kind == Kind.APPROXIMATE ); + ((TextView)findViewById( R.id.editadata_tipi )).setText( dateKinds[gedcomDateConverter.kind.ordinal()] ); + + // First wagon + setWagon( data1, findViewById( R.id.prima_giorno ), findViewById( R.id.prima_mese ), + findViewById( R.id.prima_secolo ), findViewById( R.id.prima_anno ) ); + if( Global.settings.expert ) + setCheckboxes( data1 ); + + // Second wagon + if( gedcomDateConverter.kind == Kind.BETWEEN_AND || gedcomDateConverter.kind == Kind.FROM_TO ) { + setWagon( data2, findViewById( R.id.seconda_giorno ), findViewById( R.id.seconda_mese ), + findViewById( R.id.seconda_secolo ), findViewById( R.id.seconda_anno ) ); + if( Global.settings.expert ) { + findViewById( R.id.editadata_seconda_avanzate ).setVisibility( VISIBLE ); + setCheckboxes( data2 ); + } + findViewById( R.id.editadata_seconda ).setVisibility( VISIBLE ); + } else { + findViewById( R.id.editadata_seconda_avanzate ).setVisibility( GONE ); + findViewById( R.id.editadata_seconda ).setVisibility( GONE ); + } + } + + /** + * Spin the wheels of a wagon according to a date + */ + void setWagon(GedcomDateConverter.Data data, NumberPicker dayPicker, NumberPicker monthPicker, NumberPicker centuryPicker, NumberPicker yearPicker) { + calendar.clear(); + if( data.date != null ) + calendar.setTime(data.date); + dayPicker.setMaxValue(calendar.getActualMaximum(Calendar.DAY_OF_MONTH)); + if( data.date != null && (data.isFormat(Format.D_M_Y) || data.isFormat(Format.D_M)) ) + dayPicker.setValue(data.date.getDate()); + else + dayPicker.setValue(0); + if( data.date == null || data.isFormat(Format.Y) ) + monthPicker.setValue(0); + else + monthPicker.setValue(data.date.getMonth() + 1); + if( data.date == null || data.isFormat(Format.D_M) ) + centuryPicker.setValue(0); + else + centuryPicker.setValue((data.date.getYear() + 1900) / 100); + if( data.date == null || data.isFormat(Format.D_M) ) + yearPicker.setValue(100); + else + yearPicker.setValue((data.date.getYear() + 1900) % 100); + } + + /** + * Set the Checkboxes for a date which can be negative and double + */ + void setCheckboxes(GedcomDateConverter.Data data) { + CheckBox checkboxBC, checkboxDouble; + if( data.equals(data1) ) { + checkboxBC = findViewById(R.id.editadata_negativa1); + checkboxDouble = findViewById(R.id.editadata_doppia1); + } else { + checkboxBC = findViewById(R.id.editadata_negativa2); + checkboxDouble = findViewById(R.id.editadata_doppia2); + } + if( data.date == null || data.isFormat(Format.EMPTY) || data.isFormat(Format.D_M) ) { // dates without year + checkboxBC.setVisibility(INVISIBLE); + checkboxDouble.setVisibility(INVISIBLE); + } else { + checkboxBC.setChecked(data.negative); + checkboxBC.setVisibility(VISIBLE); + checkboxDouble.setChecked(data.doubleYear); + checkboxDouble.setVisibility(VISIBLE); + } + } + + /** + * Update a Date with the new values taken from the wheels + */ + void update(GedcomDateConverter.Data data, NumberPicker dayPicker, NumberPicker monthPicker, NumberPicker centuryPicker, NumberPicker yearPicker ) { + if(keyboardIsVisible) { // Hides any visible keyboard + keyboardIsVisible = keyboard.hideSoftInputFromWindow( editText.getWindowToken(), 0 ); + // Hides the keyboard right away, but needs a second try to return false. It's not a problem anyway + } + int day = dayPicker.getValue(); + int month = monthPicker.getValue(); + int century = centuryPicker.getValue(); + int year = yearPicker.getValue(); + // Set the days of the month in dayWheel + calendar.set( century*100+year, month-1, 1 ); + dayPicker.setMaxValue( calendar.getActualMaximum(Calendar.DAY_OF_MONTH) ); + if( data.date == null ) data.date = new Date(); + data.date.setDate( day == 0 ? 1 : day ); // otherwise the M_A date goes back one month + data.date.setMonth( month == 0 ? 0 : month - 1 ); + data.date.setYear( year == 100 ? -1899 : century*100 + year - 1900 ); + if( day != 0 && month != 0 && year != 100 ) + data.format.applyPattern(Format.D_M_Y); + else if( day != 0 && month != 0 ) + data.format.applyPattern(Format.D_M); + else if( month != 0 && year != 100 ) + data.format.applyPattern(Format.M_Y); + else if( year != 100 ) + data.format.applyPattern(Format.Y); + else + data.format.applyPattern(Format.EMPTY); + setCheckboxes( data ); + userIsTyping = false; + generate(); + } + + /** + * Rebuilds the string with the end date and puts it in {@link #editText} + */ + void generate() { + String redone; + if( gedcomDateConverter.kind == Kind.EXACT ) + redone = redo(data1); + else if( gedcomDateConverter.kind == Kind.BETWEEN_AND ) + redone = "BET " + redo(data1) + " AND " + redo(data2); + else if( gedcomDateConverter.kind == Kind.FROM_TO ) + redone = "FROM " + redo(data1) + " TO " + redo(data2); + else if( gedcomDateConverter.kind == Kind.PHRASE ) { + // The phrase is replaced by the exact date + gedcomDateConverter.kind = Kind.EXACT; + ((TextView)findViewById(R.id.editadata_tipi)).setText(dateKinds[0]); + redone = redo(data1); + } else + redone = gedcomDateConverter.kind.prefix + " " + redo(data1); + editText.setText(redone); + } + + /** + * Writes the single date according to the format + * */ + String redo(GedcomDateConverter.Data data ) { + String done = ""; + if( data.date != null ) { + // Dates with double year + if( data.doubleYear && !(data.isFormat(Format.EMPTY) || data.isFormat(Format.D_M)) ) { + Date aYearLater = new Date(); + aYearLater.setYear( data.date.getYear() + 1 ); + String secondoAnno = String.format( Locale.ENGLISH, "%tY", aYearLater ); + done = data.format.format( data.date ) +"/"+ secondoAnno.substring( 2 ); + } else // The other normal dates + done = data.format.format( data.date ); + } + if( data.negative) + done += " B.C."; + return done; + } + + /** + * Called from outside essentially just to add parentheses to the given sentence + * */ + void encloseInParentheses() { + if( gedcomDateConverter.kind == Kind.PHRASE ) { + editText.setText("(" + editText.getText() + ")"); + } + } + + String s(int id) { + return Global.context.getString(id); + } +} \ No newline at end of file diff --git a/app/src/main/java/app/familygem/Magazzino.java b/app/src/main/java/app/familygem/RepositoriesFragment.java similarity index 76% rename from app/src/main/java/app/familygem/Magazzino.java rename to app/src/main/java/app/familygem/RepositoriesFragment.java index abd90457..d3ccdfa3 100644 --- a/app/src/main/java/app/familygem/Magazzino.java +++ b/app/src/main/java/app/familygem/RepositoriesFragment.java @@ -1,5 +1,3 @@ -// List of repositories - package app.familygem; import android.app.Activity; @@ -26,10 +24,13 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -import app.familygem.detail.Archivio; +import app.familygem.detail.RepositoryActivity; import static app.familygem.Global.gc; -public class Magazzino extends Fragment { +/** + * List of repositories + * */ +public class RepositoriesFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) { @@ -42,12 +43,12 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bu if( repos.size() > 1 ) setHasOptionsMenu(true); Collections.sort(repos, (r1, r2) -> { - switch( Global.ordineMagazzino ) { - case 1: // Ordina per id - return U.soloNumeri(r1.getId()) - U.soloNumeri(r2.getId()); - case 2: // Ordine alfabetico + switch( Global.repositoryOrder) { + case 1: // Sort by id + return U.extractNum(r1.getId()) - U.extractNum(r2.getId()); + case 2: // Sort alphabetically return r1.getName().compareToIgnoreCase(r2.getName()); - case 3: // Ordina per numero di fonti + case 3: // Sort by number of sources return countSources(gc, r2) - countSources(gc, r1); default: return 0; @@ -60,13 +61,13 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bu ((TextView)repoView.findViewById(R.id.magazzino_archivi)).setText(String.valueOf(countSources(gc, repo))); repoView.setOnClickListener(v -> { if( getActivity().getIntent().getBooleanExtra("magazzinoScegliArchivio", false) ) { - Intent intento = new Intent(); - intento.putExtra("idArchivio", repo.getId()); - getActivity().setResult(Activity.RESULT_OK, intento); + Intent intent = new Intent(); + intent.putExtra("idArchivio", repo.getId()); + getActivity().setResult(Activity.RESULT_OK, intent); getActivity().finish(); } else { - Memoria.setPrimo(repo); - startActivity(new Intent(getContext(), Archivio.class)); + Memory.setFirst(repo); + startActivity(new Intent(getContext(), RepositoryActivity.class)); } }); registerForContextMenu(repoView); @@ -89,7 +90,9 @@ public void onPause() { getActivity().getIntent().removeExtra("magazzinoScegliArchivio"); } - // Count how many sources are present in a repository + /** + * Count how many sources are present in a repository + */ static int countSources(Gedcom gedcom, Repository repo) { int num = 0; for( Source source : gedcom.getSources() ) { @@ -101,10 +104,12 @@ static int countSources(Gedcom gedcom, Repository repo) { return num; } - // Create a new repository, optionally linking a source to it + /** + * Create a new repository, optionally linking a source to it + */ static void newRepository(Context context, Source source) { Repository repo = new Repository(); - repo.setId(U.nuovoId(gc, Repository.class)); + repo.setId(U.newID(gc, Repository.class)); repo.setName(""); gc.addRepository(repo); if( source != null ) { @@ -113,14 +118,16 @@ static void newRepository(Context context, Source source) { source.setRepositoryRef(repoRef); } U.save(true, repo); - Memoria.setPrimo(repo); - context.startActivity(new Intent(context, Archivio.class)); + Memory.setFirst(repo); + context.startActivity(new Intent(context, RepositoryActivity.class)); } - /* Elimina l'archivio e i ref dalle fonti in cui è citato l'archivio - Restituisce un array delle Source modificate - Secondo le specifiche Gedcom 5.5, la libreria FS e Family Historian una SOUR prevede un solo Ref a un REPO - Invece secondo Gedcom 5.5.1 può avere molteplici Ref ad archivi */ + /** + * Remove the archive and refs from sources where the archive is mentioned + * According to the Gedcom 5.5 specifications, the FS and Family Historian library a SOUR provides only one Ref to a REPO + * Conversely, according to Gedcom 5.5.1, it can have multiple Refs to archives + * @return an array of modified Sources + * */ public static Source[] delete(Repository repo) { Set sources = new HashSet<>(); for( Source sour : gc.getSources() ) @@ -129,11 +136,13 @@ public static Source[] delete(Repository repo) { sources.add(sour); } gc.getRepositories().remove(repo); - Memoria.annullaIstanze(repo); + Memory.setInstanceAndAllSubsequentToNull(repo); return sources.toArray(new Source[0]); } - // overflow menu in toolbar + /** + * overflow menu in toolbar + */ @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { SubMenu subMenu = menu.addSubMenu(R.string.order_by); @@ -146,22 +155,21 @@ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { public boolean onOptionsItemSelected( MenuItem item ) { switch( item.getItemId() ) { case 1: - Global.ordineMagazzino = 1; + Global.repositoryOrder = 1; break; case 2: - Global.ordineMagazzino = 2; + Global.repositoryOrder = 2; break; case 3: - Global.ordineMagazzino = 3; + Global.repositoryOrder = 3; break; default: return false; } - getFragmentManager().beginTransaction().replace(R.id.contenitore_fragment, new Magazzino()).commit(); + getFragmentManager().beginTransaction().replace(R.id.contenitore_fragment, new RepositoriesFragment()).commit(); return true; } - // Menu contestuale Repository repository; @Override public void onCreateContextMenu(ContextMenu menu, View vista, ContextMenu.ContextMenuInfo info) { diff --git a/app/src/main/java/app/familygem/Settings.java b/app/src/main/java/app/familygem/Settings.java index c7990985..125ca6b9 100644 --- a/app/src/main/java/app/familygem/Settings.java +++ b/app/src/main/java/app/familygem/Settings.java @@ -1,10 +1,11 @@ -// Class that represents the preferences saved in 'settings.json' - package app.familygem; import android.widget.Toast; + import com.google.gson.Gson; + import org.apache.commons.io.FileUtils; + import java.io.File; import java.text.DateFormat; import java.text.SimpleDateFormat; @@ -15,231 +16,264 @@ import java.util.Locale; import java.util.Set; +/** + * Class that represents the preferences saved in 'settings.json' + */ public class Settings { - String referrer; // È 'start' appena installata l'app (cioè quando non esiste 'files/settings.json') - // Se l'installazione proviene da una condivisione accoglie un dateId tipo '20191003215337' - // Ben presto diventa null e rimane tale, a meno di cancellare tutti i dati - List trees; - public int openTree; // Number of the tree currently opened. 0 means not a particular tree. - // Must be consistent with the 'Global.gc' opened tree. - // It is not reset by closing the tree, to be reused by 'Load last opened tree at startup'. - boolean autoSave; - boolean loadTree; - public boolean expert; - boolean shareAgreement; - Diagram diagram; - - // Firt boot values - // False booleans don't need to be initialized - void init() { - referrer = "start"; - trees = new ArrayList<>(); - autoSave = true; - diagram = new Diagram().init(); - } - - int max() { - int num = 0; - for( Tree tree : trees ) { - if( tree.id > num ) - num = tree.id; - } - return num; - } - - void aggiungi(Tree tree) { - trees.add(tree); - } - - void rinomina(int id, String nuovoNome) { - for( Tree tree : trees ) { - if( tree.id == id ) { - tree.title = nuovoNome; - break; - } - } - save(); - } - - void deleteTree(int id) { - for( Tree tree : trees ) { - if( tree.id == id ) { - trees.remove(tree); - break; - } - } - if( id == openTree ) { - openTree = 0; - } - save(); - } - - public void save() { - try { - Gson gson = new Gson(); - String json = gson.toJson(this); - FileUtils.writeStringToFile(new File(Global.context.getFilesDir(), "settings.json"), json, "UTF-8"); - } catch( Exception e ) { - Toast.makeText(Global.context, e.getLocalizedMessage(), Toast.LENGTH_LONG).show(); - } - } - - // The tree currently open - Tree getCurrentTree() { - for( Tree alb : trees ) { - if( alb.id == openTree ) - return alb; - } - return null; - } - - Tree getTree(int treeId) { - /* Da quando ho installato Android Studio 4.0, quando compilo con minifyEnabled true - misteriosamente 'alberi' qui è null. - Però non è null se DOPO c'è 'trees = Global.settings.trees' - Davvero incomprensibile! + /** + * It's 'start' as soon as the app is installed (i.e. when 'files/settings.json' doesn't exist) + * If the installation comes from a share it welcomes (accepts/receives/contains) a dateId type '20191003215337' + * Soon becomes null and stays null unless all data is deleted + */ + String referrer; + List trees; + /** + * Number of the tree currently opened. 0 means not a particular tree. + * Must be consistent with the 'Global.gc' opened tree. + * It is not reset by closing the tree, to be reused by 'Load last opened tree at startup'. + */ + public int openTree; + boolean autoSave; + boolean loadTree; + public boolean expert; + boolean shareAgreement; + Diagram diagram; + + /** + * First boot values + * False booleans don't need to be initialized + */ + void init() { + referrer = "start"; + trees = new ArrayList<>(); + autoSave = true; + diagram = new Diagram().init(); + } + + int max() { + int num = 0; + for (Tree tree : trees) { + if (tree.id > num) + num = tree.id; + } + return num; + } + + void add(Tree tree) { + trees.add(tree); + } + + void rename(int id, String newName) { + for (Tree tree : trees) { + if (tree.id == id) { + tree.title = newName; + break; + } + } + save(); + } + + void deleteTree(int id) { + for (Tree tree : trees) { + if (tree.id == id) { + trees.remove(tree); + break; + } + } + if (id == openTree) { + openTree = 0; + } + save(); + } + + public void save() { + try { + FileUtils.writeStringToFile(new File(Global.context.getFilesDir(), "settings.json"), new Gson().toJson(this), "UTF-8"); //TODO extract all uses/new instances of Gson to global + } catch (Exception e) { + Toast.makeText(Global.context, e.getLocalizedMessage(), Toast.LENGTH_LONG).show(); + } + } + + /** + * The tree currently open + */ + Tree getCurrentTree() { + for (Tree alb : trees) { + if (alb.id == openTree) + return alb; + } + return null; + } + + Tree getTree(int treeId) { + /* Since I installed Android Studio 4.0, when I compile with minifyEnabled true + mysteriously 'trees' here is null. + But it is not null if AFTER there is 'trees = Global.settings.trees' + Really incomprehensible! */ - if( trees == null ) { - trees = Global.settings.trees; - } - if( trees != null ) - for( Tree tree : trees ) { - if( tree.id == treeId ) { - if( tree.uris == null ) // traghettatore inserito in Family Gem 0.7.15 - tree.uris = new LinkedHashSet<>(); - return tree; - } - } - return null; - } - - static class Diagram { - int ancestors; - int uncles; - int descendants; - int siblings; - int cousins; - boolean spouses; - - // Default values - Diagram init() { - ancestors = 3; - uncles = 2; - descendants = 3; - siblings = 2; - cousins = 1; - spouses = true; - return this; - } - } - -/* -"grado": -0 albero creato da zero in Italia - rimane 0 anche aggiungendo il submitter principale, condividendolo e ricevendo novità -9 albero spedito per la condivisione in attesa di marchiare con 'passato' tutti i submitter -10 albero ricevuto tramite condivisione in Australia - non potrà mai più ritornare 0 -20 albero ritornato in Italia dimostratosi un derivato da uno zero (o da uno 10). - solo se è 10 può diventare 20. Se per caso perde lo status di derivato ritorna 10 (mai 0) -30 albero derivato da cui sono state estratte tutte le novità OPPURE privo di novità già all'arrivo (grigio). Eliminabile -*/ - - static class Tree { - int id; - String title; - Set dirs; - Set uris; - int persons; - int generations; - int media; - String root; - List shares; // dati identificativi delle condivisioni attraverso il tempo e lo spazio - String shareRoot; // id della Person radice dell'albero in Condivisione - int grade; // grado della condivisione - Set birthdays; - - Tree(int id, String title, String dir, int persons, int generations, String root, List shares, int grade) { - this.id = id; - this.title = title; - dirs = new LinkedHashSet<>(); - if( dir != null ) - dirs.add(dir); - uris = new LinkedHashSet<>(); - this.persons = persons; - this.generations = generations; - this.root = root; - this.shares = shares; - this.grade = grade; - birthdays = new HashSet<>(); - } - - void aggiungiCondivisione(Share share) { - if( shares == null ) - shares = new ArrayList<>(); - shares.add(share); - } - } - - // The essential data of a share - static class Share { - String dateId; // on compressed date and time format: YYYYMMDDhhmmss - String submitter; // Submitter id - Share(String dateId, String submitter) { - this.dateId = dateId; - this.submitter = submitter; - } - } - - // Birthday of one person - static class Birthday { - String id; // E.g. 'I123' - String given; // 'John' - String name; // 'John Doe III' - long date; // Date of next birthday in Unix time - int age; // Turned years - public Birthday(String id, String given, String name, long date, int age) { - this.id = id; - this.given = given; - this.name = name; - this.date = date; - this.age = age; - } - @Override - public String toString() { - DateFormat sdf = new SimpleDateFormat("d MMM y", Locale.US); - return "[" + name + ": " + age + " (" + sdf.format(date) + ")]"; - } - } - - // Blueprint of the file 'settings.json' inside a backup, share or example ZIP file - // It contains basic info of the zipped tree - static class ZippedTree { - String title; - int persons; - int generations; - String root; - List shares; - int grade; // il grado di destinazione dell'albero zippato - - ZippedTree(String title, int persons, int generations, String root, List shares, int grade) { - this.title = title; - this.persons = persons; - this.generations = generations; - this.root = root; - this.shares = shares; - this.grade = grade; - } - - File salva() { - File fileSettaggi = new File(Global.context.getCacheDir(), "settings.json"); - Gson gson = new Gson(); - String salvando = gson.toJson(this); - try { - FileUtils.writeStringToFile(fileSettaggi, salvando, "UTF-8"); - } catch( Exception e ) {} - return fileSettaggi; - } - } + if (trees == null) { + trees = Global.settings.trees; + } + if (trees != null) + for (Tree tree : trees) { + if (tree.id == treeId) { + if (tree.uris == null) // ferryman ( ?? "traghettatore") added to Family Gem 0.7.15 + tree.uris = new LinkedHashSet<>(); + return tree; + } + } + return null; + } + + static class Diagram { + int ancestors; + int uncles; + int descendants; + int siblings; + int cousins; + boolean spouses; + + /** + * Default values + */ + Diagram init() { + ancestors = 3; + uncles = 2; + descendants = 3; + siblings = 2; + cousins = 1; + spouses = true; + return this; + } + } + + static class Tree { + int id; + String title; + Set dirs; + Set uris; + int persons; + int generations; + int media; + String root; + /** + * identification data of shares across time and space + */ + List shares; + /** + * id of the Person root of the Sharing tree + */ + String shareRoot; + + /** + * "grade" (degree?) of sharing + *
    + *
  • 0 tree created from scratch in Italy. it stays 0 even adding main submitter, sharing it and getting news
  • + *
  • 9 tree sent for sharing waiting to mark all submitters with 'passed'
  • + *
  • 10 tree received via sharing in Australia. Can never return to 0
  • + *
  • 20 tree returned to Italy proved to be a derivative of a zero (or a 10). Only if it is 10 can it become 20. If by chance it loses the status of derivative it returns 10 (never 0) + *
  • 30 derived tree from which all novelties have been extracted OR with no novelties already upon arrival (gray). Disposable + *
+ */ + int grade; + + Set birthdays; + + Tree(int id, String title, String dir, int persons, int generations, String root, List shares, int grade) { + this.id = id; + this.title = title; + dirs = new LinkedHashSet<>(); + if (dir != null) + dirs.add(dir); + uris = new LinkedHashSet<>(); + this.persons = persons; + this.generations = generations; + this.root = root; + this.shares = shares; + this.grade = grade; + birthdays = new HashSet<>(); + } + + void aggiungiCondivisione(Share share) { + if (shares == null) + shares = new ArrayList<>(); + shares.add(share); + } + } + + /** + * The essential data of a share + */ + static class Share { + /** + * on compressed date and time format: YYYYMMDDhhmmss + */ + String dateId; + /** + * Submitter id + */ + String submitter; + + Share(String dateId, String submitter) { + this.dateId = dateId; + this.submitter = submitter; + } + } + + /** + * Birthday of one person + */ + static class Birthday { + String id; // E.g. 'I123' + String given; // 'John' + String name; // 'John Doe III' + long date; // Date of next birthday in Unix time + int age; // Turned years + + public Birthday(String id, String given, String name, long date, int age) { + this.id = id; + this.given = given; + this.name = name; + this.date = date; + this.age = age; + } + + @Override + public String toString() { + DateFormat sdf = new SimpleDateFormat("d MMM y", Locale.US); + return "[" + name + ": " + age + " (" + sdf.format(date) + ")]"; + } + } + + /** + * Blueprint of the file 'settings.json' inside a backup, share or example ZIP file + * It contains basic info of the zipped tree + */ + static class ZippedTree { + String title; + int persons; + int generations; + String root; + List shares; + int grade; // the destination "grade" (degree?) of the zipped tree + + ZippedTree(String title, int persons, int generations, String root, List shares, int grade) { + this.title = title; + this.persons = persons; + this.generations = generations; + this.root = root; + this.shares = shares; + this.grade = grade; + } + + File save() { + File settingsFile = new File(Global.context.getCacheDir(), "settings.json"); + try { + FileUtils.writeStringToFile(settingsFile, new Gson().toJson(this), "UTF-8"); + } catch (Exception e) { + } + return settingsFile; + } + } } \ No newline at end of file diff --git a/app/src/main/java/app/familygem/SharingActivity.java b/app/src/main/java/app/familygem/SharingActivity.java new file mode 100644 index 00000000..5e780e9e --- /dev/null +++ b/app/src/main/java/app/familygem/SharingActivity.java @@ -0,0 +1,331 @@ +package app.familygem; + +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Bundle; +import android.view.MenuItem; +import android.view.View; +import android.view.inputmethod.InputMethodManager; +import android.widget.CheckBox; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.Toast; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.app.AppCompatActivity; +import org.apache.commons.net.ftp.FTP; +import org.apache.commons.net.ftp.FTPClient; +import org.folg.gedcom.model.Gedcom; +import org.folg.gedcom.model.Header; +import org.folg.gedcom.model.Person; +import org.folg.gedcom.model.Submitter; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLEncoder; + +public class SharingActivity extends BaseActivity { + + Gedcom gc; + Settings.Tree tree; + Exporter exporter; + String submitterName; + int accessible; // 0 = false, 1 = true //TODO why isn't this a boolean? + String dataId; + String submitterId; + boolean uploadSuccessful; + + @Override + protected void onCreate(Bundle bundle) { + super.onCreate(bundle); + setContentView(R.layout.condivisione); + + final int treeId = getIntent().getIntExtra("idAlbero", 1); + tree = Global.settings.getTree(treeId); + + // Title of the tree + final EditText titleEditText = findViewById(R.id.condividi_titolo); + titleEditText.setText(tree.title); + + if( tree.grade == 10 ) + ((TextView)findViewById( R.id.condividi_tit_autore )).setText( R.string.changes_submitter ); + + exporter = new Exporter( this ); + exporter.openTree( treeId ); + gc = Global.gc; + if( gc != null ) { + displayShareRoot(); + // Author name + final Submitter[] submitters = new Submitter[1]; //needs to be final one-element array because it is captured by lambda. See https://stackoverflow.com/questions/34865383/variable-used-in-lambda-expression-should-be-final-or-effectively-final + // tree in Italy with submitter referenced + if( tree.grade == 0 && gc.getHeader() != null && gc.getHeader().getSubmitter(gc) != null ) + submitters[0] = gc.getHeader().getSubmitter( gc ); + // in Italy there are authors but none referenced, it takes the last one + else if( tree.grade == 0 && !gc.getSubmitters().isEmpty() ) + submitters[0] = gc.getSubmitters().get(gc.getSubmitters().size()-1); + // in Australia there are new authors, take one + else if( tree.grade == 10 && U.newSubmitter(gc) != null ) + submitters[0] = U.newSubmitter(gc); + final EditText authorEditText = findViewById(R.id.condividi_autore); + submitterName = submitters[0] == null ? "" : submitters[0].getName(); + authorEditText.setText(submitterName); + + // Display an alert for the acknowledgment of sharing + if( !Global.settings.shareAgreement ) { + new AlertDialog.Builder(this).setTitle(R.string.share_sensitive) + .setMessage(R.string.aware_upload_server) + .setPositiveButton(android.R.string.ok, (dialog, id) -> { + Global.settings.shareAgreement = true; + Global.settings.save(); + }).setNeutralButton(R.string.remind_later, null).show(); + } + + // Collect share data and post to database + findViewById( R.id.bottone_condividi ).setOnClickListener( v -> { + if(uploadSuccessful) + showLinkSharingChooserDialog(); + else { + if( isFilledIn(titleEditText, R.string.please_title) || isFilledIn(authorEditText, R.string.please_name) ) + return; + + v.setEnabled(false); + findViewById(R.id.condividi_circolo).setVisibility(View.VISIBLE); + + // Title of the tree + String editedTitle = titleEditText.getText().toString(); + if( !tree.title.equals(editedTitle) ) { + tree.title = editedTitle; + Global.settings.save(); + } + + // Submitter update + Header header = gc.getHeader(); + if( header == null ) { + header = NewTree.createHeader(tree.id + ".json"); + gc.setHeader(header); + } else + header.setDateTime(U.actualDateTime()); + if( submitters[0] == null ) { + submitters[0] = ListOfAuthorsFragment.newAuthor(null); + } + if( header.getSubmitterRef() == null ) { + header.setSubmitterRef(submitters[0].getId()); + } + String editedAuthorName = authorEditText.getText().toString(); + if( !editedAuthorName.equals(submitterName) ) { + submitterName = editedAuthorName; + submitters[0].setName(submitterName); + U.updateChangeDate(submitters[0]); + } + submitterId = submitters[0].getId(); + U.saveJson(gc, treeId); // bypassing the preference not to save automatically + + // Tree accessibility for app developer + CheckBox accessibleTree = findViewById(R.id.condividi_allow); + accessible = accessibleTree.isChecked() ? 1 : 0; + + // Submit the data + if( !BuildConfig.utenteAruba.isEmpty() ) + new PostDataShareAsyncTask().execute( this ); + } + }); + } else + findViewById( R.id.condividi_scatola ).setVisibility( View.GONE ); + } + + /** + * The person root of the tree + */ + View rootView; + void displayShareRoot() { + String rootId; + if( tree.shareRoot != null && gc.getPerson(tree.shareRoot) != null ) + rootId = tree.shareRoot; + else if( tree.root != null && gc.getPerson(tree.root) != null ) { + rootId = tree.root; + tree.shareRoot = rootId; // to be able to share the tree immediately without changing the root + } else { + rootId = U.findRoot(gc); + tree.shareRoot = rootId; + } + Person person = gc.getPerson(rootId); + if( person != null && tree.grade < 10 ) { // it is only shown on the first share, not on return + LinearLayout rootLayout = findViewById(R.id.condividi_radice); + rootLayout.removeView(rootView); + rootLayout.setVisibility(View.VISIBLE); + rootView = U.linkPerson(rootLayout, person, 1); + rootView.setOnClickListener(v -> { + Intent intent = new Intent(this, Principal.class); + intent.putExtra("anagrafeScegliParente", true); + startActivityForResult(intent, 5007); + }); + } + } + + /** + * Check that a field is filled in + */ + boolean isFilledIn(EditText campo, int msg) { + String text = campo.getText().toString(); + if( text.isEmpty() ) { + campo.requestFocus(); + InputMethodManager imm = (InputMethodManager) getSystemService( Context.INPUT_METHOD_SERVICE); + imm.showSoftInput(campo, InputMethodManager.SHOW_IMPLICIT); + Toast.makeText(this, msg, Toast.LENGTH_SHORT ).show(); + return true; + } + return false; + } + + /** + * Inserts the summary of the share in the database of www.familygem.app + * If all goes well create the zip file with the tree and the images + */ + static class PostDataShareAsyncTask extends AsyncTask { + @Override + protected SharingActivity doInBackground(SharingActivity... contexts) { + SharingActivity activity = contexts[0]; + try { + URL url = new URL("https://www.familygem.app/inserisci.php"); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("POST"); + OutputStream out = new BufferedOutputStream( conn.getOutputStream() ); + BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out, "UTF-8")); + String data = "password=" + URLEncoder.encode( BuildConfig.passwordAruba, "UTF-8") + + "&titoloAlbero=" + URLEncoder.encode( activity.tree.title, "UTF-8") + + "&nomeAutore=" + URLEncoder.encode( activity.submitterName, "UTF-8") + + "&accessibile=" + activity.accessible; + writer.write( data ); + writer.flush(); + writer.close(); + out.close(); + + // Response + BufferedReader reader = new BufferedReader( new InputStreamReader(conn.getInputStream()) ); + String line1 = reader.readLine(); + reader.close(); + conn.disconnect(); + if( line1.startsWith("20") ) { + activity.dataId = line1.replaceAll( "[-: ]", "" ); + Settings.Share share = new Settings.Share( activity.dataId, activity.submitterId); + activity.tree.aggiungiCondivisione(share); + Global.settings.save(); + } + } catch( Exception e ) { + U.toast( activity, e.getLocalizedMessage() ); + } + return activity; + } + + @Override + protected void onPostExecute(SharingActivity activity) { + if( activity.dataId != null && activity.dataId.startsWith("20") ) { + File fileTree = new File( activity.getCacheDir(), activity.dataId + ".zip" ); + if( activity.exporter.exportBackupZip(activity.tree.shareRoot, 9, Uri.fromFile(fileTree)) ) { + new FTPUploadAsyncTask().execute( activity ); + return; + } else + Toast.makeText( activity, activity.exporter.errorMessage, Toast.LENGTH_LONG ).show(); + } + // An error Toast here would replace the toast() message in catch() + activity.findViewById( R.id.bottone_condividi ).setEnabled(true); + activity.findViewById( R.id.condividi_circolo ).setVisibility(View.INVISIBLE); + } + } + + /** + * Upload the zip file with the shared tree by ftp. + */ + static class FTPUploadAsyncTask extends AsyncTask { + protected SharingActivity doInBackground(SharingActivity... contesti) { + SharingActivity activity = contesti[0]; + try { + FTPClient ftpClient = new FTPClient(); + ftpClient.connect( "89.46.104.211", 21 ); + ftpClient.enterLocalPassiveMode(); + ftpClient.login( BuildConfig.utenteAruba, BuildConfig.passwordAruba ); + ftpClient.changeWorkingDirectory("/www.familygem.app/condivisi"); + ftpClient.setFileType( FTP.BINARY_FILE_TYPE ); + BufferedInputStream buffIn; + String zipName = activity.dataId + ".zip"; + buffIn = new BufferedInputStream( new FileInputStream( activity.getCacheDir() + "/" + zipName ) ); + activity.uploadSuccessful = ftpClient.storeFile( zipName, buffIn ); + buffIn.close(); + ftpClient.logout(); + ftpClient.disconnect(); + } catch( Exception e ) { + U.toast( activity, e.getLocalizedMessage() ); + } + return activity; + } + protected void onPostExecute(SharingActivity activity) { + if( activity.uploadSuccessful) { + Toast.makeText( activity, R.string.correctly_uploaded, Toast.LENGTH_SHORT ).show(); + activity.showLinkSharingChooserDialog(); + } else { + activity.findViewById( R.id.bottone_condividi ).setEnabled(true); + activity.findViewById( R.id.condividi_circolo ).setVisibility( View.INVISIBLE ); + } + } + } + + /** + * Show apps to share the link + */ + void showLinkSharingChooserDialog() { + Intent intent = new Intent( Intent.ACTION_SEND ); + intent.setType( "text/plain" ); + intent.putExtra( Intent.EXTRA_SUBJECT, getString( R.string.sharing_tree ) ); + intent.putExtra( Intent.EXTRA_TEXT, getString( R.string.click_this_link, + "https://www.familygem.app/share.php?tree=" + dataId ) ); + //startActivity( Intent.createChooser( intent, "Condividi con" ) ); + /* + Coming back from a messaging app the requestCode 35417 always arrives correct + Instead the resultCode can be RESULT_OK or RESULT_CANCELED at head + For example from Gmail it always comes back with RESULT_CANCELED whether the email has been sent or not + also when sending an SMS it returns RESULT_CANCELED even if the SMS has been sent + or from Whatsapp it is RESULT_OK whether the message was sent or not + In practice, there is no way to know if the message has been sent in the messaging app + */ + startActivityForResult( Intent.createChooser(intent,getText(R.string.share_with)),35417 ); + findViewById( R.id.bottone_condividi ).setEnabled(true); + findViewById( R.id.condividi_circolo ).setVisibility( View.INVISIBLE ); + } + + /** + * Update the preferences so as to show the new root chosen in the ListOfPeopleActivity. + * See links in comment in {@link #showLinkSharingChooserDialog()} for meaning of request codes + */ + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if( resultCode == AppCompatActivity.RESULT_OK ) { + if( requestCode == 5007 ) { + tree.shareRoot = data.getStringExtra("idParente"); + Global.settings.save(); + displayShareRoot(); + } + } + // Return from any sharing app, whether the message was sent or not + if( requestCode == 35417 ) { + // Todo close keyboard + Toast.makeText(getApplicationContext(), R.string.sharing_completed, Toast.LENGTH_LONG).show(); + } + } + + @Override + public boolean onOptionsItemSelected( MenuItem i ) { + onBackPressed(); + return true; + } +} \ No newline at end of file diff --git a/app/src/main/java/app/familygem/SpeechBubble.java b/app/src/main/java/app/familygem/SpeechBubble.java new file mode 100644 index 00000000..256b7bc3 --- /dev/null +++ b/app/src/main/java/app/familygem/SpeechBubble.java @@ -0,0 +1,49 @@ +package app.familygem; + +import android.app.Activity; +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.TextView; + +/** + * Speech bubble with a hint appearing above the FAB + * */ +public class SpeechBubble { + + private final View bubble; + + public SpeechBubble(Context context, int textId) { + this(context, context.getString(textId)); + } + + public SpeechBubble(Context context, String testo) { + Activity activity = (Activity)context; + bubble = activity.getLayoutInflater().inflate(R.layout.fabuloso, null); + bubble.setVisibility(View.INVISIBLE); + ((LinearLayout)activity.findViewById(R.id.fab_box)).addView(bubble, 0, + new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT)); + ((TextView) bubble.findViewById(R.id.fabuloso_text)).setText(testo); + bubble.setOnTouchListener((view, event) -> { + hide(); + return true; + }); + activity.findViewById(R.id.fab).setOnTouchListener((view, event) -> { + hide(); + //view.performClick(); + return false; // To execute click later + }); + } + + public void show() { + new Handler( Looper.myLooper()).postDelayed( () -> { // appears after one second + bubble.setVisibility( View.VISIBLE ); + }, 1000); + } + + public void hide() { + bubble.setVisibility( View.INVISIBLE ); + } +} diff --git a/app/src/main/java/app/familygem/Lapide.java b/app/src/main/java/app/familygem/TombstoneActivity.java similarity index 83% rename from app/src/main/java/app/familygem/Lapide.java rename to app/src/main/java/app/familygem/TombstoneActivity.java index b34ed84d..c1fce4d1 100644 --- a/app/src/main/java/app/familygem/Lapide.java +++ b/app/src/main/java/app/familygem/TombstoneActivity.java @@ -6,7 +6,7 @@ import android.os.Bundle; import android.widget.TextView; -public class Lapide extends BaseActivity { +public class TombstoneActivity extends BaseActivity { @Override protected void onCreate(Bundle bundle) { @@ -17,6 +17,7 @@ protected void onCreate(Bundle bundle) { version.setText(getString(R.string.version_name, BuildConfig.VERSION_NAME)); TextView link = findViewById(R.id.lapide_link); + //TODO replace with LinkMovementMethod and (or?) LinkifyCompat.addLinks() link.setPaintFlags(link.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG); link.setOnClickListener(v -> startActivity( new Intent(Intent.ACTION_VIEW, Uri.parse("https://www.familygem.app"))) diff --git a/app/src/main/java/app/familygem/TreeComparatorActivity.java b/app/src/main/java/app/familygem/TreeComparatorActivity.java new file mode 100644 index 00000000..5439a4b7 --- /dev/null +++ b/app/src/main/java/app/familygem/TreeComparatorActivity.java @@ -0,0 +1,250 @@ +package app.familygem; + +import android.content.Intent; +import android.os.Bundle; +import android.util.TypedValue; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.ProgressBar; +import android.widget.TextView; +import androidx.appcompat.content.res.AppCompatResources; +import androidx.cardview.widget.CardView; +import org.folg.gedcom.model.Change; +import org.folg.gedcom.model.Family; +import org.folg.gedcom.model.Gedcom; +import org.folg.gedcom.model.Media; +import org.folg.gedcom.model.Note; +import org.folg.gedcom.model.Person; +import org.folg.gedcom.model.Repository; +import org.folg.gedcom.model.Source; +import org.folg.gedcom.model.Submitter; + +/** + * Activity to evaluate a record of the imported tree, with possible comparison with the corresponding record of the old tree + * */ +public class TreeComparatorActivity extends BaseActivity { + + Class clazz; // the ruling class of the activity + int destiny; + + @Override + protected void onCreate( Bundle bandolo ) { + super.onCreate( bandolo ); + setContentView( R.layout.confronto ); + + if( Comparison.getList().size() > 0 ) { + + int max; + int position; + if( Comparison.get().autoContinue) { + max = Comparison.get().numChoices; + position = Comparison.get().choicesMade; + } else { + max = Comparison.getList().size(); + position = getIntent().getIntExtra("posizione",0); + } + ProgressBar progressBar = findViewById( R.id.confronto_progresso ); + progressBar.setMax( max ); + progressBar.setProgress( position ); + ((TextView)findViewById( R.id.confronto_stato )).setText( position+"/"+max ); + + final Object o = Comparison.getFront(this).object; + final Object o2 = Comparison.getFront(this).object2; + if( o != null ) clazz = o.getClass(); + else clazz = o2.getClass(); + setupCard( Global.gc, R.id.confronto_vecchio, o ); + setupCard( Global.gc2, R.id.confronto_nuovo, o2 ); + + destiny = 2; + + Button okButton = findViewById(R.id.confronto_bottone_ok); + okButton.setBackground( AppCompatResources.getDrawable(getApplicationContext(),R.drawable.frecciona) ); + if( o == null ) { + destiny = 1; + okButton.setText( R.string.add ); + okButton.setBackgroundColor( 0xff00dd00 ); // getResources().getColor(R.color.evidenzia) + okButton.setHeight( 30 ); // ineffective TODO this does not meet Material design guidelines for 48x48 dp touch targets + } else if( o2 == null ) { + destiny = 3; + okButton.setText( R.string.delete ); + okButton.setBackgroundColor( 0xffff0000 ); + } else if( Comparison.getFront(this).canBothAddAndReplace) { + // Another Add button + Button addButton = new Button( this ); + addButton.setTextSize( TypedValue.COMPLEX_UNIT_SP,16 ); + addButton.setTextColor( 0xFFFFFFFF ); + LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( LinearLayout.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT ); + params.rightMargin = 15; + params.weight = 3; + addButton.setLayoutParams( params ); + addButton.setText( R.string.add ); + addButton.setBackgroundColor( 0xff00dd00 ); + addButton.setOnClickListener( v -> { + Comparison.getFront(this).destiny = 1; + continueToNext(); + }); + (( LinearLayout)findViewById( R.id.confronto_bottoni )).addView( addButton, 1 ); + } + + // Continues automatically if there is no double action to choose + if( Comparison.get().autoContinue && !Comparison.getFront(this).canBothAddAndReplace) { + Comparison.getFront(this).destiny = destiny; + continueToNext(); + } + + // Button to accept the new + okButton.setOnClickListener( vista -> { + Comparison.getFront(this).destiny = destiny; + continueToNext(); + }); + + findViewById(R.id.confronto_bottone_ignora ).setOnClickListener( v -> { + Comparison.getFront(this).destiny = 0; + continueToNext(); + }); + } else + onBackPressed(); // Return to Compare + } + + void setupCard(Gedcom gc, int cardId, Object o ) { + String tit = ""; + String txt = ""; //TODO give better names + String data = ""; + CardView card = findViewById(cardId); + ImageView imageView = card.findViewById( R.id.confronto_foto ); + if( o instanceof Note ) { + setRecordTypeTextTo( R.string.shared_note ); + Note n = (Note) o; + txt = n.getValue(); + data = dateHour( n.getChange() ); + } + else if( o instanceof Submitter ) { + setRecordTypeTextTo( R.string.submitter ); + Submitter s = (Submitter) o; + tit = s.getName(); + if( s.getEmail() != null ) txt += s.getEmail() + "\n"; + if( s.getAddress() != null ) txt += DetailActivity.writeAddress(s.getAddress(), true); + data = dateHour(s.getChange()); + } + else if( o instanceof Repository ) { + setRecordTypeTextTo( R.string.repository ); + Repository r = (Repository) o; + tit = r.getName(); + if( r.getAddress() != null ) txt += DetailActivity.writeAddress(r.getAddress(), true) + "\n"; + if( r.getEmail() != null ) txt += r.getEmail(); + data = dateHour(r.getChange()); + } + else if( o instanceof Media ) { + setRecordTypeTextTo( R.string.shared_media ); + Media m = (Media) o; + if(m.getTitle()!=null) tit = m.getTitle(); + txt = m.getFile(); + data = dateHour( m.getChange() ); + imageView.setVisibility( View.VISIBLE ); + F.showImage( m, imageView, null ); + } + else if( o instanceof Source ) { + setRecordTypeTextTo( R.string.source ); + Source f = (Source) o; + if(f.getTitle()!=null) tit = f.getTitle(); + else if(f.getAbbreviation()!=null) tit = f.getAbbreviation(); + if(f.getAuthor()!=null) txt = f.getAuthor()+"\n"; + if(f.getPublicationFacts()!=null) txt += f.getPublicationFacts()+"\n"; + if(f.getText()!=null) txt += f.getText(); + data = dateHour( f.getChange() ); + } + else if( o instanceof Person ) { + setRecordTypeTextTo( R.string.person ); + Person p = (Person) o; + tit = U.properName( p ); + txt = U.details( p, null ); + data = dateHour( p.getChange() ); + imageView.setVisibility( View.VISIBLE ); + F.showMainImageForPerson( gc, p, imageView ); + } + else if( o instanceof Family ) { + setRecordTypeTextTo( R.string.family ); + Family f = (Family) o; + txt = U.familyText( this, gc, f, false ); + data = dateHour( f.getChange() ); + } + TextView titleText = card.findViewById( R.id.confronto_titolo ); + if( tit == null || tit.isEmpty() ) + titleText.setVisibility( View.GONE ); + else + titleText.setText( tit ); + + TextView textTextView = card.findViewById( R.id.confronto_testo ); + if( txt.isEmpty() ) + textTextView.setVisibility( View.GONE ); + else { + if( txt.endsWith( "\n" ) ) + txt = txt.substring( 0, txt.length() - 1 ); + textTextView.setText( txt ); + } + + View changesView = card.findViewById(R.id.confronto_data); + if( data.isEmpty() ) + changesView.setVisibility(View.GONE); + else + ((TextView)changesView.findViewById(R.id.cambi_testo)).setText(data); + + if( cardId == R.id.confronto_nuovo ) { + card.setCardBackgroundColor(getResources().getColor(R.color.evidenziaMedio)); + } + + if( tit.isEmpty() && txt.isEmpty() && data.isEmpty() ) // todo do you mean object null? + card.setVisibility( View.GONE ); + } + + /** + * Page title. + */ + void setRecordTypeTextTo(int string ) { + TextView typeText = findViewById( R.id.confronto_tipo ); + typeText.setText( getString(string) ); + } + + String dateHour(Change change ) { + String dateHour = ""; + if( change != null ) + dateHour = change.getDateTime().getValue() + " - " + change.getDateTime().getTime(); + return dateHour; + } + + void continueToNext() { + Intent intent = new Intent(); + if( getIntent().getIntExtra("posizione",0) == Comparison.getList().size() ) { + // The comparisons are over + intent.setClass( this, ConfirmationActivity.class ); + } else { + // Next comparison + intent.setClass( this, TreeComparatorActivity.class ); + intent.putExtra( "posizione", getIntent().getIntExtra("posizione",0) + 1 ); + } + if( Comparison.get().autoContinue) { + if( Comparison.getFront(this).canBothAddAndReplace) + Comparison.get().choicesMade++; + else + finish(); // removes the current front from the stack + } + startActivity( intent ); + } + + @Override + public boolean onOptionsItemSelected( MenuItem i ) { + onBackPressed(); + return true; + } + + @Override + public void onBackPressed() { + super.onBackPressed(); + if( Comparison.get().autoContinue) + Comparison.get().choicesMade--; + } +} \ No newline at end of file diff --git a/app/src/main/java/app/familygem/TreeInfoActivity.java b/app/src/main/java/app/familygem/TreeInfoActivity.java new file mode 100644 index 00000000..6892597a --- /dev/null +++ b/app/src/main/java/app/familygem/TreeInfoActivity.java @@ -0,0 +1,343 @@ +package app.familygem; + +import android.graphics.Typeface; +import android.os.Bundle; +import android.view.Gravity; +import android.view.MenuItem; +import android.view.View; +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.TableLayout; +import android.widget.TableRow; +import android.widget.TextView; + +import org.folg.gedcom.model.CharacterSet; +import org.folg.gedcom.model.Family; +import org.folg.gedcom.model.Gedcom; +import org.folg.gedcom.model.GedcomVersion; +import org.folg.gedcom.model.Generator; +import org.folg.gedcom.model.Header; +import org.folg.gedcom.model.Person; +import org.folg.gedcom.model.Submitter; + +import java.io.File; +import java.util.Locale; + +import app.familygem.visitor.MediaList; + +public class TreeInfoActivity extends BaseActivity { + + Gedcom gc; + + @Override + protected void onCreate(Bundle bundle) { + super.onCreate(bundle); + setContentView(R.layout.info_albero); + LinearLayout layout = findViewById(R.id.info_scatola); + + final int treeId = getIntent().getIntExtra("idAlbero", 1); + final Settings.Tree tree = Global.settings.getTree(treeId); + final File file = new File(getFilesDir(), treeId + ".json"); + StringBuilder i = new StringBuilder(getText(R.string.title) + ": " + tree.title); + if (!file.exists()) { + i.append("\n\n").append(getText(R.string.item_exists_but_file)).append("\n").append(file.getAbsolutePath()); + } else { + i.append("\n").append(getText(R.string.file)).append(": ").append(file.getAbsolutePath()); + gc = TreesActivity.openGedcomTemporarily(treeId, false); + if (gc == null) + i.append("\n\n").append(getString(R.string.no_useful_data)); + else { + // Automatic or on-demand data update + if (tree.persons < 100) { + refreshData(gc, tree); + } else { + Button updateButton = findViewById(R.id.info_aggiorna); + updateButton.setVisibility(View.VISIBLE); + updateButton.setOnClickListener(v -> { + refreshData(gc, tree); + recreate(); + }); + } + i + .append("\n\n") + .append(getText(R.string.persons)).append(": ").append(tree.persons) + .append("\n") + .append(getText(R.string.families)).append(": ").append(gc.getFamilies().size()) + .append("\n") + .append(getText(R.string.generations)).append(": ").append(tree.generations) + .append("\n") + .append(getText(R.string.media)).append(": ").append(tree.media) + .append("\n") + .append(getText(R.string.sources)).append(": ").append(gc.getSources().size()) + .append("\n") + .append(getText(R.string.repositories)).append(": ").append(gc.getRepositories().size()); + if (tree.root != null) { + i.append("\n").append(getText(R.string.root)).append(": ").append(U.properName(gc.getPerson(tree.root))); + } + if (tree.shares != null && !tree.shares.isEmpty()) { + i.append("\n\n").append(getText(R.string.shares)).append(":"); + for (Settings.Share share : tree.shares) { + i.append("\n").append(dataIdToDate(share.dateId)); + if (gc.getSubmitter(share.submitter) != null) + i.append(" - ").append(submitterName(gc.getSubmitter(share.submitter))); + } + } + } + } + ((TextView) findViewById(R.id.info_statistiche)).setText(i.toString()); + + Button headerButton = layout.findViewById(R.id.info_gestisci_testata); + if (gc != null) { + Header h = gc.getHeader(); + if (h == null) { + headerButton.setText(R.string.create_header); + headerButton.setOnClickListener(view -> { + gc.setHeader(NewTree.createHeader(file.getName())); + U.saveJson(gc, treeId); + recreate(); + }); + } else { + layout.findViewById(R.id.info_testata).setVisibility(View.VISIBLE); + if (h.getFile() != null) + place(getText(R.string.file), h.getFile()); + if (h.getCharacterSet() != null) { + place(getText(R.string.characrter_set), h.getCharacterSet().getValue()); + place(getText(R.string.version), h.getCharacterSet().getVersion()); + } + space(); // a little space + place(getText(R.string.language), h.getLanguage()); + space(); + place(getText(R.string.copyright), h.getCopyright()); + space(); + if (h.getGenerator() != null) { + place(getText(R.string.software), h.getGenerator().getName() != null ? h.getGenerator().getName() : h.getGenerator().getValue()); + place(getText(R.string.version), h.getGenerator().getVersion()); + if (h.getGenerator().getGeneratorCorporation() != null) { + place(getText(R.string.corporation), h.getGenerator().getGeneratorCorporation().getValue()); + if (h.getGenerator().getGeneratorCorporation().getAddress() != null) + place(getText(R.string.address), h.getGenerator().getGeneratorCorporation().getAddress().getDisplayValue()); // non è male + place(getText(R.string.telephone), h.getGenerator().getGeneratorCorporation().getPhone()); + place(getText(R.string.fax), h.getGenerator().getGeneratorCorporation().getFax()); + } + space(); + if (h.getGenerator().getGeneratorData() != null) { + place(getText(R.string.source), h.getGenerator().getGeneratorData().getValue()); + place(getText(R.string.date), h.getGenerator().getGeneratorData().getDate()); + place(getText(R.string.copyright), h.getGenerator().getGeneratorData().getCopyright()); + } + } + space(); + if (h.getSubmitter(gc) != null) + place(getText(R.string.submitter), submitterName(h.getSubmitter(gc))); // todo: make it clickable? + if (gc.getSubmission() != null) + place(getText(R.string.submission), gc.getSubmission().getDescription()); // todo: clickable + space(); + if (h.getGedcomVersion() != null) { + place(getText(R.string.gedcom), h.getGedcomVersion().getVersion()); + place(getText(R.string.form), h.getGedcomVersion().getForm()); + } + place(getText(R.string.destination), h.getDestination()); + space(); + if (h.getDateTime() != null) { + place(getText(R.string.date), h.getDateTime().getValue()); + place(getText(R.string.time), h.getDateTime().getTime()); + } + space(); + for (Extension est : U.findExtensions(h)) { // each extension in its own line + place(est.name, est.text); + } + space(); + if (ruler != null) + ((TableLayout) findViewById(R.id.info_tabella)).removeView(ruler); + + // Button to update the GEDCOM header with the Family Gem parameters + headerButton.setOnClickListener(view -> { + h.setFile(treeId + ".json"); + CharacterSet charSet = h.getCharacterSet(); + if (charSet == null) { + charSet = new CharacterSet(); + h.setCharacterSet(charSet); + } + charSet.setValue("UTF-8"); + charSet.setVersion(null); + + Locale loc = new Locale(Locale.getDefault().getLanguage()); + h.setLanguage(loc.getDisplayLanguage(Locale.ENGLISH)); + + Generator generator = h.getGenerator(); + if (generator == null) { + generator = new Generator(); + h.setGenerator(generator); + } + generator.setValue("FAMILY_GEM"); + generator.setName(getString(R.string.app_name)); + //generator.setVersion( BuildConfig.VERSION_NAME ); // will saveJson() + generator.setGeneratorCorporation(null); + + GedcomVersion gedcomVersion = h.getGedcomVersion(); + if (gedcomVersion == null) { + gedcomVersion = new GedcomVersion(); + h.setGedcomVersion(gedcomVersion); + } + gedcomVersion.setVersion("5.5.1"); + gedcomVersion.setForm("LINEAGE-LINKED"); + h.setDestination(null); + + U.saveJson(gc, treeId); + recreate(); + }); + + U.placeNotes(layout, h, true); + } + // Extensions of Gedcom, i.e. non-standard level 0 zero tags + for (Extension est : U.findExtensions(gc)) { + U.place(layout, est.name, est.text); + } + } else + headerButton.setVisibility(View.GONE); + } + + String dataIdToDate(String id) { + if (id == null) return ""; + return id.substring(0, 4) + "-" + id.substring(4, 6) + "-" + id.substring(6, 8) + " " + + id.substring(8, 10) + ":" + id.substring(10, 12) + ":" + id.substring(12); + } + + static String submitterName(Submitter submitter) { + String name = submitter.getName(); + if (name == null) + name = "[" + Global.context.getString(R.string.no_name) + "]"; + else if (name.isEmpty()) + name = "[" + Global.context.getString(R.string.empty_name) + "]"; + return name; + } + + /** + * Refresh the data displayed below the tree title in {@link TreesActivity} list + */ + static void refreshData(Gedcom gedcom, Settings.Tree treeItem) { + treeItem.persons = gedcom.getPeople().size(); + treeItem.generations = countGenerations(gedcom, U.getRootId(gedcom, treeItem)); + MediaList mediaList = new MediaList(gedcom, 0); + gedcom.accept(mediaList); + treeItem.media = mediaList.list.size(); + Global.settings.save(); + } + + boolean putText; // prevents putting more than one consecutive space() + + void place(CharSequence title, String text) { + if (text != null) { + TableRow row = new TableRow(this); + TextView cell1 = new TextView(this); + cell1.setTextSize(14); + cell1.setTypeface(null, Typeface.BOLD); + cell1.setPaddingRelative(0, 0, 10, 0); + cell1.setGravity(Gravity.END); // Does not work on RTL layout + cell1.setText(title); + row.addView(cell1); + TextView cell2 = new TextView(this); + cell2.setTextSize(14); + cell2.setPadding(0, 0, 0, 0); + cell2.setGravity(Gravity.START); + cell2.setText(text); + row.addView(cell2); + ((TableLayout) findViewById(R.id.info_tabella)).addView(row); + putText = true; + } + } + + TableRow ruler; + + void space() { + if (putText) { + ruler = new TableRow(getApplicationContext()); + View cell = new View(getApplicationContext()); + cell.setBackgroundResource(R.color.primario); + ruler.addView(cell); + TableRow.LayoutParams param = (TableRow.LayoutParams) cell.getLayoutParams(); + param.weight = 1; + param.span = 2; + param.height = 1; + param.topMargin = 5; + param.bottomMargin = 5; + cell.setLayoutParams(param); + ((TableLayout) findViewById(R.id.info_tabella)).addView(ruler); + putText = false; + } + } + + static int genMin; + static int genMax; + + public static int countGenerations(Gedcom gc, String root) { + if (gc.getPeople().isEmpty()) + return 0; + genMin = 0; + genMax = 0; + goToUpEarliestGeneration(gc.getPerson(root), gc, 0); + goDownToEarliestGeneration(gc.getPerson(root), gc, 0); + // Removes the 'gen' extension from people to allow for later counting + for (Person person : gc.getPeople()) { + person.getExtensions().remove("gen"); + if (person.getExtensions().isEmpty()) + person.setExtensions(null); + } + return 1 - genMin + genMax; + } + + /** + * accepts a Person and finds the number of the earliest generation of ancestors + */ + static void goToUpEarliestGeneration(Person person, Gedcom gc, int gen) { + if (gen < genMin) + genMin = gen; + // adds the extension to indicate that it has passed from this Person + person.putExtension("gen", gen); + // if he is a progenitor it counts the generations of descendants or goes back to any other marriages + if (person.getParentFamilies(gc).isEmpty()) + goDownToEarliestGeneration(person, gc, gen); + for (Family family : person.getParentFamilies(gc)) { + // intercept any siblings of the root + for (Person sibling : family.getChildren(gc)) + if (sibling.getExtension("gen") == null) + goDownToEarliestGeneration(sibling, gc, gen); + for (Person father : family.getHusbands(gc)) + if (father.getExtension("gen") == null) + goToUpEarliestGeneration(father, gc, gen - 1); + for (Person mother : family.getWives(gc)) + if (mother.getExtension("gen") == null) + goToUpEarliestGeneration(mother, gc, gen - 1); + } + } + + /** + * receives a Person and finds the number of the earliest generation of descendants + * */ + static void goDownToEarliestGeneration(Person person, Gedcom gc, int gen) { + if (gen > genMax) + genMax = gen; + person.putExtension("gen", gen); + for (Family family : person.getSpouseFamilies(gc)) { + // also identifies the spouses' family + for (Person wife : family.getWives(gc)) + if (wife.getExtension("gen") == null) + goToUpEarliestGeneration(wife, gc, gen); + for (Person husband : family.getHusbands(gc)) + if (husband.getExtension("gen") == null) + goToUpEarliestGeneration(husband, gc, gen); + for (Person child : family.getChildren(gc)) + if (child.getExtension("gen") == null) + goDownToEarliestGeneration(child, gc, gen + 1); + } + } + + /** + * back arrow in the toolbar like the hardware one + * */ + @Override + public boolean onOptionsItemSelected(MenuItem i) { + onBackPressed(); + return true; + } +} diff --git a/app/src/main/java/app/familygem/Alberi.java b/app/src/main/java/app/familygem/TreesActivity.java similarity index 61% rename from app/src/main/java/app/familygem/Alberi.java rename to app/src/main/java/app/familygem/TreesActivity.java index e5adba12..2ce77a68 100644 --- a/app/src/main/java/app/familygem/Alberi.java +++ b/app/src/main/java/app/familygem/TreesActivity.java @@ -41,40 +41,45 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import app.familygem.visitor.ListaMedia; +import app.familygem.visitor.MediaList; -public class Alberi extends AppCompatActivity { +public class TreesActivity extends AppCompatActivity { - List> elencoAlberi; + List> treeList; SimpleAdapter adapter; - View rotella; - Fabuloso welcome; - Esportatore esportatore; - private boolean autoOpenedTree; // To open automatically the tree at startup only once - // The birthday notification IDs are stored to display the corresponding person only once + View wheel; + SpeechBubble welcome; + Exporter exporter; + /** + * To open automatically the tree at startup only once + */ + private boolean autoOpenedTree; + /** + * The birthday notification IDs are stored to display the corresponding person only once + */ private ArrayList consumedNotifications = new ArrayList<>(); @Override protected void onCreate(Bundle savedState) { super.onCreate(savedState); setContentView(R.layout.alberi); - ListView vistaLista = findViewById(R.id.lista_alberi); - rotella = findViewById(R.id.alberi_circolo); - welcome = new Fabuloso(this, R.string.tap_add_tree); - esportatore = new Esportatore(Alberi.this); + ListView listView = findViewById(R.id.lista_alberi); + wheel = findViewById(R.id.alberi_circolo); + welcome = new SpeechBubble(this, R.string.tap_add_tree); + exporter = new Exporter(TreesActivity.this); - // Al primissimo avvio + // At the very first start String referrer = Global.settings.referrer; if( referrer != null && referrer.equals("start") ) - recuperaReferrer(); - // Se è stato memorizzato un dataid (che appena usato sarà cancellato) + fetchReferrer(); + // If a dataid has been stored (which will be deleted as soon as used) else if( referrer != null && referrer.matches("[0-9]{14}") ) { new AlertDialog.Builder(this).setTitle(R.string.a_new_tree) .setMessage(R.string.you_can_download) .setPositiveButton(R.string.download, (dialog, id) -> { - Facciata.scaricaCondiviso(this, referrer, rotella); + FacadeActivity.downloadShared(this, referrer, wheel); }).setNeutralButton(R.string.cancel, null).show(); - } // Se non c'è nessun albero + } // If there is no tree else if( Global.settings.trees.isEmpty() ) welcome.show(); @@ -85,154 +90,154 @@ else if( Global.settings.trees.isEmpty() ) if( Global.settings.trees != null ) { - // Lista degli alberi genealogici - elencoAlberi = new ArrayList<>(); + // List of family trees + treeList = new ArrayList<>(); - // Dà i dati in pasto all'adattatore - adapter = new SimpleAdapter(this, elencoAlberi, + // Feed the data to the adapter + adapter = new SimpleAdapter(this, treeList, R.layout.pezzo_albero, new String[]{"titolo", "dati"}, - new int[]{R.id.albero_titolo, R.id.albero_dati}) { - // Individua ciascuna vista dell'elenco + new int[]{R.id.albero_titolo, R.id.albero_dati}) { //TODO just implement custom adapter? This is part of the outdated android.widget package, and has a slightly clunky API compared to a custom adapter. + // Locate each view in the list @Override public View getView(final int position, View convertView, ViewGroup parent) { View treeView = super.getView(position, convertView, parent); - int treeId = Integer.parseInt(elencoAlberi.get(position).get("id")); + int treeId = Integer.parseInt(treeList.get(position).get("id")); Settings.Tree tree = Global.settings.getTree(treeId); - boolean derivato = tree.grade == 20; - boolean esaurito = tree.grade == 30; - if( derivato ) { + boolean derivative = tree.grade == 20; + boolean noNovelties = tree.grade == 30; + if( derivative ) { treeView.setBackgroundColor(getResources().getColor(R.color.evidenziaMedio)); ((TextView)treeView.findViewById(R.id.albero_dati)).setTextColor(getResources().getColor(R.color.text)); treeView.setOnClickListener(v -> { - if( !AlberoNuovo.confronta(Alberi.this, tree, true) ) { - tree.grade = 10; // viene retrocesso + if( !NewTree.compare(TreesActivity.this, tree, true) ) { + tree.grade = 10; // is demoted Global.settings.save(); - aggiornaLista(); - Toast.makeText(Alberi.this, R.string.something_wrong, Toast.LENGTH_LONG).show(); + updateList(); + Toast.makeText(TreesActivity.this, R.string.something_wrong, Toast.LENGTH_LONG).show(); } }); - } else if( esaurito ) { + } else if( noNovelties ) { treeView.setBackgroundColor(getResources().getColor(R.color.consumed)); ((TextView)treeView.findViewById(R.id.albero_titolo)).setTextColor(getResources().getColor(R.color.grayText)); treeView.setOnClickListener(v -> { - if( !AlberoNuovo.confronta(Alberi.this, tree, true) ) { - tree.grade = 10; // viene retrocesso + if( !NewTree.compare(TreesActivity.this, tree, true) ) { + tree.grade = 10; // is demoted Global.settings.save(); - aggiornaLista(); - Toast.makeText(Alberi.this, R.string.something_wrong, Toast.LENGTH_LONG).show(); + updateList(); + Toast.makeText(TreesActivity.this, R.string.something_wrong, Toast.LENGTH_LONG).show(); } }); } else { treeView.setBackgroundColor(getResources().getColor(R.color.back_element)); treeView.setOnClickListener(v -> { - rotella.setVisibility(View.VISIBLE); - if( !(Global.gc != null && treeId == Global.settings.openTree) ) { // se non è già aperto + wheel.setVisibility(View.VISIBLE); + if( !(Global.gc != null && treeId == Global.settings.openTree) ) { // if it's not already open if( !openGedcom(treeId, true) ) { - rotella.setVisibility(View.GONE); + wheel.setVisibility(View.GONE); return; } } - startActivity(new Intent(Alberi.this, Principal.class)); + startActivity(new Intent(TreesActivity.this, Principal.class)); }); } treeView.findViewById(R.id.albero_menu).setOnClickListener( vista -> { - boolean esiste = new File( getFilesDir(), treeId + ".json" ).exists(); - PopupMenu popup = new PopupMenu( Alberi.this, vista ); + boolean exists = new File( getFilesDir(), treeId + ".json" ).exists(); + PopupMenu popup = new PopupMenu( TreesActivity.this, vista ); Menu menu = popup.getMenu(); - if( treeId == Global.settings.openTree && Global.daSalvare ) + if( treeId == Global.settings.openTree && Global.shouldSave) menu.add(0, -1, 0, R.string.save); - if( (Global.settings.expert && derivato) || (Global.settings.expert && esaurito) ) + if( (Global.settings.expert && derivative) || (Global.settings.expert && noNovelties) ) menu.add(0, 0, 0, R.string.open); - if( !esaurito || Global.settings.expert ) + if( !noNovelties || Global.settings.expert ) menu.add(0, 1, 0, R.string.tree_info); - if( (!derivato && !esaurito) || Global.settings.expert ) + if( (!derivative && !noNovelties) || Global.settings.expert ) menu.add(0, 2, 0, R.string.rename); - if( esiste && (!derivato || Global.settings.expert) && !esaurito ) + if( exists && (!derivative || Global.settings.expert) && !noNovelties ) menu.add(0, 3, 0, R.string.media_folders); - if( !esaurito ) + if( !noNovelties ) menu.add(0, 4, 0, R.string.find_errors); - if( esiste && !derivato && !esaurito ) // non si può ri-condividere un albero ricevuto indietro, anche se sei esperto.. + if( exists && !derivative && !noNovelties ) // non si può ri-condividere un albero ricevuto indietro, anche se sei esperto.. menu.add(0, 5, 0, R.string.share_tree); - if( esiste && !derivato && !esaurito && Global.settings.expert && Global.settings.trees.size() > 1 + if( exists && !derivative && !noNovelties && Global.settings.expert && Global.settings.trees.size() > 1 && tree.shares != null && tree.grade != 0 ) // cioè dev'essere 9 o 10 menu.add(0, 6, 0, R.string.compare); - if( esiste && Global.settings.expert && !esaurito ) + if( exists && Global.settings.expert && !noNovelties ) menu.add(0, 7, 0, R.string.export_gedcom); - if( esiste && Global.settings.expert ) + if( exists && Global.settings.expert ) menu.add(0, 8, 0, R.string.make_backup); menu.add(0, 9, 0, R.string.delete); popup.show(); popup.setOnMenuItemClickListener(item -> { int id = item.getItemId(); - if( id == -1 ) { // Salva + if( id == -1 ) { // Save U.saveJson(Global.gc, treeId); - Global.daSalvare = false; - } else if( id == 0 ) { // Apre un albero derivato + Global.shouldSave = false; + } else if( id == 0 ) { // Opens a child tree openGedcom(treeId, true); - startActivity(new Intent(Alberi.this, Principal.class)); + startActivity(new Intent(TreesActivity.this, Principal.class)); } else if( id == 1 ) { // Info Gedcom - Intent intento = new Intent(Alberi.this, InfoAlbero.class); - intento.putExtra("idAlbero", treeId); - startActivity(intento); - } else if( id == 2 ) { // Rinomina albero - AlertDialog.Builder builder = new AlertDialog.Builder(Alberi.this); - View vistaMessaggio = getLayoutInflater().inflate(R.layout.albero_nomina, vistaLista, false); - builder.setView(vistaMessaggio).setTitle(R.string.title); - EditText editaNome = vistaMessaggio.findViewById(R.id.nuovo_nome_albero); - editaNome.setText(elencoAlberi.get(position).get("titolo")); - AlertDialog dialogo = builder.setPositiveButton(R.string.rename, (dialog, i1) -> { - Global.settings.rinomina(treeId, editaNome.getText().toString()); - aggiornaLista(); + Intent intent = new Intent(TreesActivity.this, TreeInfoActivity.class); + intent.putExtra("idAlbero", treeId); + startActivity(intent); + } else if( id == 2 ) { // Rename tree + AlertDialog.Builder builder = new AlertDialog.Builder(TreesActivity.this); + View messageView = getLayoutInflater().inflate(R.layout.albero_nomina, listView, false); + builder.setView(messageView).setTitle(R.string.title); + EditText nameEditText = messageView.findViewById(R.id.nuovo_nome_albero); + nameEditText.setText(treeList.get(position).get("titolo")); + AlertDialog dialog = builder.setPositiveButton(R.string.rename, (view, i1) -> { + Global.settings.rename(treeId, nameEditText.getText().toString()); + updateList(); }).setNeutralButton(R.string.cancel, null).create(); - editaNome.setOnEditorActionListener((view, action, event) -> { + nameEditText.setOnEditorActionListener((view, action, event) -> { if( action == EditorInfo.IME_ACTION_DONE ) - dialogo.getButton(AlertDialog.BUTTON_POSITIVE).performClick(); + dialog.getButton(AlertDialog.BUTTON_POSITIVE).performClick(); return false; }); - dialogo.show(); - vistaMessaggio.postDelayed( () -> { - editaNome.requestFocus(); - editaNome.setSelection(editaNome.getText().length()); + dialog.show(); + messageView.postDelayed( () -> { + nameEditText.requestFocus(); + nameEditText.setSelection(nameEditText.getText().length()); InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - inputMethodManager.showSoftInput(editaNome, InputMethodManager.SHOW_IMPLICIT); + inputMethodManager.showSoftInput(nameEditText, InputMethodManager.SHOW_IMPLICIT); }, 300); } else if( id == 3 ) { // Media folders - startActivity(new Intent(Alberi.this, CartelleMedia.class) + startActivity(new Intent(TreesActivity.this, MediaFoldersActivity.class) .putExtra("idAlbero", treeId) ); - } else if( id == 4 ) { // Correggi errori + } else if( id == 4 ) { // Correct errors findErrors(treeId, false); - } else if( id == 5 ) { // Condividi albero - startActivity(new Intent(Alberi.this, Condivisione.class) + } else if( id == 5 ) { // Share tree + startActivity(new Intent(TreesActivity.this, SharingActivity.class) .putExtra("idAlbero", treeId) ); - } else if( id == 6 ) { // Confronta con alberi esistenti - if( AlberoNuovo.confronta(Alberi.this, tree, false) ) { + } else if( id == 6 ) { // Compare with existing trees + if( NewTree.compare(TreesActivity.this, tree, false) ) { tree.grade = 20; - aggiornaLista(); + updateList(); } else - Toast.makeText(Alberi.this, R.string.no_results, Toast.LENGTH_LONG).show(); - } else if( id == 7 ) { // Esporta Gedcom - if( esportatore.apriAlbero(treeId) ) { + Toast.makeText(TreesActivity.this, R.string.no_results, Toast.LENGTH_LONG).show(); + } else if( id == 7 ) { // Export Gedcom + if( exporter.openTree(treeId) ) { String mime = "application/octet-stream"; String ext = "ged"; int code = 636; - if( esportatore.quantiFileMedia() > 0 ) { + if( exporter.numMediaFilesToAttach() > 0 ) { mime = "application/zip"; ext = "zip"; code = 6219; } - F.salvaDocumento(Alberi.this, null, treeId, mime, ext, code); + F.saveDocument(TreesActivity.this, null, treeId, mime, ext, code); } - } else if( id == 8 ) { // Fai backup - if( esportatore.apriAlbero(treeId) ) - F.salvaDocumento(Alberi.this, null, treeId, "application/zip", "zip", 327); - } else if( id == 9 ) { // Elimina albero - new AlertDialog.Builder(Alberi.this).setMessage(R.string.really_delete_tree) + } else if( id == 8 ) { // Make backups + if( exporter.openTree(treeId) ) + F.saveDocument(TreesActivity.this, null, treeId, "application/zip", "zip", 327); + } else if( id == 9 ) { // Delete tree + new AlertDialog.Builder(TreesActivity.this).setMessage(R.string.really_delete_tree) .setPositiveButton(R.string.delete, (dialog, id1) -> { - deleteTree(Alberi.this, treeId); - aggiornaLista(); + deleteTree(TreesActivity.this, treeId); + updateList(); }).setNeutralButton(R.string.cancel, null).show(); } else { return false; @@ -243,31 +248,31 @@ public View getView(final int position, View convertView, ViewGroup parent) { return treeView; } }; - vistaLista.setAdapter(adapter); - aggiornaLista(); + listView.setAdapter(adapter); + updateList(); } - // Barra personalizzata - ActionBar barra = getSupportActionBar(); - View barraAlberi = getLayoutInflater().inflate(R.layout.alberi_barra, null); - barraAlberi.findViewById(R.id.alberi_opzioni).setOnClickListener(v -> startActivity( - new Intent(Alberi.this, Opzioni.class)) + // Custom bar + ActionBar toolbar = getSupportActionBar(); + View treeToolbar = getLayoutInflater().inflate(R.layout.alberi_barra, null); + treeToolbar.findViewById(R.id.alberi_opzioni).setOnClickListener(v -> startActivity( + new Intent(TreesActivity.this, OptionsActivity.class)) ); - barra.setCustomView(barraAlberi); - barra.setDisplayShowCustomEnabled(true); + toolbar.setCustomView(treeToolbar); + toolbar.setDisplayShowCustomEnabled(true); // FAB findViewById(R.id.fab).setOnClickListener(v -> { welcome.hide(); - startActivity(new Intent(Alberi.this, AlberoNuovo.class)); + startActivity(new Intent(TreesActivity.this, NewTree.class)); }); // Automatic load of last opened tree of previous session if( !birthdayNotifyTapped(getIntent()) && !autoOpenedTree && getIntent().getBooleanExtra("apriAlberoAutomaticamente", false) && Global.settings.openTree > 0 ) { - vistaLista.post(() -> { + listView.post(() -> { if( openGedcom(Global.settings.openTree, false) ) { - rotella.setVisibility(View.VISIBLE); + wheel.setVisibility(View.VISIBLE); autoOpenedTree = true; startActivity(new Intent(this, Principal.class)); } @@ -278,19 +283,23 @@ && getIntent().getBooleanExtra("apriAlberoAutomaticamente", false) && Global.set @Override protected void onResume() { super.onResume(); - // Nasconde la rotella, in particolare quando si ritorna indietro a questa activity - rotella.setVisibility(View.GONE); + // Hides the wheel, especially when navigating back to this activity + wheel.setVisibility(View.GONE); } - // Essendo Alberi launchMode=singleTask, onRestart viene chiamato anche con startActivity (tranne il primo) - // però ovviamente solo se Alberi ha chiamato onStop (facendo veloce chiama solo onPause) + /** + * Trees being launchMode=singleTask, onRestart is also called with startActivity (except the first one) + * but obviously only if {@link TreesActivity} has called onStop (doing it fast calls only onPause) + */ @Override protected void onRestart() { super.onRestart(); - aggiornaLista(); + updateList(); } - // New intent coming from a tapped notification + /** + * New intent coming from a tapped notification + */ @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); @@ -304,14 +313,16 @@ protected void onSaveInstanceState(@NonNull Bundle outState) { super.onSaveInstanceState(outState); } - // If a birthday notification was tapped loads the relative tree and returns true + /** + * If a birthday notification was tapped loads the relative tree and returns true + */ private boolean birthdayNotifyTapped(Intent intent) { int treeId = intent.getIntExtra(Notifier.TREE_ID_KEY, 0); int notifyId = intent.getIntExtra(Notifier.NOTIFY_ID_KEY, 0); if( treeId > 0 && !consumedNotifications.contains(notifyId) ) { new Handler().post(() -> { if( openGedcom(treeId, true) ) { - rotella.setVisibility(View.VISIBLE); + wheel.setVisibility(View.VISIBLE); Global.indi = intent.getStringExtra(Notifier.INDI_ID_KEY); consumedNotifications.add(notifyId); startActivity(new Intent(this, Principal.class)); @@ -323,97 +334,103 @@ private boolean birthdayNotifyTapped(Intent intent) { return false; } - // Cerca di recuperare dal Play Store il dataID casomai l'app sia stata installata in seguito ad una condivisione - // Se trova il dataid propone di scaricare l'albero condiviso - void recuperaReferrer() { + /** + * Try to retrieve the dataID from the Play Store in case the app was installed following a share + * If it finds the dataid it offers to download the shared tree + */ + void fetchReferrer() { InstallReferrerClient irc = InstallReferrerClient.newBuilder(this).build(); irc.startConnection(new InstallReferrerStateListener() { @Override - public void onInstallReferrerSetupFinished(int risposta) { - switch( risposta ) { + public void onInstallReferrerSetupFinished(int reply) { + switch( reply ) { case InstallReferrerClient.InstallReferrerResponse.OK: try { - ReferrerDetails dettagli = irc.getInstallReferrer(); - // Normalmente 'referrer' è una stringa tipo 'utm_source=google-play&utm_medium=organic' - // Ma se l'app è stata installata dal link nella pagina di condivisione sarà un data-id come '20191003215337' - String referrer = dettagli.getInstallReferrer(); + ReferrerDetails details = irc.getInstallReferrer(); + // Normally 'referrer' is a string type 'utm_source=google-play&utm_medium=organic' + // But if the app was installed from the link in the share page it will be a data-id like '20191003215337' + String referrer = details.getInstallReferrer(); if( referrer != null && referrer.matches("[0-9]{14}") ) { // It's a dateId Global.settings.referrer = referrer; - new AlertDialog.Builder( Alberi.this ).setTitle( R.string.a_new_tree ) + new AlertDialog.Builder( TreesActivity.this ).setTitle( R.string.a_new_tree ) .setMessage( R.string.you_can_download ) .setPositiveButton( R.string.download, (dialog, id) -> { - Facciata.scaricaCondiviso( Alberi.this, referrer, rotella ); + FacadeActivity.downloadShared( TreesActivity.this, referrer, wheel); }).setNeutralButton( R.string.cancel, (di, id) -> welcome.show() ) .setOnCancelListener( d -> welcome.show() ).show(); - } else { // È qualunque altra cosa - Global.settings.referrer = null; // lo annulla così non lo cercherà più + } else { // It's anything else + Global.settings.referrer = null; // we cancel it so we won't look for it again welcome.show(); } Global.settings.save(); irc.endConnection(); } catch( Exception e ) { - U.toast(Alberi.this, e.getLocalizedMessage()); + U.toast(TreesActivity.this, e.getLocalizedMessage()); } break; - // App Play Store inesistente sul device o comunque risponde in modo errato + // App Play Store does not exist on the device or responds incorrectly case InstallReferrerClient.InstallReferrerResponse.FEATURE_NOT_SUPPORTED: - // Questo non l'ho mai visto comparire + // I've never seen this appear case InstallReferrerClient.InstallReferrerResponse.SERVICE_UNAVAILABLE: - Global.settings.referrer = null; // così non torniamo più qui + Global.settings.referrer = null; // so we never come back here Global.settings.save(); welcome.show(); } } @Override public void onInstallReferrerServiceDisconnected() { - // Mai visto comparire - U.toast(Alberi.this, "Install Referrer Service Disconnected"); + // Never seen it appear + U.toast(TreesActivity.this, "Install Referrer Service Disconnected"); } }); } - void aggiornaLista() { - elencoAlberi.clear(); + void updateList() { + treeList.clear(); for( Settings.Tree alb : Global.settings.trees ) { - Map dato = new HashMap<>(3); - dato.put("id", String.valueOf(alb.id)); - dato.put("titolo", alb.title); - // Se Gedcom già aperto aggiorna i dati + Map data = new HashMap<>(3); + data.put("id", String.valueOf(alb.id)); + data.put("titolo", alb.title); + // If Gedcom is already open, update the data if( Global.gc != null && Global.settings.openTree == alb.id && alb.persons < 100 ) - InfoAlbero.refreshData(Global.gc, alb); - dato.put("dati", scriviDati(this, alb)); - elencoAlberi.add(dato); + TreeInfoActivity.refreshData(Global.gc, alb); + data.put("dati", writeData(this, alb)); + treeList.add(data); } adapter.notifyDataSetChanged(); } - static String scriviDati(Context contesto, Settings.Tree alb) { + static String writeData(Context context, Settings.Tree alb) { String dati = alb.persons + " " + - contesto.getString(alb.persons == 1 ? R.string.person : R.string.persons).toLowerCase(); + context.getString(alb.persons == 1 ? R.string.person : R.string.persons).toLowerCase(); if( alb.persons > 1 && alb.generations > 0 ) dati += " - " + alb.generations + " " + - contesto.getString(alb.generations == 1 ? R.string.generation : R.string.generations).toLowerCase(); + context.getString(alb.generations == 1 ? R.string.generation : R.string.generations).toLowerCase(); if( alb.media > 0 ) - dati += " - " + alb.media + " " + contesto.getString(R.string.media).toLowerCase(); + dati += " - " + alb.media + " " + context.getString(R.string.media).toLowerCase(); return dati; } - // Apertura del Gedcom temporaneo per estrarne info in Alberi - static Gedcom apriGedcomTemporaneo(int idAlbero, boolean mettiInGlobale) { + /** + * Opening the temporary Gedcom to extract info in {@link TreesActivity} + */ + static Gedcom openGedcomTemporarily(int treeId, boolean putInGlobal) { Gedcom gc; - if( Global.gc != null && Global.settings.openTree == idAlbero ) + if( Global.gc != null && Global.settings.openTree == treeId ) gc = Global.gc; else { - gc = readJson(idAlbero); - if( mettiInGlobale ) { - Global.gc = gc; // per poter usare ad esempio U.unaFoto() - Global.settings.openTree = idAlbero; // così Global.gc e Global.preferenze.idAprendo sono sincronizzati + gc = readJson(treeId); + if( putInGlobal ) { + Global.gc = gc; // to be able to use for example F.showMainImageForPerson() + Global.settings.openTree = treeId; // so Global.gc and Global.settings.openTree are synchronized } } return gc; } - // Apertura del Gedcom per editare tutto in Family Gem + /** + * Opening the Gedcom to edit everything in Family Gem + */ static boolean openGedcom(int treeId, boolean savePreferences) { Global.gc = readJson(treeId); if( Global.gc == null ) @@ -423,12 +440,14 @@ static boolean openGedcom(int treeId, boolean savePreferences) { Global.settings.save(); } Global.indi = Global.settings.getCurrentTree().root; - Global.familyNum = 0; // eventualmente lo resetta se era > 0 - Global.daSalvare = false; // eventualmente lo resetta se era true + Global.familyNum = 0; // eventually resets it if it was > 0 + Global.shouldSave = false; // eventually resets it if it was true return true; } - // Read the Json and return a Gedcom + /** + * Read the Json and return a Gedcom + */ static Gedcom readJson(int treeId) { Gedcom gedcom; File file = new File(Global.context.getFilesDir(), treeId + ".json"); @@ -461,13 +480,15 @@ static Gedcom readJson(int treeId) { return gedcom; } - // Replace Italian with English in Json tree data - // Introduced in Family Gem 0.8 + /** + * Replace Italian with English in Json tree data + * Introduced in Family Gem 0.8 + */ static String updateLanguage(String json) { - json = json.replace("\"zona\":", "\"zone\":"); - json = json.replace("\"famili\":", "\"kin\":"); - json = json.replace("\"passato\":", "\"passed\":"); - return json; + return json + .replace("\"zona\":", "\"zone\":") + .replace("\"famili\":", "\"kin\":") + .replace("\"passato\":", "\"passed\":"); } static void deleteTree(Context context, int treeId) { @@ -496,67 +517,67 @@ public void onActivityResult( int requestCode, int resultCode, Intent data ) { if( resultCode == AppCompatActivity.RESULT_OK ) { Uri uri = data.getData(); boolean result = false; - if( requestCode == 636 ) { // Esporta il GEDCOM - result = esportatore.esportaGedcom( uri ); - } else if( requestCode == 6219 ) { // Esporta il GEDCOM zippato coi media - result = esportatore.esportaGedcomZippato( uri ); - } // Esporta il backup ZIP + if( requestCode == 636 ) { // Export the GEDCOM + result = exporter.exportGedcom( uri ); + } else if( requestCode == 6219 ) { // Export the zipped GEDCOM with media + result = exporter.exportGedcomToZip( uri ); + } // Export the ZIP backup else if( requestCode == 327 ) { - result = esportatore.esportaBackupZip( null, -1, uri ); + result = exporter.exportBackupZip( null, -1, uri ); } if( result ) - Toast.makeText( Alberi.this, esportatore.messaggioSuccesso, Toast.LENGTH_SHORT ).show(); + Toast.makeText( TreesActivity.this, exporter.successMessage, Toast.LENGTH_SHORT ).show(); else - Toast.makeText( Alberi.this, esportatore.messaggioErrore, Toast.LENGTH_LONG ).show(); + Toast.makeText( TreesActivity.this, exporter.errorMessage, Toast.LENGTH_LONG ).show(); } } Gedcom findErrors(final int treeId, final boolean correct) { Gedcom gc = readJson(treeId); if( gc == null ) { - // todo fai qualcosa per recuperare un file introvabile..? + // do you do something to recover an untraceable file..? return null; } int errors = 0; int num; - // Radice in preferenze - Settings.Tree albero = Global.settings.getTree(treeId); - Person radica = gc.getPerson(albero.root); - // Radice punta ad una persona inesistente - if( albero.root != null && radica == null ) { + // Root in preferences + Settings.Tree tree = Global.settings.getTree(treeId); + Person root = gc.getPerson(tree.root); + // Root points to a non-existent person + if( tree.root != null && root == null ) { if( !gc.getPeople().isEmpty() ) { if( correct ) { - albero.root = U.trovaRadice(gc); + tree.root = U.findRoot(gc); Global.settings.save(); } else errors++; - } else { // albero senza persone + } else { // tree without people if( correct ) { - albero.root = null; + tree.root = null; Global.settings.save(); } else errors++; } } - // Oppure non è indicata una radice in preferenze pur essendoci persone nell'albero - if( radica == null && !gc.getPeople().isEmpty() ) { + // Or a root is not indicated in preferences even though there are people in the tree + if( root == null && !gc.getPeople().isEmpty() ) { if( correct ) { - albero.root = U.trovaRadice(gc); + tree.root = U.findRoot(gc); Global.settings.save(); } else errors++; } - // O in preferenze è indicata una radiceCondivisione che non esiste - Person radicaCondivisa = gc.getPerson(albero.shareRoot); - if( albero.shareRoot != null && radicaCondivisa == null ) { + // Or a shareRoot is listed in preferences that doesn't exist + Person shareRoot = gc.getPerson(tree.shareRoot); + if( tree.shareRoot != null && shareRoot == null ) { if( correct ) { - albero.shareRoot = null; // la elimina e basta + tree.shareRoot = null; // just delete it Global.settings.save(); } else errors++; } - // Cerca famiglie vuote o con un solo membro per eliminarle + // Search for empty or single-member families to eliminate them for( Family f : gc.getFamilies() ) { if( f.getHusbandRefs().size() + f.getWifeRefs().size() + f.getChildRefs().size() <= 1 ) { if( correct ) { - gc.getFamilies().remove(f); // così facendo lasci i ref negli individui orfani della famiglia a cui si riferiscono... - // ma c'è il resto del correttore che li risolve + gc.getFamilies().remove(f); // in doing so you leave the refs in the orphaned individuals of the family to which they refer... + // but there's the rest of the checker to fix them break; } else errors++; } @@ -565,7 +586,7 @@ Gedcom findErrors(final int treeId, final boolean correct) { if( gc.getFamilies().isEmpty() && correct ) { gc.setFamilies(null); } - // Riferimenti da una persona alla famiglia dei genitori e dei figli + // References from a person to the parents' and children's family for( Person p : gc.getPeople() ) { for( ParentFamilyRef pfr : p.getParentFamilyRefs() ) { Family fam = gc.getFamily( pfr.getRef() ); @@ -649,8 +670,8 @@ Gedcom findErrors(final int treeId, final boolean correct) { if( p.getSpouseFamilyRefs().isEmpty() && correct ) { p.setSpouseFamilyRefs(null); } - // Riferimenti a Media inesistenti - // ok ma SOLO per le persone, forse andrebbe fatto col Visitor per tutti gli altri + // References to non-existent Media + // ok but ONLY for people, maybe it should be done with the Visitor for everyone else num = 0; for( MediaRef mr : p.getMediaRefs() ) { Media med = gc.getMedia( mr.getRef() ); @@ -781,7 +802,7 @@ Gedcom findErrors(final int treeId, final boolean correct) { } } - // Aggiunge un tag 'TYPE' ai name type che non l'hanno + // Adds a 'TYPE' tag to name types that don't have it for( Person person : gc.getPeople() ) { for( Name name : person.getNames() ) { if( name.getType() != null && name.getTypeTag() == null ) { @@ -791,10 +812,10 @@ Gedcom findErrors(final int treeId, final boolean correct) { } } - // Aggiunge un tag 'FILE' ai Media che non l'hanno - ListaMedia visitaMedia = new ListaMedia(gc, 0); - gc.accept(visitaMedia); - for( Media med : visitaMedia.lista ) { + // Adds a 'FILE' tag to Media that don't have it + MediaList mediaList = new MediaList(gc, 0); + gc.accept(mediaList); + for( Media med : mediaList.list) { if( med.getFileTag() == null ) { if( correct ) med.setFileTag("FILE"); else errors++; @@ -809,9 +830,9 @@ Gedcom findErrors(final int treeId, final boolean correct) { dialogo.cancel(); Gedcom gcCorretto = findErrors(treeId, true); U.saveJson(gcCorretto, treeId); - Global.gc = null; // così se era aperto poi lo ricarica corretto - findErrors(treeId, false); // riapre per ammirere il risultato - aggiornaLista(); + Global.gc = null; // so if it was open then reload it correct + findErrors(treeId, false); // reopen to admire (??) the result + updateList(); }); } dialog.setNeutralButton(android.R.string.cancel, null).show(); diff --git a/app/src/main/java/app/familygem/TypeView.java b/app/src/main/java/app/familygem/TypeView.java index be1cba1d..294b7ef7 100644 --- a/app/src/main/java/app/familygem/TypeView.java +++ b/app/src/main/java/app/familygem/TypeView.java @@ -1,5 +1,3 @@ -// Create a combo box to choose a type text from a list of predefined values - package app.familygem; import android.content.Context; @@ -14,6 +12,9 @@ import java.util.Locale; import java.util.Map; +/** + * Create a combo box to choose a type text from a list of predefined values + * */ public class TypeView extends AppCompatAutoCompleteTextView { enum Combo {NAME, RELATIONSHIP} @@ -24,13 +25,13 @@ public TypeView(Context context, Combo combo) { Map types = getTypes(combo); for( String type : types.keySet() ) { if( !Locale.getDefault().getLanguage().equals("en") ) - type += " - " + context.getString(types.get(type)); // Traduzione in tutte le lingue diverse dall'inglese + type += " - " + context.getString(types.get(type)); // Translation into all languages other than English completeTypes.add(type); } - AdattatoreLista adattatoreLista = new AdattatoreLista( context, android.R.layout.simple_spinner_dropdown_item, completeTypes); - setAdapter( adattatoreLista ); + ListAdapter listAdapter = new ListAdapter( context, android.R.layout.simple_spinner_dropdown_item, completeTypes); + setAdapter( listAdapter ); setId( R.id.fatto_edita ); - //setThreshold(0); // inutile, il minimo è 1 + //setThreshold(0); // useless, the minimum is 1 setInputType( InputType.TYPE_CLASS_TEXT ); setOnItemClickListener( (parent, view, position, id) -> { setText((String)types.keySet().toArray()[position]); @@ -69,7 +70,9 @@ static Map getTypes(Combo combo) { } } - // Create a Map from a list of values + /** + * Create a Map from a list of values + */ static Map ImmutableMap(Object... keyValPair) { Map map = new LinkedHashMap<>(); if( keyValPair.length % 2 != 0 ) { @@ -83,12 +86,12 @@ static Map ImmutableMap(Object... keyValPair) { @Override public boolean enoughToFilter() { - return true; // Mostra sempre i suggerimenti + return true; // Always show hints } - class AdattatoreLista extends ArrayAdapter { - AdattatoreLista( Context contesto, int pezzo, List stringhe ) { - super( contesto, pezzo, stringhe ); + class ListAdapter extends ArrayAdapter { + ListAdapter(Context context, int piece, List strings ) { + super( context, piece, strings ); } @Override public Filter getFilter() { @@ -108,7 +111,9 @@ protected void publishResults( CharSequence constraint, FilterResults results ) } } - // Find the translation for predefined English types, or returns the provided type + /** + * Find the translation for predefined English types, or returns the provided type + */ static String getTranslatedType(String type, Combo combo) { Map types = getTypes(combo); Integer translation = types.get(type); diff --git a/app/src/main/java/app/familygem/U.java b/app/src/main/java/app/familygem/U.java index 3f1312b4..0e13252a 100644 --- a/app/src/main/java/app/familygem/U.java +++ b/app/src/main/java/app/familygem/U.java @@ -1,5 +1,3 @@ -// Static methods used all across the app - package app.familygem; import android.app.Activity; @@ -76,65 +74,79 @@ import org.joda.time.Years; import app.familygem.constant.Format; import app.familygem.constant.Gender; -import app.familygem.detail.ArchivioRef; -import app.familygem.detail.Autore; -import app.familygem.detail.Cambiamenti; -import app.familygem.detail.CitazioneFonte; -import app.familygem.detail.Famiglia; -import app.familygem.detail.Fonte; -import app.familygem.detail.Immagine; -import app.familygem.detail.Nota; -import app.familygem.visitor.ContenitoriMedia; -import app.familygem.visitor.ContenitoriNota; -import app.familygem.visitor.ListaCitazioniFonte; -import app.familygem.visitor.ListaMediaContenitore; -import app.familygem.visitor.RiferimentiNota; -import app.familygem.visitor.TrovaPila; - +import app.familygem.detail.RepositoryRefActivity; +import app.familygem.detail.AuthorActivity; +import app.familygem.detail.ChangesActivity; +import app.familygem.detail.SourceCitationActivity; +import app.familygem.detail.FamilyActivity; +import app.familygem.detail.SourceActivity; +import app.familygem.detail.ImageActivity; +import app.familygem.detail.NoteActivity; +import app.familygem.visitor.MediaContainers; +import app.familygem.visitor.NoteContainers; +import app.familygem.visitor.ListOfSourceCitations; +import app.familygem.visitor.MediaListContainer; +import app.familygem.visitor.NoteReferences; +import app.familygem.visitor.FindStack; + +/** + * Static methods used all across the app + * */ public class U { public static String s(int id) { return Global.context.getString(id); } - // Da usare dove capita che 'Global.gc' possa essere null per ricaricarlo - static void gedcomSicuro(Gedcom gc) { + /** + * To use where it happens that 'Global.gc' could be null to reload it + * */ + static void ensureGlobalGedcomNotNull(Gedcom gc) { if( gc == null ) - Global.gc = Alberi.readJson(Global.settings.openTree); + Global.gc = TreesActivity.readJson(Global.settings.openTree); } - // Id of the main person of a GEDCOM or null + /** + * Id of the main person of a GEDCOM or null + */ static String getRootId(Gedcom gedcom, Settings.Tree tree) { if( tree.root != null ) { Person root = gedcom.getPerson(tree.root); if( root != null ) return root.getId(); } - return trovaRadice(gedcom); + return findRoot(gedcom); } - // restituisce l'id della Person iniziale di un Gedcom - // Todo Integrate into getRootId(Gedcom,Tree) ??? - static String trovaRadice(Gedcom gc) { + /** + * @return the id of the initial Person of a Gedcom + * Todo Integrate into {@link #getRootId(Gedcom, Settings.Tree)} ??? + */ + static String findRoot(Gedcom gc) { if( gc.getHeader() != null ) - if( valoreTag(gc.getHeader().getExtensions(), "_ROOT") != null ) - return valoreTag(gc.getHeader().getExtensions(), "_ROOT"); + if( tagValue(gc.getHeader().getExtensions(), "_ROOT") != null ) + return tagValue(gc.getHeader().getExtensions(), "_ROOT"); if( !gc.getPeople().isEmpty() ) return gc.getPeople().get(0).getId(); return null; } - // riceve una Person e restituisce stringa con nome e cognome principale - static String epiteto(Person person) { - return epiteto(person, false); + /** + * receives a Person and returns string with primary first and last name + * riceve una Person e restituisce stringa con nome e cognome principale + * */ + static String properName(Person person) { + return properName(person, false); } - static String epiteto(Person person, boolean twoLines) { + static String properName(Person person, boolean twoLines) { if( person != null && !person.getNames().isEmpty() ) - return nomeCognome(person.getNames().get(0), twoLines ? "\n" : " "); + return firstAndLastName(person.getNames().get(0), twoLines ? "\n" : " "); return "[" + s(R.string.no_name) + "]"; } - // The given name of a person or something + /** + * The given name of a person or something + */ static String givenName(Person person) { if( person.getNames().isEmpty() ) { return "[" + s(R.string.no_name) + "]"; @@ -161,53 +173,60 @@ else if( !value.isEmpty() ) // Name only } } - // riceve una Person e restituisce il titolo nobiliare - static String titolo(Person p) { + /** + * receives a Person and returns the title of nobility + */ + static String title(Person p) { // GEDCOM standard INDI.TITL for( EventFact ef : p.getEventsFacts() ) if( ef.getTag() != null && ef.getTag().equals("TITL") && ef.getValue() != null ) return ef.getValue(); - // Così invece prende INDI.NAME._TYPE.TITL, vecchio metodo di org.folg.gedcom + // So instead it takes INDI.NAME._TYPE.TITL, old method of org.folg.gedcom for( Name n : p.getNames() ) if( n.getType() != null && n.getType().equals("TITL") && n.getValue() != null ) return n.getValue(); return ""; } - // Restituisce il nome e cognome addobbato di un Name - static String nomeCognome(Name n, String divider) { - String completo = ""; + /** + * Returns the first and last name decorated with a Name + * Restituisce il nome e cognome addobbato di un Name + * */ + static String firstAndLastName(Name n, String divider) { + String fullName = ""; if( n.getValue() != null ) { - String grezzo = n.getValue().trim(); - int slashPos = grezzo.indexOf('/'); - int lastSlashPos = grezzo.lastIndexOf('/'); - if( slashPos > -1 ) // Se c'è un cognome tra '/' - completo = grezzo.substring(0, slashPos).trim(); // nome - else // Oppure è solo nome senza cognome - completo = grezzo; + String raw = n.getValue().trim(); + int slashPos = raw.indexOf('/'); + int lastSlashPos = raw.lastIndexOf('/'); + if( slashPos > -1 ) // If there is a last name between '/' + fullName = raw.substring(0, slashPos).trim(); // first name + else // Or it's just a first name without a last name + fullName = raw; if( n.getNickname() != null ) - completo += divider + "\"" + n.getNickname() + "\""; + fullName += divider + "\"" + n.getNickname() + "\""; if( slashPos < lastSlashPos ) - completo += divider + grezzo.substring(slashPos + 1, lastSlashPos).trim(); // cognome - if( lastSlashPos > -1 && grezzo.length() - 1 > lastSlashPos ) - completo += " " + grezzo.substring(lastSlashPos + 1).trim(); // dopo il cognome + fullName += divider + raw.substring(slashPos + 1, lastSlashPos).trim(); // surname + if( lastSlashPos > -1 && raw.length() - 1 > lastSlashPos ) + fullName += " " + raw.substring(lastSlashPos + 1).trim(); // after the surname } else { if( n.getPrefix() != null ) - completo = n.getPrefix(); + fullName = n.getPrefix(); if( n.getGiven() != null ) - completo += " " + n.getGiven(); + fullName += " " + n.getGiven(); if( n.getNickname() != null ) - completo += divider + "\"" + n.getNickname() + "\""; + fullName += divider + "\"" + n.getNickname() + "\""; if( n.getSurname() != null ) - completo += divider + n.getSurname(); + fullName += divider + n.getSurname(); if( n.getSuffix() != null ) - completo += " " + n.getSuffix(); + fullName += " " + n.getSuffix(); } - completo = completo.trim(); - return completo.isEmpty() ? "[" + s(R.string.empty_name) + "]" : completo; + fullName = fullName.trim(); + return fullName.isEmpty() ? "[" + s(R.string.empty_name) + "]" : fullName; } - // Return the surname of a person, optionally lowercase for comparaison. Can return null. + /** + * Return the surname of a person, optionally lowercase for comparison. Can return null. + */ static String surname(Person person) { return surname(person, false); } @@ -226,7 +245,9 @@ else if( name.getSurname() != null ) return surname; } - // Riceve una person e trova se è morto o seppellito + /** + * Receives a person and finds out if he is dead or buried + */ static boolean isDead(Person person) { for( EventFact eventFact : person.getEventsFacts() ) { if( eventFact.getTag().equals("DEAT") || eventFact.getTag().equals("BURI") ) @@ -235,7 +256,9 @@ static boolean isDead(Person person) { return false; } - // Check whether a family has a marriage event of type 'marriage' + /** + * Check whether a family has a marriage event of type 'marriage' + */ public static boolean areMarried(Family family) { if( family != null ) { for( EventFact eventFact : family.getEventsFacts() ) { @@ -260,13 +283,13 @@ public static boolean areMarried(Family family) { static String twoDates(Person person, boolean vertical) { String text = ""; String endYear = ""; - Datatore start = null, end = null; + GedcomDateConverter start = null, end = null; boolean ageBelow = false; List facts = person.getEventsFacts(); // Birth date for( EventFact fact : facts ) { if( fact.getTag() != null && fact.getTag().equals("BIRT") && fact.getDate() != null ) { - start = new Datatore(fact.getDate()); + start = new GedcomDateConverter(fact.getDate()); text = start.writeDate(false); break; } @@ -274,7 +297,7 @@ static String twoDates(Person person, boolean vertical) { // Death date for( EventFact fact : facts ) { if( fact.getTag() != null && fact.getTag().equals("DEAT") && fact.getDate() != null ) { - end = new Datatore(fact.getDate()); + end = new GedcomDateConverter(fact.getDate()); endYear = end.writeDate(false); if( !text.isEmpty() && !endYear.isEmpty() ) { if( vertical && (text.length() > 7 || endYear.length() > 7) ) { @@ -292,7 +315,7 @@ static String twoDates(Person person, boolean vertical) { if( text.isEmpty() ) { for( EventFact fact : facts ) { if( fact.getDate() != null ) { - return new Datatore(fact.getDate()).writeDate(false); + return new GedcomDateConverter(fact.getDate()).writeDate(false); } } } @@ -303,7 +326,7 @@ static String twoDates(Person person, boolean vertical) { LocalDate now = LocalDate.now(); if( end == null && (startDate.isBefore(now) || startDate.isEqual(now)) && Years.yearsBetween(startDate, now).getYears() <= 120 && !isDead(person) ) { - end = new Datatore(now.toDate()); + end = new GedcomDateConverter(now.toDate()); endYear = end.writeDate(false); } if( end != null && end.isSingleKind() && !end.data1.isFormat(Format.D_M) && !endYear.isEmpty() ) { // Plausible dates @@ -331,7 +354,9 @@ static String twoDates(Person person, boolean vertical) { return text; } - // Write the two main places of a person (initial – final) or null + /** + * Write the two main places of a person (initial – final) or null + */ static String twoPlaces(Person person) { List facts = person.getEventsFacts(); // One single event @@ -410,9 +435,11 @@ else if( facts.size() >= 2 ) { return null; } - // riceve un luogo stile Gedcom e restituisce il primo nome tra le virgole + /** + * gets a Gedcom-style location and returns the first name between the commas + */ private static String stripCommas(String place) { - // salta le virgole iniziali per luoghi tipo ',,,England' + // skip leading commas for places type ',,,England' int start = 0; for( char c : place.toCharArray() ) { if( c != ',' && c != ' ' ) @@ -425,167 +452,183 @@ private static String stripCommas(String place) { return place; } - // Estrae i soli numeri da una stringa che può contenere anche lettere - static int soloNumeri(String id) { - //return Integer.parseInt( id.replaceAll("\\D+","") ); // sintetico ma lento + /** + * Extracts only numbers from a string that can also contain letters + * Estrae i soli numeri da una stringa che può contenere anche lettere + * */ + static int extractNum(String id) { + //return Integer.parseInt( id.replaceAll("\\D+","") ); // synthetic but slow //sintetico ma lento int num = 0; int x = 1; for( int i = id.length() - 1; i >= 0; --i ) { int c = id.charAt(i); if( c > 47 && c < 58 ) { num += (c - 48) * x; - x *= 10; + x *= 10; //to convert positional notation into a base-10 representation } } return num; } - // Genera il nuovo id seguente a quelli già esistenti static int max; - public static String nuovoId(Gedcom gc, Class classe) { + /** + * Generate the new id following the existing ones + */ + public static String newID(Gedcom gc, Class clazz) { max = 0; String pre = ""; - if( classe == Note.class ) { + if( clazz == Note.class ) { pre = "N"; for( Note n : gc.getNotes() ) - calcolaMax(n); - } else if( classe == Submitter.class ) { + calculateMax(n); + } else if( clazz == Submitter.class ) { pre = "U"; for( Submitter a : gc.getSubmitters() ) - calcolaMax(a); - } else if( classe == Repository.class ) { + calculateMax(a); + } else if( clazz == Repository.class ) { pre = "R"; for( Repository r : gc.getRepositories() ) - calcolaMax(r); - } else if( classe == Media.class ) { + calculateMax(r); + } else if( clazz == Media.class ) { pre = "M"; for( Media m : gc.getMedia() ) - calcolaMax(m); - } else if( classe == Source.class ) { + calculateMax(m); + } else if( clazz == Source.class ) { pre = "S"; for( Source f : gc.getSources() ) - calcolaMax(f); - } else if( classe == Person.class ) { + calculateMax(f); + } else if( clazz == Person.class ) { pre = "I"; for( Person p : gc.getPeople() ) - calcolaMax(p); - } else if( classe == Family.class ) { + calculateMax(p); + } else if( clazz == Family.class ) { pre = "F"; for( Family f : gc.getFamilies() ) - calcolaMax(f); + calculateMax(f); } return pre + (max + 1); } - private static void calcolaMax(Object oggetto) { + private static void calculateMax(Object object) { try { - String idStringa = (String)oggetto.getClass().getMethod("getId").invoke(oggetto); - int num = soloNumeri(idStringa); + String idString = (String)object.getClass().getMethod("getId").invoke(object); + int num = extractNum(idString); if( num > max ) max = num; } catch( Exception e ) { e.printStackTrace(); } } - // Copia testo negli appunti - static void copiaNegliAppunti(CharSequence label, CharSequence text) { + /** + * Copy text to clipboard + * */ + static void copyToClipboard(CharSequence label, CharSequence text) { ClipboardManager clipboard = (ClipboardManager)Global.context.getSystemService(Context.CLIPBOARD_SERVICE); ClipData clip = ClipData.newPlainText(label, text); if( clipboard != null ) clipboard.setPrimaryClip(clip); } - // Restituisce la lista di estensioni + /** + * Returns the list of extensions + */ @SuppressWarnings("unchecked") - public static List trovaEstensioni(ExtensionContainer contenitore) { - if( contenitore.getExtension(ModelParser.MORE_TAGS_EXTENSION_KEY) != null ) { - List lista = new ArrayList<>(); - for( GedcomTag est : (List)contenitore.getExtension(ModelParser.MORE_TAGS_EXTENSION_KEY) ) { - String testo = scavaEstensione(est, 0); - if( testo.endsWith("\n") ) - testo = testo.substring(0, testo.length() - 1); - lista.add(new Estensione(est.getTag(), testo, est)); + public static List findExtensions(ExtensionContainer container) { + if( container.getExtension(ModelParser.MORE_TAGS_EXTENSION_KEY) != null ) { + List list = new ArrayList<>(); + for( GedcomTag est : (List)container.getExtension(ModelParser.MORE_TAGS_EXTENSION_KEY) ) { + String text = traverseExtension(est, 0); + if( text.endsWith("\n") ) + text = text.substring(0, text.length() - 1); + list.add(new Extension(est.getTag(), text, est)); } - return lista; + return list; } return Collections.emptyList(); } - // Costruisce un testo con il contenuto ricorsivo dell'estensione - public static String scavaEstensione(GedcomTag pacco, int grado) { - String testo = ""; - if( grado > 0 ) - testo += pacco.getTag() + " "; - if( pacco.getValue() != null ) - testo += pacco.getValue() + "\n"; - else if( pacco.getId() != null ) - testo += pacco.getId() + "\n"; - else if( pacco.getRef() != null ) - testo += pacco.getRef() + "\n"; - for( GedcomTag unPezzo : pacco.getChildren() ) - testo += scavaEstensione(unPezzo, ++grado); - return testo; - } - - public static void eliminaEstensione(GedcomTag estensione, Object contenitore, View vista) { - if( contenitore instanceof ExtensionContainer ) { // IndividuoEventi - ExtensionContainer exc = (ExtensionContainer)contenitore; + /** + * Constructs a text with the recursive content of the extension + * */ + public static String traverseExtension(GedcomTag tag, int grade) { + StringBuilder text = new StringBuilder(); + if( grade > 0 ) + text.append(tag.getTag()).append(" "); + if( tag.getValue() != null ) + text.append(tag.getValue()).append("\n"); + else if( tag.getId() != null ) + text.append(tag.getId()).append("\n"); + else if( tag.getRef() != null ) + text.append(tag.getRef()).append("\n"); + for( GedcomTag piece : tag.getChildren() ) + text.append(traverseExtension(piece, ++grade)); + return text.toString(); + } + + public static void deleteExtension(GedcomTag extension, Object container, View view) { + if( container instanceof ExtensionContainer ) { // IndividualEventsFragment + ExtensionContainer exc = (ExtensionContainer)container; @SuppressWarnings("unchecked") - List lista = (List)exc.getExtension(ModelParser.MORE_TAGS_EXTENSION_KEY); - lista.remove(estensione); - if( lista.isEmpty() ) + List list = (List)exc.getExtension(ModelParser.MORE_TAGS_EXTENSION_KEY); + list.remove(extension); + if( list.isEmpty() ) exc.getExtensions().remove(ModelParser.MORE_TAGS_EXTENSION_KEY); if( exc.getExtensions().isEmpty() ) exc.setExtensions(null); - } else if( contenitore instanceof GedcomTag ) { // Dettaglio - GedcomTag gt = (GedcomTag)contenitore; - gt.getChildren().remove(estensione); + } else if( container instanceof GedcomTag ) { // DetailActivity + GedcomTag gt = (GedcomTag)container; + gt.getChildren().remove(extension); if( gt.getChildren().isEmpty() ) gt.setChildren(null); } - Memoria.annullaIstanze(estensione); - if( vista != null ) - vista.setVisibility(View.GONE); + Memory.setInstanceAndAllSubsequentToNull(extension); + if( view != null ) + view.setVisibility(View.GONE); } - // Restituisce il valore di un determinato tag in una estensione (GedcomTag) + /** + * Returns the value of a given tag in an extension ({@link GedcomTag}) + */ @SuppressWarnings("unchecked") - static String valoreTag(Map mappaEstensioni, String nomeTag) { - for( Map.Entry estensione : mappaEstensioni.entrySet() ) { - List listaTag = (ArrayList)estensione.getValue(); - for( GedcomTag unPezzo : listaTag ) { - //l( unPezzo.getTag() +" "+ unPezzo.getValue() ); - if( unPezzo.getTag().equals(nomeTag) ) { - if( unPezzo.getId() != null ) - return unPezzo.getId(); - else if( unPezzo.getRef() != null ) - return unPezzo.getRef(); + static String tagValue(Map extensionMap, String tagName) { + for( Map.Entry extension : extensionMap.entrySet() ) { + List tagList = (ArrayList)extension.getValue(); + for( GedcomTag piece : tagList ) { + //l( piece.getTag() +" "+ piece.getValue() ); + if( piece.getTag().equals(tagName) ) { + if( piece.getId() != null ) + return piece.getId(); + else if( piece.getRef() != null ) + return piece.getRef(); else - return unPezzo.getValue(); + return piece.getValue(); } } } return null; } - // Metodi di creazione di elementi di lista + // Methods of creating list elements - // aggiunge a un Layout una generica voce titolo-testo - // Usato seriamente solo da dettaglio.Cambiamenti - public static void metti(LinearLayout scatola, String tit, String testo) { - View vistaPezzo = LayoutInflater.from(scatola.getContext()).inflate(R.layout.pezzo_fatto, scatola, false); - scatola.addView(vistaPezzo); - ((TextView)vistaPezzo.findViewById(R.id.fatto_titolo)).setText(tit); - TextView vistaTesto = vistaPezzo.findViewById(R.id.fatto_testo); - if( testo == null ) vistaTesto.setVisibility(View.GONE); + /** + * Add a generic title-text entry to a Layout. Used seriously only by [ChangesActivity] + * */ + public static void place(LinearLayout layout, String tit, String text) { + View pieceView = LayoutInflater.from(layout.getContext()).inflate(R.layout.pezzo_fatto, layout, false); + layout.addView(pieceView); + ((TextView)pieceView.findViewById(R.id.fatto_titolo)).setText(tit); + TextView textView = pieceView.findViewById(R.id.fatto_testo); + if( text == null ) textView.setVisibility(View.GONE); else { - vistaTesto.setText(testo); - //((TextView)vistaPezzo.findViewById( R.id.fatto_edita )).setText( testo ); + textView.setText(text); + //((TextView)pieceView.findViewById( R.id.fatto_edita )).setText( text ); } - //((Activity)scatola.getContext()).registerForContextMenu( vistaPezzo ); + //((Activity)layout.getContext()).registerForContextMenu( pieceView ); } - // Compone il testo coi dettagli di un individuo e lo mette nella vista testo - // inoltre restituisce lo stesso testo per Confrontatore + /** + * Composes text with details of an individual and places it in text view + * also returns the same text for {@link TreeComparatorActivity} + * */ static String details(Person person, TextView detailsView) { String dates = twoDates(person, false); String places = twoPlaces(person); @@ -604,40 +647,44 @@ else if( places != null ) return dates.trim(); } - public static View mettiIndividuo(LinearLayout scatola, Person persona, String ruolo) { - View vistaIndi = LayoutInflater.from(scatola.getContext()).inflate(R.layout.pezzo_individuo, scatola, false); - scatola.addView(vistaIndi); - TextView vistaRuolo = vistaIndi.findViewById(R.id.indi_ruolo); - if( ruolo == null ) vistaRuolo.setVisibility(View.GONE); - else vistaRuolo.setText(ruolo); - TextView vistaNome = vistaIndi.findViewById(R.id.indi_nome); - String nome = epiteto(persona); - if( nome.isEmpty() && ruolo != null ) vistaNome.setVisibility(View.GONE); - else vistaNome.setText(nome); - TextView vistaTitolo = vistaIndi.findViewById(R.id.indi_titolo); - String titolo = titolo(persona); - if( titolo.isEmpty() ) vistaTitolo.setVisibility(View.GONE); - else vistaTitolo.setText(titolo); - details(persona, vistaIndi.findViewById(R.id.indi_dettagli)); - F.unaFoto(Global.gc, persona, vistaIndi.findViewById(R.id.indi_foto)); - if( !isDead(persona) ) - vistaIndi.findViewById(R.id.indi_lutto).setVisibility(View.GONE); - if( Gender.isMale(persona) ) - vistaIndi.findViewById(R.id.indi_bordo).setBackgroundResource(R.drawable.casella_bordo_maschio); - else if( Gender.isFemale(persona) ) - vistaIndi.findViewById(R.id.indi_bordo).setBackgroundResource(R.drawable.casella_bordo_femmina); - vistaIndi.setTag(persona.getId()); - return vistaIndi; - } - - // Place all the notes of an object + public static View placeIndividual(LinearLayout layout, Person person, String role) { + View indiView = LayoutInflater.from(layout.getContext()).inflate(R.layout.pezzo_individuo, layout, false); + layout.addView(indiView); + TextView roleView = indiView.findViewById(R.id.indi_ruolo); + if( role == null ) roleView.setVisibility(View.GONE); + else roleView.setText(role); + TextView nameView = indiView.findViewById(R.id.indi_nome); + String name = properName(person); + if( name.isEmpty() && role != null ) nameView.setVisibility(View.GONE); + else nameView.setText(name); + TextView titleView = indiView.findViewById(R.id.indi_titolo); + String title = title(person); + if( title.isEmpty() ) titleView.setVisibility(View.GONE); + else titleView.setText(title); + details(person, indiView.findViewById(R.id.indi_dettagli)); + F.showMainImageForPerson(Global.gc, person, indiView.findViewById(R.id.indi_foto)); + if( !isDead(person) ) + indiView.findViewById(R.id.indi_lutto).setVisibility(View.GONE); + if( Gender.isMale(person) ) + indiView.findViewById(R.id.indi_bordo).setBackgroundResource(R.drawable.casella_bordo_maschio); + else if( Gender.isFemale(person) ) + indiView.findViewById(R.id.indi_bordo).setBackgroundResource(R.drawable.casella_bordo_femmina); + indiView.setTag(person.getId()); + return indiView; + } + + /** + * Place all the notes of an object + */ public static void placeNotes(LinearLayout layout, Object container, boolean detailed) { for( final Note nota : ((NoteContainer)container).getAllNotes(Global.gc) ) { placeNote(layout, nota, detailed); } } - // Place a single note on a layout, with details or not + /** + * Place a single note on a layout, with details or not + */ static void placeNote(final LinearLayout layout, final Note note, boolean detailed) { final Context context = layout.getContext(); View noteView = LayoutInflater.from(context).inflate(R.layout.pezzo_nota, layout, false); @@ -651,127 +698,135 @@ static void placeNote(final LinearLayout layout, final Note note, boolean detail textView.setEllipsize(TextUtils.TruncateAt.END); if( detailed ) { textView.setMaxLines(10); - noteView.setTag(R.id.tag_oggetto, note); - if( context instanceof Individuo ) { // Fragment individuoEventi + noteView.setTag(R.id.tag_object, note); + if( context instanceof IndividualPersonActivity) { // IndividualEventsFragment ((AppCompatActivity)context).getSupportFragmentManager() - .findFragmentByTag("android:switcher:" + R.id.schede_persona + ":1") // non garantito in futuro + .findFragmentByTag("android:switcher:" + R.id.schede_persona + ":1") // not guaranteed in the future .registerForContextMenu(noteView); - } else if( layout.getId() != R.id.dispensa_scatola ) // nelle AppCompatActivity tranne che nella dispensa + } else if( layout.getId() != R.id.dispensa_scatola ) // in AppCompatActivities except in the pantry (??) ((AppCompatActivity)context).registerForContextMenu(noteView); noteView.setOnClickListener(v -> { if( note.getId() != null ) - Memoria.setPrimo(note); + Memory.setFirst(note); else - Memoria.aggiungi(note); - context.startActivity(new Intent(context, Nota.class)); + Memory.add(note); + context.startActivity(new Intent(context, NoteActivity.class)); }); } else { textView.setMaxLines(3); } } - static void scollegaNota(Note nota, Object contenitore, View vista) { - List lista = ((NoteContainer)contenitore).getNoteRefs(); - for( NoteRef ref : lista ) + static void disconnectNote(Note nota, Object container, View view) { + List list = ((NoteContainer)container).getNoteRefs(); + for( NoteRef ref : list ) if( ref.getNote(Global.gc).equals(nota) ) { - lista.remove(ref); + list.remove(ref); break; } - ((NoteContainer)contenitore).setNoteRefs(lista); - if( vista != null ) - vista.setVisibility(View.GONE); + ((NoteContainer)container).setNoteRefs(list); + if( view != null ) + view.setVisibility(View.GONE); } - // Elimina una Nota inlinea o condivisa - // Restituisce un array dei capostipiti modificati - public static Object[] eliminaNota(Note note, View view) { - Set capi; + /** + * Delete an online or shared Note + * @return an array of modified parents + */ + public static Object[] deleteNote(Note note, View view) { + Set heads; if( note.getId() != null ) { // OBJECT note - // Prima rimuove i ref alla nota con un bel Visitor - RiferimentiNota eliminatoreNote = new RiferimentiNota(Global.gc, note.getId(), true); - Global.gc.accept(eliminatoreNote); - Global.gc.getNotes().remove(note); // ok la rimuove se è un'object note - capi = eliminatoreNote.capostipiti; + // First remove the refs to the note with a nice Visitor + NoteReferences noteEliminator = new NoteReferences(Global.gc, note.getId(), true); + Global.gc.accept(noteEliminator); + Global.gc.getNotes().remove(note); // ok removes it if it is an object note + heads = noteEliminator.founders; if( Global.gc.getNotes().isEmpty() ) Global.gc.setNotes(null); } else { // LOCAL note - new TrovaPila(Global.gc, note); - NoteContainer nc = (NoteContainer)Memoria.oggettoContenitore(); - nc.getNotes().remove(note); // rimuove solo se è una nota locale, non se object note + new FindStack(Global.gc, note); + NoteContainer nc = (NoteContainer) Memory.getSecondToLastObject(); + nc.getNotes().remove(note); //only removes if it is a local note, not if object note if( nc.getNotes().isEmpty() ) nc.setNotes(null); - capi = new HashSet<>(); - capi.add(Memoria.oggettoCapo()); - Memoria.arretra(); + heads = new HashSet<>(); + heads.add(Memory.firstObject()); + Memory.clearStackAndRemove(); } - Memoria.annullaIstanze(note); + Memory.setInstanceAndAllSubsequentToNull(note); if( view != null ) view.setVisibility(View.GONE); - return capi.toArray(); + return heads.toArray(); } - // Elenca tutti i media di un oggetto contenitore + /** + * List all media of a container object + */ public static void placeMedia(LinearLayout layout, Object container, boolean detailed) { - RecyclerView griglia = new AdattatoreGalleriaMedia.RiciclaVista(layout.getContext(), detailed); - griglia.setHasFixedSize(true); - RecyclerView.LayoutManager gestoreLayout = new GridLayoutManager(layout.getContext(), detailed ? 2 : 3); - griglia.setLayoutManager(gestoreLayout); - List listaMedia = new ArrayList<>(); + RecyclerView recyclerView = new MediaGalleryAdapter.MediaIconsRecyclerView(layout.getContext(), detailed); + recyclerView.setHasFixedSize(true); + RecyclerView.LayoutManager layoutManager = new GridLayoutManager(layout.getContext(), detailed ? 2 : 3); + recyclerView.setLayoutManager(layoutManager); + List listaMedia = new ArrayList<>(); for( Media med : ((MediaContainer)container).getAllMedia(Global.gc) ) - listaMedia.add(new ListaMediaContenitore.MedCont(med, container)); - AdattatoreGalleriaMedia adattatore = new AdattatoreGalleriaMedia(listaMedia, detailed); - griglia.setAdapter(adattatore); - layout.addView(griglia); + listaMedia.add(new MediaListContainer.MedCont(med, container)); + MediaGalleryAdapter adapter = new MediaGalleryAdapter(listaMedia, detailed); + recyclerView.setAdapter(adapter); + layout.addView(recyclerView); } - // Di un oggetto inserisce le citazioni alle fonti + /** + * Of an object it inserts the citations to the sources //Di un object inserisce le citazioni alle fonti + */ public static void placeSourceCitations(LinearLayout layout, Object container) { if( Global.settings.expert ) { - List listaCitaFonti; - if( container instanceof Note ) // Note non estende SourceCitationContainer - listaCitaFonti = ((Note)container).getSourceCitations(); - else listaCitaFonti = ((SourceCitationContainer)container).getSourceCitations(); - for( final SourceCitation citaz : listaCitaFonti ) { - View vistaCita = LayoutInflater.from(layout.getContext()).inflate(R.layout.pezzo_citazione_fonte, layout, false); - layout.addView(vistaCita); - if( citaz.getSource(Global.gc) != null ) // source CITATION - ((TextView)vistaCita.findViewById(R.id.fonte_testo)).setText(Biblioteca.titoloFonte(citaz.getSource(Global.gc))); - else // source NOTE, oppure Citazione di fonte che è stata eliminata - vistaCita.findViewById(R.id.citazione_fonte).setVisibility(View.GONE); + List listOfSourceCitations; + if( container instanceof Note ) // Notes does not extend SourceCitationContainer + listOfSourceCitations = ((Note)container).getSourceCitations(); + else listOfSourceCitations = ((SourceCitationContainer)container).getSourceCitations(); + for( final SourceCitation citation : listOfSourceCitations ) { + View citationView = LayoutInflater.from(layout.getContext()).inflate(R.layout.pezzo_citazione_fonte, layout, false); + layout.addView(citationView); + if( citation.getSource(Global.gc) != null ) // source CITATION + ((TextView)citationView.findViewById(R.id.fonte_testo)).setText(LibraryFragment.sourceTitle(citation.getSource(Global.gc))); + else // source NOTE, or Source citation that has been deleted + citationView.findViewById(R.id.citazione_fonte).setVisibility(View.GONE); String t = ""; - if( citaz.getValue() != null ) t += citaz.getValue() + "\n"; - if( citaz.getPage() != null ) t += citaz.getPage() + "\n"; - if( citaz.getDate() != null ) t += citaz.getDate() + "\n"; - if( citaz.getText() != null ) t += citaz.getText() + "\n"; // vale sia per sourceNote che per sourceCitation - TextView vistaTesto = vistaCita.findViewById(R.id.citazione_testo); - if( t.isEmpty() ) vistaTesto.setVisibility(View.GONE); - else vistaTesto.setText(t.substring(0, t.length() - 1)); - // Tutto il resto - LinearLayout scatolaAltro = vistaCita.findViewById(R.id.citazione_note); - placeNotes(scatolaAltro, citaz, false); - placeMedia(scatolaAltro, citaz, false); - vistaCita.setTag(R.id.tag_oggetto, citaz); - if( layout.getContext() instanceof Individuo ) { // Fragment individuoEventi + if( citation.getValue() != null ) t += citation.getValue() + "\n"; + if( citation.getPage() != null ) t += citation.getPage() + "\n"; + if( citation.getDate() != null ) t += citation.getDate() + "\n"; + if( citation.getText() != null ) t += citation.getText() + "\n"; // applies to both sourceNote and sourceCitation + TextView textView = citationView.findViewById(R.id.citazione_testo); + if( t.isEmpty() ) textView.setVisibility(View.GONE); + else textView.setText(t.substring(0, t.length() - 1)); + // All the rest + LinearLayout otherLayout = citationView.findViewById(R.id.citazione_note); + placeNotes(otherLayout, citation, false); + placeMedia(otherLayout, citation, false); + citationView.setTag(R.id.tag_object, citation); + if( layout.getContext() instanceof IndividualPersonActivity) { // IndividualEventsFragment ((AppCompatActivity)layout.getContext()).getSupportFragmentManager() .findFragmentByTag("android:switcher:" + R.id.schede_persona + ":1") - .registerForContextMenu(vistaCita); + .registerForContextMenu(citationView); } else // AppCompatActivity - ((AppCompatActivity)layout.getContext()).registerForContextMenu(vistaCita); + ((AppCompatActivity)layout.getContext()).registerForContextMenu(citationView); - vistaCita.setOnClickListener(v -> { - Intent intento = new Intent(layout.getContext(), CitazioneFonte.class); - Memoria.aggiungi(citaz); - layout.getContext().startActivity(intento); + citationView.setOnClickListener(v -> { + Intent intent = new Intent(layout.getContext(), SourceCitationActivity.class); + Memory.add(citation); + layout.getContext().startActivity(intent); }); } } } - // Inserisce nella scatola il richiamo ad una fonte, con dettagli o essenziale + /** + * Inserts the reference to a source, with details or essential, into the box + */ public static void placeSource(final LinearLayout layout, final Source source, boolean detailed) { - View vistaFonte = LayoutInflater.from(layout.getContext()).inflate(R.layout.pezzo_fonte, layout, false); - layout.addView(vistaFonte); - TextView vistaTesto = vistaFonte.findViewById(R.id.fonte_testo); + View sourceView = LayoutInflater.from(layout.getContext()).inflate(R.layout.pezzo_fonte, layout, false); + layout.addView(sourceView); + TextView textView = sourceView.findViewById(R.id.fonte_testo); String txt = ""; if( detailed ) { if( source.getTitle() != null ) @@ -786,138 +841,152 @@ else if( source.getAbbreviation() != null ) txt += source.getText().replaceAll("\n", " "); if( txt.endsWith("\n") ) txt = txt.substring(0, txt.length() - 1); - LinearLayout scatolaAltro = vistaFonte.findViewById(R.id.fonte_scatola); - placeNotes(scatolaAltro, source, false); - placeMedia(scatolaAltro, source, false); - vistaFonte.setTag(R.id.tag_oggetto, source); - ((AppCompatActivity)layout.getContext()).registerForContextMenu(vistaFonte); + LinearLayout otherLayout = sourceView.findViewById(R.id.fonte_scatola); + placeNotes(otherLayout, source, false); + placeMedia(otherLayout, source, false); + sourceView.setTag(R.id.tag_object, source); + ((AppCompatActivity)layout.getContext()).registerForContextMenu(sourceView); } else { - vistaTesto.setMaxLines(2); - txt = Biblioteca.titoloFonte(source); + textView.setMaxLines(2); + txt = LibraryFragment.sourceTitle(source); } - vistaTesto.setText(txt); - vistaFonte.setOnClickListener(v -> { - Memoria.setPrimo(source); - layout.getContext().startActivity(new Intent(layout.getContext(), Fonte.class)); + textView.setText(txt); + sourceView.setOnClickListener(v -> { + Memory.setFirst(source); + layout.getContext().startActivity(new Intent(layout.getContext(), SourceActivity.class)); }); } - // La view ritornata è usata da Condivisione - public static View linkaPersona(LinearLayout scatola, Person p, int scheda) { - View vistaPersona = LayoutInflater.from(scatola.getContext()).inflate(R.layout.pezzo_individuo_piccolo, scatola, false); - scatola.addView(vistaPersona); - F.unaFoto(Global.gc, p, vistaPersona.findViewById(R.id.collega_foto)); - ((TextView)vistaPersona.findViewById(R.id.collega_nome)).setText(epiteto(p)); - String dati = twoDates(p, false); - TextView vistaDettagli = vistaPersona.findViewById(R.id.collega_dati); - if( dati.isEmpty() ) vistaDettagli.setVisibility(View.GONE); - else vistaDettagli.setText(dati); + /** + * The returned view is used by {@link SharingActivity} + */ + public static View linkPerson(LinearLayout layout, Person p, int card) { + View personView = LayoutInflater.from(layout.getContext()).inflate(R.layout.pezzo_individuo_piccolo, layout, false); + layout.addView(personView); + F.showMainImageForPerson(Global.gc, p, personView.findViewById(R.id.collega_foto)); + ((TextView)personView.findViewById(R.id.collega_nome)).setText(properName(p)); + String dates = twoDates(p, false); + TextView detailsView = personView.findViewById(R.id.collega_dati); + if( dates.isEmpty() ) detailsView.setVisibility(View.GONE); + else detailsView.setText(dates); if( !isDead(p) ) - vistaPersona.findViewById(R.id.collega_lutto).setVisibility(View.GONE); + personView.findViewById(R.id.collega_lutto).setVisibility(View.GONE); if( Gender.isMale(p) ) - vistaPersona.findViewById(R.id.collega_bordo).setBackgroundResource(R.drawable.casella_bordo_maschio); + personView.findViewById(R.id.collega_bordo).setBackgroundResource(R.drawable.casella_bordo_maschio); else if( Gender.isFemale(p) ) - vistaPersona.findViewById(R.id.collega_bordo).setBackgroundResource(R.drawable.casella_bordo_femmina); - vistaPersona.setOnClickListener(v -> { - Memoria.setPrimo(p); - Intent intento = new Intent(scatola.getContext(), Individuo.class); - intento.putExtra("scheda", scheda); - scatola.getContext().startActivity(intento); + personView.findViewById(R.id.collega_bordo).setBackgroundResource(R.drawable.casella_bordo_femmina); + personView.setOnClickListener(v -> { + Memory.setFirst(p); + Intent intent = new Intent(layout.getContext(), IndividualPersonActivity.class); + intent.putExtra("scheda", card); + layout.getContext().startActivity(intent); }); - return vistaPersona; + return personView; } - static String testoFamiglia(Context contesto, Gedcom gc, Family fam, boolean unaLinea) { - String testo = ""; - for( Person marito : fam.getHusbands(gc) ) - testo += epiteto(marito) + "\n"; - for( Person moglie : fam.getWives(gc) ) - testo += epiteto(moglie) + "\n"; + static String familyText(Context context, Gedcom gc, Family fam, boolean oneLine) { + StringBuilder text = new StringBuilder(); + for( Person husband : fam.getHusbands(gc) ) + text.append(properName(husband)).append("\n"); + for( Person wife : fam.getWives(gc) ) + text.append(properName(wife)).append("\n"); if( fam.getChildren(gc).size() == 1 ) { - testo += epiteto(fam.getChildren(gc).get(0)); + text.append(properName(fam.getChildren(gc).get(0))); } else if( fam.getChildren(gc).size() > 1 ) - testo += contesto.getString(R.string.num_children, fam.getChildren(gc).size()); - if( testo.endsWith("\n") ) testo = testo.substring(0, testo.length() - 1); - if( unaLinea ) - testo = testo.replaceAll("\n", ", "); - if( testo.isEmpty() ) - testo = "[" + contesto.getString(R.string.empty_family) + "]"; - return testo; - } - - // Usato da dispensa - static void linkaFamiglia(LinearLayout scatola, Family fam) { - View vistaFamiglia = LayoutInflater.from(scatola.getContext()).inflate(R.layout.pezzo_famiglia_piccolo, scatola, false); - scatola.addView(vistaFamiglia); - ((TextView)vistaFamiglia.findViewById(R.id.famiglia_testo)).setText(testoFamiglia(scatola.getContext(), Global.gc, fam, false)); - vistaFamiglia.setOnClickListener(v -> { - Memoria.setPrimo(fam); - scatola.getContext().startActivity(new Intent(scatola.getContext(), Famiglia.class)); + text.append(context.getString(R.string.num_children, fam.getChildren(gc).size())); + if( text.toString().endsWith("\n") ) text.deleteCharAt(text.length() - 1); + if( oneLine ) + text = new StringBuilder(text.toString().replaceAll("\n", ", ")); + if(text.length() == 0) + text = new StringBuilder("[" + context.getString(R.string.empty_family) + "]"); + return text.toString(); + } + + /** + * Used by pantry (??) + */ + static void linkFamily(LinearLayout layout, Family fam) { + View familyView = LayoutInflater.from(layout.getContext()).inflate(R.layout.pezzo_famiglia_piccolo, layout, false); + layout.addView(familyView); + ((TextView)familyView.findViewById(R.id.famiglia_testo)).setText(familyText(layout.getContext(), Global.gc, fam, false)); + familyView.setOnClickListener(v -> { + Memory.setFirst(fam); + layout.getContext().startActivity(new Intent(layout.getContext(), FamilyActivity.class)); }); } - // Usato da dispensa - static void linkaMedia(LinearLayout scatola, Media media) { - View vistaMedia = LayoutInflater.from(scatola.getContext()).inflate(R.layout.pezzo_media, scatola, false); - scatola.addView(vistaMedia); - AdattatoreGalleriaMedia.arredaMedia(media, vistaMedia.findViewById(R.id.media_testo), vistaMedia.findViewById(R.id.media_num)); - LinearLayout.LayoutParams parami = (LinearLayout.LayoutParams)vistaMedia.getLayoutParams(); + /** + * Used from pantry + */ + static void linkMedia(LinearLayout layout, Media media) { + View imageView = LayoutInflater.from(layout.getContext()).inflate(R.layout.pezzo_media, layout, false); + layout.addView(imageView); + MediaGalleryAdapter.setupMedia(media, imageView.findViewById(R.id.media_testo), imageView.findViewById(R.id.media_num)); + LinearLayout.LayoutParams parami = (LinearLayout.LayoutParams)imageView.getLayoutParams(); parami.height = dpToPx(80); - F.dipingiMedia(media, vistaMedia.findViewById(R.id.media_img), vistaMedia.findViewById(R.id.media_circolo)); - vistaMedia.setOnClickListener(v -> { - Memoria.setPrimo(media); - scatola.getContext().startActivity(new Intent(scatola.getContext(), Immagine.class)); + F.showImage(media, imageView.findViewById(R.id.media_img), imageView.findViewById(R.id.media_circolo)); + imageView.setOnClickListener(v -> { + Memory.setFirst(media); + layout.getContext().startActivity(new Intent(layout.getContext(), ImageActivity.class)); }); } - // Aggiunge un autore al layout - static void linkAutore(LinearLayout scatola, Submitter autor) { - Context contesto = scatola.getContext(); - View vista = LayoutInflater.from(contesto).inflate(R.layout.pezzo_nota, scatola, false); - scatola.addView(vista); - TextView testoNota = vista.findViewById(R.id.nota_testo); - testoNota.setText(autor.getName()); - vista.findViewById(R.id.nota_fonti).setVisibility(View.GONE); - vista.setOnClickListener(v -> { - Memoria.setPrimo(autor); - contesto.startActivity(new Intent(contesto, Autore.class)); + /** + * Aggiunge un autore al layout + */ + static void linkSubmitter(LinearLayout layout, Submitter submitter) { + Context context = layout.getContext(); + View view = LayoutInflater.from(context).inflate(R.layout.pezzo_nota, layout, false); + layout.addView(view); + TextView noteText = view.findViewById(R.id.nota_testo); + noteText.setText(submitter.getName()); + view.findViewById(R.id.nota_fonti).setVisibility(View.GONE); + view.setOnClickListener(v -> { + Memory.setFirst(submitter); + context.startActivity(new Intent(context, AuthorActivity.class)); }); } - // Aggiunge al layout un contenitore generico con uno o più collegamenti a record capostipiti - public static void mettiDispensa(LinearLayout scatola, Object cosa, int tit) { - View vista = LayoutInflater.from(scatola.getContext()).inflate(R.layout.dispensa, scatola, false); - TextView vistaTit = vista.findViewById(R.id.dispensa_titolo); - vistaTit.setText(tit); - vistaTit.setBackground(AppCompatResources.getDrawable(scatola.getContext(), R.drawable.sghembo)); // per android 4 - scatola.addView(vista); - LinearLayout dispensa = vista.findViewById(R.id.dispensa_scatola); - if( cosa instanceof Object[] ) { - for( Object o : (Object[])cosa ) - mettiQualsiasi(dispensa, o); + /** + * Adds a generic container with one or more links to parent records to the layout // Aggiunge al layout un contenitore generico con uno o più collegamenti a record capostipiti + * */ + public static void putContainer(LinearLayout layout, Object what, int title) { + View view = LayoutInflater.from(layout.getContext()).inflate(R.layout.dispensa, layout, false); + TextView titleView = view.findViewById(R.id.dispensa_titolo); + titleView.setText(title); + titleView.setBackground(AppCompatResources.getDrawable(layout.getContext(), R.drawable.sghembo)); // per android 4 + layout.addView(view); + LinearLayout pantry = view.findViewById(R.id.dispensa_scatola); + if( what instanceof Object[] ) { + for( Object o : (Object[])what ) + putAny(pantry, o); } else - mettiQualsiasi(dispensa, cosa); + putAny(pantry, what); } - // Riconosce il tipo di record e aggiunge il link appropriato alla scatola - static void mettiQualsiasi(LinearLayout scatola, Object record) { + /** + * It recognizes the record type and adds the appropriate link to the box + */ + static void putAny(LinearLayout layout, Object record) { if( record instanceof Person ) - linkaPersona(scatola, (Person)record, 1); + linkPerson(layout, (Person)record, 1); else if( record instanceof Source ) - placeSource(scatola, (Source)record, false); + placeSource(layout, (Source)record, false); else if( record instanceof Family ) - linkaFamiglia(scatola, (Family)record); + linkFamily(layout, (Family)record); else if( record instanceof Repository ) - ArchivioRef.mettiArchivio(scatola, (Repository)record); + RepositoryRefActivity.putRepository(layout, (Repository)record); else if( record instanceof Note ) - placeNote(scatola, (Note)record, true); + placeNote(layout, (Note)record, true); else if( record instanceof Media ) - linkaMedia(scatola, (Media)record); + linkMedia(layout, (Media)record); else if( record instanceof Submitter ) - linkAutore(scatola, (Submitter)record); + linkSubmitter(layout, (Submitter)record); } - // Aggiunge al layout il pezzo con la data e tempo di Cambiamento + /** + * Adds the piece with the change date and time to the layout + */ public static View placeChangeDate(final LinearLayout layout, final Change change) { View changeView = null; if( change != null && Global.settings.expert ) { @@ -927,27 +996,29 @@ public static View placeChangeDate(final LinearLayout layout, final Change chang if( change.getDateTime() != null ) { String txt = ""; if( change.getDateTime().getValue() != null ) - txt = new Datatore(change.getDateTime().getValue()).writeDateLong(); + txt = new GedcomDateConverter(change.getDateTime().getValue()).writeDateLong(); if( change.getDateTime().getTime() != null ) txt += " - " + change.getDateTime().getTime(); textView.setText(txt); } - LinearLayout scatolaNote = changeView.findViewById(R.id.cambi_note); - for( Estensione altroTag : trovaEstensioni(change) ) - metti(scatolaNote, altroTag.nome, altroTag.testo); - // Grazie al mio contributo la data cambiamento può avere delle note - placeNotes(scatolaNote, change, false); + LinearLayout noteLayout = changeView.findViewById(R.id.cambi_note); + for( Extension otherTag : findExtensions(change) ) + place(noteLayout, otherTag.name, otherTag.text); + // Thanks to my contribution the change date can have notes + placeNotes(noteLayout, change, false); changeView.setOnClickListener(v -> { - Memoria.aggiungi(change); - layout.getContext().startActivity(new Intent(layout.getContext(), Cambiamenti.class)); + Memory.add(change); + layout.getContext().startActivity(new Intent(layout.getContext(), ChangesActivity.class)); }); } return changeView; } - // Chiede conferma di eliminare un elemento - public static boolean preserva(Object cosa) { - // todo Conferma elimina + /** + * Asks for confirmation to delete an item + */ + public static boolean preserve(Object what) { + // todo Confirm delete return false; } @@ -961,42 +1032,46 @@ public static void save(boolean refresh, Object... objects) { if( objects != null ) updateChangeDate(objects); - // al primo salvataggio marchia gli autori + // marks the authors on the first save if( Global.settings.getCurrentTree().grade == 9 ) { - for( Submitter autore : Global.gc.getSubmitters() ) - autore.putExtension("passed", true); + for( Submitter author : Global.gc.getSubmitters() ) + author.putExtension("passed", true); Global.settings.getCurrentTree().grade = 10; Global.settings.save(); } if( Global.settings.autoSave ) saveJson(Global.gc, Global.settings.openTree); - else { // mostra il tasto Salva - Global.daSalvare = true; - if( Global.principalView != null ) { - NavigationView menu = Global.principalView.findViewById(R.id.menu); + else { // shows the Save button + Global.shouldSave = true; + if( Global.mainView != null ) { + NavigationView menu = Global.mainView.findViewById(R.id.menu); menu.getHeaderView(0).findViewById(R.id.menu_salva).setVisibility(View.VISIBLE); } } } - // Update the change date of record(s) + /** + * Update the change date of record(s) + */ public static void updateChangeDate(Object... objects) { for( Object object : objects ) { - try { // Se aggiornando non ha il metodo get/setChange, passa oltre silenziosamente + try { // If updating doesn't have the get/setChange method, it passes silently Change change = (Change)object.getClass().getMethod("getChange").invoke(object); - if( change == null ) // il record non ha ancora un CHAN + if( change == null ) // the record does not yet have a CHAN change = new Change(); change.setDateTime(actualDateTime()); object.getClass().getMethod("setChange", Change.class).invoke(object, change); - // Estensione con l'id della zona, una stringa tipo 'America/Sao_Paulo' + // Extension with zone id, a string type 'America/Sao_Paulo' change.putExtension("zone", TimeZone.getDefault().getID()); } catch( Exception e ) { } } } - // Return actual DateTime + /** + * Return actual DateTime + */ public static DateTime actualDateTime() { DateTime dateTime = new DateTime(); Date now = new Date(); @@ -1005,15 +1080,17 @@ public static DateTime actualDateTime() { return dateTime; } - // Save the Json + /** + * Save the Json + */ static void saveJson(Gedcom gedcom, int treeId) { Header h = gedcom.getHeader(); - // Solo se l'header è di Family Gem + // Only if the header is from Family Gem if( h != null && h.getGenerator() != null && h.getGenerator().getValue() != null && h.getGenerator().getValue().equals("FAMILY_GEM") ) { - // Aggiorna la data e l'ora + // Update the date and time h.setDateTime(actualDateTime()); - // Eventualmente aggiorna la versione di Family Gem + // Eventually update the version of Family Gem if( (h.getGenerator().getVersion() != null && !h.getGenerator().getVersion().equals(BuildConfig.VERSION_NAME)) || h.getGenerator().getVersion() == null ) h.getGenerator().setVersion(BuildConfig.VERSION_NAME); @@ -1048,260 +1125,280 @@ static int dpToPx(float dips) { return (int)(dips * Global.context.getResources().getDisplayMetrics().density + 0.5f); } - // Valuta se ci sono individui collegabili rispetto a un individuo. - // Usato per decidere se far comparire 'Collega persona esistente' nel menu - static boolean ciSonoIndividuiCollegabili(Person person) { + /** + * Evaluate whether there are individuals connectable with respect to an individual. + * Used to decide whether to show 'Link Existing Person' in the menu + */ + static boolean containsConnectableIndividuals(Person person) { int total = Global.gc.getPeople().size(); - if( total > 0 && (Global.settings.expert // gli esperti possono sempre - || person == null) ) // in una famiglia vuota unRappresentanteDellaFamiglia è null + if( total > 0 && (Global.settings.expert // the experts always can + || person == null) ) // in an empty family aRepresentativeOfTheFamily is null return true; - int kin = Anagrafe.countRelatives(person); + int kin = ListOfPeopleFragment.countRelatives(person); return total > kin + 1; } - // Chiede se referenziare un autore nell'header - static void autorePrincipale(Context contesto, final String idAutore) { - final Header[] testa = {Global.gc.getHeader()}; - if( testa[0] == null || testa[0].getSubmitterRef() == null ) { - new AlertDialog.Builder(contesto).setMessage(R.string.make_main_submitter) + /** + * Asks whether to reference an author in the header + */ + static void mainAuthor(Context context, final String authorId) { + final Header[] head = {Global.gc.getHeader()}; + if( head[0] == null || head[0].getSubmitterRef() == null ) { + new AlertDialog.Builder(context).setMessage(R.string.make_main_submitter) .setPositiveButton(android.R.string.yes, (dialog, id) -> { - if( testa[0] == null ) { - testa[0] = AlberoNuovo.creaTestata(Global.settings.openTree + ".json"); - Global.gc.setHeader(testa[0]); + if( head[0] == null ) { + head[0] = NewTree.createHeader(Global.settings.openTree + ".json"); + Global.gc.setHeader(head[0]); } - testa[0].setSubmitterRef(idAutore); + head[0].setSubmitterRef(authorId); save(true); }).setNegativeButton(R.string.no, null).show(); } } - // Restituisce il primo autore non passato - static Submitter autoreFresco(Gedcom gc) { - for( Submitter autore : gc.getSubmitters() ) { - if( autore.getExtension("passed") == null ) - return autore; + /** + * Returns the first non-passed author + */ + static Submitter newSubmitter(Gedcom gc) { + for( Submitter author : gc.getSubmitters() ) { + if( author.getExtension("passed") == null ) + return author; } return null; } - // Verifica se un autore ha partecipato alle condivisioni, per non farlo eliminare - static boolean autoreHaCondiviso(Submitter autore) { - List condivisioni = Global.settings.getCurrentTree().shares; + /** + * Check if an author has participated in the shares, so as not to have them deleted + * */ + static boolean submitterHasShared(Submitter autore) { + List shares = Global.settings.getCurrentTree().shares; boolean inviatore = false; - if( condivisioni != null ) - for( Settings.Share share : condivisioni ) + if( shares != null ) + for( Settings.Share share : shares ) if( autore.getId().equals(share.submitter) ) inviatore = true; return inviatore; } - // Elenco di stringhe dei membri rappresentativi delle famiglie - static String[] elencoFamiglie(List listaFamiglie) { - List famigliePerno = new ArrayList<>(); - for( Family fam : listaFamiglie ) { - String etichetta = testoFamiglia(Global.context, Global.gc, fam, true); - famigliePerno.add(etichetta); - } - return famigliePerno.toArray(new String[0]); - } - - /* Per un perno che è figlio in più di una famiglia chiede quale famiglia mostrare - cosaAprire: - 0 diagramma della famiglia precedente, senza chiedere quale famiglia (primo click su Diagram) - 1 diagramma chiedendo eventualmente quale famiglia - 2 famiglia chiedendo eventualmente quale famiglia + /** + * String list of representative family members */ - public static void qualiGenitoriMostrare(Context contesto, Person perno, int cosaAprire) { - if( perno == null ) - concludiSceltaGenitori(contesto, null, 1, 0); + static String[] listFamilies(List familyList) { + List familyPivots = new ArrayList<>(); + for( Family fam : familyList ) { + String label = familyText(Global.context, Global.gc, fam, true); + familyPivots.add(label); + } + return familyPivots.toArray(new String[0]); + } + /** For a stud? anchor? reference? ("perno") who is a child in more than one family, ask which family to show + @param thingToOpen what to open:
    +
  • 0 diagram of the previous family, without asking which family (first click on Diagram)
  • +
  • 1 diagram possibly asking which family
  • +
  • 2 family possibly asking which family
  • +
+ */ + public static void askWhichParentsToShow(Context context, Person person, int thingToOpen) { + if( person == null ) + finishParentSelection(context, null, 1, 0); else { - List famiglie = perno.getParentFamilies(Global.gc); - if( famiglie.size() > 1 && cosaAprire > 0 ) { - new AlertDialog.Builder(contesto).setTitle(R.string.which_family) - .setItems(elencoFamiglie(famiglie), (dialog, quale) -> { - concludiSceltaGenitori(contesto, perno, cosaAprire, quale); + List families = person.getParentFamilies(Global.gc); + if( families.size() > 1 && thingToOpen > 0 ) { + new AlertDialog.Builder(context).setTitle(R.string.which_family) + .setItems(listFamilies(families), (dialog, quale) -> { + finishParentSelection(context, person, thingToOpen, quale); }).show(); } else - concludiSceltaGenitori(contesto, perno, cosaAprire, 0); + finishParentSelection(context, person, thingToOpen, 0); } } - private static void concludiSceltaGenitori(Context contesto, Person perno, int cosaAprire, int qualeFamiglia) { - if( perno != null ) - Global.indi = perno.getId(); - if( cosaAprire > 0 ) // Viene impostata la famiglia da mostrare - Global.familyNum = qualeFamiglia; // normalmente è la 0 - if( cosaAprire < 2 ) { // Mostra il diagramma - if( contesto instanceof Principal ) { // Diagram, Anagrafe o Principal stesso - FragmentManager fm = ((AppCompatActivity)contesto).getSupportFragmentManager(); - // Nome del frammento precedente nel backstack + private static void finishParentSelection(Context context, Person pivot, int whatToOpen, int whichFamily) { + if( pivot != null ) + Global.indi = pivot.getId(); + if( whatToOpen > 0 ) // The family to show is set + Global.familyNum = whichFamily; // it is usually 0 + if( whatToOpen < 2 ) { // Show the diagram + if( context instanceof Principal ) { // Diagram, ListOfPeopleFragment or Principal itself + FragmentManager fm = ((AppCompatActivity)context).getSupportFragmentManager(); + // Name of the previous fragment in the backstack String previousName = fm.getBackStackEntryAt(fm.getBackStackEntryCount() - 1).getName(); if( previousName != null && previousName.equals("diagram") ) - fm.popBackStack(); // Ricliccando su Diagram rimuove dalla storia il frammento di diagramma predente + fm.popBackStack(); // Clicking on Diagram removes the previous diagram fragment from the history fm.beginTransaction().replace(R.id.contenitore_fragment, new Diagram()).addToBackStack("diagram").commit(); - } else { // Da individuo o da famiglia - contesto.startActivity(new Intent(contesto, Principal.class)); + } else { // As an individual or as a family + context.startActivity(new Intent(context, Principal.class)); } - } else { // Viene mostrata la famiglia - Family family = perno.getParentFamilies(Global.gc).get(qualeFamiglia); - if( contesto instanceof Famiglia ) { // Passando di Famiglia in Famiglia non accumula attività nello stack - Memoria.replacePrimo(family); - ((Activity)contesto).recreate(); + } else { // The family is shown + Family family = pivot.getParentFamilies(Global.gc).get(whichFamily); + if( context instanceof FamilyActivity) { // Moving from Family to Family does not accumulate activities in the stack + Memory.replaceFirst(family); + ((Activity)context).recreate(); } else { - Memoria.setPrimo(family); - contesto.startActivity(new Intent(contesto, Famiglia.class)); + Memory.setFirst(family); + context.startActivity(new Intent(context, FamilyActivity.class)); } } } - // Per un perno che ha molteplici matrimoni chiede quale mostrare - public static void qualiConiugiMostrare(Context contesto, Person perno, Family famiglia) { - if( perno.getSpouseFamilies(Global.gc).size() > 1 && famiglia == null ) { - new AlertDialog.Builder(contesto).setTitle(R.string.which_family) - .setItems(elencoFamiglie(perno.getSpouseFamilies(Global.gc)), (dialog, quale) -> { - concludiSceltaConiugi(contesto, perno, null, quale); + /** + * For an anchor ("perno") who has multiple marriages it asks which one to show + * */ + public static void askWhichSpouseToShow(Context context, Person pivot, Family family) { + if( pivot.getSpouseFamilies(Global.gc).size() > 1 && family == null ) { + new AlertDialog.Builder(context).setTitle(R.string.which_family) + .setItems(listFamilies(pivot.getSpouseFamilies(Global.gc)), (dialog, quale) -> { + concludeSpouseChoice(context, pivot, null, quale); }).show(); } else { - concludiSceltaConiugi(contesto, perno, famiglia, 0); + concludeSpouseChoice(context, pivot, family, 0); } } - private static void concludiSceltaConiugi(Context contesto, Person perno, Family famiglia, int quale) { - Global.indi = perno.getId(); - famiglia = famiglia == null ? perno.getSpouseFamilies(Global.gc).get(quale) : famiglia; - if( contesto instanceof Famiglia ) { - Memoria.replacePrimo(famiglia); - ((Activity)contesto).recreate(); // Non accumula activity nello stack + private static void concludeSpouseChoice(Context context, Person pivot, Family family, int which) { + Global.indi = pivot.getId(); + family = family == null ? pivot.getSpouseFamilies(Global.gc).get(which) : family; + if( context instanceof FamilyActivity) { + Memory.replaceFirst(family); + ((Activity)context).recreate(); // It does not accumulate activities on the stack } else { - Memoria.setPrimo(famiglia); - contesto.startActivity(new Intent(contesto, Famiglia.class)); + Memory.setFirst(family); + context.startActivity(new Intent(context, FamilyActivity.class)); } } - // Usato per collegare una persona ad un'altra, solo in modalità inesperto - // Verifica se il perno potrebbe avere o ha molteplici matrimoni e chiede a quale attaccare un coniuge o un figlio - // È anche responsabile di settare 'idFamiglia' oppure 'collocazione' - static boolean controllaMultiMatrimoni(Intent intento, Context contesto, Fragment frammento) { - String idPerno = intento.getStringExtra("idIndividuo"); - Person perno = Global.gc.getPerson(idPerno); - List famGenitori = perno.getParentFamilies(Global.gc); - List famSposi = perno.getSpouseFamilies(Global.gc); - int relazione = intento.getIntExtra("relazione", 0); - ArrayAdapter adapter = new ArrayAdapter<>(contesto, android.R.layout.simple_list_item_1); - - // Genitori: esiste già una famiglia che abbia almeno uno spazio vuoto - if( relazione == 1 && famGenitori.size() == 1 - && (famGenitori.get(0).getHusbandRefs().isEmpty() || famGenitori.get(0).getWifeRefs().isEmpty()) ) - intento.putExtra("idFamiglia", famGenitori.get(0).getId()); // aggiunge 'idFamiglia' all'intent esistente - // se questa famiglia è già piena di genitori, 'idFamiglia' rimane null - // quindi verrà cercata la famiglia esistente del destinatario oppure si crearà una famiglia nuova - - // Genitori: esistono più famiglie - if( relazione == 1 && famGenitori.size() > 1 ) { - for( Family fam : famGenitori ) + /** + * Used to connect one person to another, in inexperienced mode only + * Checks if the pivot could or has multiple marriages and asks which one to attach a spouse or child to + * Also responsible for setting 'familyId' or 'location' + */ + static boolean checkMultipleMarriages(Intent intent, Context context, Fragment fragment) { + String pivotId = intent.getStringExtra("idIndividuo"); + Person pivot = Global.gc.getPerson(pivotId); + List parentsFamilies = pivot.getParentFamilies(Global.gc); + List spouseFamilies = pivot.getSpouseFamilies(Global.gc); + int relationship = intent.getIntExtra("relazione", 0); + ArrayAdapter adapter = new ArrayAdapter<>(context, android.R.layout.simple_list_item_1); + + // Parents: There is already a family that has at least one empty slot + if( relationship == 1 && parentsFamilies.size() == 1 + && (parentsFamilies.get(0).getHusbandRefs().isEmpty() || parentsFamilies.get(0).getWifeRefs().isEmpty()) ) + intent.putExtra("idFamiglia", parentsFamilies.get(0).getId()); // add 'familyId' to the existing intent + // if this family is already full of parents, 'idFamily' remains null + // then the recipient's existing family will be searched or a new family will be created + + // Parents: There are multiple families + if( relationship == 1 && parentsFamilies.size() > 1 ) { + for( Family fam : parentsFamilies ) if( fam.getHusbandRefs().isEmpty() || fam.getWifeRefs().isEmpty() ) - adapter.add(new NuovoParente.VoceFamiglia(contesto, fam)); + adapter.add(new NewRelativeDialog.FamilyItem(context, fam)); if( adapter.getCount() == 1 ) - intento.putExtra("idFamiglia", adapter.getItem(0).famiglia.getId()); + intent.putExtra("idFamiglia", adapter.getItem(0).family.getId()); else if( adapter.getCount() > 1 ) { - new AlertDialog.Builder(contesto).setTitle(R.string.which_family_add_parent) + new AlertDialog.Builder(context).setTitle(R.string.which_family_add_parent) .setAdapter(adapter, (dialog, quale) -> { - intento.putExtra("idFamiglia", adapter.getItem(quale).famiglia.getId()); - concludiMultiMatrimoni(contesto, intento, frammento); + intent.putExtra("idFamiglia", adapter.getItem(quale).family.getId()); + finishCheckingMultipleMarriages(context, intent, fragment); }).show(); return true; } } - // Fratello - else if( relazione == 2 && famGenitori.size() == 1 ) { - intento.putExtra("idFamiglia", famGenitori.get(0).getId()); - } else if( relazione == 2 && famGenitori.size() > 1 ) { - new AlertDialog.Builder(contesto).setTitle(R.string.which_family_add_sibling) - .setItems(elencoFamiglie(famGenitori), (dialog, quale) -> { - intento.putExtra("idFamiglia", famGenitori.get(quale).getId()); - concludiMultiMatrimoni(contesto, intento, frammento); + // Sibling + else if( relationship == 2 && parentsFamilies.size() == 1 ) { + intent.putExtra("idFamiglia", parentsFamilies.get(0).getId()); + } else if( relationship == 2 && parentsFamilies.size() > 1 ) { + new AlertDialog.Builder(context).setTitle(R.string.which_family_add_sibling) + .setItems(listFamilies(parentsFamilies), (dialog, quale) -> { + intent.putExtra("idFamiglia", parentsFamilies.get(quale).getId()); + finishCheckingMultipleMarriages(context, intent, fragment); }).show(); return true; } - // Coniuge - else if( relazione == 3 && famSposi.size() == 1 ) { - if( famSposi.get(0).getHusbandRefs().isEmpty() || famSposi.get(0).getWifeRefs().isEmpty() ) // Se c'è uno slot libero - intento.putExtra("idFamiglia", famSposi.get(0).getId()); - } else if( relazione == 3 && famSposi.size() > 1 ) { - for( Family fam : famSposi ) { + // Spouse + else if( relationship == 3 && spouseFamilies.size() == 1 ) { + if( spouseFamilies.get(0).getHusbandRefs().isEmpty() || spouseFamilies.get(0).getWifeRefs().isEmpty() ) // Se c'è uno slot libero + intent.putExtra("idFamiglia", spouseFamilies.get(0).getId()); + } else if( relationship == 3 && spouseFamilies.size() > 1 ) { + for( Family fam : spouseFamilies ) { if( fam.getHusbandRefs().isEmpty() || fam.getWifeRefs().isEmpty() ) - adapter.add(new NuovoParente.VoceFamiglia(contesto, fam)); + adapter.add(new NewRelativeDialog.FamilyItem(context, fam)); } - // Nel caso di zero famiglie papabili, idFamiglia rimane null + // In the case of zero eligible families, familyId remains null if( adapter.getCount() == 1 ) { - intento.putExtra("idFamiglia", adapter.getItem(0).famiglia.getId()); + intent.putExtra("idFamiglia", adapter.getItem(0).family.getId()); } else if( adapter.getCount() > 1 ) { - //adapter.add(new NuovoParente.VoceFamiglia(contesto,perno) ); - new AlertDialog.Builder(contesto).setTitle(R.string.which_family_add_spouse) - .setAdapter(adapter, (dialog, quale) -> { - intento.putExtra("idFamiglia", adapter.getItem(quale).famiglia.getId()); - concludiMultiMatrimoni(contesto, intento, frammento); + //adapter.add(new NewRelativeDialog.FamilyItem(context, pivot) ); + new AlertDialog.Builder(context).setTitle(R.string.which_family_add_spouse) + .setAdapter(adapter, (dialog, which) -> { + intent.putExtra("idFamiglia", adapter.getItem(which).family.getId()); + finishCheckingMultipleMarriages(context, intent, fragment); }).show(); return true; } } - // Figlio: esiste già una famiglia con o senza figli - else if( relazione == 4 && famSposi.size() == 1 ) { - intento.putExtra("idFamiglia", famSposi.get(0).getId()); - } // Figlio: esistono molteplici famiglie coniugali - else if( relazione == 4 && famSposi.size() > 1 ) { - new AlertDialog.Builder(contesto).setTitle(R.string.which_family_add_child) - .setItems(elencoFamiglie(famSposi), (dialog, quale) -> { - intento.putExtra("idFamiglia", famSposi.get(quale).getId()); - concludiMultiMatrimoni(contesto, intento, frammento); + // Child: A family already exists with or without children + else if( relationship == 4 && spouseFamilies.size() == 1 ) { + intent.putExtra("idFamiglia", spouseFamilies.get(0).getId()); + } // Son: there are many conjugal families + else if( relationship == 4 && spouseFamilies.size() > 1 ) { + new AlertDialog.Builder(context).setTitle(R.string.which_family_add_child) + .setItems(listFamilies(spouseFamilies), (dialog, quale) -> { + intent.putExtra("idFamiglia", spouseFamilies.get(quale).getId()); + finishCheckingMultipleMarriages(context, intent, fragment); }).show(); return true; } - // Non avendo trovato una famiglia di perno, dice ad Anagrafe di cercare di collocare perno nella famiglia del destinatario - if( intento.getStringExtra("idFamiglia") == null && intento.getBooleanExtra("anagrafeScegliParente", false) ) - intento.putExtra("collocazione", "FAMIGLIA_ESISTENTE"); + // Not having found a family of the pivot, he tells the ListOfPeopleActivity to try to place the pivot in the recipient's family + if( intent.getStringExtra("idFamiglia") == null && intent.getBooleanExtra("anagrafeScegliParente", false) ) + intent.putExtra("collocazione", "FAMIGLIA_ESISTENTE"); return false; } - // Conclusione della funzione precedente - static void concludiMultiMatrimoni(Context contesto, Intent intento, Fragment frammento) { - if( intento.getBooleanExtra("anagrafeScegliParente", false) ) { - // apre Anagrafe + /** + * Conclusion of the previous function + */ + static void finishCheckingMultipleMarriages(Context contesto, Intent intent, Fragment frammento) { + if( intent.getBooleanExtra("anagrafeScegliParente", false) ) { + // open ListOfPeopleFragment if( frammento != null ) - frammento.startActivityForResult(intento, 1401); + frammento.startActivityForResult(intent, 1401); else - ((Activity)contesto).startActivityForResult(intento, 1401); - } else // apre EditaIndividuo - contesto.startActivity(intento); - } - - // Controlla che una o più famiglie siano vuote e propone di eliminarle - // 'ancheKo' dice di eseguire 'cheFare' anche cliccando Cancel o fuori dal dialogo - static boolean controllaFamiglieVuote(Context contesto, Runnable cheFare, boolean ancheKo, Family... famiglie) { - List vuote = new ArrayList<>(); - for( Family fam : famiglie ) { - int membri = fam.getHusbandRefs().size() + fam.getWifeRefs().size() + fam.getChildRefs().size(); - if( membri <= 1 && fam.getEventsFacts().isEmpty() && fam.getAllMedia(Global.gc).isEmpty() + ((Activity)contesto).startActivityForResult(intent, 1401); + } else // open IndividualEditorActivity + contesto.startActivity(intent); + } + + /** + * Check that one or more families are empty and propose to eliminate them + * @param evenRunWhenDismissing tells to execute 'whatToDo' even when clicking Cancel or out of the dialog + */ + static boolean checkFamilyItem(Context context, Runnable whatToDo, boolean evenRunWhenDismissing, Family... families) { + List items = new ArrayList<>(); + for( Family fam : families ) { + int numMembers = fam.getHusbandRefs().size() + fam.getWifeRefs().size() + fam.getChildRefs().size(); + if( numMembers <= 1 && fam.getEventsFacts().isEmpty() && fam.getAllMedia(Global.gc).isEmpty() && fam.getAllNotes(Global.gc).isEmpty() && fam.getSourceCitations().isEmpty() ) { - vuote.add(fam); + items.add(fam); } } - if( vuote.size() > 0 ) { - new AlertDialog.Builder(contesto).setMessage(R.string.empty_family_delete) + if( items.size() > 0 ) { + new AlertDialog.Builder(context).setMessage(R.string.empty_family_delete) .setPositiveButton(android.R.string.yes, (dialog, i) -> { - for( Family fam : vuote ) - Chiesa.deleteFamily(fam); // Così capita di salvare più volte insieme... ma vabè - if( cheFare != null ) cheFare.run(); + for( Family fam : items ) + ChurchFragment.deleteFamily(fam); // So it happens to save several times together ... but oh well + if( whatToDo != null ) whatToDo.run(); }).setNeutralButton(android.R.string.cancel, (dialog, i) -> { - if( ancheKo ) cheFare.run(); + if( evenRunWhenDismissing ) whatToDo.run(); }).setOnCancelListener(dialog -> { - if( ancheKo ) cheFare.run(); + if( evenRunWhenDismissing ) whatToDo.run(); }).show(); return true; } return false; } - // Display a dialog to edit the ID of any record + /** + * Display a dialog to edit the ID of any record + */ public static void editId(Context context, ExtensionContainer record, Runnable refresh) { View view = ((Activity)context).getLayoutInflater().inflate(R.layout.id_editor, null); EditText inputField = view.findViewById(R.id.edit_id_input_field); @@ -1362,24 +1459,24 @@ public static void editId(Context context, ExtensionContainer record, Runnable r U.save(true, modified.toArray()); } else if( record instanceof Media ) { Media media = (Media)record; - ContenitoriMedia mediaContainers = new ContenitoriMedia(Global.gc, media, newId); + MediaContainers mediaContainers = new MediaContainers(Global.gc, media, newId); media.setId(newId); U.updateChangeDate(media); U.save(true, mediaContainers.containers.toArray()); } else if( record instanceof Note ) { Note note = (Note)record; - ContenitoriNota noteContainers = new ContenitoriNota(Global.gc, note, newId); + NoteContainers noteContainers = new NoteContainers(Global.gc, note, newId); note.setId(newId); U.updateChangeDate(note); U.save(true, noteContainers.containers.toArray()); } else if( record instanceof Source ) { - ListaCitazioniFonte citations = new ListaCitazioniFonte(Global.gc, oldId); - for( ListaCitazioniFonte.Tripletta triple : citations.lista ) - triple.citazione.setRef(newId); + ListOfSourceCitations citations = new ListOfSourceCitations(Global.gc, oldId); + for( ListOfSourceCitations.Triplet triple : citations.list) + triple.citation.setRef(newId); Source source = (Source)record; source.setId(newId); U.updateChangeDate(source); - U.save(true, citations.getCapi()); + U.save(true, citations.getProgenitors()); } else if( record instanceof Repository ) { Set modified = new HashSet<>(); for( Source source : Global.gc.getSources() ) { @@ -1465,7 +1562,9 @@ public void afterTextChanged(Editable e) { } } - // Mostra un messaggio Toast anche da un thread collaterale + /** + * Show a Toast message even from a side thread + */ static void toast(Activity activity, int message) { toast(activity, activity.getString(message)); } diff --git a/app/src/main/java/app/familygem/VistaTesto.java b/app/src/main/java/app/familygem/VariableWidthTextView.java similarity index 78% rename from app/src/main/java/app/familygem/VistaTesto.java rename to app/src/main/java/app/familygem/VariableWidthTextView.java index ae9de5db..60383714 100644 --- a/app/src/main/java/app/familygem/VistaTesto.java +++ b/app/src/main/java/app/familygem/VariableWidthTextView.java @@ -1,5 +1,3 @@ -// TextView che adatta la larghezza anche a molteplici linee - package app.familygem; import android.content.Context; @@ -7,9 +5,13 @@ import android.text.Layout; import android.util.AttributeSet; -public class VistaTesto extends AppCompatTextView { +/** + * TextView that adapts the width even to multiple lines + * TextView che adatta la larghezza anche a molteplici linee + * */ +public class VariableWidthTextView extends AppCompatTextView { - public VistaTesto( Context context, AttributeSet attrs ) { + public VariableWidthTextView(Context context, AttributeSet attrs ) { super( context, attrs ); } diff --git a/app/src/main/java/app/familygem/constant/Format.java b/app/src/main/java/app/familygem/constant/Format.java index d2145fcf..fddfaaff 100644 --- a/app/src/main/java/app/familygem/constant/Format.java +++ b/app/src/main/java/app/familygem/constant/Format.java @@ -1,7 +1,8 @@ -// Date formats - package app.familygem.constant; +/** + * All of the formats a Date can be displayed as + * */ public class Format { public static final String[] PATTERNS = { "d MMM yyy", "d M yyy", "MMM yyy", "M yyy", "d MMM", "yyy" }; public static final String D_M_Y = PATTERNS[0]; diff --git a/app/src/main/java/app/familygem/constant/Gender.java b/app/src/main/java/app/familygem/constant/Gender.java index a9d4b673..7b9ede91 100644 --- a/app/src/main/java/app/familygem/constant/Gender.java +++ b/app/src/main/java/app/familygem/constant/Gender.java @@ -11,7 +11,9 @@ public enum Gender { UNKNOWN, // 'SEX U' OTHER; // Some other value - // Find the gender of a Person + /** + * Finds the gender of [person] + * */ public static Gender getGender(Person person) { for( EventFact fact : person.getEventsFacts() ) { if( fact.getTag() != null && fact.getTag().equals("SEX") ) { diff --git a/app/src/main/java/app/familygem/constant/Kind.java b/app/src/main/java/app/familygem/constant/Kind.java index 95860568..6ffff6db 100644 --- a/app/src/main/java/app/familygem/constant/Kind.java +++ b/app/src/main/java/app/familygem/constant/Kind.java @@ -1,7 +1,7 @@ -// GEDCOM date types - package app.familygem.constant; - +/** + * GEDCOM date types + * */ public enum Kind { EXACT(""), diff --git a/app/src/main/java/app/familygem/constant/Status.java b/app/src/main/java/app/familygem/constant/Status.java index 4776654a..f1a92a6d 100644 --- a/app/src/main/java/app/familygem/constant/Status.java +++ b/app/src/main/java/app/familygem/constant/Status.java @@ -1,16 +1,18 @@ -// Family situation - package app.familygem.constant; import org.folg.gedcom.model.EventFact; import org.folg.gedcom.model.Family; - +/** + * Family situation + * */ public enum Status { NONE, // Generic relationship MARRIED, DIVORCED, SEPARATED; - // Find the status of a Family + /** + * Finds the status of [family] + * */ public static Status getStatus(Family family) { Status status = NONE; if( family != null ) { diff --git a/app/src/main/java/app/familygem/detail/Indirizzo.java b/app/src/main/java/app/familygem/detail/AddressActivity.java similarity index 59% rename from app/src/main/java/app/familygem/detail/Indirizzo.java rename to app/src/main/java/app/familygem/detail/AddressActivity.java index f503c35c..6ccbf002 100644 --- a/app/src/main/java/app/familygem/detail/Indirizzo.java +++ b/app/src/main/java/app/familygem/detail/AddressActivity.java @@ -1,21 +1,21 @@ package app.familygem.detail; import org.folg.gedcom.model.Address; -import app.familygem.Dettaglio; -import app.familygem.Memoria; +import app.familygem.DetailActivity; +import app.familygem.Memory; import app.familygem.R; import app.familygem.U; -public class Indirizzo extends Dettaglio { +public class AddressActivity extends DetailActivity { Address a; @Override - public void impagina() { + public void format() { setTitle(R.string.address); placeSlug("ADDR"); a = (Address)cast(Address.class); - place(getString(R.string.value), "Value", false, true); // Fortemente deprecato in favore dell'indirizzo frammentato + place(getString(R.string.value), "Value", false, true); // Strongly deprecated in favor of the fragmented address //Fortemente deprecato in favore dell'indirizzo frammentato place(getString(R.string.name), "Name", false, false); // _name non standard place(getString(R.string.line_1), "AddressLine1"); place(getString(R.string.line_2), "AddressLine2"); @@ -28,9 +28,9 @@ public void impagina() { } @Override - public void elimina() { - eliminaIndirizzo(Memoria.oggettoContenitore()); - U.updateChangeDate(Memoria.oggettoCapo()); - Memoria.annullaIstanze(a); + public void delete() { + deleteAddress(Memory.getSecondToLastObject()); + U.updateChangeDate(Memory.firstObject()); + Memory.setInstanceAndAllSubsequentToNull(a); } } diff --git a/app/src/main/java/app/familygem/detail/ArchivioRef.java b/app/src/main/java/app/familygem/detail/ArchivioRef.java deleted file mode 100644 index a595c201..00000000 --- a/app/src/main/java/app/familygem/detail/ArchivioRef.java +++ /dev/null @@ -1,65 +0,0 @@ -package app.familygem.detail; - -import android.content.Context; -import android.content.Intent; -import androidx.cardview.widget.CardView; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.LinearLayout; -import android.widget.TextView; -import org.folg.gedcom.model.Repository; -import org.folg.gedcom.model.RepositoryRef; -import org.folg.gedcom.model.Source; -import app.familygem.Dettaglio; -import app.familygem.Memoria; -import app.familygem.R; -import app.familygem.U; -import static app.familygem.Global.gc; - -public class ArchivioRef extends Dettaglio { - - RepositoryRef r; - - @Override - public void impagina() { - placeSlug("REPO"); - r = (RepositoryRef)cast(RepositoryRef.class); - if( r.getRepository(gc) != null ) { // valido - setTitle(R.string.repository_citation); - View cartaRepo = mettiArchivio(box, r.getRepository(gc)); - cartaRepo.setTag(R.id.tag_oggetto, r.getRepository(gc)); // per il menu contestuale todo ancora necessario? - registerForContextMenu(cartaRepo); - } else if( r.getRef() != null ) { // di un archivio inesistente (magari eliminato) - setTitle(R.string.inexistent_repository_citation); - } else { // senza ref?? - setTitle(R.string.repository_note); - } - place(getString(R.string.value), "Value", false, true); - place(getString(R.string.call_number), "CallNumber"); - place(getString(R.string.media_type), "MediaType"); - placeExtensions(r); - U.placeNotes(box, r, true); - } - - public static View mettiArchivio(LinearLayout scatola, final Repository repo) { - final Context contesto = scatola.getContext(); - View cartaRepo = LayoutInflater.from(contesto).inflate(R.layout.pezzo_fonte, scatola, false); - scatola.addView(cartaRepo); - ((TextView)cartaRepo.findViewById(R.id.fonte_testo)).setText(repo.getName()); - ((CardView)cartaRepo).setCardBackgroundColor(contesto.getResources().getColor(R.color.archivio)); - cartaRepo.setOnClickListener(v -> { - Memoria.setPrimo(repo); - contesto.startActivity(new Intent(contesto, Archivio.class)); - }); - return cartaRepo; - } - - @Override - public void elimina() { - // Elimina la citazione all'archivio a aggiorna la data della fonte che la conteneva - Source contenitore = (Source)Memoria.oggettoContenitore(); - contenitore.setRepositoryRef(null); - U.updateChangeDate(contenitore); - Memoria.annullaIstanze(r); - } -} \ No newline at end of file diff --git a/app/src/main/java/app/familygem/detail/Autore.java b/app/src/main/java/app/familygem/detail/AuthorActivity.java similarity index 63% rename from app/src/main/java/app/familygem/detail/Autore.java rename to app/src/main/java/app/familygem/detail/AuthorActivity.java index 9f67fbb4..fa05287f 100644 --- a/app/src/main/java/app/familygem/detail/Autore.java +++ b/app/src/main/java/app/familygem/detail/AuthorActivity.java @@ -1,21 +1,21 @@ package app.familygem.detail; import org.folg.gedcom.model.Submitter; -import app.familygem.Dettaglio; -import app.familygem.Podio; +import app.familygem.DetailActivity; +import app.familygem.ListOfAuthorsFragment; import app.familygem.R; import app.familygem.U; -public class Autore extends Dettaglio { +public class AuthorActivity extends DetailActivity { Submitter a; @Override - public void impagina() { + public void format() { setTitle(R.string.submitter); a = (Submitter)cast(Submitter.class); placeSlug("SUBM", a.getId()); - place(getString(R.string.value), "Value", false, true); // Value de che? + place(getString(R.string.value), "Value", false, true); // Value of what? //Value de che? place(getString(R.string.name), "Name"); place(getString(R.string.address), a.getAddress()); place(getString(R.string.www), "Www"); @@ -29,9 +29,9 @@ public void impagina() { } @Override - public void elimina() { - // Ricordiamo che almeno un autore deve essere specificato - // non aggiorna la data di nessun record - Podio.eliminaAutore( a ); + public void delete() { + // Remember that at least one author must be specified // Ricordiamo che almeno un autore deve essere specificato + // don't update the date of any record // non aggiorna la data di nessun record + ListOfAuthorsFragment.deleteAuthor( a ); } } diff --git a/app/src/main/java/app/familygem/detail/Cambiamenti.java b/app/src/main/java/app/familygem/detail/ChangesActivity.java similarity index 67% rename from app/src/main/java/app/familygem/detail/Cambiamenti.java rename to app/src/main/java/app/familygem/detail/ChangesActivity.java index 89b13471..a69ecc34 100644 --- a/app/src/main/java/app/familygem/detail/Cambiamenti.java +++ b/app/src/main/java/app/familygem/detail/ChangesActivity.java @@ -3,31 +3,31 @@ import android.view.Menu; import org.folg.gedcom.model.Change; import org.folg.gedcom.model.DateTime; -import app.familygem.Dettaglio; +import app.familygem.DetailActivity; import app.familygem.R; import app.familygem.U; -public class Cambiamenti extends Dettaglio { +public class ChangesActivity extends DetailActivity { Change c; @Override - public void impagina() { + public void format() { setTitle(R.string.change_date); placeSlug("CHAN"); c = (Change)cast(Change.class); DateTime dateTime = c.getDateTime(); if( dateTime != null ) { if( dateTime.getValue() != null ) - U.metti(box, getString(R.string.value), dateTime.getValue()); + U.place(box, getString(R.string.value), dateTime.getValue()); if( dateTime.getTime() != null ) - U.metti(box, getString(R.string.time), dateTime.getTime()); + U.place(box, getString(R.string.time), dateTime.getTime()); } placeExtensions(c); U.placeNotes(box, c, true); } - // Qui non c'è bisogno di un menu + // You don't need a menu here @Override public boolean onCreateOptionsMenu(Menu m) { return false; diff --git a/app/src/main/java/app/familygem/detail/CitazioneFonte.java b/app/src/main/java/app/familygem/detail/CitazioneFonte.java deleted file mode 100644 index 6a3aad9c..00000000 --- a/app/src/main/java/app/familygem/detail/CitazioneFonte.java +++ /dev/null @@ -1,52 +0,0 @@ -package app.familygem.detail; - -import org.folg.gedcom.model.Note; -import org.folg.gedcom.model.SourceCitation; -import org.folg.gedcom.model.SourceCitationContainer; -import app.familygem.Dettaglio; -import app.familygem.Memoria; -import app.familygem.R; -import app.familygem.U; -import static app.familygem.Global.gc; - -public class CitazioneFonte extends Dettaglio { - - SourceCitation c; - - @Override - public void impagina() { - placeSlug("SOUR"); - c = (SourceCitation)cast(SourceCitation.class); - if( c.getSource(gc) != null ) { // source CITATION valida - setTitle(R.string.source_citation); - U.placeSource(box, c.getSource(gc), true); - } else if( c.getRef() != null ) { // source CITATION di una fonte inesistente (magari eliminata) - setTitle(R.string.inexistent_source_citation); - } else { // source NOTE - setTitle(R.string.source_note); - place(getString(R.string.value), "Value", true, true); - } - place(getString(R.string.page), "Page", true, true); - place(getString(R.string.date), "Date"); - place(getString(R.string.text), "Text", true, true); // vale sia per sourceNote che per sourceCitation - //c.getTextOrValue(); praticamente inutile - //if( c.getDataTagContents() != null ) - // U.metti( box, "Data Tag Contents", c.getDataTagContents().toString() ); // COMBINED DATA TEXT - place(getString(R.string.certainty), "Quality"); // un numero da 0 a 3 - //metti( "Ref", "Ref", false, false ); // l'id della fonte - placeExtensions(c); - U.placeNotes(box, c, true); - U.placeMedia(box, c, true); - } - - @Override - public void elimina() { - Object contenitore = Memoria.oggettoContenitore(); - if( contenitore instanceof Note ) // Note non extende SourceCitationContainer - ((Note)contenitore).getSourceCitations().remove( c ); - else - ((SourceCitationContainer)contenitore).getSourceCitations().remove( c ); - U.updateChangeDate( Memoria.oggettoCapo() ); - Memoria.annullaIstanze(c); - } -} diff --git a/app/src/main/java/app/familygem/detail/Evento.java b/app/src/main/java/app/familygem/detail/EventActivity.java similarity index 67% rename from app/src/main/java/app/familygem/detail/Evento.java rename to app/src/main/java/app/familygem/detail/EventActivity.java index 2afcd845..7f4137c4 100644 --- a/app/src/main/java/app/familygem/detail/Evento.java +++ b/app/src/main/java/app/familygem/detail/EventActivity.java @@ -4,31 +4,33 @@ import org.folg.gedcom.model.Family; import org.folg.gedcom.model.PersonFamilyCommonContainer; import java.util.Arrays; -import app.familygem.Dettaglio; -import app.familygem.IndividuoEventi; -import app.familygem.Memoria; +import app.familygem.DetailActivity; +import app.familygem.IndividualEventsFragment; +import app.familygem.Memory; import app.familygem.R; import app.familygem.U; -public class Evento extends Dettaglio { +public class EventActivity extends DetailActivity { EventFact e; - // Lista di tag di eventi utili per evitare di mettere il Value dell'EventFact - String[] eventTags = { "BIRT","CHR","DEAT","BURI","CREM","ADOP","BAPM","BARM","BASM","BLES", // Eventi di Individuo + /** + * List of event tags useful to avoid putting the Value of the EventFact + * */ + String[] eventTags = { "BIRT","CHR","DEAT","BURI","CREM","ADOP","BAPM","BARM","BASM","BLES", // Individual events "CHRA","CONF","FCOM","ORDN","NATU","EMIG","IMMI","CENS","PROB","WILL","GRAD","RETI", - "ANUL","DIV","DIVF","ENGA","MARB","MARC","MARR","MARL","MARS" }; // eventi di Famiglia + "ANUL","DIV","DIVF","ENGA","MARB","MARC","MARR","MARL","MARS" }; // Family events @Override - public void impagina() { + public void format() { e = (EventFact)cast(EventFact.class); - if( Memoria.oggettoCapo() instanceof Family ) - setTitle(writeEventTitle((Family)Memoria.oggettoCapo(), e)); + if( Memory.firstObject() instanceof Family ) + setTitle(writeEventTitle((Family) Memory.firstObject(), e)); else - setTitle(IndividuoEventi.writeEventTitle(e)); // It includes e.getDisplayType() + setTitle(IndividualEventsFragment.writeEventTitle(e)); // It includes e.getDisplayType() placeSlug(e.getTag()); - if( Arrays.asList(eventTags).contains(e.getTag()) ) // è un evento (senza Value) + if( Arrays.asList(eventTags).contains(e.getTag()) ) // is an event (without Value) place(getString(R.string.value), "Value", false, true); - else // tutti gli altri casi, solitamente attributi (con Value) + else // all other cases, usually attributes (with Value) place(getString(R.string.value), "Value", true, true); if( e.getTag().equals("EVEN") || e.getTag().equals("MARR") ) place(getString(R.string.type), "Type"); // Type of event, relationship etc. @@ -47,7 +49,7 @@ public void impagina() { place(getString(R.string.fax), "Fax", false, false); place(getString(R.string.rin), "Rin", false, false); place(getString(R.string.user_id), "Uid", false, false); - //altriMetodi = { "WwwTag", "EmailTag", "UidTag" }; + //otherMethods = { "WwwTag", "EmailTag", "UidTag" }; placeExtensions(e); U.placeNotes(box, e, true); U.placeMedia(box, e, true); @@ -55,14 +57,17 @@ public void impagina() { } @Override - public void elimina() { - ((PersonFamilyCommonContainer)Memoria.oggettoContenitore()).getEventsFacts().remove(e); - U.updateChangeDate(Memoria.oggettoCapo()); - Memoria.annullaIstanze(e); + public void delete() { + ((PersonFamilyCommonContainer) Memory.getSecondToLastObject()).getEventsFacts().remove(e); + U.updateChangeDate(Memory.firstObject()); + Memory.setInstanceAndAllSubsequentToNull(e); } - // Elimina i principali tag vuoti e eventualmente aggiunge la 'Y' - public static void ripulisciTag( EventFact ef ) { + /** + * Delete the main empty tags and eventually add the 'Y' + * Elimina i principali tag vuoti e eventualmente aggiunge la 'Y' + * */ + public static void cleanUpTag(EventFact ef ) { if( ef.getType() != null && ef.getType().isEmpty() ) ef.setType(null); if( ef.getDate() != null && ef.getDate().isEmpty() ) ef.setDate(null); if( ef.getPlace() != null && ef.getPlace().isEmpty() ) ef.setPlace(null); diff --git a/app/src/main/java/app/familygem/detail/Estensione.java b/app/src/main/java/app/familygem/detail/ExtensionActivity.java similarity index 56% rename from app/src/main/java/app/familygem/detail/Estensione.java rename to app/src/main/java/app/familygem/detail/ExtensionActivity.java index e7735428..0c82b2ee 100644 --- a/app/src/main/java/app/familygem/detail/Estensione.java +++ b/app/src/main/java/app/familygem/detail/ExtensionActivity.java @@ -1,26 +1,26 @@ package app.familygem.detail; import org.folg.gedcom.model.GedcomTag; -import app.familygem.Dettaglio; -import app.familygem.Memoria; +import app.familygem.DetailActivity; +import app.familygem.Memory; import app.familygem.R; import app.familygem.U; -public class Estensione extends Dettaglio { +public class ExtensionActivity extends DetailActivity { GedcomTag e; @Override - public void impagina() { + public void format() { setTitle(getString(R.string.extension)); e = (GedcomTag)cast(GedcomTag.class); placeSlug(e.getTag()); place(getString(R.string.id), "Id", false, false); place(getString(R.string.value), "Value", true, true); place("Ref", "Ref", false, false); - place("ParentTagName", "ParentTagName", false, false); // non ho capito se viene usato o no + place("ParentTagName", "ParentTagName", false, false); // I did not understand if it is used or not //non ho capito se viene usato o no for( GedcomTag child : e.getChildren() ) { - String text = U.scavaEstensione(child, 0); + String text = U.traverseExtension(child, 0); if( text.endsWith("\n") ) text = text.substring(0, text.length() - 1); placePiece(child.getTag(), text, child, true); @@ -28,8 +28,8 @@ public void impagina() { } @Override - public void elimina() { - U.eliminaEstensione(e, Memoria.oggettoContenitore(), null); - U.updateChangeDate(Memoria.oggettoCapo()); + public void delete() { + U.deleteExtension(e, Memory.getSecondToLastObject(), null); + U.updateChangeDate(Memory.firstObject()); } } diff --git a/app/src/main/java/app/familygem/detail/Famiglia.java b/app/src/main/java/app/familygem/detail/FamilyActivity.java similarity index 60% rename from app/src/main/java/app/familygem/detail/Famiglia.java rename to app/src/main/java/app/familygem/detail/FamilyActivity.java index 92155319..acc51f47 100644 --- a/app/src/main/java/app/familygem/detail/Famiglia.java +++ b/app/src/main/java/app/familygem/detail/FamilyActivity.java @@ -14,12 +14,12 @@ import java.util.Arrays; import java.util.Iterator; import java.util.List; -import app.familygem.Dettaglio; -import app.familygem.EditaIndividuo; +import app.familygem.DetailActivity; +import app.familygem.IndividualEditorActivity; import app.familygem.Global; -import app.familygem.Individuo; -import app.familygem.IndividuoFamiliari; -import app.familygem.Memoria; +import app.familygem.IndividualPersonActivity; +import app.familygem.IndividualFamilyFragment; +import app.familygem.Memory; import app.familygem.R; import app.familygem.U; import app.familygem.constant.Gender; @@ -28,7 +28,7 @@ import static app.familygem.Global.gc; import androidx.appcompat.app.AlertDialog; -public class Famiglia extends Dettaglio { +public class FamilyActivity extends DetailActivity { Family f; static String[] pediTexts = {U.s(R.string.undefined) + " (" + U.s(R.string.birth).toLowerCase() + ")", @@ -36,16 +36,16 @@ public class Famiglia extends Dettaglio { static String[] pediTypes = {null, "birth", "adopted", "foster"}; @Override - public void impagina() { + public void format() { setTitle(R.string.family); f = (Family)cast(Family.class); placeSlug("FAM", f.getId()); - for( SpouseRef refMarito : f.getHusbandRefs() ) - member(refMarito, Relation.PARTNER); - for( SpouseRef refMoglie : f.getWifeRefs() ) - member(refMoglie, Relation.PARTNER); - for( ChildRef refFiglio : f.getChildRefs() ) - member(refFiglio, Relation.CHILD); + for( SpouseRef husbandRef : f.getHusbandRefs() ) + addMember(husbandRef, Relation.PARTNER); + for( SpouseRef wifeRef : f.getWifeRefs() ) + addMember(wifeRef, Relation.PARTNER); + for( ChildRef childRef : f.getChildRefs() ) + addMember(childRef, Relation.CHILD); for( EventFact ef : f.getEventsFacts() ) { place(writeEventTitle(f, ef), ef); } @@ -56,73 +56,73 @@ public void impagina() { U.placeChangeDate(box, f.getChange()); } - // Add a member to the family - void member(SpouseRef sr, Relation relation) { + /** + * Add a member to the family + * */ + void addMember(SpouseRef sr, Relation relation) { Person p = sr.getPerson(gc); if( p == null ) return; - View vistaPersona = U.mettiIndividuo(box, p, getRole(p, f, relation, true) + writeLineage(p, f)); - vistaPersona.setTag(R.id.tag_oggetto, p); // per il menu contestuale in Dettaglio - /* Ref nell'individuo verso la famiglia - Se la stessa persona è presente più volte con lo stesso ruolo (parent/child) nella stessa famiglia - i 2 loop seguenti individuano nella person il *primo* FamilyRef (INDI.FAMS / INDI.FAMC) che rimanda a quella famiglia - Non prendono quello con lo stesso indice del corrispondente Ref nella famiglia (FAM.HUSB / FAM.WIFE) - Poteva essere un problema in caso di 'Scollega', ma non più perché tutto il contenuto di Famiglia viene ricaricato + View personView = U.placeIndividual(box, p, getRole(p, f, relation, true) + writeLineage(p, f)); + personView.setTag(R.id.tag_object, p); // for the context menu in DetailActivity + /* Ref in the individual towards the family //Ref nell'individuo verso la famiglia + If the same person is present several times with the same role (parent / child) in the same family // Se la stessa persona è presente più volte con lo stesso ruolo (parent/child) nella stessa famiglia + the 2 following loops identify in the person the * first * FamilyRef (INDI.FAMS / INDI.FAMC) that refers to that family // i 2 loop seguenti individuano nella person il *primo* FamilyRef (INDI.FAMS / INDI.FAMC) che rimanda a quella famiglia + They do not take the one with the same index as the corresponding Ref in the family (FAM.HUSB / FAM.WIFE) //Non prendono quello con lo stesso indice del corrispondente Ref nella famiglia (FAM.HUSB / FAM.WIFE) + It could be a problem in case of 'Unlink', but not anymore because all the Family content is reloaded //Poteva essere un problema in caso di 'Scollega', ma non più perché tutto il contenuto di Famiglia viene ricaricato */ if( relation == Relation.PARTNER ) { for( SpouseFamilyRef sfr : p.getSpouseFamilyRefs() ) if( f.getId().equals(sfr.getRef()) ) { - vistaPersona.setTag(R.id.tag_spouse_family_ref, sfr); + personView.setTag(R.id.tag_spouse_family_ref, sfr); break; } } else if( relation == Relation.CHILD ) { for( ParentFamilyRef pfr : p.getParentFamilyRefs() ) if( f.getId().equals(pfr.getRef()) ) { - vistaPersona.setTag(R.id.tag_spouse_family_ref, pfr); + personView.setTag(R.id.tag_spouse_family_ref, pfr); break; } } - vistaPersona.setTag(R.id.tag_spouse_ref, sr); - registerForContextMenu(vistaPersona); - vistaPersona.setOnClickListener(v -> { + personView.setTag(R.id.tag_spouse_ref, sr); + registerForContextMenu(personView); + personView.setOnClickListener(v -> { List parentFam = p.getParentFamilies(gc); List spouseFam = p.getSpouseFamilies(gc); - // un coniuge con una o più famiglie in cui è figlio + // a spouse with one or more families in which he is a child if( relation == Relation.PARTNER && !parentFam.isEmpty() ) { - U.qualiGenitoriMostrare(this, p, 2); - } // un figlio con una o più famiglie in cui è coniuge + U.askWhichParentsToShow(this, p, 2); + } // a child with one or more families in which he is a spouse else if( relation == Relation.CHILD && !p.getSpouseFamilies(gc).isEmpty() ) { - U.qualiConiugiMostrare(this, p, null); - } // un figlio non sposato che ha più famiglie genitoriali + U.askWhichSpouseToShow(this, p, null); + } // an unmarried child who has multiple parental families else if( parentFam.size() > 1 ) { - if( parentFam.size() == 2 ) { // Swappa tra le 2 famiglie genitoriali + if( parentFam.size() == 2 ) { // Swap between the 2 parental families //Swappa tra le 2 famiglie genitoriali Global.indi = p.getId(); Global.familyNum = parentFam.indexOf(f) == 0 ? 1 : 0; - Memoria.replacePrimo(parentFam.get(Global.familyNum)); + Memory.replaceFirst(parentFam.get(Global.familyNum)); recreate(); - } else // Più di due famiglie - U.qualiGenitoriMostrare( this, p, 2 ); - } // un coniuge senza genitori ma con più famiglie coniugali + } else //More than two families + U.askWhichParentsToShow( this, p, 2 ); + } // a spouse without parents but with multiple marital families //un coniuge senza genitori ma con più famiglie coniugali else if( spouseFam.size() > 1 ) { - if( spouseFam.size() == 2 ) { // Swappa tra le 2 famiglie coniugali + if( spouseFam.size() == 2 ) { // Swap between the 2 marital families //Swappa tra le 2 famiglie coniugali Global.indi = p.getId(); - Family altraFamiglia = spouseFam.get(spouseFam.indexOf(f) == 0 ? 1 : 0); - Memoria.replacePrimo(altraFamiglia); + Family otherFamily = spouseFam.get(spouseFam.indexOf(f) == 0 ? 1 : 0); + Memory.replaceFirst(otherFamily); recreate(); } else - U.qualiConiugiMostrare(this, p, null); + U.askWhichSpouseToShow(this, p, null); } else { - Memoria.setPrimo(p); - startActivity(new Intent(this, Individuo.class)); + Memory.setFirst(p); + startActivity(new Intent(this, IndividualPersonActivity.class)); } }); - if( unRappresentanteDellaFamiglia == null ) - unRappresentanteDellaFamiglia = p; + if( aRepresentativeOfTheFamily == null ) + aRepresentativeOfTheFamily = p; } /** Find the role of a person from their relation with a family - * @param person * @param family Can be null - * @param relation * @param respectFamily The role to find is relative to the family (it becomes 'parent' with children) * @return A descriptor text of the person's role */ @@ -180,7 +180,9 @@ public static String getRole(Person person, Family family, Relation relation, bo return Global.context.getString(role); } - // Find the ParentFamilyRef of a child person in a family + /** + * Find the ParentFamilyRef of a child person in a family + * */ public static ParentFamilyRef findParentFamilyRef(Person person, Family family) { for( ParentFamilyRef parentFamilyRef : person.getParentFamilyRefs() ) { if( parentFamilyRef.getRef().equals(family.getId()) ) { @@ -190,7 +192,9 @@ public static ParentFamilyRef findParentFamilyRef(Person person, Family family) return null; } - // Compose the lineage definition to be added to the role + /** + * Compose the lineage definition to be added to the role + * */ public static String writeLineage(Person person, Family family) { ParentFamilyRef parentFamilyRef = findParentFamilyRef(person, family); if( parentFamilyRef != null ) { @@ -201,7 +205,9 @@ public static String writeLineage(Person person, Family family) { return ""; } - // Display the alert dialog to choose the lineage of one person + /** + * Display the alert dialog to choose the lineage of one person + * */ public static void chooseLineage(Context context, Person person, Family family) { ParentFamilyRef parentFamilyRef = findParentFamilyRef(person, family); if( parentFamilyRef != null ) { @@ -209,36 +215,38 @@ public static void chooseLineage(Context context, Person person, Family family) new AlertDialog.Builder(context).setSingleChoiceItems(pediTexts, actual, (dialog, i) -> { parentFamilyRef.setRelationshipType(pediTypes[i]); dialog.dismiss(); - if( context instanceof Individuo ) - ((IndividuoFamiliari)((Individuo)context).getSupportFragmentManager() + if( context instanceof IndividualPersonActivity) + ((IndividualFamilyFragment)((IndividualPersonActivity)context).getSupportFragmentManager() .findFragmentByTag("android:switcher:" + R.id.schede_persona + ":2")).refresh(); - else if( context instanceof Famiglia ) - ((Famiglia)context).refresh(); + else if( context instanceof FamilyActivity) + ((FamilyActivity)context).refresh(); U.save(true, person); }).show(); } } - // Collega una persona ad una famiglia come genitore o figlio - public static void aggrega( Person person, Family fam, int ruolo ) { - switch( ruolo ) { - case 5: // Genitore - // il ref dell'indi nella famiglia + /** + * Connect a person to a family as a parent or child + * */ + public static void connect(Person person, Family fam, int roleFlag ) { + switch( roleFlag ) { + case 5: // Parent //TODO code smell: use of magic number + // the ref of the indi in the family //il ref dell'indi nella famiglia SpouseRef sr = new SpouseRef(); sr.setRef(person.getId()); - EditaIndividuo.aggiungiConiuge(fam, sr); + IndividualEditorActivity.addSpouse(fam, sr); - // il ref della famiglia nell'indi + // the family ref in the indi //il ref della famiglia nell'indi SpouseFamilyRef sfr = new SpouseFamilyRef(); sfr.setRef( fam.getId() ); - //tizio.getSpouseFamilyRefs().add( sfr ); // no: con lista vuota UnsupportedOperationException - //List listaSfr = tizio.getSpouseFamilyRefs(); // Non va bene: - // quando la lista è inesistente, anzichè restituire una ArrayList restituisce una Collections$EmptyList che è IMMUTABILE cioè non ammette add() - List listaSfr = new ArrayList<>( person.getSpouseFamilyRefs() ); // ok - listaSfr.add( sfr ); // ok - person.setSpouseFamilyRefs( listaSfr ); + //tizio.getSpouseFamilyRefs().add( sfr ); // no: with empty list UnsupportedOperationException //no: con lista vuota UnsupportedOperationException + //List listOfRefs = tizio.getSpouseFamilyRefs(); // That's no good://Non va bene: + // when the list is non-existent, instead of returning an ArrayList it returns a Collections$EmptyList which is IMMUTABLE i.e. it does not allow add () + List listOfRefs = new ArrayList<>( person.getSpouseFamilyRefs() ); // ok + listOfRefs.add( sfr ); // ok + person.setSpouseFamilyRefs( listOfRefs ); break; - case 6: // Figlio + case 6: // Child ChildRef cr = new ChildRef(); cr.setRef( person.getId() ); fam.addChild( cr ); @@ -251,17 +259,19 @@ public static void aggrega( Person person, Family fam, int ruolo ) { } } - // Rimuove il singolo SpouseFamilyRef dall'individuo e il corrispondente SpouseRef dalla famiglia - public static void scollega(SpouseFamilyRef sfr, SpouseRef sr) { - // Dalla persona alla famiglia + /** + * Removes the single SpouseFamilyRef from the individual and the corresponding SpouseRef from the family + * */ + public static void disconnect(SpouseFamilyRef sfr, SpouseRef sr) { + // From person to family //Dalla persona alla famiglia Person person = sr.getPerson(gc); person.getSpouseFamilyRefs().remove(sfr); if( person.getSpouseFamilyRefs().isEmpty() ) - person.setSpouseFamilyRefs(null); // Eventuale lista vuota viene eliminata + person.setSpouseFamilyRefs(null); // Any empty list is deleted //Eventuale lista vuota viene eliminata person.getParentFamilyRefs().remove(sfr); if( person.getParentFamilyRefs().isEmpty() ) person.setParentFamilyRefs(null); - // Dalla famiglia alla persona + // From family to person //Dalla famiglia alla persona Family fam = sfr.getFamily(gc); fam.getHusbandRefs().remove(sr); if( fam.getHusbandRefs().isEmpty() ) @@ -274,15 +284,17 @@ public static void scollega(SpouseFamilyRef sfr, SpouseRef sr) { fam.setChildRefs(null); } - // Rimuove TUTTI i ref di un individuo in una famiglia - public static void scollega(String indiId, Family family) { - // Rimuove i ref dell'indi nella famiglia + /** + * Removes ALL refs from an individual in a family + * */ + public static void disconnect(String indiId, Family family) { + // Removes the refs of the indi in the family //Rimuove i ref dell'indi nella famiglia Iterator spouseRefs = family.getHusbandRefs().iterator(); while( spouseRefs.hasNext() ) if( spouseRefs.next().getRef().equals(indiId) ) spouseRefs.remove(); if( family.getHusbandRefs().isEmpty() ) - family.setHusbandRefs(null); // Elimina eventuale lista vuota + family.setHusbandRefs(null); // Delete any empty list //Elimina eventuale lista vuota spouseRefs = family.getWifeRefs().iterator(); while( spouseRefs.hasNext() ) @@ -298,7 +310,7 @@ public static void scollega(String indiId, Family family) { if( family.getChildRefs().isEmpty() ) family.setChildRefs(null); - // Rimuove i ref della famiglia nell'indi + // Removes family refs in the indi //Rimuove i ref della famiglia nell'indi Person person = gc.getPerson(indiId); Iterator iterSfr = person.getSpouseFamilyRefs().iterator(); while( iterSfr.hasNext() ) diff --git a/app/src/main/java/app/familygem/detail/Fonte.java b/app/src/main/java/app/familygem/detail/Fonte.java deleted file mode 100644 index bf9bc017..00000000 --- a/app/src/main/java/app/familygem/detail/Fonte.java +++ /dev/null @@ -1,83 +0,0 @@ -package app.familygem.detail; - -import android.content.Intent; -import androidx.cardview.widget.CardView; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.LinearLayout; -import android.widget.TextView; -import org.folg.gedcom.model.RepositoryRef; -import org.folg.gedcom.model.Source; -import app.familygem.Biblioteca; -import app.familygem.Dettaglio; -import app.familygem.Memoria; -import app.familygem.R; -import app.familygem.U; -import app.familygem.visitor.ListaCitazioniFonte; -import static app.familygem.Global.gc; - -public class Fonte extends Dettaglio { - - Source f; - - @Override - public void impagina() { - setTitle(R.string.source); - f = (Source)cast(Source.class); - placeSlug("SOUR", f.getId()); - ListaCitazioniFonte citazioni = new ListaCitazioniFonte(gc, f.getId()); - f.putExtension("citaz", citazioni.lista.size()); // per la Biblioteca - place(getString(R.string.abbreviation), "Abbreviation"); - place(getString(R.string.title), "Title", true, true); - place(getString(R.string.type), "Type", false, true); // _type - place(getString(R.string.author), "Author", true, true); - place(getString(R.string.publication_facts), "PublicationFacts", true, true); - place(getString(R.string.date), "Date"); // sempre null nel mio Gedcom - place(getString(R.string.text), "Text", true, true); - place(getString(R.string.call_number), "CallNumber", false, false); // CALN deve stare nel SOURCE_REPOSITORY_CITATION - place(getString(R.string.italic), "Italic", false, false); // _italic indicates source title to be in italics ??? - place(getString(R.string.media_type), "MediaType", false, false); // MEDI, sarebbe in SOURCE_REPOSITORY_CITATION - place(getString(R.string.parentheses), "Paren", false, false); // _PAREN indicates source facts are to be enclosed in parentheses - place(getString(R.string.reference_number), "ReferenceNumber"); // refn false??? - place(getString(R.string.rin), "Rin", false, false); - place(getString(R.string.user_id), "Uid", false, false); - placeExtensions(f); - // Mette la citazione all'archivio - if( f.getRepositoryRef() != null ) { - View vistaRef = LayoutInflater.from(this).inflate(R.layout.pezzo_citazione_fonte, box, false); - box.addView(vistaRef); - vistaRef.setBackgroundColor(getResources().getColor(R.color.archivioCitazione)); - final RepositoryRef refArchivio = f.getRepositoryRef(); - if( refArchivio.getRepository(gc) != null ) { - ((TextView)vistaRef.findViewById(R.id.fonte_testo)).setText(refArchivio.getRepository(gc).getName()); - ((CardView)vistaRef.findViewById(R.id.citazione_fonte)).setCardBackgroundColor(getResources().getColor(R.color.archivio)); - } else vistaRef.findViewById(R.id.citazione_fonte).setVisibility(View.GONE); - String t = ""; - if( refArchivio.getValue() != null ) t += refArchivio.getValue() + "\n"; - if( refArchivio.getCallNumber() != null ) t += refArchivio.getCallNumber() + "\n"; - if( refArchivio.getMediaType() != null ) t += refArchivio.getMediaType() + "\n"; - TextView vistaTesto = vistaRef.findViewById(R.id.citazione_testo); - if( t.isEmpty() ) vistaTesto.setVisibility(View.GONE); - else vistaTesto.setText(t.substring(0, t.length() - 1)); - U.placeNotes((LinearLayout)vistaRef.findViewById(R.id.citazione_note), refArchivio, false); - vistaRef.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - Memoria.aggiungi(refArchivio); - startActivity(new Intent(Fonte.this, ArchivioRef.class)); - } - }); - registerForContextMenu(vistaRef); - vistaRef.setTag(R.id.tag_oggetto, refArchivio); // per il menu contestuale - } - U.placeNotes(box, f, true); - U.placeMedia(box, f, true); - U.placeChangeDate(box, f.getChange()); - if( !citazioni.lista.isEmpty() ) - U.mettiDispensa(box, citazioni.getCapi(), R.string.cited_by); - } - - @Override - public void elimina() { - U.updateChangeDate(Biblioteca.eliminaFonte(f)); - } -} diff --git a/app/src/main/java/app/familygem/detail/ImageActivity.java b/app/src/main/java/app/familygem/detail/ImageActivity.java new file mode 100644 index 00000000..fc48d7f7 --- /dev/null +++ b/app/src/main/java/app/familygem/detail/ImageActivity.java @@ -0,0 +1,125 @@ +package app.familygem.detail; + +import android.app.Activity; +import android.content.Intent; +import android.content.pm.ResolveInfo; +import android.net.Uri; +import android.os.Build; +import android.os.StrictMode; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ImageView; +import androidx.core.content.FileProvider; +import org.folg.gedcom.model.Media; +import java.io.File; +import java.util.List; +import androidx.multidex.BuildConfig; +import app.familygem.DetailActivity; +import app.familygem.F; +import app.familygem.GalleryFragment; +import app.familygem.Global; +import app.familygem.BlackboardActivity; +import app.familygem.Memory; +import app.familygem.R; +import app.familygem.U; +import app.familygem.visitor.MediaReferences; +import static app.familygem.Global.gc; + +public class ImageActivity extends DetailActivity { + + Media m; + View imageView; + + @Override + public void format() { + m = (Media)cast(Media.class); + if( m.getId() != null ) { + setTitle(R.string.shared_media); + placeSlug("OBJE", m.getId()); // 'O1' for Multimedia Records only//'O1' solo per Multimedia Records + } else { + setTitle(R.string.media); + placeSlug("OBJE", null); + } + displayMedia(m, box.getChildCount()); + place(getString(R.string.title), "Title"); + place(getString(R.string.type), "Type", false, false); // _type + if( Global.settings.expert ) place(getString(R.string.file), "File"); // 'Angelina Guadagnoli.jpg' visible only to experts //'Angelina Guadagnoli.jpg' visibile solo agli esperti + // TODO should be max 259 characters + place(getString(R.string.format), "Format", Global.settings.expert, false); // jpeg + place(getString(R.string.primary), "Primary"); // _prim + place(getString(R.string.scrapbook), "Scrapbook", false, false); // _scbk the multimedia object should be in the scrapbook + place(getString(R.string.slideshow), "SlideShow", false, false); // + place(getString(R.string.blob), "Blob", false, true); + //s.l( m.getFileTag() ); // FILE o _FILE + placeExtensions(m); + U.placeNotes(box, m, true); + U.placeChangeDate(box, m.getChange()); + // List of records in which the media is used + MediaReferences mediaReferences = new MediaReferences(gc, m, false); + if( mediaReferences.founders.size() > 0 ) + U.putContainer(box, mediaReferences.founders.toArray(), R.string.used_by); + else if( ((Activity)box.getContext()).getIntent().getBooleanExtra("daSolo", false) ) + U.putContainer(box, Memory.firstObject(), R.string.into); + } + + void displayMedia(Media m, int position) { + imageView = LayoutInflater.from(this).inflate(R.layout.immagine_immagine, box, false); + box.addView(imageView, position); + ImageView imageView = this.imageView.findViewById(R.id.immagine_foto); + F.showImage(m, imageView, this.imageView.findViewById(R.id.immagine_circolo)); + this.imageView.setOnClickListener(vista -> { + String path = (String)imageView.getTag(R.id.tag_percorso); + Uri uri = (Uri)imageView.getTag(R.id.tag_uri); + int fileType = (int)imageView.getTag(R.id.tag_tipo_file); + if( fileType == 0 ) { // The file is to be found //Il file è da trovare + F.displayImageCaptureDialog(this, null, 5173, null); + } else if( fileType == 2 || fileType == 3 ) { // Open files with another app //Apre file con altra app + // TODO if the type is 3 but it is a url (web page without images) try to open it as a file: // + if( path != null ) { + File file = new File(path); + if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP + && path.startsWith(getExternalFilesDir(null).getPath()) ) + // An app can be a file provider of only ITS folders + uri = FileProvider.getUriForFile(this, BuildConfig.APPLICATION_ID + ".provider", file); + else // KitKat and all other folders //KitKat e tutte le altre cartelle + uri = Uri.fromFile(file); + } + String mimeType = getContentResolver().getType(uri); + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setDataAndType(uri, mimeType); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // It is for app properties folders (provider) //Serve per le cartelle di proprietà dell'app (provider) + List resolvers = getPackageManager().queryIntentActivities(intent, 0); + // for an extension like .tex that found the mime type, there is no default app //per un'estensione come .tex di cui ha trovato il tipo mime, non c'è nessuna app predefinita + if( mimeType == null || resolvers.isEmpty() ) { + intent.setDataAndType(uri, "*/*"); // Brutta lista di app generiche //Brutta lista di app generiche + } + // From android 7 (Nougat api 24) uri file: // are banned in favor of uri content: // so it can't open files + if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.N ) { // ok works in the emulator with Android 9 //ok funziona nell'emulatore con Android 9 + try { + StrictMode.class.getMethod("disableDeathOnFileUriExposure").invoke(null); //TODO don't use reflection to use functions you shouldn't! + } catch( Exception e ) {} + } + startActivity( intent ); + } else { // Real image //Immagine vera e propria + Intent intent = new Intent( ImageActivity.this, BlackboardActivity.class ); + intent.putExtra( "path", path ); + if( uri != null ) + intent.putExtra( "uri", uri.toString() ); + startActivity( intent ); + } + }); + this.imageView.setTag( R.id.tag_object, 43614 /*TODO Magic Number*/); // for its context menu //per il suo menu contestuale + registerForContextMenu(this.imageView); + } + + public void updateImage() { + int position = box.indexOfChild(imageView); + box.removeView(imageView); + displayMedia( m, position ); + } + + @Override + public void delete() { + U.updateChangeDate(GalleryFragment.deleteMedia(m, null)); + } +} \ No newline at end of file diff --git a/app/src/main/java/app/familygem/detail/Immagine.java b/app/src/main/java/app/familygem/detail/Immagine.java deleted file mode 100644 index 4105019a..00000000 --- a/app/src/main/java/app/familygem/detail/Immagine.java +++ /dev/null @@ -1,125 +0,0 @@ -package app.familygem.detail; - -import android.app.Activity; -import android.content.Intent; -import android.content.pm.ResolveInfo; -import android.net.Uri; -import android.os.Build; -import android.os.StrictMode; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.ImageView; -import androidx.core.content.FileProvider; -import org.folg.gedcom.model.Media; -import java.io.File; -import java.util.List; -import app.familygem.BuildConfig; -import app.familygem.Dettaglio; -import app.familygem.F; -import app.familygem.Galleria; -import app.familygem.Global; -import app.familygem.Lavagna; -import app.familygem.Memoria; -import app.familygem.R; -import app.familygem.U; -import app.familygem.visitor.RiferimentiMedia; -import static app.familygem.Global.gc; - -public class Immagine extends Dettaglio { - - Media m; - View vistaMedia; - - @Override - public void impagina() { - m = (Media)cast(Media.class); - if( m.getId() != null ) { - setTitle(R.string.shared_media); - placeSlug("OBJE", m.getId()); // 'O1' solo per Multimedia Records - } else { - setTitle(R.string.media); - placeSlug("OBJE", null); - } - immaginona(m, box.getChildCount()); - place(getString(R.string.title), "Title"); - place(getString(R.string.type), "Type", false, false); // _type - if( Global.settings.expert ) place(getString(R.string.file), "File"); // 'Angelina Guadagnoli.jpg' visibile solo agli esperti - // todo dovrebbe essere max 259 characters - place(getString(R.string.format), "Format", Global.settings.expert, false); // jpeg - place(getString(R.string.primary), "Primary"); // _prim - place(getString(R.string.scrapbook), "Scrapbook", false, false); // _scbk the multimedia object should be in the scrapbook - place(getString(R.string.slideshow), "SlideShow", false, false); // - place(getString(R.string.blob), "Blob", false, true); - //s.l( m.getFileTag() ); // FILE o _FILE - placeExtensions(m); - U.placeNotes(box, m, true); - U.placeChangeDate(box, m.getChange()); - // Lista dei record in cui è usato il media - RiferimentiMedia riferiMedia = new RiferimentiMedia(gc, m, false); - if( riferiMedia.capostipiti.size() > 0 ) - U.mettiDispensa(box, riferiMedia.capostipiti.toArray(), R.string.used_by); - else if( ((Activity)box.getContext()).getIntent().getBooleanExtra("daSolo", false) ) - U.mettiDispensa(box, Memoria.oggettoCapo(), R.string.into); - } - - void immaginona(Media m, int posizione) { - vistaMedia = LayoutInflater.from(this).inflate(R.layout.immagine_immagine, box, false); - box.addView(vistaMedia, posizione); - ImageView vistaImg = vistaMedia.findViewById(R.id.immagine_foto); - F.dipingiMedia(m, vistaImg, vistaMedia.findViewById(R.id.immagine_circolo)); - vistaMedia.setOnClickListener(vista -> { - String percorso = (String)vistaImg.getTag(R.id.tag_percorso); - Uri uri = (Uri)vistaImg.getTag(R.id.tag_uri); - int tipoFile = (int)vistaImg.getTag(R.id.tag_tipo_file); - if( tipoFile == 0 ) { // Il file è da trovare - F.appAcquisizioneImmagine(this, null, 5173, null); - } else if( tipoFile == 2 || tipoFile == 3 ) { // Apre file con altra app - // todo se il tipo è 3 ma è un url (pagina web senza immagini) cerca di aprirlo come un file:// - if( percorso != null ) { - File file = new File(percorso); - if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP - && percorso.startsWith(getExternalFilesDir(null).getPath()) ) - // Un'app può essere file provider solo delle SUE cartelle - uri = FileProvider.getUriForFile(this, BuildConfig.APPLICATION_ID + ".provider", file); - else // KitKat e tutte le altre cartelle - uri = Uri.fromFile(file); - } - String mimeType = getContentResolver().getType(uri); - Intent intento = new Intent(Intent.ACTION_VIEW); - intento.setDataAndType(uri, mimeType); - intento.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // Serve per le cartelle di proprietà dell'app (provider) - List resolvers = getPackageManager().queryIntentActivities(intento, 0); - // per un'estensione come .tex di cui ha trovato il tipo mime, non c'è nessuna app predefinita - if( mimeType == null || resolvers.isEmpty() ) { - intento.setDataAndType(uri, "*/*"); // Brutta lista di app generiche - } - // Da android 7 (Nougat api 24) gli uri file:// sono banditi in favore di uri content:// perciò non riesce ad aprire i file - if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.N ) { // ok funziona nell'emulatore con Android 9 - try { - StrictMode.class.getMethod("disableDeathOnFileUriExposure").invoke(null); - } catch( Exception e ) {} - } - startActivity( intento ); - } else { // Immagine vera e propria - Intent intento = new Intent( Immagine.this, Lavagna.class ); - intento.putExtra( "percorso", percorso ); - if( uri != null ) - intento.putExtra( "uri", uri.toString() ); - startActivity( intento ); - } - }); - vistaMedia.setTag( R.id.tag_oggetto, 43614 ); // per il suo menu contestuale - registerForContextMenu( vistaMedia ); - } - - public void aggiornaImmagine() { - int posizione = box.indexOfChild( vistaMedia ); - box.removeView( vistaMedia ); - immaginona( m, posizione ); - } - - @Override - public void elimina() { - U.updateChangeDate(Galleria.eliminaMedia(m, null)); - } -} \ No newline at end of file diff --git a/app/src/main/java/app/familygem/detail/Nome.java b/app/src/main/java/app/familygem/detail/NameActivity.java similarity index 59% rename from app/src/main/java/app/familygem/detail/Nome.java rename to app/src/main/java/app/familygem/detail/NameActivity.java index 180b645d..08b727dd 100644 --- a/app/src/main/java/app/familygem/detail/Nome.java +++ b/app/src/main/java/app/familygem/detail/NameActivity.java @@ -2,35 +2,35 @@ import org.folg.gedcom.model.Name; import org.folg.gedcom.model.Person; -import app.familygem.Dettaglio; +import app.familygem.DetailActivity; import app.familygem.Global; -import app.familygem.Memoria; +import app.familygem.Memory; import app.familygem.R; import app.familygem.U; import static app.familygem.Global.gc; -public class Nome extends Dettaglio { +public class NameActivity extends DetailActivity { Name n; @Override - public void impagina() { + public void format() { setTitle(R.string.name); placeSlug("NAME", null); n = (Name)cast(Name.class); if( Global.settings.expert ) place(getString(R.string.value), "Value"); else { - String nome = ""; - String cognome = ""; - String epiteto = n.getValue(); - if( epiteto != null ) { - nome = epiteto.replaceAll("/.*?/", "").trim(); // Rimuove il cognome - if( epiteto.indexOf('/') < epiteto.lastIndexOf('/') ) - cognome = epiteto.substring(epiteto.indexOf('/') + 1, epiteto.lastIndexOf('/')).trim(); + String firstName = ""; + String lastName = ""; + String epithet = n.getValue(); + if( epithet != null ) { + firstName = epithet.replaceAll("/.*?/", "").trim(); // Remove the lastName + if( epithet.indexOf('/') < epithet.lastIndexOf('/') ) + lastName = epithet.substring(epithet.indexOf('/') + 1, epithet.lastIndexOf('/')).trim(); } - placePiece(getString(R.string.given), nome, 4043, false); - placePiece(getString(R.string.surname), cognome, 6064, false); + placePiece(getString(R.string.given), firstName, 4043, false); + placePiece(getString(R.string.surname), lastName, 6064, false); } place(getString(R.string.nickname), "Nickname"); place(getString(R.string.type), "Type", true, false); // _TYPE in GEDCOM 5.5, TYPE in GEDCOM 5.5.1 @@ -45,15 +45,15 @@ public void impagina() { place(getString(R.string.phonetic), "Fone", Global.settings.expert, false); placeExtensions(n); U.placeNotes(box, n, true); - U.placeMedia(box, n, true); // Mi sembra strano che un Name abbia Media.. comunque.. + U.placeMedia(box, n, true); // It seems strange to me that a Name has Media .. anyway .. //Mi sembra strano che un Name abbia Media.. comunque.. U.placeSourceCitations(box, n); } @Override - public void elimina() { - Person costui = gc.getPerson(Global.indi); - costui.getNames().remove(n); - U.updateChangeDate(costui); - Memoria.annullaIstanze(n); + public void delete() { + Person currentPerson = gc.getPerson(Global.indi); + currentPerson.getNames().remove(n); + U.updateChangeDate(currentPerson); + Memory.setInstanceAndAllSubsequentToNull(n); } } diff --git a/app/src/main/java/app/familygem/detail/Nota.java b/app/src/main/java/app/familygem/detail/NoteActivity.java similarity index 59% rename from app/src/main/java/app/familygem/detail/Nota.java rename to app/src/main/java/app/familygem/detail/NoteActivity.java index d4e5ad0a..f61821bd 100644 --- a/app/src/main/java/app/familygem/detail/Nota.java +++ b/app/src/main/java/app/familygem/detail/NoteActivity.java @@ -2,19 +2,19 @@ import android.app.Activity; import org.folg.gedcom.model.Note; -import app.familygem.Dettaglio; +import app.familygem.DetailActivity; import app.familygem.Global; -import app.familygem.Memoria; +import app.familygem.Memory; import app.familygem.R; import app.familygem.U; -import app.familygem.visitor.RiferimentiNota; +import app.familygem.visitor.NoteReferences; -public class Nota extends Dettaglio { +public class NoteActivity extends DetailActivity { Note n; @Override - public void impagina() { + public void format() { n = (Note)cast(Note.class); if( n.getId() == null ) { setTitle(R.string.note); @@ -29,16 +29,16 @@ public void impagina() { U.placeSourceCitations(box, n); U.placeChangeDate(box, n.getChange()); if( n.getId() != null ) { - RiferimentiNota rifNota = new RiferimentiNota(Global.gc, n.getId(), false); - if( rifNota.tot > 0 ) - U.mettiDispensa(box, rifNota.capostipiti.toArray(), R.string.shared_by); + NoteReferences noteRef = new NoteReferences(Global.gc, n.getId(), false); + if( noteRef.count > 0 ) + U.putContainer(box, noteRef.founders.toArray(), R.string.shared_by); } else if( ((Activity)box.getContext()).getIntent().getBooleanExtra("daQuaderno", false) ) { - U.mettiDispensa(box, Memoria.oggettoCapo(), R.string.written_in); + U.putContainer(box, Memory.firstObject(), R.string.written_in); } } @Override - public void elimina() { - U.updateChangeDate(U.eliminaNota(n, null)); + public void delete() { + U.updateChangeDate(U.deleteNote(n, null)); } } diff --git a/app/src/main/java/app/familygem/detail/Archivio.java b/app/src/main/java/app/familygem/detail/RepositoryActivity.java similarity index 52% rename from app/src/main/java/app/familygem/detail/Archivio.java rename to app/src/main/java/app/familygem/detail/RepositoryActivity.java index ba020286..2f3c25dd 100644 --- a/app/src/main/java/app/familygem/detail/Archivio.java +++ b/app/src/main/java/app/familygem/detail/RepositoryActivity.java @@ -4,22 +4,22 @@ import org.folg.gedcom.model.Source; import java.util.ArrayList; import java.util.List; -import app.familygem.Dettaglio; +import app.familygem.DetailActivity; import app.familygem.Global; -import app.familygem.Magazzino; +import app.familygem.RepositoriesFragment; import app.familygem.R; import app.familygem.U; -public class Archivio extends Dettaglio { +public class RepositoryActivity extends DetailActivity { Repository a; @Override - public void impagina() { + public void format() { setTitle(R.string.repository); a = (Repository)cast(Repository.class); placeSlug("REPO", a.getId()); - place(getString(R.string.value), "Value", false, true); // Non molto Gedcom standard + place(getString(R.string.value), "Value", false, true); // Not very standard Gedcom //Non molto Gedcom standard place(getString(R.string.name), "Name"); place(getString(R.string.address), a.getAddress()); place(getString(R.string.www), "Www"); @@ -31,18 +31,18 @@ public void impagina() { U.placeNotes(box, a, true); U.placeChangeDate(box, a.getChange()); - // Raccoglie e mostra le fonti che citano questo Repository - List fontiCitanti = new ArrayList<>(); - for( Source fonte : Global.gc.getSources() ) - if( fonte.getRepositoryRef() != null && fonte.getRepositoryRef().getRef() != null - && fonte.getRepositoryRef().getRef().equals(a.getId()) ) - fontiCitanti.add(fonte); - if( !fontiCitanti.isEmpty() ) - U.mettiDispensa(box, fontiCitanti.toArray(), R.string.sources); + // Collects and displays the sources citing this Repository //Raccoglie e mostra le fonti che citano questo Repository + List citingSources = new ArrayList<>(); + for( Source source : Global.gc.getSources() ) + if( source.getRepositoryRef() != null && source.getRepositoryRef().getRef() != null + && source.getRepositoryRef().getRef().equals(a.getId()) ) + citingSources.add(source); + if( !citingSources.isEmpty() ) + U.putContainer(box, citingSources.toArray(), R.string.sources); } @Override - public void elimina() { - U.updateChangeDate((Object[])Magazzino.delete(a)); + public void delete() { + U.updateChangeDate((Object[]) RepositoriesFragment.delete(a)); } } diff --git a/app/src/main/java/app/familygem/detail/RepositoryRefActivity.java b/app/src/main/java/app/familygem/detail/RepositoryRefActivity.java new file mode 100644 index 00000000..3c096e2a --- /dev/null +++ b/app/src/main/java/app/familygem/detail/RepositoryRefActivity.java @@ -0,0 +1,65 @@ +package app.familygem.detail; + +import android.content.Context; +import android.content.Intent; +import androidx.cardview.widget.CardView; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.TextView; +import org.folg.gedcom.model.Repository; +import org.folg.gedcom.model.RepositoryRef; +import org.folg.gedcom.model.Source; +import app.familygem.DetailActivity; +import app.familygem.Memory; +import app.familygem.R; +import app.familygem.U; +import static app.familygem.Global.gc; + +public class RepositoryRefActivity extends DetailActivity { + + RepositoryRef r; + + @Override + public void format() { + placeSlug("REPO"); + r = (RepositoryRef)cast(RepositoryRef.class); + if( r.getRepository(gc) != null ) { // valid + setTitle(R.string.repository_citation); + View repositoryCard = putRepository(box, r.getRepository(gc)); + repositoryCard.setTag(R.id.tag_object, r.getRepository(gc)); //for the context menu TODO still needed? + registerForContextMenu(repositoryCard); + } else if( r.getRef() != null ) { // of a non-existent archive (perhaps deleted) //di un archivio inesistente (magari eliminato) + setTitle(R.string.inexistent_repository_citation); + } else { // without ref?? + setTitle(R.string.repository_note); + } + place(getString(R.string.value), "Value", false, true); + place(getString(R.string.call_number), "CallNumber"); + place(getString(R.string.media_type), "MediaType"); + placeExtensions(r); + U.placeNotes(box, r, true); + } + + public static View putRepository(LinearLayout container, final Repository repo) { + final Context context = container.getContext(); + View repositoryCard = LayoutInflater.from(context).inflate(R.layout.pezzo_fonte, container, false); + container.addView(repositoryCard); + ((TextView)repositoryCard.findViewById(R.id.fonte_testo)).setText(repo.getName()); + ((CardView)repositoryCard).setCardBackgroundColor(context.getResources().getColor(R.color.archivio)); + repositoryCard.setOnClickListener(v -> { + Memory.setFirst(repo); + context.startActivity(new Intent(context, RepositoryActivity.class)); + }); + return repositoryCard; + } + + @Override + public void delete() { + // Delete the citation from the archive and update the date of the source that contained it + Source container = (Source) Memory.getSecondToLastObject(); + container.setRepositoryRef(null); + U.updateChangeDate(container); + Memory.setInstanceAndAllSubsequentToNull(r); + } +} \ No newline at end of file diff --git a/app/src/main/java/app/familygem/detail/SourceActivity.java b/app/src/main/java/app/familygem/detail/SourceActivity.java new file mode 100644 index 00000000..f291a409 --- /dev/null +++ b/app/src/main/java/app/familygem/detail/SourceActivity.java @@ -0,0 +1,81 @@ +package app.familygem.detail; + +import android.content.Intent; +import androidx.cardview.widget.CardView; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.TextView; +import org.folg.gedcom.model.RepositoryRef; +import org.folg.gedcom.model.Source; +import app.familygem.LibraryFragment; +import app.familygem.DetailActivity; +import app.familygem.Memory; +import app.familygem.R; +import app.familygem.U; +import app.familygem.visitor.ListOfSourceCitations; +import static app.familygem.Global.gc; + +public class SourceActivity extends DetailActivity { + + Source f; + + @Override + public void format() { + setTitle(R.string.source); + f = (Source)cast(Source.class); + placeSlug("SOUR", f.getId()); + ListOfSourceCitations citations = new ListOfSourceCitations(gc, f.getId()); + f.putExtension("citaz", citations.list.size()); // for the LibraryFragment + place(getString(R.string.abbreviation), "Abbreviation"); + place(getString(R.string.title), "Title", true, true); + place(getString(R.string.type), "Type", false, true); // _type + place(getString(R.string.author), "Author", true, true); + place(getString(R.string.publication_facts), "PublicationFacts", true, true); + place(getString(R.string.date), "Date"); // always null in my Gedcom + place(getString(R.string.text), "Text", true, true); + place(getString(R.string.call_number), "CallNumber", false, false); // CALN it must be in the SOURCE_REPOSITORY_CITATION + place(getString(R.string.italic), "Italic", false, false); // _italic indicates source title to be in italics ??? + place(getString(R.string.media_type), "MediaType", false, false); //MEDI, would be in SOURCE REPOSITORY CITATION // MEDI, sarebbe in SOURCE_REPOSITORY_CITATION + place(getString(R.string.parentheses), "Paren", false, false); // _PAREN indicates source facts are to be enclosed in parentheses + place(getString(R.string.reference_number), "ReferenceNumber"); // ref num false??? + place(getString(R.string.rin), "Rin", false, false); + place(getString(R.string.user_id), "Uid", false, false); + placeExtensions(f); + // Put the quote to the archive //Mette la citazione all'archivio TODO improve translation + if( f.getRepositoryRef() != null ) { + View refView = LayoutInflater.from(this).inflate(R.layout.pezzo_citazione_fonte, box, false); + box.addView(refView); + refView.setBackgroundColor(getResources().getColor(R.color.archivioCitazione)); + final RepositoryRef repositoryRef = f.getRepositoryRef(); + if( repositoryRef.getRepository(gc) != null ) { + ((TextView)refView.findViewById(R.id.fonte_testo)).setText(repositoryRef.getRepository(gc).getName()); + ((CardView)refView.findViewById(R.id.citazione_fonte)).setCardBackgroundColor(getResources().getColor(R.color.archivio)); + } else refView.findViewById(R.id.citazione_fonte).setVisibility(View.GONE); + String t = ""; + if( repositoryRef.getValue() != null ) t += repositoryRef.getValue() + "\n"; + if( repositoryRef.getCallNumber() != null ) t += repositoryRef.getCallNumber() + "\n"; + if( repositoryRef.getMediaType() != null ) t += repositoryRef.getMediaType() + "\n"; + TextView textView = refView.findViewById(R.id.citazione_testo); + if( t.isEmpty() ) textView.setVisibility(View.GONE); + else textView.setText(t.substring(0, t.length() - 1)); + U.placeNotes((LinearLayout)refView.findViewById(R.id.citazione_note), repositoryRef, false); + refView.setOnClickListener(v -> { + Memory.add(repositoryRef); + startActivity(new Intent(SourceActivity.this, RepositoryRefActivity.class)); + }); + registerForContextMenu(refView); + refView.setTag(R.id.tag_object, repositoryRef); // for the context menu + } + U.placeNotes(box, f, true); + U.placeMedia(box, f, true); + U.placeChangeDate(box, f.getChange()); + if( !citations.list.isEmpty() ) + U.putContainer(box, citations.getProgenitors(), R.string.cited_by); + } + + @Override + public void delete() { + U.updateChangeDate(LibraryFragment.deleteSource(f)); + } +} diff --git a/app/src/main/java/app/familygem/detail/SourceCitationActivity.java b/app/src/main/java/app/familygem/detail/SourceCitationActivity.java new file mode 100644 index 00000000..23b57cf6 --- /dev/null +++ b/app/src/main/java/app/familygem/detail/SourceCitationActivity.java @@ -0,0 +1,52 @@ +package app.familygem.detail; + +import org.folg.gedcom.model.Note; +import org.folg.gedcom.model.SourceCitation; +import org.folg.gedcom.model.SourceCitationContainer; +import app.familygem.DetailActivity; +import app.familygem.Memory; +import app.familygem.R; +import app.familygem.U; +import static app.familygem.Global.gc; + +public class SourceCitationActivity extends DetailActivity { + + SourceCitation c; + + @Override + public void format() { + placeSlug("SOUR"); + c = (SourceCitation)cast(SourceCitation.class); + if( c.getSource(gc) != null ) { // valid source CITATION + setTitle(R.string.source_citation); + U.placeSource(box, c.getSource(gc), true); + } else if( c.getRef() != null ) { // source CITATION of a non-existent source (perhaps deleted) + setTitle(R.string.inexistent_source_citation); + } else { // source NOTE + setTitle(R.string.source_note); + place(getString(R.string.value), "Value", true, true); + } + place(getString(R.string.page), "Page", true, true); + place(getString(R.string.date), "Date"); + place(getString(R.string.text), "Text", true, true); // applies to both sourceNote and sourceCitation + //c.getTextOrValue(); practically useless + //if( c.getDataTagContents() != null ) + // U.metti( box, "Data Tag Contents", c.getDataTagContents().toString() ); // COMBINED DATA TEXT + place(getString(R.string.certainty), "Quality"); // a number from 0 to 3 + //metti( "Ref", "Ref", false, false ); // the id of the source + placeExtensions(c); + U.placeNotes(box, c, true); + U.placeMedia(box, c, true); + } + + @Override + public void delete() { + Object container = Memory.getSecondToLastObject(); + if( container instanceof Note ) // Note doesn't extend SourceCitationContainer + ((Note)container).getSourceCitations().remove( c ); + else + ((SourceCitationContainer)container).getSourceCitations().remove( c ); + U.updateChangeDate( Memory.firstObject() ); + Memory.setInstanceAndAllSubsequentToNull(c); + } +} diff --git a/app/src/main/java/app/familygem/s.java b/app/src/main/java/app/familygem/s.java index 4244c80b..bbbe9bfa 100644 --- a/app/src/main/java/app/familygem/s.java +++ b/app/src/main/java/app/familygem/s.java @@ -1,22 +1,23 @@ package app.familygem; -// Scrivi in breve - +/** + * Shorthand wrapper for logging + * */ public class s { public static void l(Object... objects) { - String str = ""; + StringBuilder str = new StringBuilder(); if( objects != null ) { for( Object obj : objects ) - str += obj + " "; + str.append(obj).append(" "); } else - str += objects; + str.append((String) null); System.out.println(".\t" + str); //android.util.Log.v("v", str); } - public static void p( Object parola ) { - System.out.print( parola ); + public static void p( Object word ) { + System.out.print( word ); } } diff --git a/app/src/main/java/app/familygem/visitor/CleanStack.java b/app/src/main/java/app/familygem/visitor/CleanStack.java new file mode 100644 index 00000000..4e8c79ba --- /dev/null +++ b/app/src/main/java/app/familygem/visitor/CleanStack.java @@ -0,0 +1,22 @@ +package app.familygem.visitor; + +/** + * Closely connected to [FindStack, locate objects to keep in the stack + * */ +class CleanStack extends TotalVisitor { + + private Object scope; //scopo: scope, object, goal, aim, etc. + boolean toDelete = true; + + CleanStack(Object scopo ) { + this.scope = scopo; + } + + @Override + boolean visit(Object object, boolean isProgenitor) { // the boolean is unused here + if( object.equals(scope) ) + toDelete = false; + return true; + } +} + diff --git a/app/src/main/java/app/familygem/visitor/ContenitoriNota.java b/app/src/main/java/app/familygem/visitor/ContenitoriNota.java deleted file mode 100644 index 252eec65..00000000 --- a/app/src/main/java/app/familygem/visitor/ContenitoriNota.java +++ /dev/null @@ -1,39 +0,0 @@ -/* Visitatore un po' complementare a RiferimentiNota, avente una doppia funzione: -- Modifica i ref che puntano alla nota condivisa -- Colleziona una lista dei contenitori che includono la Nota condivisa -*/ - -package app.familygem.visitor; - -import org.folg.gedcom.model.Gedcom; -import org.folg.gedcom.model.Note; -import org.folg.gedcom.model.NoteContainer; -import org.folg.gedcom.model.NoteRef; -import java.util.HashSet; -import java.util.Set; - -public class ContenitoriNota extends VisitorTotale { - - public Set containers = new HashSet<>(); - private Note note; // la nota condivisa da cercare - private String newId; // il nuovo id da mettere nei ref - - public ContenitoriNota(Gedcom gc, Note note, String newId) { - this.note = note; - this.newId = newId; - gc.accept(this); - } - - @Override - boolean visita(Object object, boolean capostipite) { - if( object instanceof NoteContainer ) { - for( NoteRef noteRef : ((NoteContainer)object).getNoteRefs() ) { - if( noteRef.getRef().equals(note.getId()) ) { - noteRef.setRef(newId); - containers.add((NoteContainer)object); - } - } - } - return true; - } -} diff --git a/app/src/main/java/app/familygem/visitor/ContaCitazioniFonte.java b/app/src/main/java/app/familygem/visitor/CountSourceCitation.java similarity index 55% rename from app/src/main/java/app/familygem/visitor/ContaCitazioniFonte.java rename to app/src/main/java/app/familygem/visitor/CountSourceCitation.java index 3c728d22..eaeccddc 100644 --- a/app/src/main/java/app/familygem/visitor/ContaCitazioniFonte.java +++ b/app/src/main/java/app/familygem/visitor/CountSourceCitation.java @@ -1,7 +1,3 @@ -// Contatore delle citazioni di una fonte -// Servirebbe per sostituire U.quanteCitazioni(Source) in Biblioteca -// è più preciso nella conta cioè non gli sfugge nulla, ma è quattro volte più lento - package app.familygem.visitor; import org.folg.gedcom.model.EventFact; @@ -12,48 +8,57 @@ import org.folg.gedcom.model.SourceCitation; import org.folg.gedcom.model.Visitor; -public class ContaCitazioniFonte extends Visitor { +/** + * Counter of citations of a source + * It would be used to replace U.quanteQuante (Source) in the Library + * is more accurate in counting, that is, nothing escapes him, but is four times slower. + * + * // Contatore delle citazioni di una fonte + * // Servirebbe per sostituire U.quanteCitazioni(Source) in Biblioteca + * // è più preciso nella conta cioè non gli sfugge nulla, ma è quattro volte più lento + * */ +public class CountSourceCitation extends Visitor { - public int quante = 0; + public int count = 0; String id; - ContaCitazioniFonte( String id ){ + CountSourceCitation(String id ){ this.id = id; } @Override public boolean visit( Person p ) { for( SourceCitation c : p.getSourceCitations() ) - if( c.getRef() != null ) // necessario perché le note-fonti non hanno Ref alla fonte - if( c.getRef().equals(id) ) quante++; + if( c.getRef() != null ) // required because source-notes have no Ref at source + if( c.getRef().equals(id) ) count++; return true; } @Override public boolean visit( Family f ) { for( SourceCitation c : f.getSourceCitations() ) if( c.getRef() != null ) - if( c.getRef().equals(id) ) quante++; + if( c.getRef().equals(id) ) count++; return true; } @Override public boolean visit( Name n ) { for( SourceCitation c : n.getSourceCitations() ) if( c.getRef() != null ) - if( c.getRef().equals(id) ) quante++; + if( c.getRef().equals(id) ) count++; return true; } @Override public boolean visit( EventFact e ) { for( SourceCitation c : e.getSourceCitations() ) if( c.getRef() != null ) - if( c.getRef().equals(id) ) quante++; + if( c.getRef().equals(id) ) count++; return true; } @Override public boolean visit( Note n ) { for( SourceCitation c : n.getSourceCitations() ) if( c.getRef() != null ) - if( c.getRef().equals(id) ) quante++; + if( c.getRef().equals(id) ) count++; return true; } } diff --git a/app/src/main/java/app/familygem/visitor/FindStack.java b/app/src/main/java/app/familygem/visitor/FindStack.java new file mode 100644 index 00000000..49fb5544 --- /dev/null +++ b/app/src/main/java/app/familygem/visitor/FindStack.java @@ -0,0 +1,132 @@ +package app.familygem.visitor; + +import org.folg.gedcom.model.Change; +import org.folg.gedcom.model.EventFact; +import org.folg.gedcom.model.Family; +import org.folg.gedcom.model.Gedcom; +import org.folg.gedcom.model.Header; +import org.folg.gedcom.model.Media; +import org.folg.gedcom.model.Name; +import org.folg.gedcom.model.Note; +import org.folg.gedcom.model.Person; +import org.folg.gedcom.model.Repository; +import org.folg.gedcom.model.RepositoryRef; +import org.folg.gedcom.model.Source; +import org.folg.gedcom.model.SourceCitation; +import org.folg.gedcom.model.Submitter; +import org.folg.gedcom.model.Visitable; +import org.folg.gedcom.model.Visitor; +import java.util.Iterator; +import java.util.List; +import app.familygem.Memory; +/** + * + * // Visitor that produces in Memory the hierarchical stack of objects between the parent record and a given object + * // e.g. Person> Simple media + * // or Family> Note> SourceCitation> Simple Note + * + * // Visitatore che produce in Memoria la pila gerarchica degli oggetti tra il record capostipite e un object dato + * // ad es. Person > Media semplice + * // oppure Family > Note > SourceCitation > Note semplice + * */ +public class FindStack extends Visitor { + + private List stack; + private Object scope; + private boolean found; + + public FindStack(Gedcom gc, Object scopo ) { + stack = Memory.addStack(); //in a new stack on purpose + this.scope = scopo; + gc.accept( this ); + } + + private boolean opera( Object object, String tag, boolean progenitor ) { + if( !found) { + if( progenitor ) + stack.clear(); // every progenitor makes a stack start all over again + Memory.Step step = new Memory.Step(); + step.object = object; + step.tag = tag; + if( !progenitor ) + step.clearStackOnBackPressed = true; // onBackPressed marks them to delete them in bulk + stack.add(step); + } + if( object.equals(scope) ) { + Iterator steps = stack.iterator(); + while( steps.hasNext() ) { + CleanStack janitor = new CleanStack(scope); + ((Visitable)steps.next().object).accept( janitor ); + if( janitor.toDelete) + steps.remove(); + } + found = true; + //Memoria.stampa("FindStack"); log? + } + return true; + } + + @Override + public boolean visit( Header step ) { + return opera(step,"HEAD",true); + } + @Override + public boolean visit( Person step ) { + return opera(step,"INDI",true); + } + @Override + public boolean visit( Family step ) { + return opera(step,"FAM",true); + } + @Override + public boolean visit( Source step ) { + return opera(step,"SOUR",true); + } + @Override + public boolean visit( Repository step ) { + return opera(step,"REPO",true); + } + @Override + public boolean visit( Submitter step ) { + return opera(step,"SUBM",true); + } + @Override + public boolean visit( Media step ) { + return opera(step,"OBJE",step.getId()!=null); + } + @Override + public boolean visit( Note step ) { + return opera(step,"NOTE",step.getId()!=null); + } + @Override + public boolean visit( Name step ) { + return opera(step,"NAME",false); + } + @Override + public boolean visit( EventFact step ) { + return opera(step,step.getTag(),false); + } + @Override + public boolean visit( SourceCitation step ) { + return opera(step,"SOUR",false); + } + @Override + public boolean visit( RepositoryRef step ) { + return opera(step,"REPO",false); + } + @Override + public boolean visit( Change step ) { + return opera(step,"CHAN",false); + } + /* ok but then GedcomTag is not Visitable and therefore does not continue the visit + @Override + public boolean visit( String chiave, Object estensioni ) { + if( chiave.equals("folg.more_tags") ) { + for( GedcomTag est : (List)estensioni ) { + //s.l(est.getClass().getName()+" "+est.getTag()); + opera( est, est.getTag(), false ); + } + } + return true; + }*/ +} \ No newline at end of file diff --git a/app/src/main/java/app/familygem/visitor/ListOfSourceCitations.java b/app/src/main/java/app/familygem/visitor/ListOfSourceCitations.java new file mode 100644 index 00000000..c1dd66af --- /dev/null +++ b/app/src/main/java/app/familygem/visitor/ListOfSourceCitations.java @@ -0,0 +1,72 @@ +package app.familygem.visitor; + +import org.folg.gedcom.model.Gedcom; +import org.folg.gedcom.model.Note; +import org.folg.gedcom.model.SourceCitation; +import org.folg.gedcom.model.SourceCitationContainer; +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +/** + * // Starting from the id of a source it generates a list of triplets: parent / container / citations of the source + * // Used by [LibraryFragment], [SourceActivity] and [ConfirmationActivity] + * + * // Partendo dall'id di una fonte genera una lista di triplette: capostipite / contenitore / citazioni della fonte + * // Usato da Biblioteca, da Fonte e da Conferma + * */ +public class ListOfSourceCitations extends TotalVisitor { + + public List list = new ArrayList<>(); + private String id; // id of the source + private Object capo; + + public ListOfSourceCitations(Gedcom gc, String id ) { + this.id = id; + gc.accept( this ); + } + + @Override + boolean visit(Object object, boolean isProgenitor) { + if(isProgenitor) + capo = object; + if( object instanceof SourceCitationContainer ) { + analyze( object, ((SourceCitationContainer)object).getSourceCitations() ); + } // Note does not extend SourceCitationContainer, but implements its own methods + else if( object instanceof Note ) { + analyze( object, ((Note)object).getSourceCitations() ); + } + return true; + } + + private void analyze(Object container, List citations ) { + for( SourceCitation citation : citations ) + // (Known sources?)[SourceCitations?] have no Ref to a source //Le fonti-note non hanno Ref ad una fonte + if( citation.getRef() != null && citation.getRef().equals(id) ) { + Triplet triplet = new Triplet(); + triplet.progenitor = capo; + triplet.container = container; + triplet.citation = citation; + list.add( triplet ); + } + } + + public Object[] getProgenitors() { + Set heads = new LinkedHashSet<>(); // merge duplicates + for( Triplet tri : list) { + heads.add(tri.progenitor); + } + return heads.toArray(); + } + + /** + * Class for storing together the three parent elements - container - quote + * Classe per stoccare insieme i tre elementi capostipite - contenitore - citazione + * */ + public static class Triplet { + public Object progenitor; + public Object container; // It would be a SourceCitationContainer but Note is an exception //Sarebbe un SourceCitationContainer ma Note fa eccezione + public SourceCitation citation; + } +} diff --git a/app/src/main/java/app/familygem/visitor/ListaCitazioniFonte.java b/app/src/main/java/app/familygem/visitor/ListaCitazioniFonte.java deleted file mode 100644 index ad852907..00000000 --- a/app/src/main/java/app/familygem/visitor/ListaCitazioniFonte.java +++ /dev/null @@ -1,65 +0,0 @@ -// Partendo dall'id di una fonte genera una lista di triplette: capostipite / contenitore / citazioni della fonte -// Usato da Biblioteca, da Fonte e da Conferma - -package app.familygem.visitor; - -import org.folg.gedcom.model.Gedcom; -import org.folg.gedcom.model.Note; -import org.folg.gedcom.model.SourceCitation; -import org.folg.gedcom.model.SourceCitationContainer; -import java.util.ArrayList; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; - -public class ListaCitazioniFonte extends VisitorTotale { - - public List lista = new ArrayList<>(); - private String id; // id della fonte - private Object capo; - - public ListaCitazioniFonte( Gedcom gc, String id ) { - this.id = id; - gc.accept( this ); - } - - @Override - boolean visita( Object oggetto, boolean capostipite ) { - if( capostipite ) - capo = oggetto; - if( oggetto instanceof SourceCitationContainer ) { - analizza( oggetto, ((SourceCitationContainer)oggetto).getSourceCitations() ); - } // Note non estende SourceCitationContainer, ma implementa i suoi propri metodi - else if( oggetto instanceof Note ) { - analizza( oggetto, ((Note)oggetto).getSourceCitations() ); - } - return true; - } - - private void analizza( Object contenitore, List citazioni ) { - for( SourceCitation citaz : citazioni ) - // Le fonti-note non hanno Ref ad una fonte - if( citaz.getRef() != null && citaz.getRef().equals(id) ) { - Tripletta tris = new Tripletta(); - tris.capostipite = capo; - tris.contenitore = contenitore; - tris.citazione = citaz; - lista.add( tris ); - } - } - - public Object[] getCapi() { - Set capi = new LinkedHashSet<>(); // unifica i duplicati - for( Tripletta tri : lista ) { - capi.add(tri.capostipite); - } - return capi.toArray(); - } - - // Classe per stoccare insieme i tre elementi capostipite - contenitore - citazione - public static class Tripletta { - public Object capostipite; - public Object contenitore; // Sarebbe un SourceCitationContainer ma Note fa eccezione - public SourceCitation citazione; - } -} diff --git a/app/src/main/java/app/familygem/visitor/ListaMedia.java b/app/src/main/java/app/familygem/visitor/ListaMedia.java deleted file mode 100644 index 5e60d906..00000000 --- a/app/src/main/java/app/familygem/visitor/ListaMedia.java +++ /dev/null @@ -1,106 +0,0 @@ -// Set ordinato dei media -// Quasi sempre può sostituire ListaMediaContenitore - -package app.familygem.visitor; - -import org.folg.gedcom.model.EventFact; -import org.folg.gedcom.model.Family; -import org.folg.gedcom.model.Gedcom; -import org.folg.gedcom.model.Media; -import org.folg.gedcom.model.MediaContainer; -import org.folg.gedcom.model.Name; -import org.folg.gedcom.model.Person; -import org.folg.gedcom.model.Source; -import org.folg.gedcom.model.SourceCitation; -import org.folg.gedcom.model.Visitor; -import java.util.LinkedHashSet; -import java.util.Set; -import app.familygem.F; -import app.familygem.Global; - -public class ListaMedia extends Visitor { - - public Set lista = new LinkedHashSet<>(); - private Gedcom gc; - /* 0 tutti i media - 1 solo gli oggetti media condivisi (per tutto il Gedcom) - 2 solo i locali (non serve gc) - 3 condivisi e locali ma solo immagini e video anteprimabili (per il menu principale) */ - private int cosaVuoi; - - public ListaMedia(Gedcom gc, int cosaVuoi) { - this.gc = gc; - this.cosaVuoi = cosaVuoi; - } - - private boolean visita(Object oggetto) { - if( oggetto instanceof MediaContainer ) { - MediaContainer contenitore = (MediaContainer)oggetto; - if( cosaVuoi == 0 ) - lista.addAll(contenitore.getAllMedia(gc)); // aggiunge media condivisi e locali - else if( cosaVuoi == 2 ) - lista.addAll(contenitore.getMedia()); // solo i media locali - else if( cosaVuoi == 3 ) - for( Media med : contenitore.getAllMedia(gc) ) - filtra(med); - } - return true; - } - - // Aggiunge solo quelli presunti bellini con anteprima - private void filtra(Media media) { - String file = F.percorsoMedia(Global.settings.openTree, media); // todo e le immagini dagli URI? - if( file != null && file.lastIndexOf('.') > 0 ) { - String estensione = file.substring(file.lastIndexOf('.') + 1); - switch( estensione ) { - case "jpg": - case "jpeg": - case "png": - case "gif": - case "bmp": - case "webp": // ok - case "heic": // ok todo l'immagine può risultare ruotata di 90° o 180° - case "heif": // sinonimo di .heic - case "mp4": - case "3gp": // ok - case "webm": // ok - case "mkv": // ok - lista.add(media); - } - } - } - - @Override - public boolean visit(Gedcom gc) { - if( cosaVuoi < 2 ) - lista.addAll(gc.getMedia()); // rastrella tutti gli oggetti media condivisi del Gedcom - else if( cosaVuoi == 3 ) - for( Media med : gc.getMedia() ) - filtra(med); - return true; - } - @Override - public boolean visit(Person p) { - return visita(p); - } - @Override - public boolean visit(Family f) { - return visita(f); - } - @Override - public boolean visit(EventFact e) { - return visita(e); - } - @Override - public boolean visit(Name n) { - return visita(n); - } - @Override - public boolean visit(SourceCitation c) { - return visita(c); - } - @Override - public boolean visit(Source s) { - return visita(s); - } -} diff --git a/app/src/main/java/app/familygem/visitor/ListaMediaContenitore.java b/app/src/main/java/app/familygem/visitor/ListaMediaContenitore.java deleted file mode 100644 index b22b3627..00000000 --- a/app/src/main/java/app/familygem/visitor/ListaMediaContenitore.java +++ /dev/null @@ -1,92 +0,0 @@ -// Mappa ordinata dei media ciascuno col suo oggetto contenitore -// Il contenitore serve praticamente solo a scollegaMedia in IndividuoMedia - -package app.familygem.visitor; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import org.folg.gedcom.model.EventFact; -import org.folg.gedcom.model.Family; -import org.folg.gedcom.model.Gedcom; -import org.folg.gedcom.model.Media; -import org.folg.gedcom.model.MediaContainer; -import org.folg.gedcom.model.Name; -import org.folg.gedcom.model.Person; -import org.folg.gedcom.model.Source; -import org.folg.gedcom.model.SourceCitation; -import org.folg.gedcom.model.Visitor; - -public class ListaMediaContenitore extends Visitor { - - public List listaMedia = new ArrayList<>(); - private Gedcom gc; - private boolean voglioTutti; // Elencare tutti i media (anche i locali) o solo gli oggetti media condivisi - - public ListaMediaContenitore( Gedcom gc, boolean voglioTutti ) { - this.gc = gc; - this.voglioTutti = voglioTutti; - } - - private boolean visita( Object oggetto ) { - if( voglioTutti && oggetto instanceof MediaContainer ) { - //for( MediaRef r : p.getMediaRefs() ) listaMedia.put( r.getMedia(gc), p ); // elenca i ref a vuoto => media null - MediaContainer contenitore = (MediaContainer) oggetto; - for( Media med : contenitore.getAllMedia( gc ) ) { // Oggetti media e media locali di ciascun record - MedCont medCont = new MedCont(med, oggetto); - if( !listaMedia.contains(medCont) ) - listaMedia.add( medCont ); - } - } - return true; - } - - @Override - public boolean visit( Gedcom gc ) { - for( Media med : gc.getMedia() ) - listaMedia.add( new MedCont(med, gc) ); // rastrella gli oggetti media - return true; - } - @Override - public boolean visit( Person p ) { - return visita( p ); - } - @Override - public boolean visit( Family f ) { - return visita( f ); - } - @Override - public boolean visit( EventFact e ) { - return visita( e ); - } - @Override - public boolean visit( Name n ) { - return visita( n ); - } - @Override - public boolean visit( SourceCitation c ) { - return visita( c ); - } - @Override - public boolean visit( Source s ) { - return visita( s ); - } - - // Classe che rappresenta un Media con il suo oggetto contenitore - static public class MedCont { - public Media media; - public Object contenitore; - public MedCont( Media media, Object contenitore ) { - this.media = media; - this.contenitore = contenitore; - } - @Override - public boolean equals( Object o ) { - return media.equals( ((MedCont)o).media); - } - @Override - public int hashCode() { - return Objects.hash( media ); - } - } -} \ No newline at end of file diff --git a/app/src/main/java/app/familygem/visitor/ContenitoriMedia.java b/app/src/main/java/app/familygem/visitor/MediaContainers.java similarity index 56% rename from app/src/main/java/app/familygem/visitor/ContenitoriMedia.java rename to app/src/main/java/app/familygem/visitor/MediaContainers.java index e7d7c452..e68eb398 100644 --- a/app/src/main/java/app/familygem/visitor/ContenitoriMedia.java +++ b/app/src/main/java/app/familygem/visitor/MediaContainers.java @@ -1,8 +1,3 @@ -/* Visitatore un po' complementare a RiferimentiMedia, avente una doppia funzione: -- Modifica i ref che puntano al Media condiviso -- Colleziona una lista dei contenitori che includono il Media condiviso -*/ - package app.familygem.visitor; import org.folg.gedcom.model.Gedcom; @@ -12,20 +7,28 @@ import java.util.HashSet; import java.util.Set; -public class ContenitoriMedia extends VisitorTotale { +/** + * Visitor is somewhat complementary to ReferencesMedia, having a double function: + * - Edit the refs pointing to the shared Media + * - Collect a list of containers that include the shared media + * Visitatore un po' complementare a RiferimentiMedia, avente una doppia funzione: + * - Modifica i ref che puntano al Media condiviso + * - Colleziona una lista dei contenitori che includono il Media condiviso + */ +public class MediaContainers extends TotalVisitor { public Set containers = new HashSet<>(); private final Media media; private final String newId; - public ContenitoriMedia(Gedcom gedcom, Media media, String newId) { + public MediaContainers(Gedcom gedcom, Media media, String newId) { this.media = media; this.newId = newId; gedcom.accept(this); } @Override - boolean visita(Object object, boolean capostipite) { + boolean visit(Object object, boolean isProgenitor) { if( object instanceof MediaContainer ) { for( MediaRef mediaRef : ((MediaContainer)object).getMediaRefs() ) { if( mediaRef.getRef().equals(media.getId()) ) { diff --git a/app/src/main/java/app/familygem/visitor/MediaList.java b/app/src/main/java/app/familygem/visitor/MediaList.java new file mode 100644 index 00000000..fc67d34d --- /dev/null +++ b/app/src/main/java/app/familygem/visitor/MediaList.java @@ -0,0 +1,121 @@ +package app.familygem.visitor; + +import org.folg.gedcom.model.EventFact; +import org.folg.gedcom.model.Family; +import org.folg.gedcom.model.Gedcom; +import org.folg.gedcom.model.Media; +import org.folg.gedcom.model.MediaContainer; +import org.folg.gedcom.model.Name; +import org.folg.gedcom.model.Person; +import org.folg.gedcom.model.Source; +import org.folg.gedcom.model.SourceCitation; +import org.folg.gedcom.model.Visitor; +import java.util.LinkedHashSet; +import java.util.Set; +import app.familygem.F; +import app.familygem.Global; + +/** + * Ordered set of media + * Can almost always replace ContainerMediaList + * */ +public class MediaList extends Visitor { + + public Set list = new LinkedHashSet<>(); + private Gedcom gc; + /** + * + * 0 all media + * 1 only shared media objects (for all Gedcom) + * 2 only (local media?) (no gc needed) + * 3 shared and local but only previewable images and videos (for the main menu) + * + * 0 tutti i media + * 1 solo gli oggetti media condivisi (per tutto il Gedcom) + * 2 solo i locali (non serve gc) + * 3 condivisi e locali ma solo immagini e video anteprimabili (per il menu principale) + */ + private int mediaType; + + public MediaList(Gedcom gc, int mediaType) { + this.gc = gc; + this.mediaType = mediaType; + } + + private boolean visita(Object object) { + if( object instanceof MediaContainer ) { + MediaContainer container = (MediaContainer)object; + if( mediaType == 0 ) + list.addAll(container.getAllMedia(gc)); // adds shared and local media + else if( mediaType == 2 ) + list.addAll(container.getMedia()); // local media only + else if( mediaType == 3 ) + for( Media med : container.getAllMedia(gc) ) + filter(med); + } + return true; + } + + /** + * Adds only the alleged (pretty? - "bellini") ones with preview + * Aggiunge solo quelli presunti bellini con anteprima + * */ + private void filter(Media media) { + String file = F.mediaPath(Global.settings.openTree, media); // TODO and images from URIs? + if(file != null) { + int index = file.lastIndexOf('.'); + if (index > 0) { + String extension = file.substring(index + 1); + switch (extension) { + case "jpg": + case "jpeg": + case "png": + case "gif": + case "bmp": + case "webp": // ok + case "heic": // ok TODO the image may be rotated 90 ° or 180 ° + case "heif": // synonymous with .heic + case "mp4": + case "3gp": // ok + case "webm": // ok + case "mkv": // ok + list.add(media); + } + } + } + } + + @Override + public boolean visit(Gedcom gc) { + if( mediaType < 2 ) + list.addAll(gc.getMedia()); // (rakes?) all Gedcom shared media items //rastrella tutti gli oggetti media condivisi del Gedcom + else if( mediaType == 3 ) + for( Media med : gc.getMedia() ) + filter(med); + return true; + } + @Override + public boolean visit(Person p) { + return visita(p); + } + @Override + public boolean visit(Family f) { + return visita(f); + } + @Override + public boolean visit(EventFact e) { + return visita(e); + } + @Override + public boolean visit(Name n) { + return visita(n); + } + @Override + public boolean visit(SourceCitation c) { + return visita(c); + } + @Override + public boolean visit(Source s) { + return visita(s); + } +} diff --git a/app/src/main/java/app/familygem/visitor/MediaListContainer.java b/app/src/main/java/app/familygem/visitor/MediaListContainer.java new file mode 100644 index 00000000..a1f78840 --- /dev/null +++ b/app/src/main/java/app/familygem/visitor/MediaListContainer.java @@ -0,0 +1,98 @@ +package app.familygem.visitor; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import org.folg.gedcom.model.EventFact; +import org.folg.gedcom.model.Family; +import org.folg.gedcom.model.Gedcom; +import org.folg.gedcom.model.Media; +import org.folg.gedcom.model.MediaContainer; +import org.folg.gedcom.model.Name; +import org.folg.gedcom.model.Person; +import org.folg.gedcom.model.Source; +import org.folg.gedcom.model.SourceCitation; +import org.folg.gedcom.model.Visitor; + +/** + * Ordered map of the media each with its own container object + * The container is used practically only to disconnectMedia in IndividualMedia + * + * Mappa ordinata dei media ciascuno col suo object contenitore + * Il contenitore serve praticamente solo a scollegaMedia in IndividuoMedia + * */ +public class MediaListContainer extends Visitor { + + public List mediaList = new ArrayList<>(); + private Gedcom gc; + private boolean requestAll; // List all media (even local) or shared media objects only //Elencare tutti i media (anche i locali) o solo gli oggetti media condivisi + + public MediaListContainer(Gedcom gc, boolean voglioTutti ) { + this.gc = gc; + this.requestAll = voglioTutti; + } + + private boolean visitInternal(Object object ) { + if( requestAll && object instanceof MediaContainer ) { + //for( MediaRef r : p.getMediaRefs() ) listaMedia.put( r.getMedia(gc), p ); //list empty refs => null media // elenca i ref a vuoto => media null + MediaContainer container = (MediaContainer) object; + for( Media med : container.getAllMedia( gc ) ) { // Media objects and local media of each record //Oggetti media e media locali di ciascun record + MedCont medCont = new MedCont(med, object); + if( !mediaList.contains(medCont) ) + mediaList.add( medCont ); + } + } + return true; + } + + @Override + public boolean visit( Gedcom gc ) { + for( Media med : gc.getMedia() ) + mediaList.add( new MedCont(med, gc) ); // (rake?) the media items //rastrella gli oggetti media + return true; + } + @Override + public boolean visit( Person p ) { + return visitInternal( p ); + } + @Override + public boolean visit( Family f ) { + return visitInternal( f ); + } + @Override + public boolean visit( EventFact e ) { + return visitInternal( e ); + } + @Override + public boolean visit( Name n ) { + return visitInternal( n ); + } + @Override + public boolean visit( SourceCitation c ) { + return visitInternal( c ); + } + @Override + public boolean visit( Source s ) { + return visitInternal( s ); + } + + /** + * Class representing a Media with its container object + * */ + static public class MedCont { + public Media media; + public Object container; + public MedCont( Media media, Object contenitore ) { + this.media = media; + this.container = contenitore; + } + @Override + public boolean equals( Object o ) { + return o instanceof MedCont && media.equals( ((MedCont)o).media); + } + @Override + public int hashCode() { + return Objects.hash( media ); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/app/familygem/visitor/MediaReferences.java b/app/src/main/java/app/familygem/visitor/MediaReferences.java new file mode 100644 index 00000000..4227eab7 --- /dev/null +++ b/app/src/main/java/app/familygem/visitor/MediaReferences.java @@ -0,0 +1,58 @@ +package app.familygem.visitor; + +import org.folg.gedcom.model.Gedcom; +import org.folg.gedcom.model.Media; +import org.folg.gedcom.model.MediaContainer; +import org.folg.gedcom.model.MediaRef; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.Set; + +/** + * + * Visitor who, compared to a shared media, has a triple function: + * * - Count media references in all MediaContainers + * * - Or delete the same media references + * * - In the meantime, list the parent objects of the stacks that contain the media + * + * + * Visitatore che rispetto a un media condiviso ha una triplice funzione: + * - Contare i riferimenti al media in tutti i MediaContainer + * - Oppure elimina gli stessi riferimenti al media + * - Nel frattempo elenca gli oggetti capostipite delle pile che contengono il media + * */ +public class MediaReferences extends TotalVisitor { + + private Media media; // the shared media + private boolean shouldEliminateRef; + private Object progenitor; // the progenitor of the stack + public int num = 0; // the number of references to a Media + public Set founders = new LinkedHashSet<>(); // the list of the founding objects containing a Media//l'elenco degli oggetti capostipiti contenti un Media + + public MediaReferences(Gedcom gc, Media media, boolean eliminate ) { + this.media = media; + this.shouldEliminateRef = eliminate; + gc.accept( this ); + } + + @Override + boolean visit(Object object, boolean isProgenitor) { + if(isProgenitor) + progenitor = object; + if( object instanceof MediaContainer ) { + MediaContainer container = (MediaContainer)object; + Iterator mediaRefs = container.getMediaRefs().iterator(); + while( mediaRefs.hasNext() ) + if( mediaRefs.next().getRef().equals(media.getId()) ) { + founders.add(progenitor); + if(shouldEliminateRef) + mediaRefs.remove(); + else + num++; + } + if( container.getMediaRefs().isEmpty() ) + container.setMediaRefs( null ); + } + return true; + } +} \ No newline at end of file diff --git a/app/src/main/java/app/familygem/visitor/NoteContainers.java b/app/src/main/java/app/familygem/visitor/NoteContainers.java new file mode 100644 index 00000000..8b522520 --- /dev/null +++ b/app/src/main/java/app/familygem/visitor/NoteContainers.java @@ -0,0 +1,43 @@ +package app.familygem.visitor; + +import org.folg.gedcom.model.Gedcom; +import org.folg.gedcom.model.Note; +import org.folg.gedcom.model.NoteContainer; +import org.folg.gedcom.model.NoteRef; +import java.util.HashSet; +import java.util.Set; + +/** + * Visitor somewhat complementary to ReferencesNote, having a double function: + * - Edit refs pointing to the shared note + * - Collect a list of containers that include the shared Note + * + * Visitatore un po' complementare a RiferimentiNota, avente una doppia funzione: + * - Modifica i ref che puntano alla nota condivisa + * - Colleziona una lista dei contenitori che includono la Nota condivisa + */ +public class NoteContainers extends TotalVisitor { + + public Set containers = new HashSet<>(); + private Note note; // the shared note to search for + private String newId; // the new id to put in the ref + + public NoteContainers(Gedcom gc, Note note, String newId) { + this.note = note; + this.newId = newId; + gc.accept(this); + } + + @Override + boolean visit(Object object, boolean isProgenitor) { + if( object instanceof NoteContainer ) { + for( NoteRef noteRef : ((NoteContainer)object).getNoteRefs() ) { + if( noteRef.getRef().equals(note.getId()) ) { + noteRef.setRef(newId); + containers.add((NoteContainer)object); + } + } + } + return true; + } +} diff --git a/app/src/main/java/app/familygem/visitor/ListaNote.java b/app/src/main/java/app/familygem/visitor/NoteList.java similarity index 59% rename from app/src/main/java/app/familygem/visitor/ListaNote.java rename to app/src/main/java/app/familygem/visitor/NoteList.java index 64e21be1..6a7d6f2f 100644 --- a/app/src/main/java/app/familygem/visitor/ListaNote.java +++ b/app/src/main/java/app/familygem/visitor/NoteList.java @@ -1,5 +1,3 @@ -// Visitatore che produce una Mappa ordinata delle note INLINE - package app.familygem.visitor; import org.folg.gedcom.model.Note; @@ -10,16 +8,19 @@ import java.util.List; import app.familygem.Global; -public class ListaNote extends VisitorTotale { +/** + * Visitor producing an ordered map of INLINE notes + * */ +public class NoteList extends TotalVisitor { - public List listaNote = new ArrayList<>(); + public List noteList = new ArrayList<>(); @Override - boolean visita( Object object, boolean capo ) { + boolean visit(Object object, boolean isProgenitor) { if( object instanceof NoteContainer && !(!Global.settings.expert && (object instanceof Source || object instanceof Repository)) ) { - NoteContainer blocco = (NoteContainer)object; - listaNote.addAll(blocco.getNotes()); + NoteContainer container = (NoteContainer)object; + noteList.addAll(container.getNotes()); } return true; } diff --git a/app/src/main/java/app/familygem/visitor/NoteReferences.java b/app/src/main/java/app/familygem/visitor/NoteReferences.java new file mode 100644 index 00000000..f34ef2af --- /dev/null +++ b/app/src/main/java/app/familygem/visitor/NoteReferences.java @@ -0,0 +1,53 @@ +package app.familygem.visitor; + +import org.folg.gedcom.model.Gedcom; +import org.folg.gedcom.model.NoteContainer; +import org.folg.gedcom.model.NoteRef; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.Set; +/** + * Visitor per shared note with a triple function: + * - To count in all the elements of the Gedcom the references to the shared note + * - Delete references to the note + * - In the meantime, collect all the founders + * + * Visitatore per nota condivisa con una triplice funzione: + * - Contare in tutti gli elementi del Gedcom i riferimenti alla nota condivisa + * - Eliminare i riferimenti alla nota + * - Nel frattempo raccogliere tutti i capostipiti + * */ +public class NoteReferences extends TotalVisitor { + + private String id; // Id of the shared note + private boolean deleteRefs; // flag to eliminate the refs to the note rather than counting them //bandierina per eliminare i ref alla nota piuttosto che contarli + private Object head; + public int count = 0; // references to the shared note + public Set founders = new LinkedHashSet<>(); + + public NoteReferences(Gedcom gc, String id, boolean deleteRegs ) { + this.id = id; + this.deleteRefs = deleteRegs; + gc.accept( this ); + } + + @Override + boolean visit(Object object, boolean isProgenitor) { + if(isProgenitor) + head = object; + if( object instanceof NoteContainer ) { + NoteContainer container = (NoteContainer) object; + Iterator refs = container.getNoteRefs().iterator(); + while( refs.hasNext() ) { + NoteRef nr = refs.next(); + if( nr.getRef().equals(id) ) { + founders.add(head); + if(deleteRefs) refs.remove(); + else count++; + } + } + if( container.getNoteRefs().isEmpty() ) container.setNoteRefs( null ); + } + return true; + } +} \ No newline at end of file diff --git a/app/src/main/java/app/familygem/visitor/PulisciPila.java b/app/src/main/java/app/familygem/visitor/PulisciPila.java deleted file mode 100644 index 11c6594e..00000000 --- a/app/src/main/java/app/familygem/visitor/PulisciPila.java +++ /dev/null @@ -1,21 +0,0 @@ -// Strettamente connesso a TrovaPila, individua gli oggetti da tenere nella pila - -package app.familygem.visitor; - -class PulisciPila extends VisitorTotale { - - private Object scopo; - boolean daEliminare = true; - - PulisciPila( Object scopo ) { - this.scopo = scopo; - } - - @Override - boolean visita( Object oggetto, boolean capo ) { // il boolean qui è inutilizzato - if( oggetto.equals(scopo) ) - daEliminare = false; - return true; - } -} - diff --git a/app/src/main/java/app/familygem/visitor/RiferimentiMedia.java b/app/src/main/java/app/familygem/visitor/RiferimentiMedia.java deleted file mode 100644 index a8777ba5..00000000 --- a/app/src/main/java/app/familygem/visitor/RiferimentiMedia.java +++ /dev/null @@ -1,52 +0,0 @@ -/* -Visitatore che rispetto a un media condiviso ha una triplice funzione: -- Contare i riferimenti al media in tutti i MediaContainer -- Oppure elimina gli stessi riferimenti al media -- Nel frattempo elenca gli oggetti capostipite delle pile che contengono il media -*/ - -package app.familygem.visitor; - -import org.folg.gedcom.model.Gedcom; -import org.folg.gedcom.model.Media; -import org.folg.gedcom.model.MediaContainer; -import org.folg.gedcom.model.MediaRef; -import java.util.Iterator; -import java.util.LinkedHashSet; -import java.util.Set; - -public class RiferimentiMedia extends VisitorTotale { - - private Media media; // il media condiviso - private boolean elimina; // eliminare i Ref o no - private Object capo; // il capostipite della pila - public int num = 0; // il conto dei riferimenti a un Media - public Set capostipiti = new LinkedHashSet<>(); // l'elenco degli oggetti capostipiti contenti un Media - - public RiferimentiMedia( Gedcom gc, Media media, boolean elimina ) { - this.media = media; - this.elimina = elimina; - gc.accept( this ); - } - - @Override - boolean visita( Object oggetto, boolean capostipite ) { - if( capostipite ) - capo = oggetto; - if( oggetto instanceof MediaContainer ) { - MediaContainer contenitore = (MediaContainer)oggetto; - Iterator refiMedia = contenitore.getMediaRefs().iterator(); - while( refiMedia.hasNext() ) - if( refiMedia.next().getRef().equals(media.getId()) ) { - capostipiti.add( capo ); - if( elimina ) - refiMedia.remove(); - else - num++; - } - if( contenitore.getMediaRefs().isEmpty() ) - contenitore.setMediaRefs( null ); - } - return true; - } -} \ No newline at end of file diff --git a/app/src/main/java/app/familygem/visitor/RiferimentiNota.java b/app/src/main/java/app/familygem/visitor/RiferimentiNota.java deleted file mode 100644 index a5dff530..00000000 --- a/app/src/main/java/app/familygem/visitor/RiferimentiNota.java +++ /dev/null @@ -1,49 +0,0 @@ -/* Visitatore per nota condivisa con una triplice funzione: - - Contare in tutti gli elementi del Gedcom i riferimenti alla nota condivisa - - Eliminare i riferimenti alla nota - - Nel frattempo raccogliere tutti i capostipiti -*/ - -package app.familygem.visitor; - -import org.folg.gedcom.model.Gedcom; -import org.folg.gedcom.model.NoteContainer; -import org.folg.gedcom.model.NoteRef; -import java.util.Iterator; -import java.util.LinkedHashSet; -import java.util.Set; - -public class RiferimentiNota extends VisitorTotale { - - private String id; // Id della nota condivisa - private boolean elimina; // bandierina per eliminare i ref alla nota piuttosto che contarli - private Object capo; - public int tot = 0; // i riferimenti alla nota condivisa - public Set capostipiti = new LinkedHashSet<>(); - - public RiferimentiNota( Gedcom gc, String id, boolean elimina ) { - this.id = id; - this.elimina = elimina; - gc.accept( this ); - } - - @Override - boolean visita( Object oggetto, boolean capostipite ) { - if( capostipite ) - capo = oggetto; - if( oggetto instanceof NoteContainer ) { - NoteContainer blocco = (NoteContainer) oggetto; - Iterator refi = blocco.getNoteRefs().iterator(); - while( refi.hasNext() ) { - NoteRef nr = refi.next(); - if( nr.getRef().equals(id) ) { - capostipiti.add( capo ); - if(elimina) refi.remove(); - else tot++; - } - } - if( blocco.getNoteRefs().isEmpty() ) blocco.setNoteRefs( null ); - } - return true; - } -} \ No newline at end of file diff --git a/app/src/main/java/app/familygem/visitor/VisitorTotale.java b/app/src/main/java/app/familygem/visitor/TotalVisitor.java similarity index 64% rename from app/src/main/java/app/familygem/visitor/VisitorTotale.java rename to app/src/main/java/app/familygem/visitor/TotalVisitor.java index 123c2941..355c7ca6 100644 --- a/app/src/main/java/app/familygem/visitor/VisitorTotale.java +++ b/app/src/main/java/app/familygem/visitor/TotalVisitor.java @@ -1,5 +1,3 @@ -// Modello di Visitor che visita tutti i possibili contenitori del Gedcom distinguendo i capostipiti - package app.familygem.visitor; import org.folg.gedcom.model.Change; @@ -17,66 +15,70 @@ import org.folg.gedcom.model.Submitter; import org.folg.gedcom.model.Visitor; -public class VisitorTotale extends Visitor { +/** + * Visitor model that visits all the possible Gedcom containers distinguishing the progenitors + * Modello di Visitor che visita tutti i possibili contenitori del Gedcom distinguendo i capostipiti + * */ +public class TotalVisitor extends Visitor { - private boolean visita( Object oggetto ) { - return visita( oggetto, false ); + private boolean visitInternal(Object object ) { + return visit( object, false ); } - boolean visita( Object oggetto, boolean capostipite ) { + boolean visit(Object object, boolean isProgenitor ) { return true; } @Override public boolean visit( Header h ) { - return visita( h, true ); + return visit( h, true ); } @Override public boolean visit( Person p ) { - return visita( p, true ); + return visit( p, true ); } @Override public boolean visit( Family f ) { - return visita( f, true ); + return visit( f, true ); } @Override public boolean visit( Source s ) { - return visita( s, true ); + return visit( s, true ); } @Override public boolean visit( Repository r ) { - return visita( r, true ); + return visit( r, true ); } @Override public boolean visit( Submitter s ) { - return visita( s, true ); + return visit( s, true ); } @Override public boolean visit( Media m ) { - return visita( m, m.getId()!=null ); + return visit( m, m.getId()!=null ); } @Override public boolean visit( Note n ) { - return visita( n, n.getId()!=null ); + return visit( n, n.getId()!=null ); } @Override public boolean visit( Name n ) { - return visita( n ); + return visitInternal( n ); } @Override public boolean visit( EventFact e ) { - return visita( e ); + return visitInternal( e ); } @Override public boolean visit( SourceCitation s ) { - return visita( s ); + return visitInternal( s ); } @Override public boolean visit( RepositoryRef r ) { - return visita( r ); + return visitInternal( r ); } @Override public boolean visit( Change c ) { - return visita( c ); + return visitInternal( c ); } } diff --git a/app/src/main/java/app/familygem/visitor/TrovaPila.java b/app/src/main/java/app/familygem/visitor/TrovaPila.java deleted file mode 100644 index 24efe824..00000000 --- a/app/src/main/java/app/familygem/visitor/TrovaPila.java +++ /dev/null @@ -1,127 +0,0 @@ -// Visitatore che produce in Memoria la pila gerarchica degli oggetti tra il record capostipite e un oggetto dato -// ad es. Person > Media semplice -// oppure Family > Note > SourceCitation > Note semplice - -package app.familygem.visitor; - -import org.folg.gedcom.model.Change; -import org.folg.gedcom.model.EventFact; -import org.folg.gedcom.model.Family; -import org.folg.gedcom.model.Gedcom; -import org.folg.gedcom.model.Header; -import org.folg.gedcom.model.Media; -import org.folg.gedcom.model.Name; -import org.folg.gedcom.model.Note; -import org.folg.gedcom.model.Person; -import org.folg.gedcom.model.Repository; -import org.folg.gedcom.model.RepositoryRef; -import org.folg.gedcom.model.Source; -import org.folg.gedcom.model.SourceCitation; -import org.folg.gedcom.model.Submitter; -import org.folg.gedcom.model.Visitable; -import org.folg.gedcom.model.Visitor; -import java.util.Iterator; -import java.util.List; -import app.familygem.Memoria; - -public class TrovaPila extends Visitor { - - private List pila; - private Object scopo; - private boolean trovato; - - public TrovaPila( Gedcom gc, Object scopo ) { - pila = Memoria.addPila(); // in una nuova pila apposta - this.scopo = scopo; - gc.accept( this ); - } - - private boolean opera( Object oggetto, String tag, boolean capostipite ) { - if( !trovato ) { - if( capostipite ) - pila.clear(); // ogni capostipite fa ricominciare da capo una pila - Memoria.Passo passo = new Memoria.Passo(); - passo.oggetto = oggetto; - passo.tag = tag; - if( !capostipite ) - passo.filotto = true; // li marchia per eliminarli poi in blocco onBackPressed - pila.add(passo); - } - if( oggetto.equals(scopo) ) { - Iterator passi = pila.iterator(); - while( passi.hasNext() ) { - PulisciPila pulitore = new PulisciPila( scopo ); - ((Visitable)passi.next().oggetto).accept( pulitore ); - if( pulitore.daEliminare ) - passi.remove(); - } - trovato = true; - //Memoria.stampa("TrovaPila"); - } - return true; - } - - @Override - public boolean visit( Header passo ) { - return opera(passo,"HEAD",true); - } - @Override - public boolean visit( Person passo ) { - return opera(passo,"INDI",true); - } - @Override - public boolean visit( Family passo ) { - return opera(passo,"FAM",true); - } - @Override - public boolean visit( Source passo ) { - return opera(passo,"SOUR",true); - } - @Override - public boolean visit( Repository passo ) { - return opera(passo,"REPO",true); - } - @Override - public boolean visit( Submitter passo ) { - return opera(passo,"SUBM",true); - } - @Override - public boolean visit( Media passo ) { - return opera(passo,"OBJE",passo.getId()!=null); - } - @Override - public boolean visit( Note passo ) { - return opera(passo,"NOTE",passo.getId()!=null); - } - @Override - public boolean visit( Name passo ) { - return opera(passo,"NAME",false); - } - @Override - public boolean visit( EventFact passo ) { - return opera(passo,passo.getTag(),false); - } - @Override - public boolean visit( SourceCitation passo ) { - return opera(passo,"SOUR",false); - } - @Override - public boolean visit( RepositoryRef passo ) { - return opera(passo,"REPO",false); - } - @Override - public boolean visit( Change passo ) { - return opera(passo,"CHAN",false); - } - /* ok ma poi tanto GedcomTag non è Visitable e quindi non prosegue la visita - @Override - public boolean visit( String chiave, Object estensioni ) { - if( chiave.equals("folg.more_tags") ) { - for( GedcomTag est : (List)estensioni ) { - //s.l(est.getClass().getName()+" "+est.getTag()); - opera( est, est.getTag(), false ); - } - } - return true; - }*/ -} \ No newline at end of file diff --git a/app/src/main/res/layout/dettaglio.xml b/app/src/main/res/layout/activity_detail.xml similarity index 96% rename from app/src/main/res/layout/dettaglio.xml rename to app/src/main/res/layout/activity_detail.xml index bb7f2509..8ad053f7 100644 --- a/app/src/main/res/layout/dettaglio.xml +++ b/app/src/main/res/layout/activity_detail.xml @@ -21,7 +21,7 @@ android:layout_marginBottom="12dp" app:flexWrap="wrap"/> diff --git a/app/src/main/res/layout/diagram_card.xml b/app/src/main/res/layout/diagram_card.xml index 29dfc66d..6330f063 100644 --- a/app/src/main/res/layout/diagram_card.xml +++ b/app/src/main/res/layout/diagram_card.xml @@ -42,14 +42,14 @@ android:layout_width="wrap_content" android:layout_height="80dp" android:adjustViewBounds="true"/> - - - - @@ -158,7 +158,7 @@ android:nextFocusForward="@id/luogo_morte"/> - - diff --git a/app/src/main/res/layout/pezzo_fatto.xml b/app/src/main/res/layout/pezzo_fatto.xml index c0c798e1..346b37a6 100644 --- a/app/src/main/res/layout/pezzo_fatto.xml +++ b/app/src/main/res/layout/pezzo_fatto.xml @@ -26,7 +26,7 @@ android:textSize="17sp" android:visibility="gone"/> - Recortar Copiar texto Valor - Tipo + type Prefijo Nombre de pila Sobrenombre @@ -159,7 +159,7 @@ Publicación Número de identificación Cursiva - Tipo multimedia + type multimedia Paréntesis Número de referencia Citada en diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index f3bf872d..ab365340 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -248,7 +248,7 @@ Vuoi che questo sia l\'autore principale dell\'albero? Valore - Tipo + type Prefisso Prenome Soprannome @@ -282,7 +282,7 @@ Pubblicazione Numero di identificazione Italico - Tipo media + type media Parentesi Numero di riferimento Citata in diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index d25c22f2..887c7c45 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -191,7 +191,7 @@ Citado em Número de referência Parênteses - Tipo de mídia + type de mídia Itálico Número de telefone Fatos de publicação @@ -221,7 +221,7 @@ Prefixo do sobrenome Apelido Prefixo - Tipo + type Valor Você quer que este apresentador seja o principal da árvore\? Cortar diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 69ab3080..41cabda0 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -66,6 +66,7 @@ 100dp @drawable/ruota_sfondo blocksDescendants + #00000000