-
Notifications
You must be signed in to change notification settings - Fork 8
User data import export #114
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
4bbb1c0
3040ceb
d3fd24e
cfa1fb8
619de4e
0ef205d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -28,13 +28,21 @@ | |
| import android.os.AsyncTask; | ||
| import android.os.Bundle; | ||
| import android.os.Handler; | ||
|
|
||
| import androidx.activity.result.ActivityResultLauncher; | ||
| import androidx.activity.result.contract.ActivityResultContracts; | ||
| import androidx.core.content.ContextCompat; | ||
| import androidx.localbroadcastmanager.content.LocalBroadcastManager; | ||
| import androidx.cursoradapter.widget.CursorAdapter; | ||
| import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; | ||
|
|
||
| import android.text.Html; | ||
| import android.text.method.LinkMovementMethod; | ||
| import android.text.Spannable; | ||
| import android.text.SpannableStringBuilder; | ||
| import android.text.style.ImageSpan; | ||
| import androidx.core.content.ContextCompat; | ||
| import android.graphics.drawable.Drawable; | ||
| import android.view.KeyEvent; | ||
| import android.view.inputmethod.InputMethodManager; | ||
| import android.widget.EditText; | ||
|
|
@@ -57,6 +65,7 @@ | |
| import android.widget.TextView; | ||
| import android.widget.Toast; | ||
|
|
||
| import java.io.IOException; | ||
| import java.util.ArrayList; | ||
| import java.util.LinkedList; | ||
| import java.util.Arrays; | ||
|
|
@@ -70,6 +79,7 @@ public class NavigationActivity extends AppCompatActivity | |
| View.OnFocusChangeListener { | ||
|
|
||
| public final static String TALK_DETAIL_EXTRA = "org.dharmaseed.android.TALK_DETAIL"; | ||
| private final static String USER_DB_EXPORT_FILE = "dharmaseed_user_data.sqlite3"; | ||
|
|
||
| NavigationView navigationView; | ||
| ListView listView; | ||
|
|
@@ -260,11 +270,7 @@ public void onRefresh() { | |
| } | ||
| }); | ||
|
|
||
| DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); | ||
| ActionBarDrawerToggle toggle = new ActionBarDrawerToggle( | ||
| this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close); | ||
| drawer.setDrawerListener(toggle); | ||
| toggle.syncState(); | ||
| initNavigationDrawer(toolbar); | ||
|
|
||
| LocalBroadcastManager.getInstance(this).registerReceiver(new BroadcastReceiver() { | ||
| @Override | ||
|
|
@@ -287,6 +293,48 @@ public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCoun | |
| }); | ||
| } | ||
|
|
||
| private void initNavigationDrawer(Toolbar toolbar) { | ||
| DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); | ||
| ActionBarDrawerToggle toggle = new ActionBarDrawerToggle( | ||
| this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close); | ||
| drawer.addDrawerListener(toggle); | ||
| toggle.syncState(); | ||
|
|
||
| addDBIcons( | ||
| navigationView.getMenu().findItem(R.id.nav_export), | ||
| getString(R.string.drawer_export) | ||
| ); | ||
|
|
||
| addDBIcons( | ||
| navigationView.getMenu().findItem(R.id.nav_import), | ||
| getString(R.string.drawer_import) | ||
| ); | ||
| } | ||
|
|
||
| private void addDBIcons(MenuItem item, String baseText) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In an ideal world, I think it would be nice to be able to embed these icons directly inside of the |
||
| if (item == null) return; | ||
| SpannableStringBuilder sb = new SpannableStringBuilder(baseText + " and "); | ||
| int iconSize = (int) (headerPrimary.getTextSize() * 0.9); | ||
| addIconToSpan(sb, R.drawable.ic_history_db, baseText.length() + 1, iconSize); | ||
| addIconToSpan(sb, R.drawable.ic_star_db, 99, iconSize); | ||
| item.setTitle(sb); | ||
| } | ||
|
|
||
| private void addIconToSpan(SpannableStringBuilder sb, int drawableId, int index, int size) { | ||
| Drawable drawable = ContextCompat.getDrawable(this, drawableId); | ||
| if (drawable != null) { | ||
| drawable.setBounds(0, 0, size, size); | ||
|
|
||
| // don't insert beyond the end of the string | ||
| if (index >= sb.length()) { | ||
| index = sb.length() - 1; | ||
| } | ||
|
|
||
| ImageSpan imageSpan = new ImageSpan(drawable, ImageSpan.ALIGN_BOTTOM); | ||
| sb.setSpan(imageSpan, index, index + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); | ||
| } | ||
| } | ||
|
|
||
| private void updateScrollLabel(View item) { | ||
| int scrollId = -1; | ||
| ViewMode v = getCurrentViewMode(); | ||
|
|
@@ -709,12 +757,19 @@ public boolean onNavigationItemSelected(MenuItem item) { | |
| // Handle navigation view item clicks here. | ||
| int id = item.getItemId(); | ||
|
|
||
| boolean highlightItem = true; | ||
| if (id == R.id.nav_talks) { | ||
| setViewMode(new ViewMode(ViewMode.VIEW_MODE_TALKS)); | ||
| } else if (id == R.id.nav_teachers) { | ||
| setViewMode(new ViewMode(ViewMode.VIEW_MODE_TEACHERS)); | ||
| } else if (id == R.id.nav_centers) { | ||
| setViewMode(new ViewMode(ViewMode.VIEW_MODE_CENTERS)); | ||
| } else if (id == R.id.nav_export) { | ||
| startUserDBExport(); | ||
| highlightItem = false; | ||
| } else if (id == R.id.nav_import) { | ||
| startUserDBImport(); | ||
| highlightItem = false; | ||
| } | ||
| // else if (id == R.id.nav_retreats) { | ||
| // Intent intent = new Intent(this, RetreatSearchActivity.class); | ||
|
|
@@ -723,7 +778,7 @@ public boolean onNavigationItemSelected(MenuItem item) { | |
|
|
||
| DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); | ||
| drawer.closeDrawer(GravityCompat.START); | ||
| return true; | ||
| return highlightItem; | ||
| } | ||
|
|
||
| public void headingDetailCollapseExpandButtonClicked(View view) { | ||
|
|
@@ -868,6 +923,80 @@ private List<String> getSearchTerms() | |
| return searchTerms; | ||
| } | ||
|
|
||
| private final ActivityResultLauncher<String> exportDatabaseLauncher = registerForActivityResult( | ||
| new ActivityResultContracts.CreateDocument("application/x-sqlite3"), | ||
| uri -> { | ||
| if (uri != null) { | ||
| performUserDBExport(uri); | ||
| } | ||
| } | ||
| ); | ||
|
|
||
| private void performUserDBExport(Uri uri) { | ||
| // We use a new thread because DB operations and file copying (I/O) | ||
| // in DBManager.exportUserTablesToUri will block the UI thread. | ||
| new Thread(() -> { | ||
| try { | ||
| dbManager.exportUserTablesToUri(uri); | ||
| Log.i(LOG_TAG, "Successfully exported user DB to " + uri); | ||
|
|
||
| // Success: Switch back to UI thread to show toast | ||
| runOnUiThread(() -> showToast(getString(R.string.export_success))); | ||
| } catch (IOException e) { | ||
| Log.e(LOG_TAG, "Export failed", e); | ||
|
|
||
| // Error: Switch back to UI thread to show toast | ||
| runOnUiThread(() -> showToast(getString(R.string.export_failed))); | ||
| } | ||
| }).start(); | ||
| } | ||
|
|
||
| private void startUserDBExport() { | ||
| // The "CreateDocument" contract opens the file picker | ||
| // We pass the suggested filename here | ||
| exportDatabaseLauncher.launch(USER_DB_EXPORT_FILE); | ||
| } | ||
|
|
||
| // 1. Add the launcher for picking a file | ||
| private final ActivityResultLauncher<String[]> importDatabaseLauncher = registerForActivityResult( | ||
| new ActivityResultContracts.OpenDocument(), | ||
| uri -> { | ||
| if (uri != null) { | ||
| performUserDBImport(uri); | ||
| } | ||
| } | ||
| ); | ||
|
|
||
| // 2. Implement the background processing logic | ||
| private void performUserDBImport(Uri uri) { | ||
| // We use a new thread because DB operations and file copying (I/O) | ||
| // in DBManager.importUserTablesFromUri will block the UI thread. | ||
| new Thread(() -> { | ||
| try { | ||
| dbManager.importUserTablesFromUri(uri); | ||
| Log.i(LOG_TAG, "Successfully imported user DB from " + uri); | ||
|
|
||
| // Success: Switch back to UI thread to refresh data and show toast | ||
| runOnUiThread(() -> { | ||
| updateDisplayedData(); // Refresh current list to show new stars/history | ||
| showToast(getString(R.string.import_success)); | ||
| }); | ||
| } catch (IOException e) { | ||
| Log.e(LOG_TAG, "Import failed", e); | ||
|
|
||
| // Error: Switch back to UI thread to show toast | ||
| runOnUiThread(() -> showToast(getString(R.string.import_failed))); | ||
| } | ||
| }).start(); | ||
| } | ||
|
|
||
| // 3. Update the existing startUserDBImport method | ||
| public void startUserDBImport() { | ||
| // Launches the system file picker to select a SQLite database file | ||
| // We accept any file type or specifically application/x-sqlite3 if supported | ||
| importDatabaseLauncher.launch(new String[]{"application/x-sqlite3", "application/octet-stream", "*/*"}); | ||
| } | ||
|
|
||
| /** | ||
| * Shows a short toast with text=message | ||
| * @param message | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||
| android:width="24dp" | ||
| android:height="24dp" | ||
| android:viewportWidth="24" | ||
| android:viewportHeight="24"> | ||
| <path | ||
| android:fillColor="#757575" | ||
| android:pathData="M5,20h14v-2H5V20z M12,4L5,11h4v5h6v-5h4L12,4z" /> | ||
| </vector> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||
| android:width="24dp" | ||
| android:height="24dp" | ||
| android:viewportWidth="24" | ||
| android:viewportHeight="24"> | ||
| <path | ||
| android:fillColor="#80000000" | ||
| android:strokeColor="#00000000" | ||
| android:strokeWidth="0.0" | ||
| android:strokeLineCap="round" | ||
| android:strokeLineJoin="round" | ||
| android:pathData="M13,3a9,9 0,0 0,-9 9H1l3.89,3.89 0.07,0.11L9,12H6a7,7 0,1 1,1 3.74l-1.42,1.42A8.98,8.98 0,0 0,13 21a9,9 0,0 0,0 -18zM12,8v5l4.25,2.52 0.75,-1.23 -3.5,-2.09V8z" /> | ||
| </vector> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think all uses of this method call it with
closeAfterCopy = true. Would it make sense to just remove this parameter?