diff --git a/opacclient/libopac/src/main/java/de/geeksfactory/opacclient/objects/Starred.java b/opacclient/libopac/src/main/java/de/geeksfactory/opacclient/objects/Starred.java new file mode 100644 index 000000000..e6c0e4fcc --- /dev/null +++ b/opacclient/libopac/src/main/java/de/geeksfactory/opacclient/objects/Starred.java @@ -0,0 +1,97 @@ +/** + * Copyright (C) 2013 by Raphael Michel under the MIT license: + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the Software + * is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +package de.geeksfactory.opacclient.objects; + +/** + * Object representing a bookmarked item. Not part of the API you are interested + * in if you want to implement a library system. + * + * @author Raphael Michel + */ +public class Starred { + private int id; + private String mnr; + private String title; + private SearchResult.MediaType mediaType; + + @Override + public String toString() { + return "Starred [id=" + id + ", mnr=" + mnr + ", title=" + title + ", mediaType=" + + mediaType.toString() + "]"; + } + + /** + * Get this item's ID in bookmark database + */ + public int getId() { + return id; + } + + /** + * Set this item's ID in bookmark database + */ + public void setId(int id) { + this.id = id; + } + + /** + * Get this item's unique identifier + */ + public String getMNr() { + return mnr; + } + + /** + * Set this item's unique identifier + */ + public void setMNr(String mnr) { + this.mnr = mnr; + } + + /** + * Get this item's title + */ + public String getTitle() { + return title; + } + + /** + * Set this item's title + */ + public void setTitle(String title) { + this.title = title; + } + + /** + * Get this item's media type + */ + public SearchResult.MediaType getMediaType() { + return mediaType; + } + + /** + * Set this item's media type + */ + public void setMediaType(SearchResult.MediaType mediaType) { + this.mediaType = mediaType; + } +} diff --git a/opacclient/opacapp/build.gradle b/opacclient/opacapp/build.gradle index 376548592..75e4150ee 100644 --- a/opacclient/opacapp/build.gradle +++ b/opacclient/opacapp/build.gradle @@ -139,6 +139,7 @@ dependencies { debugImplementation 'com.facebook.flipper:flipper:0.87.0' debugImplementation 'com.facebook.soloader:soloader:0.10.1' debugImplementation 'com.facebook.flipper:flipper-network-plugin:0.87.0' + debugImplementation 'org.slf4j:slf4j-android:1.7.26' releaseImplementation 'com.facebook.flipper:flipper-noop:0.87.0' // Testing diff --git a/opacclient/opacapp/src/main/java/de/geeksfactory/opacclient/OpacClient.java b/opacclient/opacapp/src/main/java/de/geeksfactory/opacclient/OpacClient.java index 390f5f017..8f6f20ccb 100644 --- a/opacclient/opacapp/src/main/java/de/geeksfactory/opacclient/OpacClient.java +++ b/opacclient/opacapp/src/main/java/de/geeksfactory/opacclient/OpacClient.java @@ -59,6 +59,7 @@ import de.geeksfactory.opacclient.frontend.MainActivity; import de.geeksfactory.opacclient.frontend.MainPreferenceActivity; import de.geeksfactory.opacclient.frontend.SearchResultListActivity; +import de.geeksfactory.opacclient.frontend.StarredFragment; import de.geeksfactory.opacclient.i18n.AndroidStringProvider; import de.geeksfactory.opacclient.networking.AndroidHttpClientFactory; import de.geeksfactory.opacclient.objects.Account; @@ -395,6 +396,12 @@ public void onCreate() { sp = PreferenceManager.getDefaultSharedPreferences(this); + // Starred-Preferences: reset for keys which should not survive a restart + SharedPreferences.Editor editor = sp.edit(); + editor.remove(StarredFragment.STATE_ACTIVATED_POSITION); + editor.remove(StarredFragment.STATE_FILTER_BRANCH); + editor.apply(); + if (BuildConfig.SENTRY_DSN != null) { SentryAndroid.init(this, options -> { options.setDsn(BuildConfig.SENTRY_DSN); diff --git a/opacclient/opacapp/src/main/java/de/geeksfactory/opacclient/frontend/MainActivity.java b/opacclient/opacapp/src/main/java/de/geeksfactory/opacclient/frontend/MainActivity.java index ebd78a8f8..faf63d72b 100644 --- a/opacclient/opacapp/src/main/java/de/geeksfactory/opacclient/frontend/MainActivity.java +++ b/opacclient/opacapp/src/main/java/de/geeksfactory/opacclient/frontend/MainActivity.java @@ -38,6 +38,7 @@ import de.geeksfactory.opacclient.barcode.BarcodeScanIntegrator; import de.geeksfactory.opacclient.objects.Account; import de.geeksfactory.opacclient.objects.LentItem; +import de.geeksfactory.opacclient.objects.SearchResult; import de.geeksfactory.opacclient.reminder.Alarm; import de.geeksfactory.opacclient.reminder.ReminderBroadcastReceiver; import de.geeksfactory.opacclient.searchfields.SearchField; @@ -506,12 +507,11 @@ public void onNewIntent(Intent intent) { } @Override - public void showDetail(String mNr) { + public void showDetail(String mNr, SearchResult.MediaType mediaType) { if (isTablet()) { rightFragment = new SearchResultDetailFragment(); Bundle args = new Bundle(); args.putString(SearchResultDetailFragment.ARG_ITEM_ID, mNr); - args.putString(SearchResultDetailFragment.ARG_ITEM_LIBRARY_IDENT, app.getLibrary().getIdent()); rightFragment.setArguments(args); // Insert the fragment @@ -521,7 +521,10 @@ public void showDetail(String mNr) { } else { Intent intent = new Intent(this, SearchResultDetailActivity.class); intent.putExtra(SearchResultDetailFragment.ARG_ITEM_ID, mNr); - intent.putExtra(SearchResultDetailFragment.ARG_ITEM_LIBRARY_IDENT, app.getLibrary().getIdent()); + if (mediaType!= null) { + intent.putExtra(SearchResultDetailFragment.ARG_ITEM_MEDIATYPE, + mediaType.toString()); + } startActivity(intent); } } diff --git a/opacclient/opacapp/src/main/java/de/geeksfactory/opacclient/frontend/SearchResultDetailFragment.java b/opacclient/opacapp/src/main/java/de/geeksfactory/opacclient/frontend/SearchResultDetailFragment.java index 5836cc381..7ccdb0359 100644 --- a/opacclient/opacapp/src/main/java/de/geeksfactory/opacclient/frontend/SearchResultDetailFragment.java +++ b/opacclient/opacapp/src/main/java/de/geeksfactory/opacclient/frontend/SearchResultDetailFragment.java @@ -849,7 +849,7 @@ public void onClick(DialogInterface dialog, int di) { star.remove(star.getItemByTitle(bib, title)); item.setIcon(R.drawable.ic_star_0_white_24dp); } else { - star.star(null, title, bib, getItem().getMediaType()); + star.star(null, title, bib, getItem().getMediaType(), getItem().getCopies()); Toast toast = Toast.makeText(getActivity(), getString(R.string.starred), Toast.LENGTH_SHORT); toast.show(); @@ -863,7 +863,7 @@ public void onClick(DialogInterface dialog, int di) { star.remove(star.getItem(bib, id)); item.setIcon(R.drawable.ic_star_0_white_24dp); } else { - star.star(id, title, bib, getItem().getMediaType()); + star.star(id, title, bib, getItem().getMediaType(), getItem().getCopies()); Toast toast = Toast.makeText(getActivity(), getString(R.string.starred), Toast.LENGTH_SHORT); toast.show(); diff --git a/opacclient/opacapp/src/main/java/de/geeksfactory/opacclient/frontend/StarredFragment.java b/opacclient/opacapp/src/main/java/de/geeksfactory/opacclient/frontend/StarredFragment.java index e3caa7038..e46a25fd6 100644 --- a/opacclient/opacapp/src/main/java/de/geeksfactory/opacclient/frontend/StarredFragment.java +++ b/opacclient/opacapp/src/main/java/de/geeksfactory/opacclient/frontend/StarredFragment.java @@ -21,20 +21,25 @@ */ package de.geeksfactory.opacclient.frontend; +import android.annotation.SuppressLint; import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.database.Cursor; import android.net.Uri; +import android.os.AsyncTask; import android.os.Bundle; -import androidx.loader.app.LoaderManager; -import androidx.preference.PreferenceManager; +import android.preference.PreferenceManager; import android.text.Html; import android.util.Log; 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.View.OnClickListener; import android.view.ViewGroup; @@ -43,11 +48,14 @@ import android.widget.AdapterView.OnItemClickListener; import android.widget.ImageView; import android.widget.ListView; +import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; import com.google.android.material.snackbar.Snackbar; +import org.joda.time.format.DateTimeFormat; +import org.joda.time.format.DateTimeFormatter; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -60,47 +68,93 @@ import java.io.OutputStream; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Date; import java.util.List; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; import androidx.cursoradapter.widget.SimpleCursorAdapter; import androidx.fragment.app.Fragment; import androidx.loader.app.LoaderManager.LoaderCallbacks; import androidx.loader.content.CursorLoader; import androidx.loader.content.Loader; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import de.geeksfactory.opacclient.OpacClient; import de.geeksfactory.opacclient.R; import de.geeksfactory.opacclient.frontend.OpacActivity.AccountSelectedListener; import de.geeksfactory.opacclient.objects.Account; +import de.geeksfactory.opacclient.objects.Copy; +import de.geeksfactory.opacclient.objects.DetailedItem; import de.geeksfactory.opacclient.objects.SearchResult; +import de.geeksfactory.opacclient.objects.Starred; +import de.geeksfactory.opacclient.searchfields.DropdownSearchField; import de.geeksfactory.opacclient.searchfields.SearchField; import de.geeksfactory.opacclient.searchfields.SearchField.Meaning; import de.geeksfactory.opacclient.searchfields.SearchQuery; +import de.geeksfactory.opacclient.storage.Branch; import de.geeksfactory.opacclient.storage.JsonSearchFieldDataSource; +import de.geeksfactory.opacclient.storage.SearchFieldDataSource; +import de.geeksfactory.opacclient.storage.StarBranchItem; +import de.geeksfactory.opacclient.storage.StarContentProvider; import de.geeksfactory.opacclient.storage.StarDataSource; import de.geeksfactory.opacclient.storage.StarDatabase; -import de.geeksfactory.opacclient.storage.Starred; import de.geeksfactory.opacclient.utils.CompatibilityUtils; public class StarredFragment extends Fragment implements LoaderCallbacks, AccountSelectedListener { - private static final String STATE_ACTIVATED_POSITION = "activated_position"; + private static final Logger LOGGER = new Logger(StarredFragment.class); + + public static final String STATE_ACTIVATED_POSITION = "activated_position"; + public static final String STATE_FILTER_BRANCH = "filter_branch"; + public static final String STATE_FILTER_BIB = "filter_bib"; + private static final String JSON_LIBRARY_NAME = "library_name"; private static final String JSON_STARRED_LIST = "starred_list"; private static final String JSON_ITEM_MNR = "item_mnr"; private static final String JSON_ITEM_TITLE = "item_title"; private static final String JSON_ITEM_MEDIATYPE = "item_mediatype"; + private static final String JSON_ITEM_BRANCHES = "item_branches"; private static final int REQUEST_CODE_EXPORT = 123; private static final int REQUEST_CODE_IMPORT = 124; + private static final int LOADER_ID = 0; // !=1 wie bei History protected View view; protected OpacClient app; private ItemListAdapter adapter; private Callback callback; private ListView listView; - private int activatedPosition = ListView.INVALID_POSITION; private TextView tvWelcome; - private Starred sItem; + private TextView tvHeader; + private ProgressBar progressBar; + private SwipeRefreshLayout swipeRefreshLayout; + + private static final int FILTER_BRANCH_NONE = -1; + private static final int FILTER_BRANCH_ALL = 0; + private int currentFilterBranchId; + + private static class Logger { + private String tag; + + public Logger(@NonNull Class clazz) { + tag = clazz.getSimpleName(); + if (tag.length()>23) { + tag = tag.substring(0, 23); + } + } + + public void d(String fmt, Object ... args) { + if (!Log.isLoggable(tag, Log.INFO)) { + return; + } + String msg = String.format(fmt, args); + Log.i(tag, msg); + } + } + + private static void logDebug(String format, Object ... args) { + LOGGER.d(format, args); + } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, @@ -113,8 +167,21 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, adapter = new ItemListAdapter(); - listView = (ListView) view.findViewById(R.id.lvStarred); - tvWelcome = (TextView) view.findViewById(R.id.tvWelcome); + listView = view.findViewById(R.id.lvStarred); + tvWelcome = view.findViewById(R.id.tvWelcome); + tvHeader = view.findViewById(R.id.tvStarredHeader); + progressBar = view.findViewById(R.id.progressBar); + progressBar.setVisibility(View.GONE); + + swipeRefreshLayout = view.findViewById(R.id.swipeStarred); + swipeRefreshLayout.setColorSchemeResources(R.color.primary_red); + swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { + @Override + public void onRefresh() { + logDebug("onRefresh called. currentFilterBranchId = %d",currentFilterBranchId); + refreshBranches(); + } + }); listView.setOnItemClickListener(new OnItemClickListener() { @Override @@ -124,69 +191,221 @@ public void onItemClick(AdapterView parent, View view, .getTag(); if (item.getMNr() == null || item.getMNr().equals("null") || item.getMNr().equals("")) { - - SharedPreferences sp = PreferenceManager - .getDefaultSharedPreferences(getActivity()); - List query = new ArrayList<>(); - List fields = new JsonSearchFieldDataSource( - app).getSearchFields(app.getLibrary().getIdent()); - if (fields != null) { - SearchField title_field = null, free_field = null; - for (SearchField field : fields) { - if (field.getMeaning() == Meaning.TITLE) { - title_field = field; - } else if (field.getMeaning() == Meaning.FREE) { - free_field = field; - } else if (field.getMeaning() == Meaning.HOME_BRANCH) { - query.add(new SearchQuery(field, sp.getString( - OpacClient.PREF_HOME_BRANCH_PREFIX - + app.getAccount().getId(), - null))); - } - } - if (title_field != null) { - query.add(new SearchQuery(title_field, item - .getTitle())); - } else if (free_field != null) { - query.add(new SearchQuery(free_field, item - .getTitle())); - } - app.startSearch(getActivity(), query); - } else { - Toast.makeText(getActivity(), R.string.no_search_cache, - Toast.LENGTH_LONG).show(); - } + // keine eindeutige Id, via search + startSearch(item); } else { - callback.showDetail(item.getMNr()); + // warum ? + // getActivity().invalidateOptionsMenu(); + callback.showDetail(item.getMNr(), item.getMediaType()); } } }); listView.setClickable(true); listView.setTextFilterEnabled(true); - LoaderManager.getInstance(this) - .initLoader(0, null, this); + getActivity().getSupportLoaderManager() + .initLoader(LOADER_ID, null, this); listView.setAdapter(adapter); - // Restore the previously serialized activated item position. - if (savedInstanceState != null - && savedInstanceState.containsKey(STATE_ACTIVATED_POSITION)) { - setActivatedPosition(savedInstanceState - .getInt(STATE_ACTIVATED_POSITION)); - } + restoreState(savedInstanceState); setActivateOnItemClick(((OpacActivity) getActivity()).isTablet()); return view; } + private void restoreState(Bundle savedInstanceState) { + SharedPreferences sp = PreferenceManager + .getDefaultSharedPreferences(getContext()); + + String bib = app.getLibrary().getIdent(); + String savedBib = sp.getString(STATE_FILTER_BIB, null); + if (!bib.equals(savedBib)) { + // Status gehört zu einer anderen Bibliothek, + // daher nichts restoren. Fertig! + return; + } + + currentFilterBranchId = sp.getInt(STATE_FILTER_BRANCH, FILTER_BRANCH_ALL); + if (sp.contains(STATE_ACTIVATED_POSITION)) { + setActivatedPosition(sp + .getInt(STATE_ACTIVATED_POSITION, AdapterView.INVALID_POSITION)); + } + + if (savedInstanceState != null) { + // Restore the previously serialized activated item position. + if (savedInstanceState.containsKey(STATE_ACTIVATED_POSITION)) { + setActivatedPosition(savedInstanceState + .getInt(STATE_ACTIVATED_POSITION)); + } + + if (savedInstanceState.containsKey(STATE_FILTER_BRANCH)) { + currentFilterBranchId = savedInstanceState.getInt(STATE_FILTER_BRANCH); + } + } + } + + private void startSearch(Starred item) { + + SharedPreferences sp = PreferenceManager + .getDefaultSharedPreferences(getActivity()); + + List fields = new JsonSearchFieldDataSource( + app).getSearchFields(app.getLibrary().getIdent()); + + if (fields == null) { + Toast.makeText(getActivity(), R.string.no_search_cache, + Toast.LENGTH_LONG).show(); + return; + } + + List query = new ArrayList<>(); + + SearchField title_field = null, free_field = null, category_field = null; + for (SearchField field : fields) { + if (field.getMeaning() == Meaning.TITLE) { + title_field = field; + } else if (field.getMeaning() == Meaning.FREE) { + free_field = field; +// } else if (field.getMeaning() == Meaning.CATEGORY) { +// category_field = field; + } else if (field.getMeaning() == Meaning.HOME_BRANCH) { + query.add(new SearchQuery(field, sp.getString( + OpacClient.PREF_HOME_BRANCH_PREFIX + + app.getAccount().getId(), + null))); + } + } + if (title_field != null) { + query.add(new SearchQuery(title_field, item + .getTitle())); + } else if (free_field != null) { + query.add(new SearchQuery(free_field, item + .getTitle())); + } + /* + if (category_field != null) { + // Leider nicht so einfach + // category ist ein Drop-Down; + // Text-MediaType-Zuordnung in Adis protected Hashmap types und mehrdeutig + query.add(new SearchQuery(category_field, item + .getMediaType().toString())); + }*/ + app.startSearch(getActivity(), query); + } + @Override public void onCreateOptionsMenu(android.view.Menu menu, MenuInflater inflater) { + inflater.inflate(R.menu.activity_starred, menu); + + // FilterMenu um Branches erweitern + if (true) { + addSubMenuBranchesFromDb(menu); + } else { + addSubMenuBranchesFromSearchField(menu); + } + enableRefreshBranches(menu); + super.onCreateOptionsMenu(menu, inflater); } + private void enableRefreshBranches(android.view.Menu menu) { + StarDataSource data = new StarDataSource(getActivity()); + String bib = app.getLibrary().getIdent(); + + int countItemWithValidMnr = data.getCountItemWithValidMnr(bib); + + // MenuItem verstecken + MenuItem item = menu.findItem(R.id.action_refresh_branches); + item.setVisible(countItemWithValidMnr > 0); + // No SwipeRefresh + swipeRefreshLayout.setEnabled(countItemWithValidMnr > 0); + } + + private void addSubMenuBranchesFromDb(android.view.Menu menu) { + MenuItem itemFilter = menu.findItem(R.id.action_filter); + // menu.addSubMenu(Menu.NONE, R.id.action_filter, Menu.NONE,"Menu1"); + SubMenu subMenu = itemFilter.getSubMenu(); + + StarDataSource data = new StarDataSource(getActivity()); + String bib = app.getLibrary().getIdent(); + List branches = data.getStarredBranches(bib); + + int countStarredWithoutBranch = data.getCountStarredWithoutBranch(bib); + logDebug("addSubMenuBranchesFromDb - branches.size = %s, countStarredWithoutBranch = %s" + , branches.size(), countStarredWithoutBranch); + + if (((null == branches) || branches.isEmpty()) && (countStarredWithoutBranch == 0)){ + // auch wenn noch keine Branches zugeordnet sind Filter anzeigen? + // Ja, damit auf "Ohne Branch selektiert werden kann und + // diese Auswahl dann refresh werden kann + itemFilter.setVisible(true); + return; + } + + MenuItem itemFilterNoBranch = menu.findItem(R.id.action_filter_no_branch); + if (countStarredWithoutBranch > 0) { + String text = getString(R.string.starred_filter_no_branch_count, countStarredWithoutBranch); + itemFilterNoBranch.setTitle(text); + itemFilterNoBranch.setVisible(true); + } else { + itemFilterNoBranch.setVisible(false); + } + + final int groupId = Menu.NONE; + for (Branch branch : branches) { + String text = String.format("%s (%d)", branch.getName(), branch.getCount()); + MenuItem menuItem = subMenu.add(groupId, branch.getId(), Menu.NONE, text); + menuItem.setCheckable(false); + } + } + + private void addSubMenuBranchesFromSearchField(android.view.Menu menu) { + MenuItem itemFilter = menu.findItem(R.id.action_filter); + // menu.addSubMenu(Menu.NONE, R.id.action_filter, Menu.NONE,"Menu1"); + SubMenu subMenu = itemFilter.getSubMenu(); + SearchField field = findSearchField(Meaning.BRANCH); + if (null == field) { + itemFilter.setVisible(false); + return; + } + + if (!(field instanceof DropdownSearchField)) { + itemFilter.setVisible(false); + return; + } + + DropdownSearchField ddSearchField = (DropdownSearchField) field; + if ((ddSearchField.getDropdownValues() == null) || ddSearchField.getDropdownValues().isEmpty()){ + itemFilter.setVisible(false); + return; + } + + // final int groupId = Menu.NONE; + // final int groupId = R.id.group_filter; + // subMenu.setGroupCheckable(groupId, true, true); + // final RadioGroup radioGroup = (RadioGroup) menu.findViewById(R.id.group_filter); + for (DropdownSearchField.Option value : ddSearchField.getDropdownValues()) { + // MenuItem menuItem = subMenu.add(groupId, R.id.action_filter_branch, Menu.NONE,value.getValue()); + MenuItem menuItem = subMenu.add(value.getValue()); + menuItem.setCheckable(true); + } + } + + private SearchField findSearchField(Meaning meaning) { + SearchFieldDataSource dataSource = new JsonSearchFieldDataSource(app); + List fields = dataSource.getSearchFields(app.getLibrary().getIdent()); + + for (SearchField field : fields) { + if (field.getMeaning() == meaning) { + return field; + } + } + return null; + } + @Override public boolean onOptionsItemSelected(android.view.MenuItem item) { if (item.getItemId() == R.id.action_export) { @@ -197,66 +416,281 @@ public boolean onOptionsItemSelected(android.view.MenuItem item) { return true; } else if (item.getItemId() == R.id.action_import_from_storage) { importFromStorage(); + refreshViewAfterChange(); + return true; + } else if (item.getItemId() == R.id.action_refresh_branches) { + swipeRefreshLayout.setRefreshing(true); + refreshBranches(); return true; + } else if (item.getItemId() == R.id.action_filter) { + return super.onOptionsItemSelected(item); + } else if (item.getItemId() == R.id.action_remove_all) { + if (true) { + removeAllWithAlertDialog(); + } else { + removeAllWithSnackbar(); + } + refreshViewAfterChange(); + return true; + } else if (item.getItemId() == R.id.action_filter_no_branch) { + currentFilterBranchId = FILTER_BRANCH_NONE; + refreshViewAfterChange(); + return true; + + } else if (item.getItemId() == R.id.action_filter_all) { + // clear selection + currentFilterBranchId = FILTER_BRANCH_ALL; + refreshViewAfterChange(); + return true; + + } else { + // Hier FilterSubMenu + if (item.getItemId() == currentFilterBranchId) { + // Bereits ausgewählt: Do nothing + return true; + } else { + // new selection + currentFilterBranchId = item.getItemId(); + + StarDataSource data = new StarDataSource(getActivity()); + data.updateBranchFiltertimestamp(item.getItemId(), new Date().getTime()); + + refreshViewAfterChange(); + return true; + } } - return super.onOptionsItemSelected(item); } @Override public void accountSelected(Account account) { - LoaderManager.getInstance(this).restartLoader(0, null, this); + currentFilterBranchId = FILTER_BRANCH_ALL; + refreshViewAfterChange(); } - public void remove(Starred item) { - StarDataSource data = new StarDataSource(getActivity()); - sItem = item; - showSnackBar(); - data.remove(item); + private void refreshViewAfterChange() { + updateHeader(); + getActivity().invalidateOptionsMenu(); + getActivity().getSupportLoaderManager().restartLoader(LOADER_ID, null, this); } + private void refreshBranches() { + if (currentFilterBranchId == FILTER_BRANCH_ALL) { + swipeRefreshLayout.setRefreshing(false); + return; + } + FetchBranchesTask ft = new FetchBranchesTask(currentFilterBranchId); + ft.execute(); + } + + //Added code to show SnackBar when clicked on Remove button in Favorites screen - private void showSnackBar() { + private void remove(Starred item) { Snackbar snackbar = Snackbar.make(view, getString(R.string.starred_removed), Snackbar.LENGTH_LONG); snackbar.setAction(R.string.starred_removed_undo, new View.OnClickListener() { @Override public void onClick(View view) { + /* StarDataSource data = new StarDataSource(getActivity()); String bib = app.getLibrary().getIdent(); data.star(sItem.getMNr(), sItem.getTitle(), bib, sItem.getMediaType()); + */ + } + }); + + // https://stackoverflow.com/questions/30926380/how-can-i-be-notified-when-a-snackbar-has-dismissed-itself + snackbar.addCallback(new Snackbar.Callback() { + + @Override + public void onDismissed(Snackbar snackbar, int event) { + //see Snackbar.Callback docs for event details + if (event == Snackbar.Callback.DISMISS_EVENT_TIMEOUT) { + // Snackbar closed on its own + // also kein Undo, also remove item! + StarDataSource data = new StarDataSource(getActivity()); + String bib = app.getLibrary().getIdent(); + data.remove(item); + + refreshViewAfterChange(); + } + } + + }); + snackbar.show(); + } + + private void removeAllWithSnackbar() { + // TODO ev. besser mit Dialog? https://material.io/components/dialogs + Snackbar snackbar = + Snackbar.make(view, getString(R.string.starred_remove_all_sure), Snackbar.LENGTH_LONG); + snackbar.setAction(R.string.starred_remove_all_ok, new View.OnClickListener() { + + @Override + public void onClick(View view) { + StarDataSource data = new StarDataSource(getActivity()); + String bib = app.getLibrary().getIdent(); + data.removeAll(bib); + + currentFilterBranchId = FILTER_BRANCH_ALL; + refreshViewAfterChange(); } }); snackbar.show(); } + // Bei AccountEdit in Activity sein + private void removeAllWithAlertDialog() { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setMessage(R.string.starred_remove_all_sure) + .setCancelable(true) + .setNegativeButton(R.string.no, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface d, int id) { + d.cancel(); + } + }) + .setPositiveButton(R.string.delete, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface d, int id) { + d.dismiss(); + StarDataSource data = new StarDataSource(getActivity()); + String bib = app.getLibrary().getIdent(); + data.removeAll(bib); + + currentFilterBranchId = FILTER_BRANCH_ALL; + + refreshViewAfterChange(); + } + }) + .setOnCancelListener( + new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface d) { + if (d != null) { + d.cancel(); + } + } + }); + AlertDialog alert = builder.create(); + alert.show(); + } + @Override public Loader onCreateLoader(int arg0, Bundle arg1) { - if (app.getLibrary() != null) { + logDebug("onCreateLoader - currentFilterBranchId = %s", currentFilterBranchId); + + if (app.getLibrary() == null) { + return null; + } + + final String bib = app.getLibrary().getIdent(); + final String[] projection = {"starred.id AS _id", "medianr", "starred.bib AS bib", "title", "mediatype" + , "id_branch", "status", "statusTime", "returnDate"}; + + if (currentFilterBranchId == FILTER_BRANCH_ALL) { + // Nur auf Library selektieren return new CursorLoader(getActivity(), - app.getStarProviderStarUri(), StarDatabase.COLUMNS, - StarDatabase.STAR_WHERE_LIB, new String[]{app - .getLibrary().getIdent()}, null); + StarContentProvider.STAR_JOIN_STAR_BRANCH_URI, + projection, // StarDatabase.COLUMNS, + StarDatabase.STAR_WHERE_LIB, + new String[]{bib}, + null); + } else if (currentFilterBranchId == FILTER_BRANCH_NONE) { + // Auf Library und id_branch = null selektieren + return new CursorLoader(getActivity(), + StarContentProvider.STAR_JOIN_STAR_BRANCH_URI, + projection, + StarDatabase.STAR_WHERE_LIB_BRANCH_IS_NULL, + new String[]{bib}, + null); } else { - return null; + // Auf Library und id_branch selektieren + return new CursorLoader(getActivity(), + StarContentProvider.STAR_JOIN_STAR_BRANCH_URI, + projection, + StarDatabase.STAR_WHERE_LIB_BRANCH, + new String[]{bib, Integer.toString(currentFilterBranchId)}, + null); } } @Override public void onLoadFinished(Loader loader, Cursor cursor) { + logDebug("onCreateLoader- cursor.getCount() = %d", cursor.getCount()); adapter.swapCursor(cursor); if (cursor.getCount() == 0) { tvWelcome.setVisibility(View.VISIBLE); } else { tvWelcome.setVisibility(View.GONE); + updateHeader(); } } @Override - public void onLoaderReset(Loader arg0) { + public void onLoaderReset(Loader loader) { + logDebug("onLoaderReset- loader = %s", loader); adapter.swapCursor(null); } + private void updateHeader() { + // getString needs context + if (getContext() == null) { + return; + } + + String text = null; + int countItems = adapter.getCount(); + if (currentFilterBranchId == FILTER_BRANCH_ALL) { + text = getString(R.string.starred_header, countItems); + } else if (currentFilterBranchId == FILTER_BRANCH_NONE) { + text = getString(R.string.starred_without_branch, countItems); + } else { + StarDataSource data = new StarDataSource(getActivity()); + String bib = app.getLibrary().getIdent(); + Branch branch = data.getBranch(bib, currentFilterBranchId); + if (branch == null) { + logDebug("No Branch for bib=%s branchId=%d", bib, currentFilterBranchId); + } + text = getString(R.string.starred_header_branch, countItems, + branch.getName()); + long minStatusTime = branch.getMinStatusTime(); + if (minStatusTime>0) { + text = text + " (" + getAge(minStatusTime) + ")"; + } + } + tvHeader.setText(text); + } + + private String getAge(long minStatusTime) { + // age in milliseconds + long age = System.currentTimeMillis() - minStatusTime; + + age /= 1000; // age in seconds + if (age < 60) { + // less than a minute + return getResources().getString(R.string.starred_up_to_date); + } + age /= 60; // age in minutes + if (age < 60) { + // less than an hour + return getResources() + .getQuantityString(R.plurals.starred_age_minutes, (int) age, (int) age); + } + age /= 60; // age in hours + if (age < 24) { + // less than a day + return getResources() + .getQuantityString(R.plurals.starred_age_hours, (int) age, (int) age); + } + // more than 1 day + + age /= 24; // age in days + return getResources().getQuantityString(R.plurals.starred_age_days, (int) age, (int) age); + } + protected void share() { Intent intent = new Intent(android.content.Intent.ACTION_SEND); intent.setType("text/plain"); @@ -347,6 +781,16 @@ private JSONObject getEncodedStarredObjects() { item.put(JSON_ITEM_MNR, libItem.getMNr()); item.put(JSON_ITEM_TITLE, libItem.getTitle()); item.put(JSON_ITEM_MEDIATYPE, libItem.getMediaType()); + + List branches = data.getBranches(libItem.getId()); + if ((branches != null) && (!branches.isEmpty())) { + JSONArray branchItems = new JSONArray(); + for (String branch: branches) { + branchItems.put(branch); + } + item.put(JSON_ITEM_BRANCHES, branchItems); + } + items.put(item); } starred.put(JSON_STARRED_LIST, items); @@ -356,6 +800,48 @@ private JSONObject getEncodedStarredObjects() { return starred; } + private void importJson(StarDataSource dataSource, String list) throws JSONException { + JSONObject savedList = new JSONObject(list); + String bib = savedList.getString(JSON_LIBRARY_NAME); + //disallow import if from different library than current library + if (bib != null && !bib.equals(app.getLibrary().getIdent())) { + Snackbar.make(getView(), R.string.info_different_library, + Snackbar.LENGTH_SHORT).show(); + return; + } + JSONArray items = savedList.getJSONArray(JSON_STARRED_LIST); + for (int i = 0; i < items.length(); i++) { + JSONObject entry = items.getJSONObject(i); + if (entry.has(JSON_ITEM_MNR) && + !dataSource.isStarred(bib, entry.getString(JSON_ITEM_MNR)) || + !entry.has(JSON_ITEM_MNR) && !dataSource.isStarredTitle(bib, + entry.getString(JSON_ITEM_TITLE))) { //disallow dupes + + String mediatype = entry.optString(JSON_ITEM_MEDIATYPE, null); + + if (entry.has(JSON_ITEM_BRANCHES)) { + List copies = new ArrayList(); + JSONArray branchItems = entry.getJSONArray(JSON_ITEM_BRANCHES); + for (int j = 0; j < branchItems.length(); j++) { + String branch = branchItems.getString(j); + Copy copy = new Copy(); + copy.setBranch(branch); + copies.add(copy); + } + dataSource.star(entry.optString(JSON_ITEM_MNR), + entry.getString(JSON_ITEM_TITLE), bib, + mediatype != null ? SearchResult.MediaType.valueOf(mediatype) : + null, copies); + } else { + dataSource.star(entry.optString(JSON_ITEM_MNR), + entry.getString(JSON_ITEM_TITLE), bib, + mediatype != null ? SearchResult.MediaType.valueOf(mediatype) : + null); + } + } + } + } + public void importFromStorage() { //Use SAF Intent intent; @@ -419,28 +905,10 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { } String list = builder.toString(); - JSONObject savedList = new JSONObject(list); - String bib = savedList.getString(JSON_LIBRARY_NAME); - //disallow import if from different library than current library - if (bib != null && !bib.equals(app.getLibrary().getIdent())) { - Snackbar.make(getView(), R.string.info_different_library, - Snackbar.LENGTH_SHORT).show(); - return; - } - JSONArray items = savedList.getJSONArray(JSON_STARRED_LIST); - for (int i = 0; i < items.length(); i++) { - JSONObject entry = items.getJSONObject(i); - if (entry.has(JSON_ITEM_MNR) && - !dataSource.isStarred(bib, entry.getString(JSON_ITEM_MNR)) || - !entry.has(JSON_ITEM_MNR) && !dataSource.isStarredTitle(bib, - entry.getString(JSON_ITEM_TITLE))) { //disallow dupes - String mediatype = entry.optString(JSON_ITEM_MEDIATYPE, null); - dataSource.star(entry.optString(JSON_ITEM_MNR), - entry.getString(JSON_ITEM_TITLE), bib, - mediatype != null ? SearchResult.MediaType.valueOf(mediatype) : - null); - } - } + + importJson(dataSource, list); + + refreshViewAfterChange(); adapter.notifyDataSetChanged(); Snackbar.make(getView(), R.string.info_starred_updated, Snackbar.LENGTH_SHORT).show(); @@ -464,22 +932,53 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { } @Override - public void onAttach(Context context) { - super.onAttach(context); + public void onAttach(Activity activity) { + super.onAttach(activity); try { - callback = (Callback) context; + callback = (Callback) activity; } catch (ClassCastException e) { - throw new ClassCastException(context.toString() - + " must implement SearchFragment.Callback"); + throw new ClassCastException(activity.toString() + + " must implement StarredFragment.Callback"); } } @Override public void onResume() { - LoaderManager.getInstance(this).restartLoader(0, null, this); + getActivity().getSupportLoaderManager().restartLoader(LOADER_ID, null, this); + if (getContext() != null) { + restoreState(null); + } super.onResume(); } + @Override + public void onPause() { + if (getContext() != null) { + SharedPreferences sp = PreferenceManager + .getDefaultSharedPreferences(getContext()); + SharedPreferences.Editor editor = sp.edit(); + + String currentBib = app.getLibrary().getIdent(); + if (currentBib != null) { + editor.putString(STATE_FILTER_BIB, currentBib); + } + + if (currentFilterBranchId != 0) { + editor.putInt(STATE_FILTER_BRANCH, currentFilterBranchId); + } + + int activatedPosition = listView.getFirstVisiblePosition(); + if (activatedPosition != AdapterView.INVALID_POSITION) { + // Serialize and persist the activated item position. + editor.putInt(STATE_ACTIVATED_POSITION, activatedPosition); + } + + editor.apply(); + } + + super.onPause(); + } + /** * Turns on activate-on-click mode. When this mode is on, list items will be given the * 'activated' state when touched. @@ -493,25 +992,27 @@ private void setActivateOnItemClick(boolean activateOnItemClick) { private void setActivatedPosition(int position) { if (position == AdapterView.INVALID_POSITION) { - listView.setItemChecked(activatedPosition, false); + listView.setSelection(position); } else { - listView.setItemChecked(position, true); + listView.setSelection(position); } - - activatedPosition = position; } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); + int activatedPosition = listView.getFirstVisiblePosition(); if (activatedPosition != AdapterView.INVALID_POSITION) { // Serialize and persist the activated item position. outState.putInt(STATE_ACTIVATED_POSITION, activatedPosition); } + if (currentFilterBranchId > 0) { + outState.putInt(STATE_FILTER_BRANCH, currentFilterBranchId); + } } public interface Callback { - public void showDetail(String mNr); + public void showDetail(String mNr, SearchResult.MediaType mediatype); public void removeFragment(); } @@ -525,7 +1026,8 @@ public ItemListAdapter() { @Override public void bindView(View view, Context context, Cursor cursor) { - Starred item = StarDataSource.cursorToItem(cursor); + + StarBranchItem item = StarDataSource.cursorToStarBranchItem(cursor); TextView tv = (TextView) view.findViewById(R.id.tvTitle); if (item.getTitle() != null) { @@ -534,6 +1036,45 @@ public void bindView(View view, Context context, Cursor cursor) { tv.setText(""); } + TextView tvStatus = (TextView) view.findViewById(R.id.tvStatus); + ImageView ivStatus= (ImageView) view.findViewById(R.id.ivStatus); + + if (currentFilterBranchId > 0) { + if (item.getStatus() == null) { + if (item.getReturnDate()>0) { + // nur ReturnDate + DateTimeFormatter fmt = DateTimeFormat.shortDate(); + tvStatus.setText(fmt.print(item.getReturnDate())); + } else { + // weder Status noch ReturnDate + tvStatus.setText(""); + } + ivStatus.setVisibility(View.GONE); + tvStatus.setVisibility(View.VISIBLE); + } else { + if (item.isAusleihbar()) { + tvStatus.setVisibility(View.INVISIBLE); + ivStatus.setVisibility(View.VISIBLE); + ivStatus.setImageResource(R.drawable.status_light_green_check); + } else { + // TODO !isAusleihbar(), dann status_red_cross anzeigen? + ivStatus.setVisibility(View.GONE); + tvStatus.setVisibility(View.VISIBLE); + if (item.getReturnDate()>0) { + DateTimeFormatter fmt = DateTimeFormat.shortDate(); + tvStatus.setText(fmt.print(item.getReturnDate())); + } else { + tvStatus.setText(item.getStatus()); + } + } + } + } else { + // items zu allen Branches werden angezeigt + // in diesem Fall kein Status beim item anzeigen + tvStatus.setVisibility(View.INVISIBLE); + ivStatus.setVisibility(View.GONE); + } + ImageView ivType = (ImageView) view.findViewById(R.id.ivMediaType); if (item.getMediaType() != null) { ivType.setImageResource(ResultsAdapter.getResourceByMediaType(item.getMediaType())); @@ -556,6 +1097,132 @@ public void onClick(View arg0) { } } + private class FetchBranchesResult { + int starId; + List copies; + public FetchBranchesResult(int starId, List copies) { + this.starId = starId; + this.copies = copies; + } + } + + public class FetchBranchesTask extends AsyncTask> { + protected int branchId; + protected boolean success = true; + protected String message; + protected List starredList; + + public FetchBranchesTask(int branchId) { + logDebug("FetchBranchesTask(branchId = %s)", branchId); + this.branchId = branchId; + message = ""; + + String bib = app.getLibrary().getIdent(); + StarDataSource dataSource = new StarDataSource(getActivity()); + starredList = dataSource.getStarredInBranch(bib, branchId); + logDebug("FetchBranchesTask - bib = %s, starredList.size = %d", bib, starredList.size()); + } + + @Override + protected List doInBackground(Void... voids) { + List res = null; + try { + SharedPreferences sp = PreferenceManager + .getDefaultSharedPreferences(getActivity()); + String homebranch = sp.getString( + OpacClient.PREF_HOME_BRANCH_PREFIX + + app.getAccount().getId(), null); + + // reservation notwendig?? + if (getActivity().getIntent().hasExtra("reservation") + && getActivity().getIntent().getBooleanExtra( + "reservation", false)) { + app.getApi().start(); + } + + int nProgress = 0; + res = new ArrayList(); + for (Starred starred: starredList) { + logDebug("FetchBranchesTask.doInBackground starred.title = %s", starred.getTitle()); + logDebug("FetchBranchesTask.doInBackground starrd.mnr = %s", starred.getMNr()); + + if (starred.getMNr() == null || starred.getMNr().isEmpty()) { + // Mediennummer ist notwendig für getResultById + continue; + } + + publishProgress(++nProgress); + + DetailedItem di = app.getApi().getResultById(starred.getMNr(), homebranch); + // MediaType auch setzen?? + if (di.getMediaType() == null && (starred.getMediaType() != null)) { + di.setMediaType(starred.getMediaType()); + } + res.add(new FetchBranchesResult(starred.getId(), di.getCopies())); + } + success = true; + return res; + } catch (Exception e) { + message = e.getMessage(); + success = false; + e.printStackTrace(); + } + return null; + } + + @Override + protected void onPreExecute() { + progressBar.setMax(starredList.size()); + progressBar.setVisibility(View.VISIBLE); + } + + @Override + @SuppressLint("NewApi") + protected void onPostExecute(List res) { + if (getActivity() == null) { + return; + } + + swipeRefreshLayout.setRefreshing(false); + progressBar.setVisibility(View.GONE); + + if (!success || res == null || res.isEmpty()) { + String text = getString(R.string.starred_update_branch_fail, message); + Toast.makeText(getActivity(), text, Toast.LENGTH_LONG).show(); + return; + } + + String bib = app.getLibrary().getIdent(); + StarDataSource dataSource = new StarDataSource(getActivity()); + + logDebug("FetchBranchesTask.onPostExecute res.size = %s", res.size()); + for (FetchBranchesResult itemRes: res) { + List copies = itemRes.copies; + if ((copies == null) || (copies.isEmpty())) { + // TODO: Toast? + } else { + // Branches updaten/inserten + logDebug("FetchBranchesTask.onPostExecute starId = %s, copies.size = %s" + , itemRes.starId, copies.size()); + dataSource.insertBranches(bib, itemRes.starId, copies); + } + } + + refreshViewAfterChange(); + + String text = getString(R.string.starred_update_branch_success, res.size()); + Toast.makeText(getActivity(), text, Toast.LENGTH_LONG).show(); + } + + @Override + protected void onProgressUpdate(Integer... values) { + if ((values == null) || (values.length==0)) return; + int step = values[0]; + logDebug("FetchBranchesTask.onProgressUpdate = %d)", step); + progressBar.setProgress(step); + } + } + private class WrongFileFormatException extends Exception { } } diff --git a/opacclient/opacapp/src/main/java/de/geeksfactory/opacclient/storage/Branch.java b/opacclient/opacapp/src/main/java/de/geeksfactory/opacclient/storage/Branch.java new file mode 100644 index 000000000..24c51074c --- /dev/null +++ b/opacclient/opacapp/src/main/java/de/geeksfactory/opacclient/storage/Branch.java @@ -0,0 +1,73 @@ +package de.geeksfactory.opacclient.storage; + +/** + * Branch-data for a stared media-item + */ +public class Branch { + + /** + * unique db-row-id + */ + private int id; + + /** + * Branch name + */ + private String name; + + /** + * count media-items starred in this brach + */ + private int count; + + /** + * minimal (oldest) statusTime off all starred items in this branch + */ + private long minStatusTime; + + /** + * most recent timestamp this branch was used for filtering. + * Used for LRU-sortorder of filter menu items + */ + private int filtertimestamp; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getCount() { + return count; + } + + public void setCount(int count) { + this.count = count; + } + + public int getFiltertimestamp() { + return filtertimestamp; + } + + public void setFiltertimestamp(int filtertimestamp) { + this.filtertimestamp = filtertimestamp; + } + + public long getMinStatusTime() { + return minStatusTime; + } + + public void setMinStatusTime(long minStatusTime) { + this.minStatusTime = minStatusTime; + } +} diff --git a/opacclient/opacapp/src/main/java/de/geeksfactory/opacclient/storage/StarBranchItem.java b/opacclient/opacapp/src/main/java/de/geeksfactory/opacclient/storage/StarBranchItem.java new file mode 100644 index 000000000..6c3160d4d --- /dev/null +++ b/opacclient/opacapp/src/main/java/de/geeksfactory/opacclient/storage/StarBranchItem.java @@ -0,0 +1,79 @@ +package de.geeksfactory.opacclient.storage; + +import org.joda.time.LocalDate; + +import java.util.Date; + +import de.geeksfactory.opacclient.objects.Starred; + +public class StarBranchItem extends Starred { + private long branchId; + private String status; + private long statusTime; + private long returnDate; + + public long getBranchId() { + return branchId; + } + + public void setBranchId(long branchId) { + this.branchId = branchId; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public long getStatusTime() { + return statusTime; + } + + public void setStatusTime(long statusTime) { + this.statusTime = statusTime; + } + + public Date getStatusDate() { + if (statusTime == 0) { + return null; + } + return new Date(statusTime); + } + + public long getReturnDate() { + return returnDate; + } + + public void setReturnDate(long returnDate) { + this.returnDate = returnDate; + } + + @Override + public String toString() { + return "StarBranch [id=" + getId() + + (getTitle() == null ? "" : ", title=" + getTitle()) + + (getMediaType() == null ? "" : ", mediaType=" + getMediaType().toString()) + + ", branchId=" + branchId + + (returnDate>0 ? "" : "returnDate = " + returnDate) + + (getMNr() == null ? "" : ", mnr=" + getMNr()) + + "]"; + } + + public Boolean isAusleihbar() { + if (status == null) { + return null; + } + + return Boolean.valueOf( + // siehe Adis.java line 514 ff + status.matches(".*ist verf.+gbar") || + status.contains("is available") || + status.equalsIgnoreCase("verfügbar") || + status.equalsIgnoreCase("Ausleihbar") || + status.contains("ist ausleihbar") + ); + } +} diff --git a/opacclient/opacapp/src/main/java/de/geeksfactory/opacclient/storage/StarContentProvider.java b/opacclient/opacapp/src/main/java/de/geeksfactory/opacclient/storage/StarContentProvider.java index d26c87e2a..c557e4613 100644 --- a/opacclient/opacapp/src/main/java/de/geeksfactory/opacclient/storage/StarContentProvider.java +++ b/opacclient/opacapp/src/main/java/de/geeksfactory/opacclient/storage/StarContentProvider.java @@ -45,6 +45,18 @@ public class StarContentProvider extends ContentProvider { + STAR_MIME_POSTFIX; private StarDatabase database; + public static final String BRANCH_TYPE = "branch"; + public static final Uri BRANCH_URI = Uri.parse(BASE_URI + BRANCH_TYPE); + + public static final String STAR_BRANCH_TYPE = "starbranch"; + public static final Uri STAR_BRANCH_URI = Uri.parse(BASE_URI + STAR_BRANCH_TYPE); + + public static final String STAR_BRANCH_JOIN_BRANCH_TYPE = "starbranch_branch"; + public static final Uri STAR_BRANCH_JOIN_BRANCH_URI = Uri.parse(BASE_URI + STAR_BRANCH_JOIN_BRANCH_TYPE); + + public static final String STAR_JOIN_STAR_BRANCH_TYPE = "star_starbranch"; + public static final Uri STAR_JOIN_STAR_BRANCH_URI = Uri.parse(BASE_URI + STAR_JOIN_STAR_BRANCH_TYPE); + private static Mime getTypeMime(Uri uri) { if (!AUTHORITY.equals(uri.getAuthority()) && !uri.getAuthority().startsWith("de.opacapp.") @@ -66,6 +78,42 @@ private static Mime getTypeMime(Uri uri) { default: return null; } + } else if (BRANCH_TYPE.equals(type)) { + switch (segments.size()) { + case 1: + return Mime.BRANCH_DIR; + case 2: + return Mime.BRANCH_ITEM; + default: + return null; + } + } else if (STAR_BRANCH_TYPE.equals(type)) { + switch (segments.size()) { + case 1: + return Mime.STAR_BRANCH_DIR; + case 2: + return Mime.STAR_BRANCH_ITEM; + default: + return null; + } + } else if (STAR_BRANCH_JOIN_BRANCH_TYPE.equals(type)) { + switch (segments.size()) { + case 1: + return Mime.STAR_BRANCH_JOIN_BRANCH_DIR; + case 2: + return Mime.STAR_BRANCH_JOIN_BRANCH_ITEM; + default: + return null; + } + } else if (STAR_JOIN_STAR_BRANCH_TYPE.equals(type)) { + switch (segments.size()) { + case 1: + return Mime.STAR_JOIN_STAR_BRANCH_DIR; + case 2: + return Mime.STAR_JOIN_STAR_BRANCH_ITEM; + default: + return null; + } } else { return null; } @@ -105,6 +153,14 @@ public int delete(Uri uri, String selection, String[] selectionArgs) { rowsAffected = deleteInDatabase(StarDatabase.STAR_TABLE, StarDatabase.STAR_WHERE_ID, selectionForUri(uri)); break; + case STAR_BRANCH_DIR: + rowsAffected = deleteInDatabase(StarDatabase.STAR_BRANCH_TABLE, + selection, selectionArgs); + break; + case STAR_BRANCH_ITEM: + rowsAffected = deleteInDatabase(StarDatabase.STAR_BRANCH_TABLE, + "id = ?", selectionForUri(uri)); + break; default: rowsAffected = 0; break; @@ -131,6 +187,16 @@ public Uri insert(Uri uri, ContentValues values) { itemUri = ContentUris.withAppendedId(uri, id); notifyUri(uri); break; + case BRANCH_DIR: + id = insertIntoDatabase(StarDatabase.BRANCH_TABLE, values); + itemUri = ContentUris.withAppendedId(uri, id); + notifyUri(uri); + break; + case STAR_BRANCH_DIR: + id = insertIntoDatabase(StarDatabase.STAR_BRANCH_TABLE, values); + itemUri = ContentUris.withAppendedId(uri, id); + notifyUri(uri); + break; case STAR_ITEM: default: itemUri = null; @@ -163,6 +229,27 @@ public Cursor query(Uri uri, String[] projection, String selection, StarDatabase.STAR_WHERE_ID, selectionForUri(uri), null, null, sortOrder); break; + case BRANCH_DIR: + cursor = queryDatabase(StarDatabase.BRANCH_TABLE, projection, + selection, selectionArgs, null, null, sortOrder); + break; + case STAR_BRANCH_JOIN_BRANCH_DIR: + cursor = queryDatabase(StarDatabase.BRANCH_TABLE + " INNER JOIN " + + StarDatabase.STAR_BRANCH_TABLE + " ON " + + StarDatabase.BRANCH_TABLE + ".id = " + + StarDatabase.STAR_BRANCH_TABLE + ".id_branch" + , projection, + selection, selectionArgs, "name", "count >0", sortOrder); + break; + case STAR_JOIN_STAR_BRANCH_DIR: + String table = StarDatabase.STAR_TABLE + " LEFT JOIN " + + StarDatabase.STAR_BRANCH_TABLE + " ON " + + StarDatabase.STAR_TABLE + ".id = " + + StarDatabase.STAR_BRANCH_TABLE + ".id_star"; + cursor = queryDatabase(table + , projection, + selection, selectionArgs, "starred.id", null, sortOrder); + break; default: return null; } @@ -189,6 +276,10 @@ public int update(Uri uri, ContentValues values, String selection, rowsAffected = updateInDatabase(StarDatabase.STAR_TABLE, values, StarDatabase.STAR_WHERE_ID, selectionForUri(uri)); break; + case BRANCH_DIR: + rowsAffected = updateInDatabase(StarDatabase.BRANCH_TABLE, values, + StarDatabase.BRANCH_WHERE_ID, selectionArgs); + break; default: rowsAffected = 0; break; @@ -209,6 +300,10 @@ private String[] selectionForUri(Uri uri) { } private enum Mime { - STAR_ITEM, STAR_DIR + STAR_ITEM, STAR_DIR, + BRANCH_ITEM, BRANCH_DIR, + STAR_BRANCH_ITEM, STAR_BRANCH_DIR, + STAR_BRANCH_JOIN_BRANCH_ITEM, STAR_BRANCH_JOIN_BRANCH_DIR, + STAR_JOIN_STAR_BRANCH_ITEM, STAR_JOIN_STAR_BRANCH_DIR } } \ No newline at end of file diff --git a/opacclient/opacapp/src/main/java/de/geeksfactory/opacclient/storage/StarDataSource.java b/opacclient/opacapp/src/main/java/de/geeksfactory/opacclient/storage/StarDataSource.java index 0e2dc11c5..1766e82b0 100644 --- a/opacclient/opacapp/src/main/java/de/geeksfactory/opacclient/storage/StarDataSource.java +++ b/opacclient/opacapp/src/main/java/de/geeksfactory/opacclient/storage/StarDataSource.java @@ -2,21 +2,21 @@ * Copyright (C) 2013 by Raphael Michel under the MIT license: * * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the Software + * of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the Software * is furnished to do so, subject to the following conditions: * - * The above copyright notice and this permission notice shall be included in + * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, - * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ package de.geeksfactory.opacclient.storage; @@ -24,18 +24,28 @@ import android.app.Activity; import android.content.ContentValues; import android.database.Cursor; +import android.net.Uri; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import de.geeksfactory.opacclient.OpacClient; +import de.geeksfactory.opacclient.objects.Copy; import de.geeksfactory.opacclient.objects.SearchResult; +import de.geeksfactory.opacclient.objects.Starred; public class StarDataSource { - private Activity context; + private static final Logger LOGGER = LoggerFactory.getLogger(StarDataSource.class.getName()); + + private final Activity context; public StarDataSource(Activity context) { this.context = context; @@ -43,8 +53,14 @@ public StarDataSource(Activity context) { public static Starred cursorToItem(Cursor cursor) { Starred item = new Starred(); + mapCursoToItem(cursor, item); + return item; + } + + private static void mapCursoToItem(Cursor cursor, Starred item) { item.setId(cursor.getInt(0)); item.setMNr(cursor.getString(1)); + // columnIndex 2 = bib; no matching field, interessiert nicht item.setTitle(cursor.getString(3)); try { item.setMediaType( @@ -54,17 +70,119 @@ public static Starred cursorToItem(Cursor cursor) { } catch (IllegalArgumentException e) { // Do not crash on invalid media types stored in the database } - return item; } - public void star(String nr, String title, String bib, SearchResult.MediaType mediaType) { + public long star(String nr, String title, String bib, SearchResult.MediaType mediaType, List copies) { + + // star + int starId = star(nr, title, bib, mediaType); + + insertBranches(bib, starId, copies); + return starId; + } + + public void insertBranches(String bib, int starId, List copies) { + + LOGGER.info("bib = {}, starId = {}, copies.size = {}" , bib, starId, copies.size()); + + // Copies aggregieren zu Branches + Map branches = copiesToBranches(copies); + + LOGGER.info("branches.keySet().size = {}" , branches.keySet().size()); + + // alle Beziehungen von starred Media zu Branches (zunächst) löschen, + // da alle nachher unten neu angelegt / insertet werden + LOGGER.info("removeStarBranch(starId = {})" , starId); + removeStarBranch(starId); + + final long now = new Date().getTime(); + + // jetzt über alle Branches zu starId ... + for (String branch: branches.keySet()) { + + // LOGGER.info("branch = {}" , branch); + + // Prüfen, ob es Branch schon in StarDatabase gibt + long branchId = getBranchId(bib, branch); + LOGGER.info("branch = {}, branchId = {}" , branch, branchId); + if (branchId == 0) { + // Branch noch nicht vorhanden, anlegen + branchId = insertBranch(bib, branch); + } + + StarBranchItem item = branches.get(branch); + // Starred-Branch-Relation neu anlegen + LOGGER.info("insertStarBranch( starId = {}, branchId = {})" , starId, branchId); + long statusTime = ((item.getStatus() !=null) || (item.getReturnDate()!=0)) ? now : 0; + LOGGER.info("item.status = {}, .returnDate = {}, statusTime = {}" + , item.getStatus(), item.getReturnDate(), statusTime); + insertStarBranch(starId, branchId, item.getStatus(), statusTime, item.getReturnDate()); + } + } + + private Map copiesToBranches(List copies) { + HashMap map = new HashMap<>(); + if ((copies == null) || (copies.isEmpty())) { + // leere Map zurückgeben + return map; + } + + for (Copy copy : copies) { + String branch = copy.getBranch(); + if (branch == null) { + // Copy hat keine Branch + continue; + } + StarBranchItem item = map.get(branch); + if (item == null) { + // branch kommt noch nicht vor, + // anlegen und in Map ablegen + item = new StarBranchItem(); + + item.setStatus(copy.getStatus()); + if (copy.getReturnDate() == null) { + item.setReturnDate(0); + } else { + item.setReturnDate(copy.getReturnDate().toDate().getTime()); + } + map.put(branch, item); + } else { + // es gab schon eine Copy in diesem Brach + // dann Abgleich + if (copy.getReturnDate() == null) { + // Annahme: kein ReturnDate: ev. Ausleihbar. Status übernehmen + // Muss aber nicht stimmen. Z. B. "zur Zeit vermisst" + // Beispiel: Irma la Douce [DVD] in Stuttgart-Vaihingen: + // erste Copy "Ausleihbar", zweite Copy "zur Zeit vermisst" + + if ((item.getStatus() == null) || !item.isAusleihbar()) { + // status noch nicht gesetzt oder + // status bereits gesetzt, aber nicht ausleihbar, + // dannn übernehmen + item.setStatus(copy.getStatus()); + } + // sonst belassen + } else { + // vergleichen, wir suchen das nächste ReturnDate aller Copies = Minimum + long returnDate = copy.getReturnDate().toDate().getTime(); + if (returnDate < item.getReturnDate()) { + item.setReturnDate(returnDate); + } + } + } + } + return map; + } + + public int star(String nr, String title, String bib, SearchResult.MediaType mediaType) { ContentValues values = new ContentValues(); values.put("medianr", nr); values.put("title", title); values.put("bib", bib); values.put("mediatype", mediaType != null ? mediaType.toString() : null); - context.getContentResolver() + Uri uri = context.getContentResolver() .insert(((OpacClient) context.getApplication()).getStarProviderStarUri(), values); + return getId(uri); } public List getAllItems(String bib) { @@ -88,6 +206,50 @@ public List getAllItems(String bib) { return items; } + public List getStarredInBranch(String bib, int branchId) { + + String selection; + String[] selArgs; + if ( branchId == 0) { + selArgs = new String[] {bib}; + selection = StarDatabase.STAR_WHERE_LIB; + } else if ( branchId == -1) { + selArgs = new String[]{bib}; + selection = StarDatabase.STAR_WHERE_LIB_BRANCH_IS_NULL; + } else { + selArgs = new String[] {bib, Integer.toString(branchId)}; + selection = StarDatabase.STAR_WHERE_LIB_BRANCH; + } + + final String[] projection = {"id AS _id", "medianr", "bib", "title", "mediatype" + , "id_branch", "status", "statusTime", "returnDate"}; + + Cursor cursor = context.getContentResolver().query( + StarContentProvider.STAR_JOIN_STAR_BRANCH_URI, + projection, selection, selArgs, null); + + List items = new ArrayList<>(); + cursor.moveToFirst(); + while (!cursor.isAfterLast()) { + StarBranchItem item = cursorToStarBranchItem(cursor); + items.add(item); + cursor.moveToNext(); + } + cursor.close(); + + return items; + } + + public static StarBranchItem cursorToStarBranchItem(Cursor cursor) { + StarBranchItem item = new StarBranchItem(); + mapCursoToItem(cursor, item); + item.setBranchId(cursor.getLong(5)); + item.setStatus(cursor.getString(6)); + item.setStatusTime(cursor.getLong(7)); + item.setReturnDate(cursor.getLong(8)); + return item; + } + public Starred getItemByTitle(String bib, String title) { String[] selA = {bib, title}; Cursor cursor = context @@ -128,6 +290,27 @@ public Starred getItem(String bib, String id) { return item; } + public int getCountItemWithValidMnr(String bib) { + + final String sel = "bib = ? AND medianr IS NOT NULL"; + String[] selArg = {bib}; + String[] proj = { "count(*)" }; + + Cursor cursor = context + .getContentResolver() + .query(((OpacClient) context.getApplication()) + .getStarProviderStarUri(), + proj, sel, selArg, null); + + int count = 0; + cursor.moveToFirst(); + if (!cursor.isAfterLast()) { + count = cursor.getInt(0); + } + cursor.close(); + return count; + } + public Starred getItem(long id) { String[] selA = {String.valueOf(id)}; Cursor cursor = context @@ -188,6 +371,14 @@ public void remove(Starred item) { StarDatabase.STAR_WHERE_ID, selA); } + public void removeAll(String bib) { + String[] selA = {bib}; + context.getContentResolver() + .delete(((OpacClient) context.getApplication()) + .getStarProviderStarUri(), + StarDatabase.STAR_WHERE_LIB, selA); + } + public void renameLibraries(Map map) { for (Entry entry : map.entrySet()) { ContentValues cv = new ContentValues(); @@ -200,4 +391,187 @@ public void renameLibraries(Map map) { new String[]{entry.getKey()}); } } + + /** + * setzt Column Filtertimestamp zur branchId + * + * @param id branchId + * @param time filtertimestamp to set + */ + public void updateBranchFiltertimestamp(int id, long time) { + ContentValues cv = new ContentValues(); + cv.put("filtertimestamp", time); + context.getContentResolver() + .update(StarContentProvider.BRANCH_URI, + cv, StarDatabase.BRANCH_WHERE_ID, + new String[]{Integer.toString(id)}); + } + + /** + * ermittelt alle Branches (Namen) zu einer starId + * + * @param starId unique id for starred item + * @return list of branches, where copies for starred item exist + */ + public List getBranches(int starId) { + String[] proj = {"name, count(*) as count"}; + String[] selA = { Long.toString(starId)}; + Cursor cursor = context + .getContentResolver() + .query(StarContentProvider.STAR_BRANCH_JOIN_BRANCH_URI, proj, + "id_star = ?", selA, null); + + List list = new ArrayList<>(); + cursor.moveToFirst(); + while (!cursor.isAfterLast()) { + list.add(cursor.getString(0)); + cursor.moveToNext(); + } + // Make sure to close the cursor + cursor.close(); + + return list; + } + + + private int getBranchId(String bib, String name) { + if (name == null) { + return 0; + } + String[] selC = {"id"}; + String[] selA = {bib, name}; + Cursor cursor = context + .getContentResolver() + .query(StarContentProvider.BRANCH_URI, + selC, + StarDatabase.BRANCH_WHERE_LIB_NAME, selA, null); + + int id = 0; + cursor.moveToFirst(); + if (!cursor.isAfterLast()) { + id = cursor.getInt(0); + } + // Make sure to close the cursor + cursor.close(); + + return id; + } + + /** + * Reads the (one or none) Branch for a branchId from the branch-table + * + * @param bib library (TODO neccessary?) + * @param branchId unique key branch.id > 0 + * + * @return Branch if found, else null + */ + public Branch getBranch(String bib, int branchId) { + if (branchId <= 0) { + return null; + } + + String[] selC = { "id, name, filtertimestamp, count(*) as count, MIN(statusTime)" }; + String[] selA = { bib, Integer.toString(branchId)}; + Cursor cursor = context.getContentResolver() + .query(StarContentProvider.STAR_BRANCH_JOIN_BRANCH_URI, + selC, + StarDatabase.STAR_WHERE_LIB_ID, selA, null); + + Branch branch = null; + cursor.moveToFirst(); + if (!cursor.isAfterLast()) { + branch = cursorToBranch(cursor); + } + // Make sure to close the cursor + cursor.close(); + + return branch; + } + + /** + * Reads all branches of a library, to which starred media items exist + * + * @param bib name of libray + * @return List of branches + */ + public List getStarredBranches(String bib) { + String[] proj = {"id, name, filtertimestamp, count(*) as count, MIN(statusTime)"}; + String[] selA = { bib }; + Cursor cursor = context + .getContentResolver() + .query(StarContentProvider.STAR_BRANCH_JOIN_BRANCH_URI, proj, + "bib = ?", selA, "filtertimestamp DESC"); + + List list = new ArrayList<>(); + cursor.moveToFirst(); + while (!cursor.isAfterLast()) { + Branch item = cursorToBranch(cursor); + if (item.getCount() > 0) { + list.add(item); + } + cursor.moveToNext(); + } + // Make sure to close the cursor + cursor.close(); + + return list; + } + + public int getCountStarredWithoutBranch(String bib) { + String[] proj = {"starred.id"}; + String[] selA = {bib}; + Cursor cursor = context + .getContentResolver() + .query(StarContentProvider.STAR_JOIN_STAR_BRANCH_URI, proj, + StarDatabase.STAR_WHERE_LIB_BRANCH_IS_NULL, selA, null); + + int count = cursor.getCount(); + cursor.close(); + + return count; + } + + private static Branch cursorToBranch(Cursor cursor) { + Branch item = new Branch(); + item.setId(cursor.getInt(0)); + item.setName(cursor.getString(1)); + item.setFiltertimestamp(cursor.getInt(2)); + item.setCount(cursor.getInt(3)); + item.setMinStatusTime(cursor.getLong(4)); + return item; + } + + private long insertBranch(String bib, String name) { + ContentValues values = new ContentValues(); + values.put("bib", bib); + values.put("name", name); + Uri uri = context.getContentResolver().insert(StarContentProvider.BRANCH_URI, values); + return getId(uri); + } + + + private long insertStarBranch(int starId, long branchId, String status, long statusTime, long returnDate) { + ContentValues values = new ContentValues(); + values.put("id_star", starId); + values.put("id_branch", branchId); + values.put("status", status); + if (returnDate != 0) { + values.put("returnDate", returnDate); + } + if (statusTime != 0) { + values.put("statusTime", statusTime); + } + Uri uri = context.getContentResolver().insert(StarContentProvider.STAR_BRANCH_URI, values); + return getId(uri); + } + + private int removeStarBranch(int starId) { + String where = "id_star = ?"; + String[] selection = {Integer.toString(starId)}; + return context.getContentResolver().delete(StarContentProvider.STAR_BRANCH_URI, where, selection); + } + + private int getId(Uri uri) { + return Integer.parseInt(uri.getLastPathSegment()); + } } diff --git a/opacclient/opacapp/src/main/java/de/geeksfactory/opacclient/storage/StarDatabase.java b/opacclient/opacapp/src/main/java/de/geeksfactory/opacclient/storage/StarDatabase.java index f19439a13..b7e823ecc 100644 --- a/opacclient/opacapp/src/main/java/de/geeksfactory/opacclient/storage/StarDatabase.java +++ b/opacclient/opacapp/src/main/java/de/geeksfactory/opacclient/storage/StarDatabase.java @@ -29,18 +29,49 @@ public class StarDatabase extends SQLiteOpenHelper { public static final String STAR_TABLE = "starred"; - private static final String DATABASE_CREATE = "create table " + STAR_TABLE + private static final String STAR_TABLE_CREATE = "create table " + STAR_TABLE + " ( id integer primary key autoincrement," + " medianr text," - + " bib text," + " title text," + " mediatype text" + ");"; + + " bib text," + " title text," + " mediatype text" + + " );"; + + /* table for branches, which are used by starred-media-items */ + public static final String BRANCH_TABLE = "branch"; + private static final String BRANCH_TABLE_CREATE = "create table " + BRANCH_TABLE + + " ( id integer primary key autoincrement," + + " bib text," + + " name text," + + " filtertimestamp integer" + + " );"; + + /* table for relation between starred-media-items and branches */ + public static final String STAR_BRANCH_TABLE = "starred_branch"; + private static final String STAR_BRANCH_TABLE_CREATE = "create table " + STAR_BRANCH_TABLE + + " ( id_star integer," + + " id_branch integer," + + " status text," + + " statusTime integer," + + " returnDate integer," + + " PRIMARY KEY (id_star, id_branch)," + + " FOREIGN KEY ( id_star ) REFERENCES " + STAR_TABLE + "( id ) ON DELETE CASCADE," + + " FOREIGN KEY ( id_branch ) REFERENCES " + BRANCH_TABLE + "( id ) ON DELETE CASCADE" + + ");"; + // CHANGE THIS public static final String STAR_WHERE_ID = "id = ?"; public static final String STAR_WHERE_LIB = "bib = ?"; + public static final String STAR_WHERE_LIB_ID = "bib = ? and id = ?"; + public static final String STAR_WHERE_LIB_BRANCH = "bib = ? and id_branch = ?"; + public static final String STAR_WHERE_LIB_BRANCH_IS_NULL = "bib = ? and id_branch is null"; public static final String STAR_WHERE_TITLE_LIB = "bib = ? AND medianr IS NULL AND title = ?"; public static final String STAR_WHERE_NR_LIB = "bib = ? AND medianr = ?"; public static final String[] COLUMNS = {"id AS _id", "medianr", "bib", "title", "mediatype"}; + + public static final String BRANCH_WHERE_LIB_NAME = "bib = ? and name = ?"; + public static final String BRANCH_WHERE_ID = "id = ?"; + private static final String DATABASE_NAME = "starred.db"; - private static final int DATABASE_VERSION = 6; // REPLACE ONUPGRADE IF YOU + private static final int DATABASE_VERSION = 7; // REPLACE ONUPGRADE IF YOU public StarDatabase(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); @@ -48,7 +79,17 @@ public StarDatabase(Context context) { @Override public void onCreate(SQLiteDatabase db) { - db.execSQL(DATABASE_CREATE); + db.execSQL(STAR_TABLE_CREATE); + db.execSQL(BRANCH_TABLE_CREATE); + db.execSQL(STAR_BRANCH_TABLE_CREATE); + } + + @Override + public void onOpen(SQLiteDatabase db) { + super.onOpen(db); + if (!db.isReadOnly()) { + db.execSQL("PRAGMA foreign_keys=ON;"); + } } @Override @@ -59,7 +100,12 @@ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // Add column for media type db.execSQL("alter table " + STAR_TABLE + " add column mediatype text"); } + if (oldVersion <7) { + db.execSQL(BRANCH_TABLE_CREATE); + db.execSQL(STAR_BRANCH_TABLE_CREATE); + } } else { + // oldVersion < 5 Log.w(StarDatabase.class.getName(), "Upgrading database from version " + oldVersion + " to " + newVersion + ", which will destroy all old data"); diff --git a/opacclient/opacapp/src/main/res/drawable/baseline_filter_list_24.xml b/opacclient/opacapp/src/main/res/drawable/baseline_filter_list_24.xml new file mode 100644 index 000000000..454bd7deb --- /dev/null +++ b/opacclient/opacapp/src/main/res/drawable/baseline_filter_list_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/opacclient/opacapp/src/main/res/drawable/ic_baseline_autorenew_24.xml b/opacclient/opacapp/src/main/res/drawable/ic_baseline_autorenew_24.xml new file mode 100644 index 000000000..c031e04a9 --- /dev/null +++ b/opacclient/opacapp/src/main/res/drawable/ic_baseline_autorenew_24.xml @@ -0,0 +1,11 @@ + + + + diff --git a/opacclient/opacapp/src/main/res/layout/fragment_starred.xml b/opacclient/opacapp/src/main/res/layout/fragment_starred.xml index 5fa188e6f..1b04877a8 100644 --- a/opacclient/opacapp/src/main/res/layout/fragment_starred.xml +++ b/opacclient/opacapp/src/main/res/layout/fragment_starred.xml @@ -16,10 +16,61 @@ android:textAppearance="?android:attr/textAppearanceMedium" android:visibility="gone"/> - - + android:layout_height="wrap_content" + android:layout_alignParentLeft="false" + android:layout_alignParentTop="true" + android:background="@color/account_head_bg" + android:orientation="vertical" + android:padding="8dp" + android:transitionName="@string/transition_gray_box" + > - \ No newline at end of file + + + + + + + + + + + + + + + diff --git a/opacclient/opacapp/src/main/res/layout/listitem_starred.xml b/opacclient/opacapp/src/main/res/layout/listitem_starred.xml index 6e7376771..f8383b062 100644 --- a/opacclient/opacapp/src/main/res/layout/listitem_starred.xml +++ b/opacclient/opacapp/src/main/res/layout/listitem_starred.xml @@ -7,33 +7,58 @@ android:orientation="horizontal" android:padding="5dp"> - - + android:contentDescription="@string/mediatype" + tools:src="@drawable/type_book"/> - \ No newline at end of file + + + + + + + diff --git a/opacclient/opacapp/src/main/res/menu/activity_starred.xml b/opacclient/opacapp/src/main/res/menu/activity_starred.xml index d1a1b2cd3..04488c870 100644 --- a/opacclient/opacapp/src/main/res/menu/activity_starred.xml +++ b/opacclient/opacapp/src/main/res/menu/activity_starred.xml @@ -2,6 +2,38 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/opacclient/opacapp/src/main/res/values-de/strings.xml b/opacclient/opacapp/src/main/res/values-de/strings.xml index b45c663cb..6993346ca 100644 --- a/opacclient/opacapp/src/main/res/values-de/strings.xml +++ b/opacclient/opacapp/src/main/res/values-de/strings.xml @@ -178,6 +178,7 @@ %d Suchergebnisse Exportieren + Filtern Als Text teilen Finde Bibliotheken in meiner Nähe Standort wird ermittelt… @@ -351,4 +352,29 @@ Verlängerbare fällige Medien werden verlängert… Es gibt keine verlängerbaren fälligen Medien. Ein anonymes Konto wurde für dich angelegt. + Status aktualisieren + Ohne Zweigstelle + Ohne Zweigstelle (%1$d) + %1$d Medien in %2$s + %1d Medien + Alle löschen + Ja + Bei %1$d Medien wurden der Status aktualisiert + Fehler beim Aktualisieren der Stati (%1$s) + Alle + %1d Medien ohne Zweigstelle + Alle löschen? + Der Status ist aktuell + + Der Status ist eine Minute alt. + Der Status ist %d Minuten alt. + + + Der Status ist eine Stunde alt. + Der Status ist %d Stunden alt. + + + Der Status ist einen Tag alt. + Der Status ist %d Tage alt. + diff --git a/opacclient/opacapp/src/main/res/values/strings.xml b/opacclient/opacapp/src/main/res/values/strings.xml index 1a9863dec..36d49e89d 100644 --- a/opacclient/opacapp/src/main/res/values/strings.xml +++ b/opacclient/opacapp/src/main/res/values/strings.xml @@ -183,6 +183,7 @@ Share Export Import + Filter Share as text An error occurred during export. Please try again later. @@ -350,4 +351,29 @@ There are no renewable due items. An anonymous account was created for your. + Refresh status + Without Branch + Without Branch (%1$d) + %1$d items in %2$s + %1d items + Remove all + Remove all? + Yes + Update branches for %1$d items + Update branches failed %1$s + All + %1d item without brach + The status is up-to-date. + + The status is one minute old. + The is status is %d minutes old. + + + The status is one hour old. + The status is %d hours old. + + + The status is one day old. + The status is %d days old. +