diff --git a/README.md b/README.md index 9c9cbcf..b1712a5 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ D&DB Android app and database for searching and displaying data for Dungeons & Dragons (5e). -Pre-built binaries available in the [releases tab](https://github.com/dseguin/dndb/releases/latest), or download the [latest release directly](https://github.com/dseguin/dndb/releases/download/v0.1.1/dndb-androidapi19-0.1.1.apk). +Pre-built binaries available in the [releases tab](https://github.com/dseguin/dndb/releases/latest), or download the [latest release directly](https://github.com/dseguin/dndb/releases/download/v0.1.2/dndb-androidapi19-0.1.2.apk). Spells ------ diff --git a/app/src/main/java/ca/printf/dndb/data/DndbSQLManager.java b/app/src/main/java/ca/printf/dndb/data/DndbSQLManager.java index eef36b6..c8b9986 100644 --- a/app/src/main/java/ca/printf/dndb/data/DndbSQLManager.java +++ b/app/src/main/java/ca/printf/dndb/data/DndbSQLManager.java @@ -5,11 +5,13 @@ import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.util.Log; +import androidx.fragment.app.FragmentActivity; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.zip.ZipFile; import ca.printf.dndb.R; +import ca.printf.dndb.view.ErrorFragment; public class DndbSQLManager extends SQLiteOpenHelper { private static final String DB_NAME = "dndb.sqlite"; @@ -33,18 +35,21 @@ public class DndbSQLManager extends SQLiteOpenHelper { public static final String TABLE_SPELL_COMPONENT = "spell_component"; public static final String TABLE_COMPONENT = "component"; private Context ctx; + private FragmentActivity act; - public DndbSQLManager(Context c) { + public DndbSQLManager(Context c, FragmentActivity a) { super(c, DB_NAME, null, DB_VER); this.ctx = c; + this.act = a; } public void onCreate(SQLiteDatabase db) { try { CommonIO.execSQLFromFile(ctx, R.raw.spells_ddl, db); CommonIO.execSQLFromFile(ctx, R.raw.spells_init_dml, db); - } catch (IOException e) { + } catch (Exception e) { Log.e(this.getClass().getName(), "Error creating database ", e); + ErrorFragment.errorScreen(act.getSupportFragmentManager(), "Error creating database", e); } } diff --git a/app/src/main/java/ca/printf/dndb/entity/Spell.java b/app/src/main/java/ca/printf/dndb/entity/Spell.java index 6584e68..32a3ff2 100644 --- a/app/src/main/java/ca/printf/dndb/entity/Spell.java +++ b/app/src/main/java/ca/printf/dndb/entity/Spell.java @@ -76,6 +76,8 @@ public class Spell implements Serializable { public static final String QUERY_CONDITION_OPTIONS = QUERY_ATTR_OPTIONS(COL_CONDITION, DndbSQLManager.TABLE_CONDITION); public static final String QUERY_SOURCE_OPTIONS = QUERY_ATTR_OPTIONS(COL_SOURCE_SHORTNAME, DndbSQLManager.TABLE_SOURCE); public static final String QUERY_CLASS_OPTIONS = QUERY_ATTR_OPTIONS(COL_CLASS, DndbSQLManager.TABLE_CLASS_LIST); + // Get spell count + public static final String QUERY_SPELL_COUNT = "SELECT COUNT(" + COL_ID + ") FROM " + DndbSQLManager.TABLE_SPELL + ";"; // Instance vars private long id; private String name; diff --git a/app/src/main/java/ca/printf/dndb/view/RootActivity.java b/app/src/main/java/ca/printf/dndb/view/RootActivity.java index acc9230..645e63b 100644 --- a/app/src/main/java/ca/printf/dndb/view/RootActivity.java +++ b/app/src/main/java/ca/printf/dndb/view/RootActivity.java @@ -34,16 +34,19 @@ protected void onCreate(Bundle savedInstanceState) { ActionBarDrawerToggle drw_tog = new ActionBarDrawerToggle(this, drw, tb, R.string.app_name, R.string.app_name); drw.addDrawerListener(drw_tog); - drw.openDrawer(GravityCompat.START); + if(savedInstanceState == null) + drw.openDrawer(GravityCompat.START); drw_tog.syncState(); ((NavigationView)findViewById(R.id.nav_sidebar)).setNavigationItemSelectedListener(this); - content_frag = new DefaultFragment(); - getSupportFragmentManager() - .beginTransaction() - .add(R.id.content_frame, content_frag, FRAG_DEFAULT) - .commit(); + if(savedInstanceState == null) { + content_frag = new DefaultFragment(); + getSupportFragmentManager() + .beginTransaction() + .add(R.id.content_frame, content_frag, FRAG_DEFAULT) + .commit(); + } } public boolean onCreateOptionsMenu(Menu menu) { diff --git a/app/src/main/java/ca/printf/dndb/view/SettingsFragment.java b/app/src/main/java/ca/printf/dndb/view/SettingsFragment.java index b57594b..1412e17 100644 --- a/app/src/main/java/ca/printf/dndb/view/SettingsFragment.java +++ b/app/src/main/java/ca/printf/dndb/view/SettingsFragment.java @@ -63,19 +63,21 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { if(requestCode != FILEPICKER_RESULT || resultCode != Activity.RESULT_OK) return; try { - DndbSQLManager dbman = new DndbSQLManager(getContext()); + DndbSQLManager dbman = new DndbSQLManager(getContext(), getActivity()); SQLiteDatabase db = dbman.getWritableDatabase(); dbman.execZipPackage(db, getActivity().getContentResolver().openInputStream(data.getData())); db.close(); } catch (FileNotFoundException | NullPointerException e) { Log.e("onActivityResult", "Error loading file from file picker", e); - } catch (IOException e) { - Log.e("onActivityResult", "Error processing SQL in " + data.getData().toString(), e); + } catch (Exception e) { + Log.e("onActivityResult", "Error processing source package", e); + ErrorFragment.errorScreen(getActivity().getSupportFragmentManager(), + "Error processing source package", e); } } private void resetDB() { - DndbSQLManager dbman = new DndbSQLManager(getContext()); + DndbSQLManager dbman = new DndbSQLManager(getContext(), getActivity()); SQLiteDatabase db = dbman.getWritableDatabase(); Log.d("resetDB", "Clearing database with onCreate()"); dbman.onCreate(db); diff --git a/app/src/main/java/ca/printf/dndb/view/SpellDetailsFragment.java b/app/src/main/java/ca/printf/dndb/view/SpellDetailsFragment.java index 1cb7ce9..0bccd05 100644 --- a/app/src/main/java/ca/printf/dndb/view/SpellDetailsFragment.java +++ b/app/src/main/java/ca/printf/dndb/view/SpellDetailsFragment.java @@ -14,6 +14,7 @@ import ca.printf.dndb.entity.Spell; public class SpellDetailsFragment extends Fragment { + private static final String PREV_SPELL = "previous_spell"; private Spell spell; public SpellDetailsFragment(Spell spell) { @@ -24,6 +25,18 @@ public SpellDetailsFragment() {} public void onCreate(Bundle b) { super.onCreate(b); + if(b == null) + return; + spell = (Spell)b.getSerializable(PREV_SPELL); + } + + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(saveCurrentSpell(outState)); + } + + private Bundle saveCurrentSpell(Bundle b) { + b.putSerializable(PREV_SPELL, this.spell); + return b; } public View onCreateView(LayoutInflater li, ViewGroup vg, Bundle b) { diff --git a/app/src/main/java/ca/printf/dndb/view/SpellsListFragment.java b/app/src/main/java/ca/printf/dndb/view/SpellsListFragment.java index eb4330f..3411017 100644 --- a/app/src/main/java/ca/printf/dndb/view/SpellsListFragment.java +++ b/app/src/main/java/ca/printf/dndb/view/SpellsListFragment.java @@ -9,15 +9,20 @@ import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; -import android.widget.Button; import android.widget.CheckBox; import android.widget.EditText; import android.widget.ListView; import android.widget.Spinner; import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.Fragment; +import java.io.EOFException; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.util.ArrayList; import java.util.Collections; import ca.printf.dndb.R; @@ -29,6 +34,7 @@ import ca.printf.dndb.list.SpellSortSpinner; public class SpellsListFragment extends Fragment { + private static final String SPELL_CACHE = "spell_cache"; private ArrayList spells; private DndbSQLManager dbman; private SpellListManager adapter; @@ -41,14 +47,28 @@ public SpellsListFragment() { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - dbman = new DndbSQLManager(getContext()); + dbman = new DndbSQLManager(getContext(), getActivity()); initSpells(); + try { + if(loadSpellCache()) { + Log.d("loadSpellCache", "Spell cache successfully loaded"); + return; + } + } catch (Exception e) { + Log.e("loadSpellCache", "Error while reading spell cache", e); + } try { readSpellsFromDB(); } catch (Exception e) { ErrorFragment.errorScreen(getActivity().getSupportFragmentManager(), "readSpellsFromDB: Error reading spells from database", e); } + try { + if(saveSpellCache()) + Log.d("saveSpellCache", "Spell cache was written successfully"); + } catch (Exception e) { + Log.e("saveSpellCache", "Error while writing to spell cache", e); + } } public View onCreateView(LayoutInflater li, ViewGroup v, Bundle b) { @@ -57,8 +77,8 @@ public View onCreateView(LayoutInflater li, ViewGroup v, Bundle b) { ListView list = root.findViewById(R.id.spells_listview); list.setAdapter(adapter); list.setOnItemClickListener(spellSelection); - ((Button)root.findViewById(R.id.spells_btn_filter_spells)).setOnClickListener(filterButton); - ((Button)root.findViewById(R.id.spells_btn_sort_spells)).setOnClickListener(sortButton); + root.findViewById(R.id.spells_btn_filter_spells).setOnClickListener(filterButton); + root.findViewById(R.id.spells_btn_sort_spells).setOnClickListener(sortButton); return root; } @@ -441,4 +461,63 @@ private void setSpellMultivalues(Spell s, SQLiteDatabase db) { private String stripTableFromCol(String col) { return col.substring(col.lastIndexOf('.') + 1); } + + private File getSpellCache() throws IOException { + File dir = getActivity().getCacheDir(); + if(dir == null) + throw new IOException("getCacheDir(): Could not get file handler to cache directory"); + File f = new File(dir.getPath() + File.separator + SPELL_CACHE); + if(f.createNewFile()) + Log.d("getSpellCache", "Cache file \"" + SPELL_CACHE + "\" does not exist. Creating..."); + return f; + } + + private boolean saveSpellCache() throws IOException { + File f = getSpellCache(); + if(f.delete() && f.createNewFile()) { + ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(f)); + for(Spell s : spells) + oos.writeObject(s); + oos.close(); + } else { + return false; + } + return true; + } + + private boolean loadSpellCache() throws IOException { + ArrayList tmp = new ArrayList<>(); + File f = getSpellCache(); + if(f.length() == 0) + return false; + ObjectInputStream ois = new ObjectInputStream(new FileInputStream(f)); + while(ois.available() != -1) { + try { + tmp.add((Spell)ois.readObject()); + } catch (ClassNotFoundException e) { + ois.close(); + return false; + } catch (EOFException e) { + break; + } + } + ois.close(); + if(tmp.size() != getDBSpellCount()) + return false; + spells = tmp; + return true; + } + + private int getDBSpellCount() { + int ret = 0; + SQLiteDatabase db = dbman.getReadableDatabase(); + Cursor res = db.rawQuery(Spell.QUERY_SPELL_COUNT, null); + if(res.getCount() != 0) { + res.moveToFirst(); + ret = res.getInt(0); + } + res.close(); + db.close(); + return ret; + } } diff --git a/app/src/main/res/layout/about_dialog.xml b/app/src/main/res/layout/about_dialog.xml index 521cb59..ad3b3f5 100644 --- a/app/src/main/res/layout/about_dialog.xml +++ b/app/src/main/res/layout/about_dialog.xml @@ -24,6 +24,7 @@ android:id="@+id/about_text" android:layout_width="match_parent" android:layout_height="wrap_content" + android:textSize="16sp" android:textColorLink="@color/colorAccent" android:text="@string/about_text"/> diff --git a/app/src/main/res/layout/spells_listview_item.xml b/app/src/main/res/layout/spells_listview_item.xml index bafbd6e..cdabad8 100644 --- a/app/src/main/res/layout/spells_listview_item.xml +++ b/app/src/main/res/layout/spells_listview_item.xml @@ -15,6 +15,7 @@ - ]> + ]> &appname; @@ -72,10 +72,18 @@ Sort by : Sort Sort Spells - &appname;, created by David Seguin\n\n + &appname; is an application for browsing data from + the 5th edition ruleset of Dungeons & Dragons.\n\n + Author : David Seguin\n App Version : &appversion;\n Source Code : GitHub\n - License : MIT + License : MIT\n\n + Material included comes from the + Wizards of the Coast SRD + and other sources released under the + Open Game License.\n\n + The \"dragon ampersand\" is a press asset + provided by Wizards of the Coast. Sort in descending order Exclude selected components @@ -87,4 +95,4 @@ Are you sure? Confirm Are you sure you want to reset your database to initial defaults? - \ No newline at end of file + diff --git a/build.gradle b/build.gradle index 343345b..b58dc04 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { jcenter() } dependencies { - classpath "com.android.tools.build:gradle:4.0.0-beta05" + classpath 'com.android.tools.build:gradle:4.0.1' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files