Skip to content

Commit

Permalink
Added spell bookmarking feature
Browse files Browse the repository at this point in the history
Highlights:
- Added ability to create and delete bookmark lists
- Spells can be added to a bookmark from the spell details page
- Spells in bookmark can be sorted in the same way as in SpellListPage
- Long-clicking on a spell in the bookmark list presents "remove" option
- Installing this release upgrades the previous version of the DB

Fixes:
- Various layout changes
- Refactored spell list classes
  • Loading branch information
dseguin committed Sep 10, 2020
1 parent 4dc5c30 commit 0bce138
Show file tree
Hide file tree
Showing 36 changed files with 1,530 additions and 639 deletions.
8 changes: 3 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ D&DB

![Spell Pages](images/screen_group1.png)

Android app and database for searching and displaying data for Dungeons & Dragons (5e).
D&DB is an Android app/database for searching and displaying data for Dungeons & Dragons (5e). The only data included by default comes from free source material by Wizards of the Coast, including the "System Reference Document", "Basic Rules", and "Elemental Evil Player's Companion".

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).
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.3/dndb-androidapi19-0.1.3.apk).

Spells
------
Expand Down Expand Up @@ -58,9 +58,7 @@ License

Source code specific to D&DB is released under the MIT License.

Data for the "Basic Rules" and other included material is provided by [Wizards of the Coast](https://dnd.wizards.com) under the [Open Game License](https://media.wizards.com/2016/downloads/DND/SRD-OGL_V5.1.pdf).

The "dragon ampersand" is part of the Dungeons & Dragons logo and [is available as a press asset from Wizards of the Coast](https://dnd.wizards.com/pressassets).
Data included in this app is derived only from the SRD and other material by [Wizards of the Coast](https://dnd.wizards.com), either released under the [Open Game License](https://media.wizards.com/2016/downloads/DND/SRD-OGL_V5.1.pdf) or as free source material.

This software was developed using Google's Android Studio, which is based on IntelliJ IDEA Community Edition, and released under the Apache v2 License. The software makes use of the Android SDK, which is subject to [the Android SDK terms and conditions](https://developer.android.com/studio/terms).

Expand Down
2 changes: 1 addition & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".view.RootActivity">
<activity android:name=".RootActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package ca.printf.dndb.view;
package ca.printf.dndb;

import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.appcompat.app.AlertDialog;
Expand All @@ -14,12 +14,16 @@
import android.view.View;
import android.widget.FrameLayout;
import android.widget.TextView;
import ca.printf.dndb.R;
import ca.printf.dndb.logic.BookmarkListController;
import ca.printf.dndb.logic.SpellListController;
import ca.printf.dndb.view.BookmarkListPage;
import ca.printf.dndb.view.DefaultPage;
import ca.printf.dndb.view.SettingsPage;
import ca.printf.dndb.view.SpellListPage;
import com.google.android.material.navigation.NavigationView;

public class RootActivity extends AppCompatActivity
implements NavigationView.OnNavigationItemSelectedListener {
private static final String FRAG_DEFAULT = "FRAG_DEFAULT";
private Fragment content_frag;
private DrawerLayout drw;

Expand All @@ -41,17 +45,20 @@ protected void onCreate(Bundle savedInstanceState) {
((NavigationView)findViewById(R.id.nav_sidebar)).setNavigationItemSelectedListener(this);

if(savedInstanceState == null) {
content_frag = new DefaultFragment();
content_frag = new DefaultPage();
getSupportFragmentManager()
.beginTransaction()
.add(R.id.content_frame, content_frag, FRAG_DEFAULT)
.add(R.id.content_frame, content_frag)
.commit();
}

SpellListController.initSpells(this);
BookmarkListController.initBookmarks(this);
}

public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.nav_menu, menu);
menu.findItem(R.id.menu_spells).setVisible(false);
menu.setGroupVisible(R.id.menu_group_categories, false);
return true;
}

Expand All @@ -66,10 +73,15 @@ public boolean onOptionsItemSelected(MenuItem item) {
private boolean menuAction(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_spells :
openContentFragment(new SpellsListFragment());
SpellListController.initSpells(this);
openContentFragment(new SpellListPage());
break;
case R.id.menu_bookmarks :
SpellListController.initSpells(this);
openContentFragment(new BookmarkListPage());
break;
case R.id.menu_settings :
openContentFragment(new SettingsFragment());
openContentFragment(new SettingsPage());
break;
case R.id.menu_about :
createAboutDialog().show();
Expand Down
113 changes: 113 additions & 0 deletions app/src/main/java/ca/printf/dndb/entity/Bookmark.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package ca.printf.dndb.entity;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import ca.printf.dndb.io.DndbSQLManager;

public class Bookmark implements Serializable {
private static final long serialVersionUID = 1L;
public static final String COL_ID = DndbSQLManager.TABLE_BOOKMARK + ".rowid";
public static final String COL_BOOKMARK_NAME = DndbSQLManager.TABLE_BOOKMARK + ".name";
public static final String COL_BOOKMARK_SPELL_ID = DndbSQLManager.TABLE_BOOKMARK_SPELL + ".rowid";
public static final String CREATE_BOOKMARK_TABLE =
"DROP TABLE IF EXISTS " + DndbSQLManager.TABLE_BOOKMARK + ";\n" +
"CREATE TABLE " + DndbSQLManager.TABLE_BOOKMARK + " (\n "
+ DndbSQLManager.stripTableFromCol(COL_BOOKMARK_NAME) + " TEXT\n" +
");";
public static final String CREATE_BOOKMARK_SPELL_TABLE =
"DROP TABLE IF EXISTS " + DndbSQLManager.TABLE_BOOKMARK_SPELL + ";\n" +
"CREATE TABLE " + DndbSQLManager.TABLE_BOOKMARK_SPELL + " (\n "
+ DndbSQLManager.TABLE_BOOKMARK + "_id INTEGER NOT NULL,\n "
+ DndbSQLManager.TABLE_SPELL + "_id INTEGER NOT NULL,\n "
+ "UNIQUE(" + DndbSQLManager.TABLE_BOOKMARK + "_id," + DndbSQLManager.TABLE_SPELL + "_id)\n" +
");";
public static final String JOIN_BOOKMARK_SPELL = "INNER JOIN " + DndbSQLManager.TABLE_BOOKMARK_SPELL +
" ON " + COL_ID + " = " + DndbSQLManager.TABLE_BOOKMARK_SPELL + "." +
DndbSQLManager.TABLE_BOOKMARK + "_id INNER JOIN " + DndbSQLManager.TABLE_SPELL + " ON " +
Spell.COL_ID + " = " + DndbSQLManager.TABLE_BOOKMARK_SPELL + "." + DndbSQLManager.TABLE_SPELL + "_id";
public static final String[] QUERY_BOOKMARKS_COLS = {DndbSQLManager.stripTableFromCol(COL_ID), DndbSQLManager.stripTableFromCol(COL_BOOKMARK_NAME)};
public static final String[] QUERY_BOOKMARK_SPELLS_COLS = {COL_BOOKMARK_SPELL_ID, COL_BOOKMARK_NAME, Spell.COL_NAME};
public static final String QUERY_BOOKMARKS = "SELECT " + COLATE_COLS(QUERY_BOOKMARKS_COLS) +
" FROM " + DndbSQLManager.TABLE_BOOKMARK + ";";
private long id;
private String name;
private List<Spell> spells;

public Bookmark() {this(-1);}
public Bookmark(long id) {
this.id = id;
this.spells = new ArrayList<>();
}

public long getId() {return this.id;}
public String getName() {return this.name;}
public List<Spell> getSpellList() {return this.spells;}
public void setId(long id) {this.id = id;}
public void setName(String name) {this.name = name;}
public void setSpellList(List<Spell> spellList) {this.spells = spellList;}
public boolean equals(Object o) {
return super.equals(o) || (o instanceof Bookmark && ((Bookmark)o).getId() == this.getId());
}

public static String query_bookmark_spells(long bookmarkid) {
return "SELECT " + COLATE_COLS(QUERY_BOOKMARK_SPELLS_COLS) + " FROM " +
DndbSQLManager.TABLE_BOOKMARK + " " + JOIN_BOOKMARK_SPELL + " WHERE " +
DndbSQLManager.TABLE_BOOKMARK_SPELL + "." + DndbSQLManager.TABLE_BOOKMARK +
"_id = " + bookmarkid + " ORDER BY " + Spell.COL_NAME + ";";
}

public static String insert_bookmark_spell(String bookmarkname, String spellname) {
String bookmark_id = "(SELECT " + DndbSQLManager.stripTableFromCol(COL_ID) + " FROM " +
DndbSQLManager.TABLE_BOOKMARK + " WHERE " +
DndbSQLManager.stripTableFromCol(COL_BOOKMARK_NAME) + " LIKE '" + bookmarkname + "')";
String spell_id = "(SELECT " + DndbSQLManager.stripTableFromCol(Spell.COL_ID) + " FROM " +
DndbSQLManager.TABLE_SPELL + " WHERE " +
DndbSQLManager.stripTableFromCol(Spell.COL_NAME) + " LIKE '" + spellname + "')";
return _insert_bookmark_spell(bookmark_id, spell_id);
}

public static String insert_bookmark_spell(long bookmarkid, String spellname) {
String spell_id = "(SELECT " + DndbSQLManager.stripTableFromCol(Spell.COL_ID) + " FROM " +
DndbSQLManager.TABLE_SPELL + " WHERE " +
DndbSQLManager.stripTableFromCol(Spell.COL_NAME) + " LIKE '" + spellname + "')";
return _insert_bookmark_spell(Long.toString(bookmarkid), spell_id);
}

private static String _insert_bookmark_spell(String bookmark_id, String spell_id) {
return "INSERT OR IGNORE INTO " + DndbSQLManager.TABLE_BOOKMARK_SPELL +
" (" + DndbSQLManager.TABLE_BOOKMARK + "_id," + DndbSQLManager.TABLE_SPELL + "_id) VALUES\n " +
"(" + bookmark_id + "," + spell_id + ");";
}

public static String delete_bookmark_spell(long bookmarkid, String spellname) {
return "DELETE FROM " + DndbSQLManager.TABLE_BOOKMARK_SPELL + " WHERE " +
DndbSQLManager.TABLE_BOOKMARK + "_id = " + bookmarkid + " AND " +
DndbSQLManager.TABLE_SPELL + "_id = (SELECT " +
DndbSQLManager.stripTableFromCol(Spell.COL_ID) + " FROM " +
DndbSQLManager.TABLE_SPELL + " WHERE " +
DndbSQLManager.stripTableFromCol(Spell.COL_NAME) + " LIKE '" + spellname + "');";
}

public static String insert_new_bookmark(String bookmarkname) {
return "INSERT OR IGNORE INTO " + DndbSQLManager.TABLE_BOOKMARK +
" (" + DndbSQLManager.stripTableFromCol(COL_BOOKMARK_NAME) +
") VALUES\n ('" + bookmarkname + "');";
}

public static String delete_bookmark(long id) {
return "DELETE FROM " + DndbSQLManager.TABLE_BOOKMARK + " WHERE " +
DndbSQLManager.stripTableFromCol(COL_ID) + " = " + id + ";\n" +
"DELETE FROM " + DndbSQLManager.TABLE_BOOKMARK_SPELL + " WHERE " +
DndbSQLManager.TABLE_BOOKMARK + "_id = " + id + ";";
}

private static final String COLATE_COLS(final String[] COLS) {
String ret = "";
for(String s : COLS)
ret += (s + ",");
if(ret.isEmpty())
return ret;
return ret.substring(0, ret.length() - 1);
}
}
10 changes: 7 additions & 3 deletions app/src/main/java/ca/printf/dndb/entity/Spell.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import ca.printf.dndb.data.DndbSQLManager;
import ca.printf.dndb.io.DndbSQLManager;

public class Spell implements Serializable {
private static final long serialVersionUID = 1L;
Expand Down Expand Up @@ -106,7 +106,7 @@ public class Spell implements Serializable {
private ArrayList<String> classes = new ArrayList<>();

public Spell(long id) {this.id = id;}
public Spell() {this.id = -1;}
public Spell() {this(-1);}

public long getId() {return id;}
public String getName() {return name;}
Expand Down Expand Up @@ -158,6 +158,10 @@ public class Spell implements Serializable {
public void setSources(Map<String, String> sources) {this.sources = sources;}
public void setClasses(ArrayList<String> classes) {this.classes = classes;}

public boolean equals(Object o) {
return super.equals(o) || (o instanceof Spell && ((Spell)o).getName().equals(this.getName()));
}

private static final String JOIN_SPELL_TABLE(final String TABLE) {
return "INNER JOIN " + TABLE + " ON " + TABLE + ".rowid = "
+ DndbSQLManager.TABLE_SPELL + "." + TABLE;
Expand All @@ -181,7 +185,7 @@ private static final String COLATE_COLS(final String[] COLS) {
private static String createWhereClause(String spellname) {
if(spellname == null || spellname.isEmpty())
return " ";
spellname = ca.printf.dndb.data.CommonIO.sanitizeString(spellname);
spellname = ca.printf.dndb.io.CommonIO.sanitizeString(spellname);
return " WHERE " + COL_NAME + " LIKE '" + spellname + "'";
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package ca.printf.dndb.data;
package ca.printf.dndb.io;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
Expand Down Expand Up @@ -51,7 +51,7 @@ private static void execSQLFromStream(BufferedReader reader, SQLiteDatabase db)
}
}

private static void execSQLFromString(String str, SQLiteDatabase db) throws IOException {
public static void execSQLFromString(String str, SQLiteDatabase db) throws IOException {
StringTokenizer tok = new StringTokenizer(str, "\n", false);
String stmt = "";
while(tok.hasMoreTokens()) {
Expand Down Expand Up @@ -117,6 +117,8 @@ private static String getZipFileContent(ZipFile zf, ZipEntry ze) throws IOExcept

private static String _readZipStreamEntry(ZipEntry ze, ZipInputStream zis) throws IOException {
int size = (int)ze.getSize();
if(size < 0)
return null;
byte[] buf = new byte[size];
int bytes_read;
for(bytes_read = 0; bytes_read < size;) {
Expand All @@ -138,7 +140,7 @@ private static ArrayList<String> getZipFilesContents(ZipFile zf, ArrayList<Strin
continue;
}
String s = getZipFileContent(zf, entry);
if(s.isEmpty())
if(s == null || s.isEmpty())
Log.w("getZipFilesContents", "No data read from " + file);
else
contents.add(s);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package ca.printf.dndb.data;
package ca.printf.dndb.io;

import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
Expand All @@ -11,11 +10,12 @@
import java.io.InputStream;
import java.util.zip.ZipFile;
import ca.printf.dndb.R;
import ca.printf.dndb.view.ErrorFragment;
import ca.printf.dndb.entity.Bookmark;
import ca.printf.dndb.view.ErrorPage;

public class DndbSQLManager extends SQLiteOpenHelper {
private static final String DB_NAME = "dndb.sqlite";
private static final int DB_VER = 1;
private static final int DB_VER = 2;
public static final String TABLE_SPELL = "spell";
public static final String TABLE_SCHOOL = "school";
public static final String TABLE_SPELL_TARGET = "spell_target";
Expand All @@ -34,27 +34,46 @@ public class DndbSQLManager extends SQLiteOpenHelper {
public static final String TABLE_CLASS_LIST = "class_list";
public static final String TABLE_SPELL_COMPONENT = "spell_component";
public static final String TABLE_COMPONENT = "component";
private Context ctx;
public static final String TABLE_BOOKMARK = "bookmark";
public static final String TABLE_BOOKMARK_SPELL = "bookmark_spell";
private FragmentActivity act;

public DndbSQLManager(Context c, FragmentActivity a) {
super(c, DB_NAME, null, DB_VER);
this.ctx = c;
public DndbSQLManager(FragmentActivity a) {
super(a, DB_NAME, null, DB_VER);
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);
CommonIO.execSQLFromFile(act, R.raw.spells_ddl, db);
CommonIO.execSQLFromFile(act, R.raw.spells_init_dml, db);
createBookmarkTable(db);
} catch (Exception e) {
Log.e(this.getClass().getName(), "Error creating database ", e);
ErrorFragment.errorScreen(act.getSupportFragmentManager(), "Error creating database", e);
ErrorPage.errorScreen(act.getSupportFragmentManager(), "Error creating database", e);
}
}

public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
onCreate(db);
if(oldVersion != 1)
onCreate(db);
try {
createBookmarkTable(db);
} catch (Exception e) {
Log.e(this.getClass().getName(), "Error upgrading database ", e);
ErrorPage.errorScreen(act.getSupportFragmentManager(), "Error upgrading database", e);
}
}

private void createBookmarkTable(SQLiteDatabase db) throws IOException {
db.beginTransaction();
try {
String sql = Bookmark.CREATE_BOOKMARK_TABLE + "\n\n" + Bookmark.CREATE_BOOKMARK_SPELL_TABLE;
CommonIO.execSQLFromString(sql, db);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}

public boolean dbHasSpells(SQLiteDatabase db) {
Expand All @@ -72,4 +91,8 @@ public void execZipPackage(SQLiteDatabase db, File zipfile) throws IOException {
public void execZipPackage(SQLiteDatabase db, InputStream zipfile) throws IOException {
CommonIO.execSQLFromZipStream(zipfile, db);
}

public static String stripTableFromCol(String col) {
return col.substring(col.lastIndexOf('.') + 1);
}
}
Loading

0 comments on commit 0bce138

Please sign in to comment.