diff --git a/.github/ISSUE_TEMPLATE.md b/.github/old_ISSUE_TEMPLATE.md similarity index 100% rename from .github/ISSUE_TEMPLATE.md rename to .github/old_ISSUE_TEMPLATE.md diff --git a/README.md b/README.md index e7c1ed35..29fa6625 100644 --- a/README.md +++ b/README.md @@ -77,5 +77,6 @@ Privacy: Feedback, translation and contributions are welcomed. +* [Discuss pro and cons, features, featurerequests and to provide news and support about the app on reddit.](https://www.reddit.com/r/APhotoManager/) * [Issue tracker](https://github.com/k3b/APhotoManager/issues) * You can help to translate this app via https://crowdin.com/project/AndroFotoFinder diff --git a/app/build.gradle b/app/build.gradle index cbeebaa0..39a5813b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -48,9 +48,11 @@ android { // 0.6.3.180211 (35) Extended Applocking; Autoprocessingmode for copy/move; Private images // 0.6.4.180314 (36) Buggy: Fix/Improved Autoprocessingmode, Menu "open in filemanager" & "rename folder"-Crash in Settings // 0.6.4.180321 (37) Bugfix for 0.6.4.180314 (36) + // 0.6.9.180813 (37) public betta: Searchbar, vitual-folder, new icons + // 0.7.0.180823 (38) public betta: Searchbar, vitual-folder, new icons - versionCode = 37 - versionName = '0.6.4.180321' + versionCode = 38 + versionName = '0.7.0.180823' } compileOptions { sourceCompatibility JavaVersion.VERSION_1_7 diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 00509ee7..8cf54129 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -20,6 +20,7 @@ -dontwarn com.caverock.androidsvg.** -keep class org.xmlpull.** { *; } -keep class de.k3b.android.widget.EditTextPreferenceWithSummary { *; } +-keep class de.k3b.android.widget.SearchViewWithHistory { *; } -assumenosideeffects class com.google.android.gms.ads.MobileAds { *; } ############### diff --git a/app/src/debug/res/values-ar/fdroid.xml b/app/src/debug/res/values-ar/fdroid.xml new file mode 100644 index 00000000..007e9a15 --- /dev/null +++ b/app/src/debug/res/values-ar/fdroid.xml @@ -0,0 +1,63 @@ + + + + + + A Photo Manager + + مدير للصور المحلية يقوم بـ: بحث ونسخ وتعديل الصور ووضعها في معرض صور او في خريطة. + + + diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1fa2a709..c2e771c1 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -79,6 +79,7 @@ > @@ -115,12 +116,30 @@ + + + + + + + + + + + + + + + @@ -181,8 +200,24 @@ + android:icon="@drawable/album" + android:label="@string/filter_menu_title" > + + + + + + + + + + + + + + + @@ -387,6 +422,21 @@ + + + + + + + + + + + + + + + - */ - -package de.k3b.android.androFotoFinder; - -import android.app.Activity; -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.util.Log; -import android.widget.Toast; - -import java.io.File; -import java.io.FilenameFilter; -import java.io.IOException; -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import de.k3b.android.androFotoFinder.queries.FotoSql; -import de.k3b.android.util.IntentUtil; -import de.k3b.android.widget.Dialogs; -import de.k3b.database.QueryParameter; -import de.k3b.io.FileUtils; - -/** - * Created by k3b on 07.10.2015. - */ -public class BookmarkController { - /** #76: used as default for save-as */ - private static final String STATE_LastBookmarkFileName = "LastBookmarkFileName"; - - /** virtual bookmarkfile to reset is sourounden by this. */ - private static final String RESET_PREFIX = "<< "; - private static final String RESET_SUFFIX = " >>"; - - private QueryParameter mCurrentFilter = null; - - /** #76: used as default for save-as */ - private String mLastBookmarkFileName = null; - - private final Activity mContext; - - public interface IQueryConsumer { - void setQuery(String fileName, QueryParameter newQuery); - } - - public BookmarkController(Activity context) { - mContext = context; - } - - public static String getlastBookmarkFileName(Intent intent) { - return (intent != null) ? intent.getStringExtra(BookmarkController.STATE_LastBookmarkFileName) : null; - } - - public String getlastBookmarkFileName() {return mLastBookmarkFileName;} - public void setlastBookmarkFileName(String lastBookmarkFileName) {mLastBookmarkFileName = lastBookmarkFileName;} - - public void saveState(Intent intent, Bundle savedInstanceState) { - saveState(getlastBookmarkFileName(), intent, savedInstanceState); - } - - public static void saveState(String lastBookmarkFileName, Intent intent, Bundle savedInstanceState) { - if (savedInstanceState != null) { - savedInstanceState.putString(BookmarkController.STATE_LastBookmarkFileName, lastBookmarkFileName); - } else if (intent != null) { - intent.putExtra(BookmarkController.STATE_LastBookmarkFileName, lastBookmarkFileName); - } - } - - public static boolean isReset(String bookmarkFilname) { - return (bookmarkFilname != null) && bookmarkFilname.startsWith(RESET_PREFIX); - } - - public static boolean isReset(Intent intent) { - return isReset(getlastBookmarkFileName(intent)); - } - - public void loadState(Intent intent, Bundle savedInstanceState) { - if (savedInstanceState != null) { - setlastBookmarkFileName(savedInstanceState.getString(BookmarkController.STATE_LastBookmarkFileName, null)); - } else if (intent != null) { - setlastBookmarkFileName(intent.getStringExtra(BookmarkController.STATE_LastBookmarkFileName)); - } - } - - public void onSaveAsQuestion(final String name, final QueryParameter currentFilter) { - mCurrentFilter = currentFilter; - Dialogs dlg = new Dialogs() { - @Override - protected void onDialogResult(String fileName, Object[] parameters) { - onSaveAsAnswer(fileName, true); - } - - }; - - if (isReset(name)) { - dlg.editFileName(mContext, mContext.getString(R.string.bookmark_save_as_menu_title), null, 0); - } else { - dlg.editFileName(mContext, mContext.getString(R.string.bookmark_save_as_menu_title), name, 0); - } - } - - private void onSaveAsAnswer(final String fileName, boolean askToOverwrite) { - if (Global.debugEnabled) { - Log.d(Global.LOG_CONTEXT, "onSaveAsAnswer(" + fileName + - ")"); - } - if (fileName != null) { - Global.reportDir.mkdirs(); - File outFile = getFile(fileName); - if (askToOverwrite && outFile.exists()) { - Dialogs dialog = new Dialogs() { - @Override - protected void onDialogResult(String result, Object... parameters) { - if (result != null) { - // yes, overwrite - onSaveAsAnswer(fileName, false); - } else { - // no, do not overwrite - onSaveAsQuestion(fileName, mCurrentFilter); - } - } - }; - dialog.yesNoQuestion(mContext, mContext.getString(R.string.overwrite_question_title) , - mContext.getString(R.string.image_err_file_exists_format, outFile.getAbsoluteFile())); - } else { - PrintWriter out = null; - try { - out = new PrintWriter(outFile); - out.println(mCurrentFilter.toReParseableString()); - out.close(); - out = null; - } catch (IOException err) { - String errorMessage = mContext.getString(R.string.mk_err_failed_format, outFile.getAbsoluteFile()); - Toast.makeText(mContext, errorMessage, Toast.LENGTH_LONG).show(); - Log.e(Global.LOG_CONTEXT, errorMessage, err); - } - } - } - } - - @NonNull - private File getFile(String fileName) { - String fileNameWithExt = fileName; - - if (!fileNameWithExt.contains(".")) { - fileNameWithExt += Global.reportExt; - } - return new File(Global.reportDir, fileNameWithExt); - } - - public void onLoadFromQuestion(final IQueryConsumer consumer, final QueryParameter currentFilter) { - mCurrentFilter = currentFilter; - List fileNamesPlusReset = new ArrayList(); - fileNamesPlusReset.add(RESET_PREFIX + mContext.getString(R.string.bookmark_reset) + RESET_SUFFIX); - String[] fileNames = Global.reportDir.list(new FilenameFilter() { - @Override public boolean accept(File dir, String filename) { - return ((filename != null) && (filename.endsWith(Global.reportExt))); - } - }); - - if ((fileNames != null) && (fileNames.length > 0)) { - fileNamesPlusReset.addAll(Arrays.asList(fileNames)); - } - Dialogs dlg = new Dialogs() { - @Override protected boolean onContextMenuItemClick(int menuItemId, int itemIndex, String[] items) { - return onBookmarkMenuItemClick(menuItemId, itemIndex, items); - } - - @Override protected void onDialogResult(String fileName, Object[] parameters) {onLoadFromAnswer(fileName, consumer);} - }; - dlg.pickFromStrings(mContext, mContext.getString(R.string.bookmark_load_from_menu_title), R.menu.menu_bookmark_context, fileNamesPlusReset); - } - - public void onLoadFromAnswer(final String fileName, final IQueryConsumer consumer) { - if (Global.debugEnabled) { - Log.d(Global.LOG_CONTEXT, "onLoadFromAnswer(" + fileName + ")"); - } - - // #76: used as default for save-as - mLastBookmarkFileName = fileName; - if (fileName != null) { - if (isReset(fileName)) { - consumer.setQuery(fileName, new QueryParameter(FotoSql.queryDetail)); - } else { - File inFile = getFile(fileName); - - String sql; - try { - sql = FileUtils.readFile(inFile); - QueryParameter query = QueryParameter.parse(sql); - consumer.setQuery(fileName, query); - } catch (Exception e) { - Toast.makeText(mContext, - e.getMessage(), - Toast.LENGTH_LONG).show(); - Log.e(Global.LOG_CONTEXT, "Error load query file '" + inFile.getAbsolutePath() + "'", e); - e.printStackTrace(); - } - } - } - } - - protected boolean onBookmarkMenuItemClick(int menuItemId, int itemIndex, String[] items) { - if ((itemIndex > 0) && (itemIndex < items.length)) { - switch (menuItemId) { - case R.id.action_save_as: - onSaveAsQuestion(mLastBookmarkFileName, mCurrentFilter); return true; - case R.id.action_edit: - return onEdit(getFile(items[itemIndex])); - case R.id.menu_item_rename: - return onRenameQuestion(items[itemIndex], items[itemIndex]); - case R.id.cmd_delete: - return onDeleteQuestion(itemIndex, items); - default:break; - } - } // ignore index 0 = reset. - return false; - } - - private boolean onEdit(File file) { - Intent sendIntent = new Intent(); - IntentUtil.setDataAndTypeAndNormalize(sendIntent, Uri.fromFile(file), "text/plain"); - sendIntent.setAction(Intent.ACTION_EDIT); - mContext.startActivity(sendIntent); - return true; - } - - private boolean onRenameQuestion(String _newName, final String oldName) { - if (_newName == null) return false; - - String newName = (_newName.endsWith(Global.reportExt)) - ? _newName.substring(0, _newName.length() - Global.reportExt.length()) - : _newName; - - Dialogs dialog = new Dialogs() { - @Override - protected void onDialogResult(String newFileName, Object... parameters) { - if (newFileName != null) { - onRenameAnswer(newFileName, oldName); - } - } - }; - dialog.editFileName(mContext, mContext.getString(R.string.rename_menu_title), newName); - return true; - } - - private void onRenameAnswer(String newFileName, String oldName) { - if ((newFileName != null) || (newFileName.length() > 0)) { // || (oldName.compareToIgnoreCase(newFileName) == 0)) { - File from = getFile(oldName); - File to = getFile(newFileName); - - if ((from.getAbsolutePath().compareToIgnoreCase(to.getAbsolutePath()) != 0) && !to.exists()) { - from.renameTo(to); - } - } - } - - private boolean onDeleteQuestion(final int itemIndex, final String[] items) { - Dialogs dlg = new Dialogs() { - @Override protected void onDialogResult(String result, Object[] parameters) { - if (result != null) { - onDeleteAnswer(getFile(items[itemIndex]), itemIndex, items); - } - } - }; - - dlg.yesNoQuestion(mContext, items[itemIndex], mContext.getString(R.string.bookmark_delete_question)); - return true; - } - - private void onDeleteAnswer(File file, int itemIndex, String[] items) { - if (file.exists() && file.delete()) { - String message = mContext.getString(R.string.bookmark_delete_answer_format, file.getAbsoluteFile() ); - Toast.makeText(mContext, - message, - Toast.LENGTH_LONG).show(); - Log.d(Global.LOG_CONTEXT, message); - items[itemIndex] = null; - } else { - String message = mContext.getString(R.string.bookmark_delete_error_format, file.getAbsoluteFile() ); - Toast.makeText(mContext, - message, - Toast.LENGTH_LONG).show(); - Log.d(Global.LOG_CONTEXT, message); - - } - } - -} diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/Common.java b/app/src/main/java/de/k3b/android/androFotoFinder/Common.java index eaf4c37c..5165bc58 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/Common.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/Common.java @@ -28,10 +28,10 @@ public interface Common { /** * gallery,filter: - * Format:GalleryFilterParameter.toString/parseMultiple. + * Format:GalleryFilterParameter.toString/parse as a "," seperated list of values. * See https://github.com/k3b/AndroFotoFinder/wiki/intentapi#filter */ - static final String EXTRA_FILTER = "de.k3b.extra.FILTER"; + public static final String EXTRA_FILTER = "de.k3b.extra.FILTER"; /** detail,gallery: sql where ... order by ... group by ... */ public static final String EXTRA_QUERY = "de.k3b.extra.SQL"; diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/FotoGalleryActivity.java b/app/src/main/java/de/k3b/android/androFotoFinder/FotoGalleryActivity.java index b6382f67..8b1a3c67 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/FotoGalleryActivity.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/FotoGalleryActivity.java @@ -20,410 +20,70 @@ package de.k3b.android.androFotoFinder; import android.app.Activity; -import android.app.DialogFragment; import android.app.Fragment; import android.app.FragmentManager; -import android.content.Context; import android.content.Intent; -import android.content.SharedPreferences; import android.database.ContentObserver; import android.net.Uri; import android.os.Bundle; import android.os.Handler; -import android.preference.PreferenceManager; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; -import android.widget.Toast; - -// import com.squareup.leakcanary.RefWatcher; - -import java.util.ArrayList; -import java.util.List; import de.k3b.FotoLibGlobal; import de.k3b.android.androFotoFinder.directory.DirectoryGui; -import de.k3b.android.androFotoFinder.directory.DirectoryLoaderTask; import de.k3b.android.androFotoFinder.gallery.cursor.GalleryCursorFragment; import de.k3b.android.androFotoFinder.imagedetail.ImageDetailActivityViewPager; import de.k3b.android.androFotoFinder.locationmap.GeoEditActivity; -import de.k3b.android.androFotoFinder.locationmap.LocationMapFragment; -import de.k3b.android.androFotoFinder.directory.DirectoryPickerFragment; +import de.k3b.android.androFotoFinder.queries.AndroidAlbumUtils; import de.k3b.android.androFotoFinder.queries.FotoSql; import de.k3b.android.androFotoFinder.queries.FotoViewerParameter; import de.k3b.android.androFotoFinder.queries.Queryable; -import de.k3b.android.androFotoFinder.tagDB.TagSql; -import de.k3b.android.androFotoFinder.tagDB.TagsPickerFragment; -import de.k3b.android.osmdroid.OsmdroidUtil; import de.k3b.android.util.GarbageCollector; -import de.k3b.android.util.IntentUtil; -import de.k3b.android.util.MediaScanner; import de.k3b.android.widget.AboutDialogPreference; -import de.k3b.android.widget.LocalizedActivity; +import de.k3b.android.widget.BaseQueryActivity; import de.k3b.database.QueryParameter; import de.k3b.io.collections.SelectedItems; -import de.k3b.io.Directory; -import de.k3b.io.DirectoryFormatter; -import de.k3b.io.GalleryFilterParameter; -import de.k3b.io.GeoRectangle; import de.k3b.io.IDirectory; -import de.k3b.io.IGalleryFilter; -import de.k3b.io.ListUtils; -import de.k3b.tagDB.Tag; - -public class FotoGalleryActivity extends LocalizedActivity implements Common, - OnGalleryInteractionListener, DirectoryPickerFragment.OnDirectoryInteractionListener, - LocationMapFragment.OnDirectoryInteractionListener, - TagsPickerFragment.ITagsPicker -{ - private static final String mDebugPrefix = "GalleryA-"; - - private static final String DEFAULT_BOOKMARKNAME_PICK_GEO = "pickGeoFromPhoto"; - /** intent parameters supported by FotoGalleryActivity: EXTRA_... */ - - private static final String DLG_NAVIGATOR_TAG = "navigator"; - - /** after media db change cached Directories must be recalculated */ - private final ContentObserver mMediaObserverDirectory = new ContentObserver(null) { - @Override - public void onChange(boolean selfChange) { - invalidateDirectories(mDebugPrefix + "#onChange from mMediaObserverDirectory"); - } - }; - private final BookmarkController.IQueryConsumer mLoadBookmarkResultConsumer = new BookmarkController.IQueryConsumer() { - @Override - public void setQuery(String fileName, QueryParameter newQuery) { - final IGalleryFilter whereFilter = TagSql.parseQueryEx(newQuery, true); - mGalleryQueryParameter.mGalleryContentQuery = newQuery; - mBookmarkController.setlastBookmarkFileName(fileName); - String why = "#loadedBookmark " + fileName; - onFilterChanged(whereFilter, why); - invalidateDirectories(mDebugPrefix + why); - mGalleryQueryParameter.setHasUserDefinedQuery(true); - reloadGui(why); - } - }; - /** set while dir picker is active */ - private DialogFragment mDirPicker = null; +/** + * Gallery: Show zeoro or more images in a grid optionally filtered by a + * * base query plus + * * sub query (either path or date or geo or tag or in-any) + * + * Use cases + * * stand alone gallery (i.e. from file manager with intent-uri of image.jpg/virtual-album/directory) + * * sub gallery drill down from other activity with intent containing file-uri/query-extra and/or filter-extra + * * as picker for + * * * ACTION_PICK uri=geo:... pick geo via image + * * * ACTION_PICK pick image old android-2.1 api + * * * ACTION_GET_CONTENT image new android-4.4 api + */ +public class FotoGalleryActivity extends BaseQueryActivity implements + OnGalleryInteractionListener { + /** + * intent parameters supported by FotoGalleryActivity: EXTRA_... + */ - private GalleryQueryParameter mGalleryQueryParameter = new GalleryQueryParameter(); // multi selection support private SelectedItems mSelectedItems = null; private Queryable mGalleryGui; - private boolean mHasEmbeddedDirPicker = false; private DirectoryGui mDirGui; - private IDirectory mPopUpSelection = null; - - private String mTitleResultCount = ""; - - private IDirectory mDirectoryRoot = null; - - /** true if activity should show navigator dialog after loading mDirectoryRoot is complete */ - private boolean mMustShowNavigator = false; - - private static class GalleryQueryParameter { - private static final String STATE_SUB_FILTER_CurrentPath = "CurrentPath"; - private static final String STATE_DirQueryID = "DirQueryID"; - private static final String STATE_SortID = "SortID"; - private static final String STATE_SortAscending = "SortAscending"; - private static final String STATE_Filter = "filter"; - private static final String STATE_SUB_FILTER_LAT_LON = "currentLatLon"; - private static final String STATE_SUB_FILTER_TAGS = "currentTags"; - private static final String STATE_SUB_FILTR_MODE = "currentSubFilterMode"; - private static final String PICK_GEO_SUFFIX = "-pick-geo"; - - /** true use latLonPicker; false use directoryPicker */ - private static final int SUB_FILTER_MODE_PATH = 0; - private static final int SUB_FILTER_MODE_GEO = 1; - private static final int SUB_FILTER_MODE_TAG = 2; - private int mCurrentSubFilterMode = SUB_FILTER_MODE_PATH; - - private GeoRectangle mCurrentLatLonFromGeoAreaPicker = new GeoRectangle(); - private List mCurrentTagsFromPicker = new ArrayList(); - - /** one of the FotoSql.QUERY_TYPE_xxx values */ - protected int mDirQueryID = FotoSql.QUERY_TYPE_GROUP_DEFAULT; - - private boolean mHasUserDefinedQuery = false; - - /** current sort order */ - private int mCurrentSortID = FotoSql.SORT_BY_DEFAULT; - /** current sort order */ - private boolean mCurrentSortAscending = false; - - private String mCurrentPathFromFolderPicker = "/"; - - /** Filter parameter defining current visible items */ - private IGalleryFilter mCurrentFilterSettings; - - /** sql defines current visible items with optional sort order */ - protected QueryParameter mGalleryContentQuery = null; - - /** true: if activity started without special intent-parameters, - * the last mCurrentFilterSettings is saved/loaded for next use */ - private boolean mSaveToSharedPrefs = true; - - /** view/pick-image/pick-geo have different state persistence. - * naem=STATE_XXXXX + mStatSuffix - * ""==view; "-pick-image"; "-pick-geo" */ - private String mStatSuffix = ""; - - /** one of the FotoSql.QUERY_TYPE_xxx values. if undefined use default */ - private int getDirQueryID() { - if (this.mDirQueryID == FotoSql.QUERY_TYPE_UNDEFINED) - return FotoSql.QUERY_TYPE_GROUP_DEFAULT; - - return this.mDirQueryID; - } - - public int getSortID() { - return mCurrentSortID; - } - public void setSortID(int sortID) { - if (sortID == mCurrentSortID) { - mCurrentSortAscending = !mCurrentSortAscending; - } else { - mCurrentSortAscending = true; - mCurrentSortID = sortID; - } - } - - public String getSortDisplayName(Context context) { - return FotoSql.getName(context, this.mCurrentSortID) + " " + ((mCurrentSortAscending) ? IGalleryFilter.SORT_DIRECTION_ASCENDING : IGalleryFilter.SORT_DIRECTION_DESCENDING); - } - - public boolean clearPathIfActive() { - if ((mCurrentSubFilterMode == SUB_FILTER_MODE_PATH) && (mCurrentPathFromFolderPicker != null)) { - mCurrentPathFromFolderPicker = null; - return true; - } - return false; - } - - /** combine root-query plus current selected directoryRoot/geo/tags */ - private QueryParameter calculateEffectiveGalleryContentQuery() { - return calculateEffectiveGalleryContentQuery(mGalleryContentQuery); - } - - /** combine root-query plus current selected directoryRoot */ - private QueryParameter calculateEffectiveGalleryContentQuery(QueryParameter rootQuery) { - if (rootQuery == null) return null; - - // .nomedia folder has no current sql - if ((this.getCurrentFilterSettings() != null) && MediaScanner.isNoMedia(this.getCurrentFilterSettings().getPath(), MediaScanner.DEFAULT_SCAN_DEPTH)) { - return null; - } - - QueryParameter result = new QueryParameter(rootQuery); - - TagSql.filter2QueryEx(result, this.getCurrentFilterSettings(), !hasUserDefinedQuery()); - if (result == null) return null; - - if (mCurrentSubFilterMode == SUB_FILTER_MODE_GEO) { - FotoSql.addWhereFilterLatLon(result, mCurrentLatLonFromGeoAreaPicker); - } else if (mCurrentSubFilterMode == SUB_FILTER_MODE_TAG) { - TagSql.addWhereTagsIncluded(result, mCurrentTagsFromPicker,false); - } else if (this.mCurrentPathFromFolderPicker != null) { - FotoSql.addPathWhere(result, this.mCurrentPathFromFolderPicker, this.getDirQueryID()); - } - - if (mCurrentSortID != IGalleryFilter.SORT_BY_NONE) { - FotoSql.setSort(result, mCurrentSortID, mCurrentSortAscending); - } - return result; - } - - private void saveInstanceState(Context context, Bundle savedInstanceState) { - saveSettings(context); - - // save InstanceState - savedInstanceState.putInt(STATE_DirQueryID , this.getDirQueryID()); - if (mStatSuffix.length() == 0) { - savedInstanceState.putString(STATE_SUB_FILTER_LAT_LON, this.mCurrentLatLonFromGeoAreaPicker.toString()); - savedInstanceState.putString(STATE_SUB_FILTER_CurrentPath, this.mCurrentPathFromFolderPicker); - savedInstanceState.putString(STATE_SUB_FILTER_TAGS, ListUtils.toString(mCurrentTagsFromPicker)); - } - savedInstanceState.putInt(STATE_SortID, this.mCurrentSortID); - savedInstanceState.putBoolean(STATE_SortAscending, this.mCurrentSortAscending); - if (this.getCurrentFilterSettings() != null) { - savedInstanceState.putString(STATE_Filter, this.getCurrentFilterSettings().toString()); - } - savedInstanceState.putInt(STATE_SUB_FILTR_MODE, this.mCurrentSubFilterMode); - } - - private void saveSettings(Context context) { - if (mSaveToSharedPrefs) { - // save settings - SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context); - SharedPreferences.Editor edit = sharedPref.edit(); - - edit.putInt(STATE_DirQueryID + mStatSuffix, this.getDirQueryID()); - edit.putInt(STATE_SortID + mStatSuffix, this.mCurrentSortID); - edit.putBoolean(STATE_SortAscending + mStatSuffix, this.mCurrentSortAscending); - - if (mStatSuffix.length() == 0) { - edit.putString(STATE_SUB_FILTER_CurrentPath, this.mCurrentPathFromFolderPicker); - edit.putString(STATE_SUB_FILTER_TAGS, ListUtils.toString(mCurrentTagsFromPicker)); - edit.putString(STATE_SUB_FILTER_LAT_LON, this.mCurrentLatLonFromGeoAreaPicker.toString()); - } - - if (getCurrentFilterSettings() != null) { - edit.putString(STATE_Filter + mStatSuffix, getCurrentFilterSettings().toString()); - } - edit.apply(); - } - } - - public boolean isGeoPick() { - return (mStatSuffix != null) && mStatSuffix.equals(PICK_GEO_SUFFIX); - } - - // load from settings/instanceState - private void loadSettingsAndInstanceState(Activity context, Bundle savedInstanceState) { - - Intent intent = context.getIntent(); - - // for debugging: where does the filter come from - StringBuilder dbgFilter = (Global.debugEnabled) ? new StringBuilder() : null; - String filter = null; - String pathFilter = null; - - if (intent != null) { - filter = intent.getStringExtra(EXTRA_FILTER); - - if ((filter != null) && (dbgFilter != null)) dbgFilter.append("filter from ").append(EXTRA_FILTER).append("=").append(filter).append("\n"); - - Uri uri = IntentUtil.getUri(intent); - boolean fileUri = IntentUtil.isFileUri(uri); - if (filter == null) { - - if (fileUri) { - pathFilter = uri.getSchemeSpecificPart(); - if (pathFilter != null) pathFilter = pathFilter.replace('*', '%'); - if (dbgFilter != null) dbgFilter.append("path from uri=").append(pathFilter).append("\n"); - } else { - String action = (intent != null) ? intent.getAction() : null; - - if ((action != null) && ((Intent.ACTION_PICK.compareTo(action) == 0) || (Intent.ACTION_GET_CONTENT.compareTo(action) == 0))) { - mStatSuffix = "-pick-image"; - String schema = intent.getScheme(); - if ((schema != null) && ("geo".compareTo(schema) == 0)) { - mStatSuffix = PICK_GEO_SUFFIX; - } - } - } - } - this.mSaveToSharedPrefs = ((filter == null) && (pathFilter == null) && (!fileUri) ); // false if controlled via intent - } else { - this.mSaveToSharedPrefs = true; - } - - SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context); - if (this.mSaveToSharedPrefs) { - if (mStatSuffix.length() == 0) { - this.mCurrentLatLonFromGeoAreaPicker.get(DirectoryFormatter.parseLatLon(sharedPref.getString(STATE_SUB_FILTER_LAT_LON, null))); - this.mCurrentPathFromFolderPicker = sharedPref.getString(STATE_SUB_FILTER_CurrentPath, this.mCurrentPathFromFolderPicker); - this.mCurrentTagsFromPicker = new ArrayList<>(ListUtils.fromString(sharedPref.getString(STATE_SUB_FILTER_TAGS, ListUtils.toString(mCurrentTagsFromPicker)))); - } - this.mDirQueryID = sharedPref.getInt(STATE_DirQueryID + mStatSuffix, this.getDirQueryID()); - this.mCurrentSortID = sharedPref.getInt(STATE_SortID + mStatSuffix, this.mCurrentSortID); - this.mCurrentSortAscending = sharedPref.getBoolean(STATE_SortAscending + mStatSuffix, this.mCurrentSortAscending); - } - - // instance state overrides settings - if (savedInstanceState != null) { - if (mStatSuffix.length() == 0) { - this.mCurrentLatLonFromGeoAreaPicker.get(DirectoryFormatter.parseLatLon(savedInstanceState.getString(STATE_SUB_FILTER_LAT_LON))); - this.mCurrentPathFromFolderPicker = savedInstanceState.getString(STATE_SUB_FILTER_CurrentPath, this.mCurrentPathFromFolderPicker); - this.mCurrentTagsFromPicker = new ArrayList<>(ListUtils.fromString(savedInstanceState.getString(STATE_SUB_FILTER_TAGS, ListUtils.toString(mCurrentTagsFromPicker)))); - } - this.mDirQueryID = savedInstanceState.getInt(STATE_DirQueryID, this.getDirQueryID()); - this.mCurrentSortID = savedInstanceState.getInt(STATE_SortID, this.mCurrentSortID); - this.mCurrentSortAscending = savedInstanceState.getBoolean(STATE_SortAscending, this.mCurrentSortAscending); - filter = savedInstanceState.getString(STATE_Filter); - if ((filter != null) && (dbgFilter != null)) dbgFilter.append("filter from savedInstanceState=").append(filter).append("\n"); - - this.mCurrentSubFilterMode = savedInstanceState.getInt(STATE_SUB_FILTR_MODE, this.mCurrentSubFilterMode); - } - - if ((pathFilter == null) && (filter == null) && (this.getCurrentFilterSettings() == null)) { - filter = sharedPref.getString(STATE_Filter + mStatSuffix, null); - if ((filter != null) && (dbgFilter != null)) dbgFilter.append("filter from sharedPref=").append(filter).append("\n"); - } - - if (filter != null) { - this.setCurrentFilterSettings(GalleryFilterParameter.parse(filter, new GalleryFilterParameter())); - } else if (pathFilter != null) { - if (!pathFilter.endsWith("%")) pathFilter += "%"; - this.setCurrentFilterSettings(new GalleryFilterParameter().setPath(pathFilter)); - } - - // extra parameter - final String sqlString = intent.getStringExtra(EXTRA_QUERY); - if (sqlString != null) { - if (dbgFilter != null) dbgFilter.append("query from ").append(EXTRA_QUERY).append("\n\t").append(sqlString).append("\n"); - this.mGalleryContentQuery = QueryParameter.parse(sqlString); - setHasUserDefinedQuery(true); - } - - if (this.mGalleryContentQuery == null) this.mGalleryContentQuery = FotoSql.getQuery(FotoSql.QUERY_TYPE_DEFAULT); - - if (dbgFilter != null) { - Log.i(Global.LOG_CONTEXT, mDebugPrefix + dbgFilter.toString()); - } - } - - public boolean hasUserDefinedQuery() { - return mHasUserDefinedQuery; - } - - public void setHasUserDefinedQuery(boolean mHasUserDefinedQuery) { - this.mHasUserDefinedQuery = mHasUserDefinedQuery; - } - - public IGalleryFilter getCurrentFilterSettings() { - return mCurrentFilterSettings; - } - - public void setCurrentFilterSettings(IGalleryFilter newFilterSettings) { - if ((newFilterSettings != null) && (PICK_GEO_SUFFIX.compareTo(mStatSuffix) == 0)) { - // geopick: only photos that have lat/lon - GalleryFilterParameter parameter = new GalleryFilterParameter().get(newFilterSettings); - parameter.setNonGeoOnly(false); - if (parameter.isEmpty()) { - parameter.setLatitude(-90.0,90.0).setLogitude(-180.0,180.0); - } - this.mCurrentFilterSettings = parameter; - } else { - this.mCurrentFilterSettings = newFilterSettings; - } - } - } - - private BookmarkController mBookmarkController = null; /** * shows a new instance of FotoGalleryActivity. - * - * @param context calling activity - * @param filter if != null set initial filter to new FotoGalleryActivity - * @param query if != null set initial filter to new FotoGalleryActivity + * @param context calling activity + * @param query if != null set initial filter to new FotoGalleryActivity * @param requestCode if != 0 start for result. else start without result */ - public static void showActivity(Activity context, GalleryFilterParameter filter, QueryParameter query, int requestCode) { + public static void showActivity(Activity context, QueryParameter query, int requestCode) { Intent intent = new Intent(context, FotoGalleryActivity.class); - if (filter != null) { - intent.putExtra(EXTRA_FILTER, filter.toString()); - } - - if (query != null) { - intent.putExtra(EXTRA_QUERY, query.toReParseableString()); - } + AndroidAlbumUtils.saveFilterAndQuery(context, null, intent, null, null, query); if (requestCode != 0) { context.startActivityForResult(intent, requestCode); @@ -433,14 +93,7 @@ public static void showActivity(Activity context, GalleryFilterParameter filter, } @Override - public void onSaveInstanceState(Bundle savedInstanceState) { - this.mGalleryQueryParameter.saveInstanceState(this, savedInstanceState); - mBookmarkController.saveState(null, savedInstanceState); - super.onSaveInstanceState(savedInstanceState); - } - - @Override - protected void onCreate(Bundle savedInstanceState){ + protected void onCreate(Bundle savedInstanceState) { Global.debugMemory(mDebugPrefix, "onCreate"); super.onCreate(savedInstanceState); final Intent intent = getIntent(); @@ -449,23 +102,13 @@ protected void onCreate(Bundle savedInstanceState){ // not implemented yet FotoLibGlobal.itpcWriteSupport = false; } - if (Global.debugEnabled && (intent != null)){ + if (Global.debugEnabled && (intent != null)) { Log.d(Global.LOG_CONTEXT, mDebugPrefix + "onCreate " + intent.toUri(Intent.URI_INTENT_SCHEME)); } - this.getContentResolver().registerContentObserver(FotoSql.SQL_TABLE_EXTERNAL_CONTENT_URI, true, mMediaObserverDirectory); - this.getContentResolver().registerContentObserver(FotoSql.SQL_TABLE_EXTERNAL_CONTENT_URI_FILE, true, mMediaObserverDirectory); setContentView(R.layout.activity_gallery); // .gallery_activity); - this.mGalleryQueryParameter.loadSettingsAndInstanceState(this, savedInstanceState); - - mBookmarkController = new BookmarkController(this); - mBookmarkController.loadState(intent,savedInstanceState); - - if (this.mGalleryQueryParameter.isGeoPick()) { - // #76: load predefined bookmark file - this.mBookmarkController.onLoadFromAnswer(DEFAULT_BOOKMARKNAME_PICK_GEO, this.mLoadBookmarkResultConsumer); - } + onCreateData(savedInstanceState); FragmentManager fragmentManager = getFragmentManager(); mGalleryGui = (Queryable) fragmentManager.findFragmentById(R.id.galleryCursor); @@ -496,36 +139,22 @@ protected void onCreate(Bundle savedInstanceState){ } @Override - protected void onPause () { - Global.debugMemory(mDebugPrefix, "onPause"); - this.mGalleryQueryParameter.saveSettings(this); - super.onPause(); - } - - @Override - protected void onResume () { - Global.debugMemory(mDebugPrefix, "onResume"); + public void onResume() { super.onResume(); - } - - @Override public void onLowMemory() { - super.onLowMemory(); - invalidateDirectories(mDebugPrefix + "#onLowMemory"); + if (mGalleryGui instanceof GalleryCursorFragment) { + this.mSelectedItems = ((GalleryCursorFragment) mGalleryGui).getSelectedItems(); + } } @Override protected void onDestroy() { Global.debugMemory(mDebugPrefix, "onDestroy start"); super.onDestroy(); - this.getContentResolver().unregisterContentObserver(mMediaObserverDirectory); - mPopUpSelection = null; + // to avoid memory leaks GarbageCollector.freeMemory(findViewById(R.id.root_view)); - - this.mGalleryQueryParameter.mGalleryContentQuery = null; mGalleryGui = null; mDirGui = null; - invalidateDirectories(mDebugPrefix + "#onDestroy"); System.gc(); Global.debugMemory(mDebugPrefix, "onDestroy end"); @@ -550,7 +179,8 @@ public boolean onCreateOptionsMenu(Menu menu) { Global.fixMenu(this, menu); } - return super.onCreateOptionsMenu(menu); + final boolean result = super.onCreateOptionsMenu(menu); + return result; } @Override @@ -569,62 +199,7 @@ public boolean onPrepareOptionsMenu(Menu menu) { @Override public boolean onOptionsItemSelected(MenuItem item) { - // Handle presses on the action bar items switch (item.getItemId()) { - case R.id.cmd_select_folder: - openFolderPicker(); - return true; - - case R.id.cmd_select_lat_lon: - openLatLonPicker(); - return true; - case R.id.cmd_select_tag: - openTagPicker(); - return true; - case R.id.cmd_filter: - openFilter(); - return true; - case R.id.cmd_load_bookmark: - loadBookmark(); - return true; - case R.id.cmd_sort_date: - this.mGalleryQueryParameter.setSortID(FotoSql.SORT_BY_DATE); - reloadGui("sort date"); - return true; - case R.id.cmd_sort_directory: - this.mGalleryQueryParameter.setSortID(FotoSql.SORT_BY_NAME); - reloadGui("sort dir"); - return true; - case R.id.cmd_sort_path_len: - this.mGalleryQueryParameter.setSortID(FotoSql.SORT_BY_NAME_LEN); - reloadGui("sort len"); - return true; - case R.id.cmd_sort_file_len: - this.mGalleryQueryParameter.setSortID(FotoSql.SORT_BY_FILE_LEN); - reloadGui("sort size"); - return true; - - case R.id.cmd_sort_width: - this.mGalleryQueryParameter.setSortID(FotoSql.SORT_BY_WIDTH); - reloadGui("sort width"); - return true; - - case R.id.cmd_sort_location: - this.mGalleryQueryParameter.setSortID(FotoSql.SORT_BY_LOCATION); - reloadGui("sort geo"); - return true; - - case R.id.cmd_sort_rating: - this.mGalleryQueryParameter.setSortID(FotoSql.SORT_BY_RATING); - reloadGui("sort rating"); - return true; - - case R.id.cmd_sort_modification: - this.mGalleryQueryParameter.setSortID(FotoSql.SORT_BY_MODIFICATION); - reloadGui("sort modification"); - return true; - - case R.id.cmd_settings: SettingsActivity.show(this); return true; @@ -640,14 +215,11 @@ public void run() { }, 200); return true; default: - return super.onOptionsItemSelected(item); + return onOptionsItemSelected(item, this.mSelectedItems); } } - private void loadBookmark() { - mBookmarkController.onLoadFromQuestion(mLoadBookmarkResultConsumer, this.mGalleryQueryParameter.calculateEffectiveGalleryContentQuery()); - } /** * Call back from sub-activities.
* Process Change StartTime (longpress start), Select StopTime before stop @@ -661,20 +233,7 @@ protected void onActivityResult(final int requestCode, ((Fragment) mGalleryGui).onActivityResult(requestCode, resultCode, intent); } - if (mDirPicker instanceof Fragment) { - ((Fragment) mDirPicker).onActivityResult(requestCode, resultCode, intent); - } - - if (mPopUpSelection != null) mPopUpSelection.refresh(); - switch (requestCode) { - case GalleryFilterActivity.resultID : - if (BookmarkController.isReset(intent)) { - mGalleryQueryParameter.mGalleryContentQuery = new QueryParameter(FotoSql.queryDetail); - } - mBookmarkController.loadState(intent, null); - onFilterChanged(GalleryFilterActivity.getFilter(intent), mDebugPrefix + "#onActivityResult from GalleryFilterActivity"); - break; case ImageDetailActivityViewPager.ACTIVITY_ID: if (resultCode == ImageDetailActivityViewPager.RESULT_CHANGE) { invalidateDirectories(mDebugPrefix + "#onActivityResult from ImageDetailActivityViewPager"); @@ -685,124 +244,11 @@ protected void onActivityResult(final int requestCode, invalidateDirectories(mDebugPrefix + "#onActivityResult from GeoEditActivity"); } break; - default:break; - } - } - - private void onFilterChanged(IGalleryFilter filter, String why) { - if (filter != null) { - this.mGalleryQueryParameter.setCurrentFilterSettings(filter); - this.mGalleryQueryParameter.setHasUserDefinedQuery(false); - - invalidateDirectories(mDebugPrefix + "#filter changed " + why); - - reloadGui("filter changed"); - } - } - - private void openLatLonPicker() { - mGalleryQueryParameter.mCurrentSubFilterMode = GalleryQueryParameter.SUB_FILTER_MODE_GEO; - - final FragmentManager manager = getFragmentManager(); - LocationMapFragment dialog = new LocationMapFragment(); - dialog.defineNavigation(this.mGalleryQueryParameter.getCurrentFilterSettings(), - this.mGalleryQueryParameter.mCurrentLatLonFromGeoAreaPicker, OsmdroidUtil.NO_ZOOM, mSelectedItems, null); - - dialog.show(manager, DLG_NAVIGATOR_TAG); - } - - private void openTagPicker() { - mGalleryQueryParameter.mCurrentSubFilterMode = GalleryQueryParameter.SUB_FILTER_MODE_TAG; - - final FragmentManager manager = getFragmentManager(); - TagsPickerFragment dlg = new TagsPickerFragment(); - dlg.setFragmentOnwner(this); - dlg.setTitleId(R.string.tags_activity_title); - dlg.setAddNames(mGalleryQueryParameter.mCurrentTagsFromPicker); - dlg.show(manager, DLG_NAVIGATOR_TAG); - } - - /** called by {@link TagsPickerFragment} */ - @Override - public boolean onCancel(String msg) { - return true; - } - - /** called by {@link TagsPickerFragment} */ - @Override - public boolean onOk(List addNames, List removeNames) { - Log.d(Global.LOG_CONTEXT, "FotoGalleryActivity.navigateTo " + ListUtils.toString(addNames) + " from " - + ListUtils.toString(mGalleryQueryParameter.mCurrentTagsFromPicker)); - mGalleryQueryParameter.mCurrentTagsFromPicker = new ArrayList(addNames); - reloadGui("navigate to tags"); - return true; - } - - /** called by {@link TagsPickerFragment} */ - @Override - public boolean onTagPopUpClick(int menuItemItemId, Tag selectedTag) { - return TagsPickerFragment.handleMenuShow(menuItemItemId, selectedTag, this, this.mGalleryQueryParameter.getCurrentFilterSettings()); - } - - private void openFolderPicker() { - mGalleryQueryParameter.mCurrentSubFilterMode = GalleryQueryParameter.SUB_FILTER_MODE_PATH; - - int dirQueryID = this.mGalleryQueryParameter.getDirQueryID(); - - /** if wrong datatype was saved: gallery is not allowed for dirPicker */ - if (FotoSql.QUERY_TYPE_GALLERY == dirQueryID) { - dirQueryID = FotoSql.QUERY_TYPE_GROUP_ALBUM; - } - - if (mDirectoryRoot == null) { - // not loaded yet. load directoryRoot in background - final QueryParameter currentDirContentQuery = new QueryParameter(FotoSql.getQuery(dirQueryID)); - TagSql.filter2QueryEx(currentDirContentQuery, this.mGalleryQueryParameter.getCurrentFilterSettings(), - this.mGalleryQueryParameter.getSortID() != IGalleryFilter.SORT_BY_NONE); - - this.mGalleryQueryParameter.mDirQueryID = (currentDirContentQuery != null) ? currentDirContentQuery.getID() : FotoSql.QUERY_TYPE_UNDEFINED; - - if (currentDirContentQuery != null) { - this.mMustShowNavigator = true; - DirectoryLoaderTask loader = new DirectoryLoaderTask(this, mDebugPrefix) { - @Override - protected void onPostExecute(IDirectory directoryRoot) { - onDirectoryDataLoadComplete(directoryRoot); - } - }; - loader.execute(currentDirContentQuery); - } else { - Log.e(Global.LOG_CONTEXT, mDebugPrefix + " this.mDirQueryID undefined " - + this.mGalleryQueryParameter.mDirQueryID); - } - } else { - this.mMustShowNavigator = false; - final FragmentManager manager = getFragmentManager(); - DirectoryPickerFragment dirDialog =new DirectoryPickerFragment() { - protected boolean onPopUpClick(MenuItem menuItem, IDirectory popUpSelection) { - mPopUpSelection = popUpSelection; - return super.onPopUpClick(menuItem, popUpSelection); - } - }; - - // (DirectoryPickerFragment) manager.findFragmentByTag(DLG_NAVIGATOR_TAG); - dirDialog.setContextMenuId(LockScreen.isLocked(this) ? 0 : R.menu.menu_context_dirpicker); - - dirDialog.defineDirectoryNavigation(mDirectoryRoot, dirQueryID, - this.mGalleryQueryParameter.mCurrentPathFromFolderPicker); - - mDirPicker = dirDialog; - dirDialog.show(manager, DLG_NAVIGATOR_TAG); + default: + break; } } - private void openFilter() { - GalleryFilterActivity.showActivity(this, - this.mGalleryQueryParameter.getCurrentFilterSettings(), - this.mGalleryQueryParameter.mGalleryContentQuery, - mBookmarkController.getlastBookmarkFileName(), GalleryFilterActivity.resultID); - } - /** called by Fragment: a fragment Item was clicked */ @Override public void onGalleryImageClick(long imageId, Uri imageUri, int position) { @@ -811,88 +257,16 @@ public void onGalleryImageClick(long imageId, Uri imageUri, int position) { ImageDetailActivityViewPager.showActivity(this, imageUri, position, imageDetailQuery, ImageDetailActivityViewPager.ACTIVITY_ID); } - /** GalleryFragment tells the Owning Activity that querying data has finisched */ - @Override - public void setResultCount(int count) { - this.mTitleResultCount = (count > 0) ? ("(" + count + ")") : ""; - setTitle(); - - // current path does not contain photo => refreshLocal witout current path - if ((count == 0) &&(mGalleryQueryParameter.clearPathIfActive())) { - setTitle(); - reloadGui("query changed"); - } - } - - /** - * called when user selects a new directoryRoot - */ - @Override - public void onDirectoryPick(String selectedAbsolutePath, int queryTypeId) { - if (!this.mHasEmbeddedDirPicker) { - navigateTo(selectedAbsolutePath, queryTypeId); - } - mDirPicker = null; - } - @Override - public void invalidateDirectories(String why) { - - if (mDirectoryRoot != null) { - if (Global.debugEnabled) { - StringBuilder name = new StringBuilder(mDirectoryRoot.getAbsolute()); - Directory.appendCount(name, mDirectoryRoot, Directory.OPT_DIR | Directory.OPT_SUB_DIR); - Log.i(Global.LOG_CONTEXT, mDebugPrefix + "invalidateDirectories(" + name + ") because of " + why); - } - if (mDirPicker == null) { - mDirectoryRoot.destroy(); - mDirectoryRoot = null; // must refreshLocal next time - } + protected void defineDirectoryNavigation(IDirectory directoryRoot) { + if (mDirGui != null) { + mDirGui.defineDirectoryNavigation(directoryRoot, mGalleryQueryParameter.getDirQueryID(), + mGalleryQueryParameter.getCurrentSubFilterSettings().getPath()); } } - /** - * called when user cancels selection of a new directoryRoot - */ - @Override - public void onDirectoryCancel(int queryTypeId) { - mDirPicker = null; - } - - /** called after the selection in tree has changed */ @Override - public void onDirectorySelectionChanged(String selectedAbsolutePath, int queryTypeId) { - if (this.mHasEmbeddedDirPicker) { - navigateTo(selectedAbsolutePath, queryTypeId); - } - } - - private void navigateTo(String selectedAbsolutePath, int queryTypeId) { - - if (selectedAbsolutePath != null) { - if (mGalleryQueryParameter.mCurrentSubFilterMode == GalleryQueryParameter.SUB_FILTER_MODE_GEO) { - Log.d(Global.LOG_CONTEXT, "FotoGalleryActivity.navigateTo " + selectedAbsolutePath + " from " + mGalleryQueryParameter.mCurrentLatLonFromGeoAreaPicker); - this.mGalleryQueryParameter.mCurrentLatLonFromGeoAreaPicker.get(DirectoryFormatter.parseLatLon(selectedAbsolutePath)); - - reloadGui("navigate to geo"); - - } else if (mGalleryQueryParameter.mCurrentSubFilterMode == GalleryQueryParameter.SUB_FILTER_MODE_TAG) { - Log.d(Global.LOG_CONTEXT, "FotoGalleryActivity.navigateTo " + selectedAbsolutePath + " from " - + ListUtils.toString(this.mGalleryQueryParameter.mCurrentTagsFromPicker)); - this.mGalleryQueryParameter.mCurrentTagsFromPicker = new ArrayList<>(ListUtils.fromString(selectedAbsolutePath)); - reloadGui("navigate to tags"); - } else if (mGalleryQueryParameter.mCurrentSubFilterMode == GalleryQueryParameter.SUB_FILTER_MODE_PATH) { - Log.d(Global.LOG_CONTEXT, "FotoGalleryActivity.navigateTo " + selectedAbsolutePath + " from " + this.mGalleryQueryParameter.mCurrentPathFromFolderPicker); - this.mGalleryQueryParameter.mCurrentPathFromFolderPicker = selectedAbsolutePath; - this.mGalleryQueryParameter.mDirQueryID = queryTypeId; - setTitle(); - - reloadGui("navigate to dir"); - } - } - } - - private void reloadGui(String why) { + protected void reloadGui(String why) { if (mGalleryGui != null) { QueryParameter query = this.mGalleryQueryParameter.calculateEffectiveGalleryContentQuery(); if (query != null) { @@ -901,63 +275,17 @@ private void reloadGui(String why) { } if (mDirGui != null) { - String currentPath = this.mGalleryQueryParameter.mCurrentPathFromFolderPicker; + String currentPath = this.mGalleryQueryParameter.getCurrentSubFilterSettings().getPath(); if (currentPath != null) { mDirGui.navigateTo(currentPath); } } } - private void onDirectoryDataLoadComplete(IDirectory directoryRoot) { - if (directoryRoot == null) { - final String message = getString(R.string.folder_err_load_failed_format, FotoSql.getName(this, this.mGalleryQueryParameter.getDirQueryID())); - Toast.makeText(this, message,Toast.LENGTH_LONG).show(); - } else { - mDirectoryRoot = directoryRoot; - final boolean mustDefineNavigation = (mDirGui != null) && (this.mGalleryQueryParameter.mCurrentPathFromFolderPicker != null); - final boolean mustShowFolderPicker = (mDirectoryRoot != null) && (this.mMustShowNavigator); - - if (Global.debugEnabled) { - StringBuilder name = new StringBuilder(mDirectoryRoot.getAbsolute()); - Directory.appendCount(name, mDirectoryRoot, Directory.OPT_DIR | Directory.OPT_SUB_DIR); - Log.i(Global.LOG_CONTEXT, mDebugPrefix + "onDirectoryDataLoadComplete(" + - "mustDefineNavigation=" + mustDefineNavigation + - ", mustShowFolderPicker=" + mustShowFolderPicker + - ", content=" + name + ")"); - } - - if (mustDefineNavigation) { - mDirGui.defineDirectoryNavigation(directoryRoot, this.mGalleryQueryParameter.getDirQueryID(), this.mGalleryQueryParameter.mCurrentPathFromFolderPicker); - } - Global.debugMemory(mDebugPrefix, "onDirectoryDataLoadComplete"); - - if (mustShowFolderPicker) { - openFolderPicker(); - } - } - } - - private void setTitle() { - Intent intent = getIntent(); - String title = (intent == null) ? null : intent.getStringExtra(EXTRA_TITLE); - - if (title == null) { - if (mGalleryQueryParameter.mCurrentSubFilterMode == GalleryQueryParameter.SUB_FILTER_MODE_GEO) { - title = getString(R.string.gallery_title); - } else if (this.mGalleryQueryParameter.mCurrentPathFromFolderPicker != null) { - title = FotoSql.getName(this, this.mGalleryQueryParameter.getDirQueryID()) - + " - " + this.mGalleryQueryParameter.mCurrentPathFromFolderPicker; - } else { - title = FotoSql.getName(this, this.mGalleryQueryParameter.getDirQueryID()); - } - } - if (title != null) { - this.setTitle(title + mTitleResultCount); - } - } - @Override public String toString() { return mDebugPrefix + "->" + this.mGalleryGui; } + + } diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/GalleryFilterActivity.java b/app/src/main/java/de/k3b/android/androFotoFinder/GalleryFilterActivity.java index f7cb14fe..e3f7420a 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/GalleryFilterActivity.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/GalleryFilterActivity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2017 by k3b. + * Copyright (c) 2015-2018 by k3b. * * This file is part of AndroFotoFinder / #APhotoManager. * @@ -20,9 +20,11 @@ package de.k3b.android.androFotoFinder; import android.app.Activity; +import android.app.DialogFragment; import android.app.FragmentManager; import android.content.Intent; import android.content.SharedPreferences; +import android.net.Uri; import android.os.Bundle; import android.preference.PreferenceManager; import android.util.Log; @@ -35,6 +37,7 @@ import android.widget.RatingBar; import android.widget.Toast; +import java.io.File; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; @@ -44,25 +47,33 @@ import de.k3b.FotoLibGlobal; import de.k3b.android.androFotoFinder.directory.DirectoryLoaderTask; import de.k3b.android.androFotoFinder.directory.DirectoryPickerFragment; +import de.k3b.android.androFotoFinder.imagedetail.ImageDetailMetaDialogBuilder; import de.k3b.android.androFotoFinder.locationmap.LocationMapFragment; import de.k3b.android.androFotoFinder.locationmap.MapGeoPickerActivity; +import de.k3b.android.androFotoFinder.queries.AndroidAlbumUtils; import de.k3b.android.androFotoFinder.queries.FotoSql; import de.k3b.android.androFotoFinder.tagDB.TagSql; import de.k3b.android.androFotoFinder.tagDB.TagsPickerFragment; import de.k3b.android.osmdroid.OsmdroidUtil; +import de.k3b.android.util.OsUtils; import de.k3b.android.widget.AboutDialogPreference; import de.k3b.android.widget.ActivityWithAutoCloseDialogs; +import de.k3b.android.widget.BaseQueryActivity; import de.k3b.android.widget.HistoryEditText; import de.k3b.database.QueryParameter; +import de.k3b.io.AlbumFile; import de.k3b.io.DirectoryFormatter; import de.k3b.io.GalleryFilterParameter; import de.k3b.io.IDirectory; import de.k3b.io.IGalleryFilter; import de.k3b.io.IGeoRectangle; +import de.k3b.io.StringUtils; import de.k3b.io.VISIBILITY; import de.k3b.tagDB.Tag; /** + * Editor for GalleryFilterParameter and for parsed *.query and *.album QueryParameter-files. + * * Defines a gui for global foto filter: only fotos from certain filepath, date and/or lat/lon will be visible. */ public class GalleryFilterActivity extends ActivityWithAutoCloseDialogs @@ -72,23 +83,34 @@ public class GalleryFilterActivity extends ActivityWithAutoCloseDialogs { private static final String mDebugPrefix = "GalF-"; - public static final int resultID = 522; private static final String DLG_NAVIGATOR_TAG = "GalleryFilterActivity"; + private static final String DLG_SAVE_AS_TAG = "GalleryFilterActivitySaveAs"; private static final String SETTINGS_KEY = "GalleryFilterActivity-"; - private static final String FILTER_VALUE = "CURRENT_FILTER"; + private static final String LAST_SELECTED_DIR_KEY = "mLastSelectedAlbumDir"; + private static final String WILDCARD = "%"; - private static QueryParameter mRootQuery; + private static final int SAVE_AS_VALBUM_PICK = 9921; private GalleryFilterParameter mFilter = new GalleryFilterParameter(); + // where to navigate to when folder picker is opend: last path or last picked album file + private String mLastSelectedAlbumDir = null; + + // parsed filter part of query private FilterValue mFilterValue = null; + // contains the non parsebale part of query + private QueryParameter mQueryWithoutFilter; + private HistoryEditText mHistory; - private BookmarkController mBookmarkController = null; - private IDirectory mPopUpSelection = null; - public static void showActivity(Activity context, IGalleryFilter filter, QueryParameter rootQuery, + private GalleryFilterPathState mGalleryFilterPathState = null; + private VirtualAlbumController mBookmarkController = null; + + /** set while dir picker is active */ + private DirectoryPickerFragment mDirPicker = null; + + public static void showActivity(Activity context, IGalleryFilter filter, QueryParameter query, String lastBookmarkFileName, int requestCode) { - mRootQuery = rootQuery; if (Global.debugEnabled) { Log.d(Global.LOG_CONTEXT, context.getClass().getSimpleName() + " > GalleryFilterActivity.showActivity"); @@ -97,11 +119,7 @@ public static void showActivity(Activity context, IGalleryFilter filter, QueryPa final Intent intent = new Intent().setClass(context, GalleryFilterActivity.class); - if ((intent != null) && (filter != null)) { - intent.putExtra(EXTRA_FILTER, filter.toString()); - } - - BookmarkController.saveState(lastBookmarkFileName, intent, null); + AndroidAlbumUtils.saveFilterAndQuery(context, null, intent, null, filter, query); if (requestCode != 0) { context.startActivityForResult(intent, requestCode); } else { @@ -109,18 +127,14 @@ public static void showActivity(Activity context, IGalleryFilter filter, QueryPa } } - public static GalleryFilterParameter getFilter(Intent intent) { - if (intent == null) return null; - String filter = intent.getStringExtra(EXTRA_FILTER); - if (filter == null) return null; - return GalleryFilterParameter.parse(filter, new GalleryFilterParameter()); - } - @Override public void onSaveInstanceState(Bundle savedInstanceState) { fromGui(mFilter); - savedInstanceState.putString(FILTER_VALUE, mFilter.toString()); - mBookmarkController.saveState(null, savedInstanceState); + AndroidAlbumUtils.saveFilterAndQuery(this, null, null, savedInstanceState, mFilter, mQueryWithoutFilter); + + savedInstanceState.putString(LAST_SELECTED_DIR_KEY, mLastSelectedAlbumDir); + + mGalleryFilterPathState.save(this, savedInstanceState); super.onSaveInstanceState(savedInstanceState); } @@ -130,25 +144,46 @@ protected void onCreate(Bundle savedInstanceState) { Global.debugMemory(mDebugPrefix, "onCreate"); super.onCreate(savedInstanceState); final Intent intent = getIntent(); - if (Global.debugEnabled && (intent != null)){ - Log.d(Global.LOG_CONTEXT, mDebugPrefix + "onCreate " + intent.toUri(Intent.URI_INTENT_SCHEME)); + + mGalleryFilterPathState = new GalleryFilterPathState().load(this, intent, + savedInstanceState); + // for debugging: where does the filter come from + StringBuilder dbgMessageResult = (Global.debugEnabled) ? new StringBuilder() : null; + + if ((dbgMessageResult != null) && (intent != null)){ + dbgMessageResult.append(StringUtils.appendMessage(dbgMessageResult, + mDebugPrefix , "onCreate", intent.toUri(Intent.URI_INTENT_SCHEME))); } setContentView(R.layout.activity_gallery_filter); this.mFilterValue = new FilterValue(); onCreateButtos(); - GalleryFilterParameter filter = (savedInstanceState == null) - ? getFilter(intent) - : GalleryFilterParameter.parse(savedInstanceState.getString(FILTER_VALUE, ""), new GalleryFilterParameter()) ; + final QueryParameter query = AndroidAlbumUtils.getQuery(this, "", + savedInstanceState, intent, null, null, dbgMessageResult); + setQueryAndFilter(query); - if (filter != null) { - mFilter = filter; - toGui(mFilter); - mFilterValue.showLatLon(filter.isNonGeoOnly()); + if (dbgMessageResult != null) { + Log.i(Global.LOG_CONTEXT, dbgMessageResult.toString()); } - mBookmarkController = new BookmarkController(this); - mBookmarkController.loadState(intent,savedInstanceState); + mBookmarkController = new VirtualAlbumController(this); + + if (savedInstanceState != null) { + mLastSelectedAlbumDir = savedInstanceState.getString(LAST_SELECTED_DIR_KEY); + } + } + + private void setQueryAndFilter(QueryParameter query) { + this.mQueryWithoutFilter = query; + if (this.mQueryWithoutFilter == null) { + this.mQueryWithoutFilter = new QueryParameter(); + this.mFilter = new GalleryFilterParameter(); + } else { + this.mFilter.get(TagSql.parseQueryEx(this.mQueryWithoutFilter, true)); + } + + mFilterValue.showLatLon(this.mFilter.isNonGeoOnly()); + toGui(this.mFilter); } private void onCreateButtos() { @@ -156,14 +191,30 @@ private void onCreateButtos() { cmd.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - showDirectoryPicker(FotoSql.queryGroupByDir); + String path = (!StringUtils.isNullOrEmpty(mLastSelectedAlbumDir)) ? mLastSelectedAlbumDir : getAsGalleryFilter().getPath(); + + if (StringUtils.isNullOrEmpty(path)) { + path = mGalleryFilterPathState.load(GalleryFilterActivity.this, + null, null) + .getPathDefault(null); + } + if (path != null) path = path.replaceAll("%",""); + showDirectoryPickerForFilterParamValue( + mDebugPrefix + " path picker " + path, + FotoSql.queryGroupByDir, + true, + false, path); } }); cmd = (Button) findViewById(R.id.cmd_date); cmd.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - showDirectoryPicker(FotoSql.queryGroupByDate); + String path = getAsGalleryFilter().getDatePath(); + showDirectoryPickerForFilterParamValue( + mDebugPrefix + " date picker " + path, + FotoSql.queryGroupByDate, false, + FotoLibGlobal.datePickerUseDecade, path); } }); cmd = (Button) findViewById(R.id.cmd_select_lat_lon); @@ -219,25 +270,26 @@ public boolean onOptionsItemSelected(MenuItem item) { return true; case R.id.cmd_gallery: - FotoGalleryActivity.showActivity(this, getAsGalleryFilter(), null, 0); + FotoGalleryActivity.showActivity(this, getAsMergedQuery(), 0); + // TagSql.filter2NewQuery(getAsGalleryFilter()), 0); return true; case R.id.cmd_show_geo: { - MapGeoPickerActivity.showActivity(this, null, getAsGalleryFilter()); + MapGeoPickerActivity.showActivity(this, null, getAsMergedQuery(), 0); return true; } + case R.id.action_details: + cmdShowDetails(); + return true; case R.id.action_save_as: - mBookmarkController.onSaveAsQuestion(mBookmarkController.getlastBookmarkFileName(), getAsQuery()); - return true; - case R.id.action_load_from: - mBookmarkController.onLoadFromQuestion(new BookmarkController.IQueryConsumer() { - @Override - public void setQuery(String fileName, QueryParameter newQuery) { - IGalleryFilter filter = TagSql.parseQueryEx(newQuery, false); - toGui(filter); - } - }, getAsQuery()); + mGalleryFilterPathState.load(this,null,null); + // mBookmarkController.onSaveAsQuestion(mBookmarkController.getlastBookmarkFileName(), getAsQuery()); + File valbum = mGalleryFilterPathState.getSaveAlbumAs(getString(R.string.mk_dir_default), AlbumFile.SUFFIX_VALBUM); + DialogFragment dlg = mBookmarkController.onSaveAsVirutalAlbumQuestion(valbum, getAsMergedQuery()); + setAutoClose(dlg, null, null); + invalidatePathData(); + return true; default: return super.onOptionsItemSelected(item); @@ -254,7 +306,8 @@ protected void onActivityResult(final int requestCode, final int resultCode, final Intent intent) { super.onActivityResult(requestCode, resultCode, intent); - if (mPopUpSelection != null) mPopUpSelection.refresh(); + IDirectory lastPopUpSelection = (mDirPicker == null) ? null : mDirPicker.getLastPopUpSelection(); + if (lastPopUpSelection != null) lastPopUpSelection.refresh(); } private GalleryFilterParameter getAsGalleryFilter() { @@ -263,12 +316,8 @@ private GalleryFilterParameter getAsGalleryFilter() { return filter; } - private QueryParameter getAsQuery() { - IGalleryFilter filter = new GalleryFilterParameter(); - fromGui(filter); - QueryParameter query = new QueryParameter(mRootQuery); - TagSql.filter2QueryEx(query, filter, true); - return query; + private QueryParameter getAsMergedQuery() { + return AndroidAlbumUtils.getAsMergedNewQueryParameter(mQueryWithoutFilter, getAsGalleryFilter()); } @Override @@ -283,12 +332,18 @@ protected void onResume () { Global.debugMemory(mDebugPrefix, "onResume"); loadLastFilter(); super.onResume(); + if (LockScreen.isLocked(this)) { + // filter is forbidden when locked. Might occur via + // gallery -> filter -> dir-pick -> openInNew Gallery -> exit + finish(); + } } private void loadLastFilter() { SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this); loadLastFilter(sharedPref, FotoSql.QUERY_TYPE_GROUP_ALBUM); + loadLastFilter(sharedPref, FotoSql.QUERY_TYPE_GALLERY); loadLastFilter(sharedPref, FotoSql.QUERY_TYPE_GROUP_DATE); loadLastFilter(sharedPref, FotoSql.QUERY_TYPE_GROUP_PLACE); } @@ -298,13 +353,13 @@ private void loadLastFilter(SharedPreferences sharedPref, int queryTypeID) { } private void saveLastFilter() { - if (dirInfos != null) + if (mDirInfos != null) { SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this); SharedPreferences.Editor edit = sharedPref.edit(); - - for(Integer id : dirInfos.keySet()) { - DirInfo dir = dirInfos.get(id); + + for(Integer id : mDirInfos.keySet()) { + DirInfo dir = mDirInfos.get(id); if ((dir != null) && (dir.currentPath != null) && (dir.currentPath.length() > 0)) { edit.putString(SETTINGS_KEY + id, dir.currentPath); } @@ -318,16 +373,15 @@ protected void onDestroy() { Global.debugMemory(mDebugPrefix, "onDestroy start"); super.onDestroy(); - mPopUpSelection = null; - if (dirInfos != null) + if (mDirInfos != null) { - for(Integer id : dirInfos.keySet()) { - DirInfo dir = dirInfos.get(id); + for(Integer id : mDirInfos.keySet()) { + DirInfo dir = mDirInfos.get(id); if (dir.directoryRoot != null) { dir.directoryRoot.destroy(); } } - dirInfos = null; + mDirInfos = null; } Global.debugMemory(mDebugPrefix, "onDestroy end"); @@ -619,6 +673,10 @@ private long convertDate(String string) throws RuntimeException { } } + @Override public String toString() { + return new GalleryFilterParameter().get(this).toString(); + } + } private void toGui(IGalleryFilter gf) { @@ -638,6 +696,19 @@ private boolean fromGui(IGalleryFilter dest) { } } + private void cmdShowDetails() { + final QueryParameter asMergedQuery = getAsMergedQuery(); + + ImageDetailMetaDialogBuilder.createImageDetailDialog( + this, + getTitle().toString(), + asMergedQuery.toSqlString(), + StringUtils.appendMessage(null, + getString(R.string.show_photo), + TagSql.getCount(this, asMergedQuery)) + ).show(); + } + private void clearFilter() { GalleryFilterParameter filter = new GalleryFilterParameter(); @@ -645,7 +716,8 @@ private void clearFilter() { filter.setSort(mFilter.getSortID(), mFilter.isSortAscending()); } - this.mFilter = filter; + mFilter = filter; + mQueryWithoutFilter = new QueryParameter(); toGui(mFilter); } @@ -653,19 +725,20 @@ private void onOk() { if (fromGui(mFilter)) { mHistory.saveHistory(); - final Intent intent = new Intent(); - if (this.mFilter != null) { - intent.putExtra(EXTRA_FILTER, this.mFilter.toString()); - } + final Intent resultIntent = new Intent(); + final Intent originalIntent = getIntent(); + + // if result must be saved to file + Uri originalUri = (originalIntent == null) ? null : originalIntent.getData(); - mBookmarkController.saveState(intent, null); + AndroidAlbumUtils.saveFilterAndQuery(this, originalUri, resultIntent, null, mFilter, mQueryWithoutFilter); + + this.setResult(BaseQueryActivity.resultID, resultIntent); - this.setResult(resultID, intent); finish(); } } - /**************** DirectoryPicker *****************/ private static class DirInfo { public int queryId = 0; @@ -673,13 +746,14 @@ private static class DirInfo { public String currentPath = null; } - private HashMap dirInfos = new HashMap(); + /** one DirInfo per possible queyType */ + private HashMap mDirInfos = new HashMap(); private DirInfo getOrCreateDirInfo(int queryId) { - DirInfo result = dirInfos.get(queryId); + DirInfo result = mDirInfos.get(queryId); if (result == null) { result = new DirInfo(); result.queryId = queryId; - dirInfos.put(queryId, result); + mDirInfos.put(queryId, result); } return result; } @@ -724,43 +798,49 @@ private void showLatLonPicker() { if (fromGui(mFilter)) { final FragmentManager manager = getFragmentManager(); LocationMapFragment dlg = new LocationMapFragment(); - dlg.defineNavigation(null, mFilter, OsmdroidUtil.NO_ZOOM, null, null); + dlg.defineNavigation(null, null, mFilter, OsmdroidUtil.NO_ZOOM, null, null, false); dlg.show(manager, DLG_NAVIGATOR_TAG); setAutoClose(dlg, null, null); } } - private void showDirectoryPicker(final QueryParameter currentDirContentQuery) { + private void showDirectoryPickerForFilterParamValue( + final String debugContext, final QueryParameter currentDirContentQuery, + boolean addVAlbums, boolean datePickerUseDecade, + final String initialSelection) { if (fromGui(mFilter)) { - IDirectory directoryRoot = getOrCreateDirInfo(currentDirContentQuery.getID()).directoryRoot; + final DirInfo dirInfo = getOrCreateDirInfo(currentDirContentQuery.getID()); + IDirectory directoryRoot = dirInfo.directoryRoot; + dirInfo.currentPath = initialSelection; if (directoryRoot == null) { - DirectoryLoaderTask loader = new DirectoryLoaderTask(this, mDebugPrefix) { + DirectoryLoaderTask loader = new DirectoryLoaderTask(this, datePickerUseDecade, debugContext) { @Override protected void onPostExecute(IDirectory directoryRoot) { - onDirectoryDataLoadComplete(directoryRoot, currentDirContentQuery.getID()); + onDirectoryDataLoadCompleteForFilterParamValue(directoryRoot, currentDirContentQuery.getID()); } }; - loader.execute(currentDirContentQuery); + + if (addVAlbums) { + // load dir-s + "*.album" + loader.execute(currentDirContentQuery, FotoSql.queryVAlbum); + } else { + loader.execute(currentDirContentQuery); + } } else { - onDirectoryDataLoadComplete(directoryRoot, currentDirContentQuery.getID()); + onDirectoryDataLoadCompleteForFilterParamValue(directoryRoot, currentDirContentQuery.getID()); } } } - private void onDirectoryDataLoadComplete(IDirectory directoryRoot, int queryId) { + private void onDirectoryDataLoadCompleteForFilterParamValue(IDirectory directoryRoot, int queryId) { if (directoryRoot != null) { Global.debugMemory(mDebugPrefix, "onDirectoryDataLoadComplete"); DirInfo dirInfo = getOrCreateDirInfo(queryId); dirInfo.directoryRoot = directoryRoot; final FragmentManager manager = getFragmentManager(); - DirectoryPickerFragment dlg = new DirectoryPickerFragment() { - protected boolean onPopUpClick(MenuItem menuItem, IDirectory popUpSelection) { - mPopUpSelection = popUpSelection; - return super.onPopUpClick(menuItem, popUpSelection); - } - }; + DirectoryPickerFragment dlg = new DirectoryPickerFragment(); int menuResId = 0; // no menu in app lock mode if (!LockScreen.isLocked(this)) { @@ -769,22 +849,52 @@ protected boolean onPopUpClick(MenuItem menuItem, IDirectory popUpSelection) { dlg.setContextMenuId(menuResId); dlg.defineDirectoryNavigation(dirInfo.directoryRoot, dirInfo.queryId, dirInfo.currentPath); - dlg.show(manager, DLG_NAVIGATOR_TAG); + mDirPicker = dlg; + setAutoClose(dlg, null, null); } } /** - * called when user picks a new directory + * called when user picks a new directory. + * Sources: Filter-Path, Filter-Date or SaveAs distinguished via queryTypeId */ @Override - public void onDirectoryPick(String selectedAbsolutePath, int queryTypeId) { - DirInfo dirInfo = getOrCreateDirInfo(queryTypeId); - dirInfo.currentPath=selectedAbsolutePath; + public void onDirectoryPick(final String selectedAbsolutePath, int queryTypeId) { + closeDialogIfNeeded(); + mGalleryFilterPathState.load(this, null, null); + + File selectedAlbumFile = AlbumFile.getExistingQueryFileOrNull(selectedAbsolutePath); + if (selectedAlbumFile != null) { + // selection was an album file + final String selectedAlbumPath = selectedAlbumFile.getPath(); + setQueryAndFilter(AndroidAlbumUtils.getQueryFromUri(mDebugPrefix + ".onDirectoryPick loading album " + + selectedAlbumPath, this, Uri.fromFile(selectedAlbumFile), null)); + mGalleryFilterPathState.setAlbum(Uri.fromFile(selectedAlbumFile)); + mGalleryFilterPathState.setLastPath(selectedAlbumFile.getParent()); + mLastSelectedAlbumDir = selectedAlbumPath; //??electedAlbumFile.getParent(); + } else if (AlbumFile.isQueryFile(selectedAbsolutePath)) { + // album does not exist (any more) rescan + AndroidAlbumUtils.albumMediaScan(mDebugPrefix + " onAlbumPick not found in filesystem => ", + this, new File(selectedAbsolutePath), 1); + + mGalleryFilterPathState.setLastPath(selectedAlbumFile.getParent()); + invalidatePathData(); + } else { + // selection was a path (os-dir or date) + mLastSelectedAlbumDir = null; + FotoSql.set(mFilter, selectedAbsolutePath, queryTypeId); + if (queryTypeId == FotoSql.QUERY_TYPE_GROUP_ALBUM) { + mGalleryFilterPathState.setLastPath(selectedAbsolutePath); + } + toGui(mFilter); + } + } - FotoSql.set(mFilter,selectedAbsolutePath, queryTypeId); - toGui(mFilter); + private void invalidatePathData() { + getOrCreateDirInfo(FotoSql.QUERY_TYPE_GROUP_ALBUM).directoryRoot = null; + getOrCreateDirInfo(FotoSql.QUERY_TYPE_GALLERY).directoryRoot = null; } /** interface DirectoryPickerFragment.invalidateDirectories not used */ @@ -794,7 +904,13 @@ public void invalidateDirectories(String why) { /** interface DirectoryPickerFragment.OnDirectoryInteractionListener not used */ @Override - public void onDirectoryCancel(int queryTypeId) {} + public void onDirectoryCancel(int queryTypeId) {closeDialogIfNeeded();} + + @Override + protected void closeDialogIfNeeded() { + super.closeDialogIfNeeded(); + mDirPicker = null; + } /** interface DirectoryPickerFragment.OnDirectoryInteractionListener not used */ @Override diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/GalleryFilterPathState.java b/app/src/main/java/de/k3b/android/androFotoFinder/GalleryFilterPathState.java new file mode 100644 index 00000000..815f4bd5 --- /dev/null +++ b/app/src/main/java/de/k3b/android/androFotoFinder/GalleryFilterPathState.java @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2018 by k3b. + * + * This file is part of AndroFotoFinder / #APhotoManager. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see + */ + +package de.k3b.android.androFotoFinder; + +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.net.Uri; +import android.os.Bundle; +import android.os.Environment; +import android.preference.PreferenceManager; + +import java.io.File; + +import de.k3b.android.util.IntentUtil; +import de.k3b.io.AlbumFile; +import de.k3b.io.FileUtils; +import de.k3b.io.StringUtils; + +/** + * Created by k3b on 09.08.2018. + */ +public class GalleryFilterPathState { + private static final String KEY_LastAlbum = "GalleryFilterActivity-LastAlbum"; + private static final String KEY_CurrentAlbum = "GalleryFilterActivity-CurrentAlbum"; + private static final String KEY_LastFilterPath = "GalleryFilterActivity-LastFilterPath"; + + /* + Concept path currend + - (1) settings-lastAlbum = dir for "save album as" + loaded-from/saved-to settings + default for save album as + - (2) bundle-currentAlbum (that will be saved on exit) + initially set from intent-uri + overwrites (1) + modified by "save as" + saved/loaded from bundle + - (3) settings-last used "filtered path-directory" + loaded-from/saved-to settings + default for get filter path + - (4) data-current filtered path-directory + overwrites (3) (or 1 if album) + */ + private Uri mLastAlbum = null; + private Uri mCurrentAlbum = null; + private String mLastFilterPath = null; + + public GalleryFilterPathState load(Context context, Intent intent, Bundle savedInstanceState) { + if (context != null) { + SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context); + mLastAlbum = get(sharedPref.getString(KEY_LastAlbum, null), mLastAlbum); + mLastFilterPath = sharedPref.getString(KEY_LastFilterPath, mLastFilterPath); + + } + if (savedInstanceState != null) { + mCurrentAlbum = get(savedInstanceState.getString(KEY_CurrentAlbum), mCurrentAlbum); + } + if (intent != null) { + if (mCurrentAlbum == null) { + Uri uri = IntentUtil.getUri(intent); + if ((uri != null) && AlbumFile.isQueryFile(uri.getPath())) { + mCurrentAlbum = uri; + mLastAlbum = uri; + + } + } + } + return this; + } + + public GalleryFilterPathState save(Context context, Bundle savedInstanceState) { + saveAsPreference(context, this.mLastAlbum, this.mLastFilterPath); + + if (savedInstanceState != null) { + if (mCurrentAlbum != null) + savedInstanceState.putString(KEY_CurrentAlbum, mCurrentAlbum.toString()); + } + return this; + } + + public static void saveAsPreference(Context context, Uri lastAlbum, String lastFilterPath) { + if (context != null) { + SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context); + SharedPreferences.Editor editor = sharedPref.edit(); + if (lastFilterPath != null) editor.putString(KEY_LastFilterPath, lastFilterPath); + if (lastAlbum != null) editor.putString(KEY_LastAlbum, lastAlbum.toString()); + editor.commit(); + } + } + + private Uri get(String str, Uri defaultValue) { + Uri uri = null; + if (str != null) uri = Uri.parse(str); + if (uri != null) return uri; + return defaultValue; + } + + public Uri getAlbumDefault() { + return mLastAlbum; + } + + public File getSaveAlbumAs(String newFilePrefix, String newFileSuffix) { + if (mCurrentAlbum != null) return getFile(mCurrentAlbum); + File parentDir = getExistingParentDirFile(this.mLastAlbum); + if (parentDir == null) { + parentDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM); + } + return FileUtils.getFirstNonExistingFile(parentDir, newFilePrefix, 0, newFileSuffix); + } + + public static File getFile(Uri uri) { + if (uri != null) { + String path = FileUtils.fixPath(uri.getPath()); + if (path != null) { + return new File(path); + } + } + return null; + } + + public static File getExistingParentDirFile(Uri uri) { + return FileUtils.getFirstExistingDir(getFile(uri)); + } + + public void setAlbum(Uri album) { + this.mLastAlbum = album; + this.mCurrentAlbum = album; + } + + public Uri getCurrentAlbum() { + return mCurrentAlbum; + } + + public String getPathDefault(String currentPath) { + if (!StringUtils.isNullOrEmpty(currentPath)) return currentPath; + return mLastFilterPath; + } + + public void setLastPath(String mLastFilterPath) { + this.mLastFilterPath = mLastFilterPath; + } +} diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/LockScreen.java b/app/src/main/java/de/k3b/android/androFotoFinder/LockScreen.java index 34efc206..7106517b 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/LockScreen.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/LockScreen.java @@ -27,6 +27,8 @@ import android.view.Menu; import android.view.MenuItem; +import de.k3b.android.util.MenuUtils; + /** * #105: Management of app locking (aka Android "Screen"-pinning, "Kiosk Mode", "LockTask") * https://developer.android.com/about/versions/android-5.0.html#ScreenPinning. @@ -75,13 +77,12 @@ public static boolean isLocked(Context ctx) { return Global.locked; } - public static void fixMenu(Menu menu) { - if ((menu != null) && OS_APPLOCK_ENABLED) { - MenuItem unlock = menu.findItem(R.id.cmd_app_unpin2); - if (unlock != null) menu.removeItem(R.id.cmd_app_unpin2); - - menu.removeItem(R.id.cmd_show_geo); - menu.removeItem(R.id.cmd_gallery); + public static void removeDangerousCommandsFromMenu(Menu menu) { + if (OS_APPLOCK_ENABLED) { + // use os-unlock method instead + MenuUtils.removeItems(menu,R.id.cmd_app_unpin2); } + MenuUtils.removeItems(menu,R.id.cmd_show_geo, + R.id.cmd_gallery, R.id.cmd_filter); } } diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/SettingsActivity.java b/app/src/main/java/de/k3b/android/androFotoFinder/SettingsActivity.java index 4f8584ce..3a671c02 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/SettingsActivity.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/SettingsActivity.java @@ -150,6 +150,8 @@ public static void global2Prefs(Context context) { prefs.putBoolean("debugEnabledMemory", Global.debugEnabledMemory); + prefs.putBoolean("datePickerUseDecade", FotoLibGlobal.datePickerUseDecade); + prefs.putBoolean("debugEnabledJpgMetaIo", FotoLibGlobal.debugEnabledJpgMetaIo); prefs.putBoolean("debugEnabledJpg", FotoLibGlobal.debugEnabledJpg); @@ -204,6 +206,8 @@ public static void prefs2Global(Context context) { Global.debugEnabledMemory = getPref(prefs, "debugEnabledMemory", Global.debugEnabledMemory); + FotoLibGlobal.datePickerUseDecade = getPref(prefs, "datePickerUseDecade", FotoLibGlobal.datePickerUseDecade); + Global.locked = getPref(prefs, "locked", Global.locked); Global.passwordHash = getPref(prefs, "passwordHash", Global.passwordHash); diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/VirtualAlbumController.java b/app/src/main/java/de/k3b/android/androFotoFinder/VirtualAlbumController.java new file mode 100644 index 00000000..342a5404 --- /dev/null +++ b/app/src/main/java/de/k3b/android/androFotoFinder/VirtualAlbumController.java @@ -0,0 +1,87 @@ +package de.k3b.android.androFotoFinder; + +import android.app.Activity; +import android.app.DialogFragment; +import android.app.FragmentManager; + +import java.io.File; + +import de.k3b.android.androFotoFinder.directory.SaveAsPickerFragment; +import de.k3b.android.androFotoFinder.queries.AndroidAlbumUtils; +import de.k3b.android.widget.Dialogs; +import de.k3b.database.QueryParameter; + +/** + * Encapsulates logic to handle virtual albums (former known as Bookmarks) + * + * Created by k3b on 21.05.2018. + */ + +public class VirtualAlbumController { + public static final String DLG_SAVE_ALBUM_AS = "GalleryFilterActivitySaveAs"; + private final Activity mContext; + + public VirtualAlbumController(Activity context) { + super(); + mContext = context; + } + + public static class SaveAs extends SaveAsPickerFragment { + private final VirtualAlbumController mVirtualAlbumController; + private final QueryParameter mCurrentFilter; + + // only needed to prevent crash on rotation + public SaveAs() {this(null, null, null);} + public SaveAs(VirtualAlbumController virtualAlbumController, final File valbum, final QueryParameter currentFilter) { + super(valbum); + this.mVirtualAlbumController = virtualAlbumController; + this.mCurrentFilter = currentFilter; + } + @Override + protected void onFilePick(File pickedOrCreatedFile) { + if (mVirtualAlbumController != null) mVirtualAlbumController.onSaveAsVirutalAlbumAnswer(pickedOrCreatedFile, mCurrentFilter); + } + } + // workflow onSaveAsVirutalAlbumQuestion-onSaveAsVirutalAlbumAnswer-onSaveAsVirutalAlbumAllowOverwriteAnswer + public DialogFragment onSaveAsVirutalAlbumQuestion(final File valbum, final QueryParameter currentFilter) { + SaveAs dirDialog = new SaveAs(this, valbum, currentFilter); + + final FragmentManager manager = this.mContext.getFragmentManager(); + dirDialog.show(manager, DLG_SAVE_ALBUM_AS); + return dirDialog; + } + + // workflow onSaveAsVirutalAlbumQuestion-onSaveAsVirutalAlbumAnswer-onSaveAsVirutalAlbumAllowOverwriteAnswer + private void onSaveAsVirutalAlbumAnswer(final File valbum, final QueryParameter currentFilter) { + if (mustAskOverwrite(valbum)) { + Dialogs dialog = new Dialogs() { + @Override + protected void onDialogResult(String result, Object... parameters) { + if (result != null) { + // yes, overwrite + onSaveAsVirutalAlbumAllowOverwriteAnswer(valbum, currentFilter); + } else { + // no, do not overwrite, ask again + onSaveAsVirutalAlbumQuestion(valbum, currentFilter); + } + } + }; + dialog.yesNoQuestion(this.mContext, this.mContext.getString(R.string.overwrite_question_title) , + this.mContext.getString(R.string.image_err_file_exists_format, valbum.getAbsolutePath())); + + } else { + // does not exist yet + onSaveAsVirutalAlbumAllowOverwriteAnswer(valbum, currentFilter); + } + } + + private boolean mustAskOverwrite(File valbum) { + if (valbum.exists()) return true; + return false; + } + + // workflow onSaveAsVirutalAlbumQuestion-onSaveAsVirutalAlbumAnswer-onSaveAsVirutalAlbumAllowOverwriteAnswer + private void onSaveAsVirutalAlbumAllowOverwriteAnswer(File valbum, final QueryParameter currentFilter) { + AndroidAlbumUtils.saveAs(mContext, valbum, currentFilter); + } +} diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/directory/DirectoryListAdapter.java b/app/src/main/java/de/k3b/android/androFotoFinder/directory/DirectoryListAdapter.java index 21c05b24..2448ac65 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/directory/DirectoryListAdapter.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/directory/DirectoryListAdapter.java @@ -32,7 +32,9 @@ import de.k3b.android.androFotoFinder.Global; import de.k3b.android.androFotoFinder.R; +import de.k3b.io.AlbumFile; import de.k3b.io.Directory; +import de.k3b.io.FileUtils; import de.k3b.io.IDirectory; import de.k3b.io.IExpandableListViewNavigation; @@ -186,6 +188,15 @@ public static Spanned getDirectoryDisplayText(String prefix, IDirectory director case IDirectory.DIR_FLAG_APM_DIR: formatPrefix = IDirectory.APM_DIR_PREFIX; break; + case IDirectory.DIR_FLAG_VIRTUAL_DIR: + if ((options & Directory.OPT_AS_HTML) != 0) { + formatPrefix = "{"; + formatSuffix = "}"; + } else { + formatPrefix = "{"; + formatSuffix = "}"; + } + break; case IDirectory.DIR_FLAG_NONE: if ((options & Directory.OPT_AS_HTML) != 0) { formatPrefix = ""; @@ -198,7 +209,11 @@ public static Spanned getDirectoryDisplayText(String prefix, IDirectory director if (prefix != null) result.append(prefix); result.append(formatPrefix); - result.append(directory.getRelPath()).append(" "); + String dirName = directory.getRelPath(); + if (dirName.endsWith(AlbumFile.SUFFIX_VALBUM)) { + dirName = dirName.substring(0, dirName.length() - AlbumFile.SUFFIX_VALBUM.length()); + } + result.append(dirName).append(" "); result.append(formatSuffix); Directory.appendCount(result, directory, options); diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/directory/DirectoryLoaderTask.java b/app/src/main/java/de/k3b/android/androFotoFinder/directory/DirectoryLoaderTask.java index 180804cb..8933b1cd 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/directory/DirectoryLoaderTask.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/directory/DirectoryLoaderTask.java @@ -61,14 +61,16 @@ public class DirectoryLoaderTask extends AsyncTask= 0) ? cursor.getString(colText) : getLatLonPath(cursor.getDouble(colLat), cursor.getDouble(colLon)); - if (path != null) { - if (colCount != -1) markerItemCount = cursor.getInt(colCount); - builder.add(path, markerItemCount, cursor.getInt(colIconID)); - itemCount++; - if ((--increment) <= 0) { - publishProgress(itemCount, expectedCount); - increment = PROGRESS_INCREMENT; - - // Escape early if cancel() is called - if (isCancelled()) break; - } + if ((colText == -1) && ((colLat == -1) || (colLon == -1))) { + throw new IllegalArgumentException("Missing SQL Column. Need either " + + FotoSql.SQL_COL_DISPLAY_TEXT + + " or " + FotoSql.SQL_COL_LAT + + " + " + FotoSql.SQL_COL_LON); } - } - if (mStatus != null) { - mStatus.append("\n\tfound ").append(itemCount).append(" db rows"); - } - IDirectory result = builder.getRoot(); - if (colText < 0) { - compressLatLon(result); - } - return result; - } catch (Exception ex) { - mException = ex; - if (mStatus != null) { - mStatus.append("\n\t").append(ex.getMessage()); - } - return null; - } finally { - if (cursor != null) { - cursor.close(); - } - if (mStatus != null) { - if (Global.debugEnabledSql) { - Log.w(Global.LOG_CONTEXT, mStatus.toString()); - } else if (Global.debugEnabled) { - Log.i(Global.LOG_CONTEXT, mStatus.toString()); + while (cursor.moveToNext()) { + String path = (colText >= 0) ? cursor.getString(colText) : getLatLonPath(cursor.getDouble(colLat), cursor.getDouble(colLon)); + if (path != null) { + if (colCount != -1) markerItemCount = cursor.getInt(colCount); + final int iconID = cursor.getInt(colIconID); + addItem(builder, path, markerItemCount, iconID); + itemCount++; + if ((--increment) <= 0) { + publishProgress(itemCount, expectedCount); + increment = PROGRESS_INCREMENT; + + // Escape early if cancel() is called + if (isCancelled()) break; + } + } + } + if (mStatus != null) { + mStatus.append("\n\tfound ").append(itemCount).append(" db rows"); + } + } catch (Exception ex) { + mException = ex; + if (mStatus != null) { + mStatus.append("\n\t").append(ex.getMessage()); + } + return null; + } finally { + if (cursor != null) { + cursor.close(); + cursor = null; + } + if (mStatus != null) { + if (Global.debugEnabledSql) { + Log.w(Global.LOG_CONTEXT, mStatus.toString()); + } else if (Global.debugEnabled) { + Log.i(Global.LOG_CONTEXT, mStatus.toString()); + } } + } + } + IDirectory result = builder.getRoot(); + if (colText < 0) { + compressLatLon(result); + } + return result; + } + + protected void addItem(DirectoryBuilder builder, String path, int markerItemCount, int iconID) { + if (path != null) { + String decade = (datePickerUseDecade) ? DirectoryFormatter.getDecade(path,1) : null; + String newPath = (decade != null) + ? ("/" + decade + path) + : path; + builder.add(newPath, markerItemCount, iconID); } } diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/directory/DirectoryPickerFragment.java b/app/src/main/java/de/k3b/android/androFotoFinder/directory/DirectoryPickerFragment.java index 6e0a5449..506892cc 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/directory/DirectoryPickerFragment.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/directory/DirectoryPickerFragment.java @@ -26,11 +26,13 @@ import android.content.ContentValues; import android.content.Context; import android.content.Intent; +import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.support.annotation.NonNull; import android.util.Log; import android.view.LayoutInflater; +import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; @@ -51,6 +53,7 @@ import de.k3b.android.androFotoFinder.ThumbNailUtils; import de.k3b.android.androFotoFinder.imagedetail.ImageDetailActivityViewPager; import de.k3b.android.androFotoFinder.imagedetail.ImageDetailMetaDialogBuilder; +import de.k3b.android.androFotoFinder.queries.AndroidAlbumUtils; import de.k3b.android.androFotoFinder.queries.FotoSql; import de.k3b.android.androFotoFinder.queries.FotoThumbSql; import de.k3b.android.androFotoFinder.queries.FotoViewerParameter; @@ -60,9 +63,13 @@ import de.k3b.android.util.AndroidFileCommands; import de.k3b.android.util.ClipboardUtil; import de.k3b.android.util.FileManagerUtil; +import de.k3b.android.util.IntentUtil; import de.k3b.android.util.MediaScanner; import de.k3b.android.widget.Dialogs; import de.k3b.database.QueryParameter; +import de.k3b.io.AlbumFile; +import de.k3b.io.ListUtils; +import de.k3b.io.VISIBILITY; import de.k3b.io.collections.SelectedFiles; import de.k3b.io.Directory; import de.k3b.io.DirectoryNavigator; @@ -75,7 +82,6 @@ import java.io.File; import java.util.HashMap; import java.util.List; -// import static android.view.MenuItem.SHOW_AS_ACTION_NEVER; /** * A fragment with a Listing of Directories to be picked. @@ -130,7 +136,9 @@ protected void onProgressUpdate(Integer... values) { private static final java.lang.String INSTANCE_STATE_CONTEXT_MENU = "contextmenu"; // public state - private IDirectory mCurrentSelection = null; + protected IDirectory mCurrentSelection = null; + + private IDirectory mLastPopUpSelection = null; // Layout private HorizontalScrollView mParentPathBarScroller; @@ -145,12 +153,13 @@ protected void onProgressUpdate(Integer... values) { // local data protected Activity mContext; private DirectoryListAdapter mAdapter; - private DirectoryNavigator mNavigation; + protected DirectoryNavigator mNavigation; private int mDirTypId = 0; protected int mTitleId = 0; // api to fragment owner or null private OnDirectoryInteractionListener mDirectoryListener = null; + private OnDirectoryPickListener mDirectoryPickListener = null; // for debugging private static int id = 1; @@ -186,6 +195,10 @@ public DirectoryPickerFragment setContextMenuId(int contextMenuId) { return this; } + public IDirectory getLastPopUpSelection() { + return mLastPopUpSelection; + } + /****** live cycle ********/ @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, @@ -329,36 +342,36 @@ private PopupMenu onCreatePopupMenu(View anchor, IDirectory selection) { PopupMenu popup = new PopupMenu(getActivity(), anchor); popup.setOnMenuItemClickListener(popUpListener); MenuInflater inflater = popup.getMenuInflater(); - inflater.inflate(this.mContextMenue, popup.getMenu()); + Menu menu = popup.getMenu(); + inflater.inflate(this.mContextMenue, menu); if (selection != null) { - mPopUpSelection = selection; - MenuItem menuItem = popup.getMenu().findItem(R.id.cmd_fix_link); String absoluteSelectedPath = selection.getAbsolute(); - if ((menuItem != null) && (FileUtils.isSymlinkDir(new File(absoluteSelectedPath), false))) { - menuItem.setVisible(true); - } + boolean isAlbumFile = AlbumFile.isQueryFile(absoluteSelectedPath); + mPopUpSelection = selection; - menuItem = popup.getMenu().findItem(R.id.cmd_folder_hide_images); - if ((menuItem != null) && MediaScanner.canHideFolderMedia(absoluteSelectedPath)) { - menuItem.setVisible(true); - } + setMenuVisibility(menu, R.id.cmd_fix_link, FileUtils.isSymlinkDir(new File(absoluteSelectedPath), false)); + setMenuVisibility(menu, R.id.cmd_folder_hide_images, !isAlbumFile && MediaScanner.canHideFolderMedia(absoluteSelectedPath)); - if (!FotoLibGlobal.apmEnabled) { - menuItem = popup.getMenu().findItem(R.id.cmd_apm_edit); - if (menuItem != null) { - menuItem.setVisible(false); - } - } - menuItem = popup.getMenu().findItem(R.id.cmd_filemanager); - if ((menuItem != null) && !FileManagerUtil.hasShowInFilemanager(getActivity(), absoluteSelectedPath)) { - // no filemanager installed - menuItem.setVisible(false); - } + setMenuVisibility(menu, R.id.cmd_apm_edit, !isAlbumFile && FotoLibGlobal.apmEnabled); + + setMenuVisibility(menu, R.id.cmd_filemanager, !isAlbumFile && FileManagerUtil.hasShowInFilemanager(getActivity(), absoluteSelectedPath)); + setMenuVisibility(menu, R.id.cmd_delete, isAlbumFile); + setMenuVisibility(menu, R.id.action_edit, isAlbumFile); + + setMenuVisibility(menu, android.R.id.copy, !isAlbumFile); } return popup; } + private void setMenuVisibility(Menu menu, int menuId, boolean visibility) { + MenuItem menuItem = menu.findItem(menuId); + if (menuItem != null) { + menuItem.setVisible(visibility); + } + + } + private IDirectory mPopUpSelection = null; private final PopupMenu.OnMenuItemClickListener popUpListener = new PopupMenu.OnMenuItemClickListener() { @Override @@ -368,6 +381,7 @@ public boolean onMenuItemClick(MenuItem menuItem) { }; protected boolean onPopUpClick(MenuItem menuItem, IDirectory popUpSelection) { + this.mLastPopUpSelection = popUpSelection; switch (menuItem.getItemId()) { case R.id.cmd_mk_dir: return onCreateSubDirQuestion(popUpSelection); @@ -378,6 +392,11 @@ protected boolean onPopUpClick(MenuItem menuItem, IDirectory popUpSelection) { case R.id.menu_item_rename: return onRenameDirQuestion(popUpSelection); + case R.id.cmd_delete: + return onDeleteDirQuestion(popUpSelection); + + case R.id.action_edit: + return onEdit(popUpSelection); case R.id.cmd_photo: return showPhoto(popUpSelection); @@ -402,10 +421,16 @@ protected boolean onPopUpClick(MenuItem menuItem, IDirectory popUpSelection) { if ((requestCode == R.id.cmd_apm_edit) && (resultCode == Activity.RESULT_OK) && (mPopUpSelection != null)) { // autoprocessing status may have changed: refresh data and gui mPopUpSelection.refresh(); - this.mAdapter.notifyDataSetChanged(); + // notifyDataSetChanged(); + mAdapter = null; + reloadTreeViewIfAvailable(); } } + public void notifyDataSetChanged() { + if (this.mAdapter != null) this.mAdapter.notifyDataSetChanged(); + } + private boolean onCopy(IDirectory selection) { String path = (selection == null) ? null : selection.getAbsolute(); return ClipboardUtil.addDirToClipboard(this.getActivity(), path); @@ -414,7 +439,7 @@ private boolean onCopy(IDirectory selection) { private boolean onEditApm(IDirectory selection) { String path = (selection == null) ? null : selection.getAbsolute(); if (!StringUtils.isNullOrEmpty(path)) { - PhotoAutoprocessingEditActivity.showActivity(getActivity(), null, path, this.getSrcFotos(), R.id.cmd_apm_edit); + PhotoAutoprocessingEditActivity.showActivity(getActivity(), null, path, getSrcFotos(), R.id.cmd_apm_edit); return true; } return false; @@ -439,6 +464,69 @@ protected void onDialogResult(String result, Object[] parameters) { return true; } + private boolean onEdit(IDirectory dir) { + if (dir != null) { + File file = FileUtils.tryGetCanonicalFile(dir.getAbsolute()); + Intent sendIntent = new Intent(); + IntentUtil.setDataAndTypeAndNormalize(sendIntent, Uri.fromFile(file), "text/plain"); + sendIntent.setAction(Intent.ACTION_EDIT); + mContext.startActivity(sendIntent); + return true; + } + return false; + } + + private boolean onDeleteDirQuestion(final IDirectory parentDir) { + Dialogs dlg = new Dialogs() { + @Override protected void onDialogResult(String result, Object[] parameters) { + if (result != null) { + if (parentDir != null) { + File parentFile = FileUtils.tryGetCanonicalFile(parentDir.getAbsolute()); + if (parentFile != null) { + onDeleteAnswer(parentFile, parentDir); + } + } + } + } + }; + + dlg.yesNoQuestion(mContext, parentDir.getRelPath(), mContext.getString(R.string.bookmark_delete_question)); + return true; + } + + private void onDeleteAnswer(File file, IDirectory dir) { + boolean deleteSuccess = false; + + // delete from filesystem + if (file.exists() && file.delete()) { + deleteSuccess = true; + } + + // delete from database + if (FotoSql.deleteMedia("delete album", getActivity(), + ListUtils.toStringList(file.getAbsolutePath()),false) > 0) { + deleteSuccess = true; + } + + // delete from dir tree + IDirectory parent = (dir != null) ? dir.getParent() :null; + if (parent != null) { + parent.getChildren().remove(dir); + dir.destroy(); + } + + String message = (deleteSuccess) + ? mContext.getString(R.string.bookmark_delete_answer_format, file.getAbsoluteFile() ) + : mContext.getString(R.string.bookmark_delete_error_format, file.getAbsoluteFile() ); + Toast.makeText(mContext, + message, + Toast.LENGTH_LONG).show(); + Log.d(Global.LOG_CONTEXT, message); + + // notifyDataSetChanged(); + mAdapter = null; + reloadTreeViewIfAvailable(); + } private boolean onRenameDirQuestion(final IDirectory parentDir) { if (parentDir != null) { File parentFile = FileUtils.tryGetCanonicalFile(parentDir.getAbsolute()); @@ -478,7 +566,7 @@ private void onRenameDirAnswer(final IDirectory srcDir, String newFolderName) { } else { // update dirpicker srcDir.rename(srcDirFile.getName(), newFolderName); - this.mAdapter.notifyDataSetChanged(); + notifyDataSetChanged(); } } @@ -534,11 +622,11 @@ private boolean fixLinks(IDirectory linkDir) { if (!canonicalPath.endsWith("/")) canonicalPath+="/"; String sqlWhereLink = FotoSql.SQL_COL_PATH + " like '" + linkPath + "%'"; - SelectedFiles linkFiles = FotoSql.getSelectedfiles(context, sqlWhereLink); + SelectedFiles linkFiles = FotoSql.getSelectedfiles(context, sqlWhereLink, VISIBILITY.PRIVATE_PUBLIC); String sqlWhereCanonical = FotoSql.SQL_COL_PATH + " in (" + linkFiles.toString() + ")"; sqlWhereCanonical = sqlWhereCanonical.replace(linkPath,canonicalPath); - SelectedFiles canonicalFiles = FotoSql.getSelectedfiles(context, sqlWhereCanonical); + SelectedFiles canonicalFiles = FotoSql.getSelectedfiles(context, sqlWhereCanonical, VISIBILITY.PRIVATE_PUBLIC); HashMap link2canonical = new HashMap(); for(String cann : canonicalFiles.getFileNames()) { link2canonical.put(linkPath + cann.substring(canonicalPath.length()), cann); @@ -592,17 +680,27 @@ private boolean showDirInfo(IDirectory selectedDir) { return false; } - private boolean showPhoto(IDirectory selectedDir) { + private QueryParameter getSelectionQuery(String dbgContext, IDirectory selectedDir) { String pathFilter = (selectedDir != null) ? selectedDir.getAbsolute() : null; + QueryParameter query = null; if (pathFilter != null) { - GalleryFilterParameter filter = new GalleryFilterParameter(); //.setPath(pathFilter); - if (!FotoSql.set(filter, pathFilter, mDirTypId)) - { - filter.setPath(pathFilter + "/%"); + query = AndroidAlbumUtils.getQueryFromUri(dbgContext, getActivity(), Uri.fromFile(new File(pathFilter)), null); + + if (query == null) { + GalleryFilterParameter filter = new GalleryFilterParameter(); //.setPath(pathFilter); + if (!FotoSql.set(filter, pathFilter, mDirTypId)) { + filter.setPath(pathFilter + "/%"); + } + + query = TagSql.filter2NewQuery(filter); } + } + return query; - QueryParameter query = new QueryParameter(); - TagSql.filter2QueryEx(query, filter, true); + } + private boolean showPhoto(IDirectory selectedDir) { + QueryParameter query = getSelectionQuery("showPhoto", selectedDir); + if (query != null) { FotoSql.setSort(query, FotoSql.SORT_BY_DATE, false); ImageDetailActivityViewPager.showActivity(this.getActivity(), null, 0, query, 0); return true; @@ -611,15 +709,9 @@ private boolean showPhoto(IDirectory selectedDir) { } private boolean showGallery(IDirectory selectedDir) { - String pathFilter = (selectedDir != null) ? selectedDir.getAbsolute() : null; - if (pathFilter != null) { - GalleryFilterParameter filter = new GalleryFilterParameter(); //.setPath(pathFilter); - if (!FotoSql.set(filter, pathFilter, mDirTypId)) - { - filter.setPath(pathFilter + "/%"); - } - - FotoGalleryActivity.showActivity(this.getActivity(), filter, null, 0); + QueryParameter query = getSelectionQuery("showGallery", selectedDir); + if (query != null) { + FotoGalleryActivity.showActivity(this.getActivity(), query, 0); return true; } return false; @@ -641,8 +733,8 @@ private void onDirectoryCancel() { protected void onDirectoryPick(IDirectory selection) { closeAll(); Log.d(Global.LOG_CONTEXT, debugPrefix + "onDirectoryPick: " + selection); - if ((mDirectoryListener != null) && (selection != null)) { - mDirectoryListener.onDirectoryPick(selection.getAbsolute() + if ((mDirectoryPickListener != null) && (selection != null)) { + mDirectoryPickListener.onDirectoryPick(selection.getAbsolute() , mDirTypId); dismiss(); } @@ -674,6 +766,7 @@ public void closeAll() { } @Override public void onDestroy() { + mLastPopUpSelection = null; closeAll(); super.onDestroy(); // RefWatcher refWatcher = AndroFotoFinderApp.getRefWatcher(getActivity()); @@ -688,7 +781,13 @@ public void onAttach(Activity activity) { protected void setDirectoryListener(Activity activity) { try { - mDirectoryListener = (OnDirectoryInteractionListener) activity; + if ((activity == null) || activity instanceof OnDirectoryInteractionListener) { + mDirectoryListener = (OnDirectoryInteractionListener) activity; + } + + if ((activity == null) || activity instanceof OnDirectoryPickListener) { + mDirectoryPickListener = (OnDirectoryPickListener) activity; + } } catch (ClassCastException e) { throw new ClassCastException(activity.toString() + " must implement OnDirectoryInteractionListener"); @@ -752,11 +851,9 @@ protected String getStatusErrorMessage(String path) { } private void updateStatus() { - int itemCount = getItemCount(mCurrentSelection); - String selectedPath = (this.mCurrentSelection != null) ? this.mCurrentSelection.getAbsolute() : null; - String statusMessage = (itemCount == 0) ? mContext.getString(R.string.selection_none_hint) : getStatusErrorMessage(selectedPath); + String statusMessage = (!isPickable(mCurrentSelection)) ? mContext.getString(R.string.selection_none_hint) : getStatusErrorMessage(selectedPath); boolean canPressOk = (statusMessage == null); if (mCmdOk != null) mCmdOk.setEnabled(canPressOk); @@ -765,6 +862,14 @@ private void updateStatus() { setStatusMessage(statusMessage); } + /** decides if an item can be picked */ + protected boolean isPickable(IDirectory selection) { + if (selection == null) return false; + if (AlbumFile.isQueryFile (selection.getRelPath())) return true; + int itemCount = getItemCount(selection); + return (itemCount > 0); + } + private void setStatusMessage(String statusMessage) { if (mStatus != null) { if (statusMessage == null) { @@ -787,7 +892,7 @@ private int getItemCount(IDirectory _directory) { /*********************** local helper *******************************************/ private void updateParentPathBar(String currentSelection) { if (this.mNavigation != null) { - updateParentPathBar(this.mNavigation.getRoot().find(currentSelection)); + updateParentPathBar(getSelectedDir(currentSelection)); } } @@ -864,6 +969,11 @@ public void defineDirectoryNavigation(IDirectory root, int dirTypId, String init navigateTo(initialAbsolutePath); } + public IDirectory getRoot() { + if (mNavigation != null) return mNavigation.getRoot(); + return null; + } + /** refreshLocal tree to new newGrandParent by preserving selection */ private void navigateTo(int newGroupSelection, IDirectory newGrandParent) { if (newGrandParent != null) { @@ -888,7 +998,7 @@ public void navigateTo(String absolutePath) { } if ((mNavigation != null) && (absolutePath != null)) { - mCurrentSelection = mNavigation.getRoot().find(absolutePath); + mCurrentSelection = getSelectedDir(absolutePath); mNavigation.navigateTo(mCurrentSelection); } @@ -897,6 +1007,10 @@ public void navigateTo(String absolutePath) { reloadTreeViewIfAvailable(); } + protected IDirectory getSelectedDir(String absolutePath) { + return mNavigation.getRoot().find(absolutePath); + } + /** Does nothing if either OnCreate() or defineDirectoryNavigation() has NOT been called yet */ private boolean reloadTreeViewIfAvailable() { if ((mTreeView != null) && (mNavigation != null)) { @@ -922,6 +1036,11 @@ public SelectedFiles getSrcFotos() { return null; } + public interface OnDirectoryPickListener { + /** called when user picks a new directory */ + void onDirectoryPick(String selectedAbsolutePath, int queryTypeId); + } + /** * This interface must be implemented by activities that contain this * fragment to allow an interaction in this fragment to be communicated @@ -932,10 +1051,7 @@ public SelectedFiles getSrcFotos() { * "http://developer.android.com/training/basics/fragments/communicating.html" * >Communicating with Other Fragments for more information. */ - public interface OnDirectoryInteractionListener { - /** called when user picks a new directory */ - void onDirectoryPick(String selectedAbsolutePath, int queryTypeId); - + public interface OnDirectoryInteractionListener extends OnDirectoryPickListener { /** called when user cancels picking of a new directory */ void onDirectoryCancel(int queryTypeId); diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/directory/SaveAsPickerFragment.java b/app/src/main/java/de/k3b/android/androFotoFinder/directory/SaveAsPickerFragment.java new file mode 100644 index 00000000..ffcd0231 --- /dev/null +++ b/app/src/main/java/de/k3b/android/androFotoFinder/directory/SaveAsPickerFragment.java @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2018 by k3b. + * + * This file is part of AndroFotoFinder / #APhotoManager. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see + */ + +package de.k3b.android.androFotoFinder.directory; + +import android.app.Activity; +import android.net.Uri; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.EditText; + +import java.io.File; + +import de.k3b.android.androFotoFinder.AffUtils; +import de.k3b.android.androFotoFinder.R; +import de.k3b.android.androFotoFinder.imagedetail.ImageDetailActivityViewPager; +import de.k3b.android.androFotoFinder.queries.FotoSql; +import de.k3b.android.util.AndroidFileCommands; +import de.k3b.android.util.OsUtils; +import de.k3b.io.AlbumFile; +import de.k3b.io.FileUtils; +import de.k3b.io.IDirectory; +import de.k3b.io.OSDirOrVirtualAlbumFile; +import de.k3b.io.OSDirectory; +import de.k3b.io.StringUtils; +import de.k3b.io.collections.SelectedFiles; + +/** + * a picker with a fale name field and a directory picker. + * + * Created by k3b on 12.04.2018. + */ + +public abstract class SaveAsPickerFragment extends DirectoryPickerFragment { + // these get lost on screen rotation so dialog must be closed on screen rotation. + private EditText edName = null; + private File path = null; + private String extension = ".txt"; + + public SaveAsPickerFragment(File path) { + this.path = path; + if (path != null) { + this.extension = FileUtils.getExtension(path.getName()); + + OSDirectory root = OsUtils.getRootOSDirectory(new OSDirOrVirtualAlbumFile(null, null, null)); + this.defineDirectoryNavigation(root, FotoSql.QUERY_TYPE_UNDEFINED, FileUtils.tryGetCanonicalPath(path, null)); + } + } + + /* do not use activity callback */ + @Override + protected void setDirectoryListener(Activity activity) {/* do not use activity callback */} + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View result = super.onCreateView(inflater,container,savedInstanceState); + + result.findViewById(R.id.lblName).setVisibility(View.VISIBLE); + edName = (EditText) result.findViewById(R.id.edName); + edName.setVisibility(View.VISIBLE); + + if (path != null) { + edName.setText(FileUtils.replaceExtension(path.getName(), "")); + } + return result; + } + + public File getCurrentPath() { + if (mCurrentSelection == null) return null; + return new File(mCurrentSelection.getAbsolute(), edName.getText().toString() + extension ); + } + + /** + * To be overwritten to check if a path can be picked. + * + * @param path to be checked if it cannot be handled + * @return null if no error else error message with the reason why it cannot be selected + */ + @Override + protected String getStatusErrorMessage(String path) { + // writeprotected + // overwriteExisting + // ok (new file or original file name proposal + String errorMessage = null; + if (errorMessage != null) { + int pos = errorMessage.indexOf('\n'); + return (pos > 0) ? errorMessage.substring(0,pos) : errorMessage; + } + return super.getStatusErrorMessage(path); + } + + @Override + protected void onDirectoryPick(IDirectory selection) { + IDirectory result = null; + + String filenameWithoutExtension = StringUtils.trim(this.edName.getText()); + String fullPath = (selection == null) ? null : selection.getAbsolute(); + File sel = (StringUtils.isNullOrEmpty(fullPath)) ? null : new File(fullPath); + if (sel != null) { + if (sel.isFile()) { + result = selection; + } else if (sel.isDirectory() && !StringUtils.isNullOrEmpty(filenameWithoutExtension)) { + final String newName = filenameWithoutExtension + AlbumFile.SUFFIX_VALBUM; + final File newFile = new File(sel, newName); + result = selection.find(newFile.getAbsolutePath()); + if (result == null) { + result = selection.createOsDirectory(newFile, selection, null); + } + } + } + + if (result != null) { + // close dialog and return to caller + super.onDirectoryPick(result); + onFilePick(new File(result.getAbsolute())); + this.notifyDataSetChanged(); + dismiss(); + } + } + + /** decides if an item can be picked */ + protected boolean isPickable(IDirectory selection) { + // if ((selection != null) && AlbumFile.isQueryFile (selection.getRelPath()) ) return true; + return super.isPickable(selection); + } + + @Override + protected IDirectory getSelectedDir(String absolutePath) { + if (absolutePath == null) return null; + + File abs = new File(absolutePath); + if (!abs.isDirectory()) return super.getSelectedDir(abs.getParent()); + + return super.getSelectedDir(absolutePath); + } + + abstract protected void onFilePick(File pickedOrCreatedFile); +} diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/gallery/cursor/GalleryCursorFragment.java b/app/src/main/java/de/k3b/android/androFotoFinder/gallery/cursor/GalleryCursorFragment.java index 019951a6..6a9ba9d2 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/gallery/cursor/GalleryCursorFragment.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/gallery/cursor/GalleryCursorFragment.java @@ -19,12 +19,16 @@ package de.k3b.android.androFotoFinder.gallery.cursor; +import android.annotation.TargetApi; import android.app.Activity; import android.app.LoaderManager; +import android.content.ClipData; +import android.content.ContentResolver; import android.content.Intent; import android.content.Loader; import android.database.Cursor; import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.app.Fragment; import android.support.annotation.NonNull; @@ -68,6 +72,7 @@ import de.k3b.android.androFotoFinder.OnGalleryInteractionListener; import de.k3b.android.androFotoFinder.queries.Queryable; import de.k3b.android.androFotoFinder.queries.SqlJobTaskBase; +import de.k3b.android.androFotoFinder.tagDB.TagSql; import de.k3b.android.androFotoFinder.tagDB.TagTask; import de.k3b.android.androFotoFinder.tagDB.TagWorflow; import de.k3b.android.androFotoFinder.tagDB.TagsPickerFragment; @@ -76,8 +81,11 @@ import de.k3b.android.util.MediaScanner; import de.k3b.android.util.OsUtils; import de.k3b.android.util.ResourceUtils; +import de.k3b.android.widget.AboutDialogPreference; import de.k3b.android.widget.Dialogs; import de.k3b.database.QueryParameter; +import de.k3b.io.ListUtils; +import de.k3b.io.StringUtils; import de.k3b.io.VISIBILITY; import de.k3b.io.collections.SelectedFiles; import de.k3b.io.collections.SelectedItems; @@ -108,9 +116,11 @@ public class GalleryCursorFragment extends Fragment implements Queryable, Direc private static final String INSTANCE_STATE_SEL_ONLY = "selectedOnly"; private static final String INSTANCE_STATE_LOADER_ID = "loaderID"; - private static final int MODE_VIEW = 0; - private static final int MODE_PICK_SINGLE = 1; - private static final int MODE_PICK_MULTIBLE = 2; + private static final int MODE_VIEW_PICKER_NONE = 0; + private static final int MODE_VIEW_PICK_SINGLE = 1; + private static final int MODE_VIEW_PICK_MULTIBLE = 2; + + private static final boolean SUPPORT_MULTISELECTION = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN); private static int nextLoaderID = 100; private int loaderID = -1; @@ -151,9 +161,10 @@ public class GalleryCursorFragment extends Fragment implements Queryable, Direc private boolean mNoShareError = true; /** true pick geo; false pick image */ - private boolean mGetGeo = false; + private boolean mModePickGeoElsePickImaage = false; - private int mMode = MODE_VIEW; + /** one of the MODE_VIEW_PICKER_XXXX */ + private int mMode = MODE_VIEW_PICKER_NONE; private MoveOrCopyDestDirPicker mDestDirPicker = null; /**************** construction ******************/ @@ -366,10 +377,10 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, String action = (intent != null) ? intent.getAction() : null; if ((action != null) && ((Intent.ACTION_PICK.compareTo(action) == 0) || (Intent.ACTION_GET_CONTENT.compareTo(action) == 0))) { - this.mMode = (intent.getBooleanExtra(Intent.EXTRA_ALLOW_MULTIPLE,false)) ? MODE_PICK_MULTIBLE : MODE_PICK_SINGLE; + this.mMode = (intent.getBooleanExtra(Intent.EXTRA_ALLOW_MULTIPLE,false)) ? MODE_VIEW_PICK_MULTIBLE : MODE_VIEW_PICK_SINGLE; mMustReplaceMenue = true; String schema = intent.getScheme(); - mGetGeo = ((schema != null) && ("geo".compareTo(schema) == 0)); + mModePickGeoElsePickImaage = ((schema != null) && ("geo".compareTo(schema) == 0)); } String path = (intent == null) ? null : intent.getStringExtra(AffUtils.EXTRA_SELECTED_ITEM_PATHS); @@ -627,7 +638,7 @@ private void onOpenChildGallery(QueryParameter subGalleryQuery) { if (Global.debugEnabledSql) { Log.i(Global.LOG_CONTEXT, "Exec child gallery\n\t" + subGalleryQuery.toSqlString()); } - FotoGalleryActivity.showActivity(getActivity(), null, subGalleryQuery, 0); + FotoGalleryActivity.showActivity(getActivity(), subGalleryQuery, 0); } /****************** path navigation *************************/ @@ -758,6 +769,27 @@ private void startMultiSelectionMode() { getActivity().invalidateOptionsMenu(); } + /** + * Different menu modes + * * normal name searchbar-icon folder map (tags) (filter) menu + * * R.menu.menu_gallery_non_selected_only R.menu.menu_gallery_non_multiselect + * * selected selected cancel seleted-only share (save) menu + * * (Filter not available; this.isMultiSelectionActive(); + * * R.menu.menu_gallery_multiselect_mode_all R.menu.menu_image_commands + * * locked name lock folder map menu + * * (no multiselection, no base-filters) + * * (this.locked; R.menu.menu_locked) + * * searchbar bar cancel-searc-bar (folder) (map) (tags) (filter) menu + * * picker-locked + * * R.menu.menu_gallery_pick R.menu.menu_locked + * * picker-non-locked selected ok cancel filter settings + * * R.menu.menu_gallery_pick R.menu.menu_gallery_non_multiselect + * + * (xxxx) with "IFROOM" (in wide screen only) + * searchbarmode is like "normal" where there is no "IFROOM" on no-searchbar items + * + * @param menu + */ @Override public void onPrepareOptionsMenu(Menu menu) { super.onPrepareOptionsMenu(menu); @@ -769,12 +801,16 @@ public void onPrepareOptionsMenu(Menu menu) { mMustReplaceMenue = false; menu.clear(); mMenuRemoveAllSelected = null; - if (mMode == MODE_VIEW) { + if (mMode == MODE_VIEW_PICKER_NONE) { + // if (locked) { // view-locked - mSelectedItems.clear(); - inflater.inflate(R.menu.menu_gallery_locked, menu); - LockScreen.fixMenu(menu); - } else if (isMultiSelectionActive()) { // view-multiselect + if (isMultiSelectionActive()) { + clearSelections(); + } + inflater.inflate(R.menu.menu_locked, menu); + LockScreen.removeDangerousCommandsFromMenu(menu); + AboutDialogPreference.onPrepareOptionsMenu(getActivity(), menu); + } else if (this.isMultiSelectionActive()) { // view-multiselect inflater.inflate(R.menu.menu_gallery_multiselect_mode_all, menu); mShareOnlyToggle = menu.findItem(R.id.cmd_selected_only); @@ -796,14 +832,14 @@ public void onPrepareOptionsMenu(Menu menu) { } - } else { + } else { // picker mode inflater.inflate(R.menu.menu_gallery_pick, menu); - if (locked) { // pick-locked - mSelectedItems.clear(); - inflater.inflate(R.menu.menu_gallery_locked, menu); - } else { // pick-single/multible + if (!locked) { inflater.inflate(R.menu.menu_gallery_non_multiselect, menu); + } else { // pick-locked + LockScreen.removeDangerousCommandsFromMenu(menu); } + AboutDialogPreference.onPrepareOptionsMenu(getActivity(), menu); } mMenuRemoveAllSelected = menu.findItem(R.id.cmd_selection_remove_all); } @@ -852,7 +888,7 @@ public boolean onOptionsItemSelected(MenuItem menuItem) { case R.id.cmd_move: return cmdMoveOrCopyWithDestDirPicker(true, fileCommands.getLastCopyToPath(), selectedFiles); case R.id.cmd_show_geo: - MapGeoPickerActivity.showActivity(this.getActivity(), selectedFiles, null); + MapGeoPickerActivity.showActivity(this.getActivity(), selectedFiles, null, 0); return true; case R.id.cmd_edit_geo: GeoEditActivity.showActivity(this.getActivity(), selectedFiles, GeoEditActivity.RESULT_ID); @@ -883,14 +919,28 @@ public boolean onOptionsItemSelected(MenuItem menuItem) { private void cmdShowDetails() { SelectedItems ids = getSelectedItems(); - String files = ((ids != null) && (ids.size() > 0)) ? mAdapter.createSelectedFiles(this.getActivity(), ids).toString().replace(",","\n") : null; + + final Activity activity = this.getActivity(); + CharSequence subQueryTypName = (activity instanceof FotoGalleryActivity) + ? ((FotoGalleryActivity)activity).getValueAsTitle(true) + : null; + + String files = ((ids != null) && (ids.size() > 0)) ? mAdapter.createSelectedFiles(activity, ids).toString().replace(",","\n") : null; ImageDetailMetaDialogBuilder.createImageDetailDialog( - this.getActivity(), + activity, getActivity().getTitle().toString(), this.toString(), ids, files, - (mGalleryContentQuery != null) ? mGalleryContentQuery.toSqlString() : null + (mGalleryContentQuery != null) ? mGalleryContentQuery.toSqlString() : null, + StringUtils.appendMessage(null, + getString(R.string.show_photo), + TagSql.getCount(activity, mGalleryContentQuery)), + subQueryTypName, + (mGalleryContentQuery == null) ? null : StringUtils.appendMessage(null, + getString(R.string.show_photo), + TagSql.getCount(activity, mGalleryContentQuery)) + ).show(); } @@ -991,12 +1041,11 @@ protected void onDirectoryPick(IDirectory selection) { dismiss(); } }; - private boolean cmdMoveOrCopyWithDestDirPicker(final boolean move, String lastCopyToPath, final SelectedFiles fotos) { if (AndroidFileCommands.canProcessFile(this.getActivity(), false)) { mDestDirPicker = MoveOrCopyDestDirPicker.newInstance(move, fotos); - mDestDirPicker.defineDirectoryNavigation(OsUtils.getRootOSDirectory(), + mDestDirPicker.defineDirectoryNavigation(OsUtils.getRootOSDirectory(null), (move) ? FotoSql.QUERY_TYPE_GROUP_MOVE : FotoSql.QUERY_TYPE_GROUP_COPY, lastCopyToPath); mDestDirPicker.setContextMenuId(LockScreen.isLocked(this.getActivity()) ? 0 : R.menu.menu_context_osdir); @@ -1008,14 +1057,15 @@ private boolean cmdMoveOrCopyWithDestDirPicker(final boolean move, String lastCo private boolean onPickOk() { mDestDirPicker = null; Activity parent = getActivity(); - Uri resultUri = getSelectedUri(parent); + List resultUri = getSelectedUri(parent, "onPickOk"); - if (resultUri != null) { + if ((resultUri != null) && (resultUri.size() > 0)) { final Intent intent = new Intent(); - // permission result.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); - intent.setData(resultUri); - if (!mGetGeo) { + intent.setData(resultUri.get(0)); + addAsClip(intent, parent.getContentResolver(), resultUri); + if (!mModePickGeoElsePickImaage) { + // permission result.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); } @@ -1026,27 +1076,63 @@ private boolean onPickOk() { return true; } + @TargetApi(Build.VERSION_CODES.JELLY_BEAN) + private void addAsClip(Intent intent, ContentResolver contentResolver, List resultUri) { + if (SUPPORT_MULTISELECTION && (resultUri.size() > 1)) { + ClipData clip = null; + for (Uri uri : resultUri) { + ClipData clipData = ClipData.newUri(contentResolver, uri.toString(), uri); + if (clip == null) { + clip = clipData; + } else { + clip.addItem(clipData.getItemAt(0)); + } + intent.setClipData(clip); + } + } + } + @Nullable - private Uri getSelectedUri(Activity parent) { - Uri resultUri = null; + private List getSelectedUri(Activity parent, Object... dbgContext) { + StringBuilder debugMessage = StringUtils.createDebugMessage(Global.debugEnabledSql,mDebugPrefix,"getSelectedUri", dbgContext); + + List resultUri = null; SelectedItems items = getSelectedItems(); if ((items != null) && (items.size() > 0)) { long id = items.first(); - if (mGetGeo) { - IGeoPoint initialPoint = FotoSql.execGetPosition(parent, null, id); + if (resultUri == null) { + resultUri = new ArrayList(); + } + resultUri.add(getUri(parent, id)); + } + if (debugMessage != null) { - if (initialPoint != null) { - GeoUri PARSER = new GeoUri(GeoUri.OPT_PARSE_INFER_MISSING); + StringUtils.appendMessage(debugMessage, "result", + ListUtils.toString(" ", resultUri)); + Log.i(Global.LOG_CONTEXT, debugMessage.toString()); + } + return resultUri; + } - resultUri = Uri.parse(PARSER.toUriString(new GeoPointDto(initialPoint.getLatitude(),initialPoint.getLongitude(), IGeoPointInfo.NO_ZOOM))); - } + private Uri getUri(Activity parent, long id) { + Uri resultUri = null; + if (mModePickGeoElsePickImaage) { + // mode pick gep + IGeoPoint initialPoint = FotoSql.execGetPosition(null, parent, + null, id, mDebugPrefix, "getSelectedUri"); + if (initialPoint != null) { + GeoUri PARSER = new GeoUri(GeoUri.OPT_PARSE_INFER_MISSING); - } else { - resultUri = FotoSql.getUri(id); + resultUri = Uri.parse(PARSER.toUriString(new GeoPointDto(initialPoint.getLatitude(),initialPoint.getLongitude(), IGeoPointInfo.NO_ZOOM))); } + + + } else { + // mode pick image + resultUri = FotoSql.getUri(id); } return resultUri; } @@ -1316,7 +1402,7 @@ private void onDuplicatesFound(SelectedItems selectedItems, StringBuffer debugMe } private boolean isMultiSelectionActive() { - if (mMode != MODE_VIEW) return true; + if (mMode != MODE_VIEW_PICKER_NONE) return true; return !mSelectedItems.isEmpty(); } @@ -1344,7 +1430,7 @@ private void fix() { /** return true if included; false if excluded */ private boolean toggleSelection(long imageID) { boolean contains = mSelectedItems.contains(imageID); - if (mMode == MODE_PICK_SINGLE) { + if (mMode == MODE_VIEW_PICK_SINGLE) { clearSelections(); } if (contains) { diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/imagedetail/ImageDetailActivityViewPager.java b/app/src/main/java/de/k3b/android/androFotoFinder/imagedetail/ImageDetailActivityViewPager.java index a7d4bf1d..3745893f 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/imagedetail/ImageDetailActivityViewPager.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/imagedetail/ImageDetailActivityViewPager.java @@ -830,7 +830,7 @@ public boolean onCreateOptionsMenu(Menu menu) { private void defineMenu(Menu menu) { if (LockScreen.isLocked(this)) { getMenuInflater().inflate(R.menu.menu_image_detail_locked, menu); - LockScreen.fixMenu(menu); + LockScreen.removeDangerousCommandsFromMenu(menu); } else { getMenuInflater().inflate(R.menu.menu_image_detail, menu); @@ -950,18 +950,20 @@ public boolean onOptionsItemSelected(MenuItem item) { newFilter.setPath(dirPath); // int callBackId = (MediaScanner.isNoMedia(dirPath,MediaScanner.DEFAULT_SCAN_DEPTH)) ? NOMEDIA_GALLERY : 0; - FotoGalleryActivity.showActivity(this, this.mFilter, null, 0); + QueryParameter query = TagSql.filter2NewQuery(this.mFilter); + FotoGalleryActivity.showActivity(this, query, 0); } break; } case R.id.cmd_show_geo: - MapGeoPickerActivity.showActivity(this, getCurrentFoto(), null); + MapGeoPickerActivity.showActivity(this, getCurrentFoto(), null, 0); break; case R.id.cmd_show_geo_as: { final long imageId = getCurrentImageId(); - IGeoPoint _geo = FotoSql.execGetPosition(this, null, imageId); + IGeoPoint _geo = FotoSql.execGetPosition(null, this, + null, imageId, mDebugPrefix, "on cmd_show_geo_as"); final String currentFilePath = getCurrentFilePath(); GeoPointDto geo = new GeoPointDto(_geo.getLatitude(), _geo.getLongitude(), GeoPointDto.NO_ZOOM); @@ -1056,14 +1058,22 @@ private void onSlideShowNext() { private void cmdShowDetails(String fullFilePath, long currentImageId) { - ImageDetailMetaDialogBuilder.createImageDetailDialog(this, fullFilePath, currentImageId, mGalleryContentQuery, mViewPager.getCurrentItem()).show(); + StringBuilder countMsg = (mGalleryContentQuery == null) ? null : StringUtils.appendMessage(null, + getString(R.string.show_photo), + TagSql.getCount(this, mGalleryContentQuery)); + + ImageDetailMetaDialogBuilder.createImageDetailDialog(this, fullFilePath, currentImageId, + mGalleryContentQuery, + mViewPager.getCurrentItem(), + countMsg).show(); + } private boolean cmdMoveOrCopyWithDestDirPicker(final boolean move, String lastCopyToPath, final SelectedFiles fotos) { if (AndroidFileCommands.canProcessFile(this, false)) { mDestDirPicker = MoveOrCopyDestDirPicker.newInstance(move, fotos); - mDestDirPicker.defineDirectoryNavigation(OsUtils.getRootOSDirectory(), + mDestDirPicker.defineDirectoryNavigation(OsUtils.getRootOSDirectory(null), (move) ? FotoSql.QUERY_TYPE_GROUP_MOVE : FotoSql.QUERY_TYPE_GROUP_COPY, lastCopyToPath); mDestDirPicker.setContextMenuId(LockScreen.isLocked(this) ? 0 : R.menu.menu_context_osdir); diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/imagedetail/ImageDetailMetaDialogBuilder.java b/app/src/main/java/de/k3b/android/androFotoFinder/imagedetail/ImageDetailMetaDialogBuilder.java index 2009544e..f05a2774 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/imagedetail/ImageDetailMetaDialogBuilder.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/imagedetail/ImageDetailMetaDialogBuilder.java @@ -51,7 +51,7 @@ public class ImageDetailMetaDialogBuilder { public static Dialog createImageDetailDialog(Activity context, String filePath, long imageId, QueryParameter query, - long offset) { + long offset, Object... moreBlocks) { StringBuilder result = new StringBuilder(); result .append(imageId) @@ -59,6 +59,15 @@ public static Dialog createImageDetailDialog(Activity context, String filePath, .append("\n"); appendExifInfo(result, context, filePath, imageId); appendQueryInfo(result, query, offset); + + if ((moreBlocks != null) && (moreBlocks.length > 0)) { + for (Object subBlock : moreBlocks) { + if (subBlock != null) { + append(result,"\n"); + append(result, subBlock.toString()); + } + } + } return createImageDetailDialog(context, filePath, result.toString()); } diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/locationmap/LocationMapFragment.java b/app/src/main/java/de/k3b/android/androFotoFinder/locationmap/LocationMapFragment.java index 58bfcddb..f8964a3d 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/locationmap/LocationMapFragment.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/locationmap/LocationMapFragment.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2017 by k3b. + * Copyright (c) 2015-2018 by k3b. * * This file is part of AndroFotoFinder / #APhotoManager. * @@ -40,6 +40,7 @@ import android.widget.ImageView; import android.widget.PopupMenu; import android.widget.SeekBar; +import android.widget.Toast; import org.osmdroid.api.*; import org.osmdroid.events.DelayedMapListener; @@ -64,8 +65,8 @@ import de.k3b.android.androFotoFinder.R; import de.k3b.android.androFotoFinder.ThumbNailUtils; import de.k3b.android.androFotoFinder.imagedetail.ImageDetailActivityViewPager; +import de.k3b.android.androFotoFinder.queries.AndroidAlbumUtils; import de.k3b.android.androFotoFinder.queries.FotoSql; -import de.k3b.android.androFotoFinder.tagDB.TagSql; import de.k3b.android.osmdroid.FolderOverlayEx; import de.k3b.android.osmdroid.GuestureOverlay; import de.k3b.android.osmdroid.IconOverlay; @@ -83,7 +84,6 @@ import de.k3b.geo.api.IGeoPointInfo; import de.k3b.geo.io.GeoUri; import de.k3b.geo.io.gpx.GpxReaderBase; -import de.k3b.io.GalleryFilterParameter; import de.k3b.io.GeoRectangle; import de.k3b.io.IGalleryFilter; import de.k3b.io.IGeoRectangle; @@ -98,6 +98,8 @@ public class LocationMapFragment extends DialogFragment { protected String STATE_LAST_VIEWPORT = "LAST_VIEWPORT"; private static final int NO_ZOOM = OsmdroidUtil.NO_ZOOM; + private static final int RECALCULATE_ZOOM = OsmdroidUtil.RECALCULATE_ZOOM; + /** If there is more than 200 millisecs no zoom/scroll update markers */ protected static final int DEFAULT_INACTIVITY_DELAY_IN_MILLISECS = 200; @@ -137,10 +139,11 @@ public class LocationMapFragment extends DialogFragment { * see http://stackoverflow.com/questions/10411975/how-to-get-the-width-and-height-of-an-image-view-in-android/10412209#10412209 */ private BoundingBox mDelayedZoomToBoundingBox = null; - private int mDelayedZoomLevel = NO_ZOOM; + private int mDelayedZoomLevel = RECALCULATE_ZOOM; private boolean mIsInitialized = false; - private IGalleryFilter mRootFilter; + private QueryParameter mRootQuery; + private double mMinZoomLevel; private double mMaxZoomLevel; @@ -328,7 +331,8 @@ public boolean onZoom(ZoomEvent event) { @Override public void onFirstLayout(View v, int left, int top, int right, int bottom) { mIsInitialized = true; - zoomToBoundingBox("onFirstLayout", mDelayedZoomToBoundingBox, mDelayedZoomLevel); + final String why = "onFirstLayout"; + int newZoom = zoomToBoundingBox(why, mDelayedZoomToBoundingBox, mDelayedZoomLevel); mDelayedZoomToBoundingBox = null; mDelayedZoomLevel = NO_ZOOM; } @@ -437,14 +441,16 @@ private void createZoomBar(View view) { mZoomBar = (SeekBar) view.findViewById(R.id.zoomBar); mMinZoomLevel = mMapView.getMinZoomLevel(); - mMaxZoomLevel = mMapView.getMaxZoomLevel(); + mMaxZoomLevel = OsmdroidUtil.getMaximumZoomLevel(mMapView); mZoomBar.setMax((int) (mMaxZoomLevel - mMinZoomLevel)); mZoomBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - if (fromUser) + if (fromUser) { mMapView.getController().setZoom(progress + mMinZoomLevel); + } + Toast.makeText(getActivity(), "" + progress, Toast.LENGTH_SHORT).show(); } @Override @@ -467,38 +473,58 @@ private FolderOverlayEx createFolderOverlay(List overlays) { return result; } - public void defineNavigation(IGalleryFilter rootFilter, GeoRectangle rectangle, int zoomlevel, - SelectedItems selectedItems, Uri gpxAdditionalPointsContentUri) { + /** + * (re)define map display. Data sohow from rootQuery + rectangle + zoomlevel. + * + * @param rootQuery if not null contain database where to limit the photo data displayed + * @param depricated_rootFilter should be null. if not null contain database where to limit the data displayed + * @param rectangle if nut null the initial visible rectange + * @param zoomlevel the initial zoomlevel + * @param selectedItems if not null: items to be displayed as blue markers + * @param gpxAdditionalPointsContentUri if not null the file containing additional geo coords for blue marker + * @param zoomToFit true mean recalculate zoomlevel from rectangle + */ + public void defineNavigation(QueryParameter rootQuery, + IGalleryFilter depricated_rootFilter, + GeoRectangle rectangle, int zoomlevel, + SelectedItems selectedItems, + Uri gpxAdditionalPointsContentUri, boolean zoomToFit) { + String debugContext = mDebugPrefix + "defineNavigation: "; if (Global.debugEnabled || Global.debugEnabledMap) { - Log.i(Global.LOG_CONTEXT, mDebugPrefix + "defineNavigation: " + rectangle + ";z=" + zoomlevel); - } - - if (rootFilter != null) { - this.mRootFilter = rootFilter; + Log.i(Global.LOG_CONTEXT, debugContext + rectangle + ";z=" + zoomlevel); } - // load this.mFolderOverlayBlueGpxMarker - GeoRectangle increasedRrectangle = defineGpxAdditionalPoints(gpxAdditionalPointsContentUri, rectangle); + this.mRootQuery = AndroidAlbumUtils.getAsMergedNewQueryParameter(rootQuery, depricated_rootFilter); mSelectedItemsHandler.define(selectedItems); + if (zoomToFit) { + zoomToFit(null, "defineNavigation"); + } else { + // load this.mFolderOverlayBlueGpxMarker + GeoRectangle increasedRrectangle = defineGpxAdditionalPoints(gpxAdditionalPointsContentUri, rectangle); - String debugContext = "defineNavigation"; - zoomToBoundingBox(debugContext, increasedRrectangle, zoomlevel); - - if (rootFilter != null) { + zoomToBoundingBox(debugContext, increasedRrectangle, zoomlevel); + } + if ((rootQuery != null) || (depricated_rootFilter != null)) { reloadFotoMarker(debugContext); } } protected void zoomToBoundingBox(String debugContext, GeoRectangle rectangle, int zoomlevel) { - if ((rectangle != null) && !rectangle.isEmpty()) { - BoundingBox boundingBox = new BoundingBox( - rectangle.getLatitudeMax(), - rectangle.getLogituedMin(), - rectangle.getLatitudeMin(), - rectangle.getLogituedMax()); - - zoomToBoundingBox(debugContext, boundingBox, zoomlevel); + if (rectangle != null) { + if (!rectangle.isEmpty()) { + BoundingBox boundingBox = new BoundingBox( + rectangle.getLatitudeMax(), + rectangle.getLogituedMin(), + rectangle.getLatitudeMin(), + rectangle.getLogituedMax()); + + zoomToBoundingBox(debugContext, boundingBox, zoomlevel); + } else { + // rectangle is single point (size=0) + GeoPoint point = new GeoPoint(rectangle.getLatitudeMin(),rectangle.getLogituedMin()); + OsmdroidUtil.zoomTo(this.mMapView, zoomlevel,point , null); + } } } @@ -533,6 +559,8 @@ public boolean onGeoInfo(IGeoPointInfo iGeoPointInfo) { gpxBox.increase(Global.mapMultiselectionBoxIncreaseByProcent, Global.mapMultiselectionBoxIncreaseMinSizeInDegrees); } } catch (IOException e) { + Log.w(Global.LOG_CONTEXT, mDebugPrefix + "defineGpxAdditionalPoints cannot open points from " + gpxAdditionalPointsContentUri); + e.printStackTrace(); } if (mFolderOverlayBlueGpxMarker != null) { @@ -569,37 +597,51 @@ private Overlay createMarkerEx(GeoPointDtoEx point) { return new MarkerEx(mPopup).set(mId++, position, mSelectedItemsHandler.mBlueMarker, position ); } - private void zoomToBoundingBox(String why, BoundingBox boundingBox, int zoomLevel) { + private int zoomToBoundingBox(String why, BoundingBox boundingBox, int zoomLevel) { + int recalculatedZoom = NO_ZOOM; if (boundingBox != null) { + final double oldZoomLevelDouble = (mMapView == null) ? -1 : mMapView.getZoomLevelDouble(); + final BoundingBox oldBoundingBox = (mMapView == null) ? null : mMapView.getBoundingBox(); if (mIsInitialized) { // if map is already initialized GeoPoint min = new GeoPoint(boundingBox.getLatSouth(), boundingBox.getLonWest()); if (zoomLevel != NO_ZOOM) { - OsmdroidUtil.zoomTo(this.mMapView, zoomLevel, min, null); + recalculatedZoom = OsmdroidUtil.zoomTo(this.mMapView, zoomLevel, min, null); } else { GeoPoint max = new GeoPoint(boundingBox.getLatNorth(), boundingBox.getLonEast()); - OsmdroidUtil.zoomTo(this.mMapView, OsmdroidUtil.NO_ZOOM, min, max); + recalculatedZoom = OsmdroidUtil.zoomTo(this.mMapView, OsmdroidUtil.NO_ZOOM, min, max); // this.mMapView.zoomToBoundingBox(boundingBox); this is to inexact } if (Global.debugEnabledMap) { Log.i(Global.LOG_CONTEXT, mDebugPrefix + "zoomToBoundingBox(" + why - + "; z=" + mMapView.getZoomLevelDouble() + ") :" - + boundingBox + + boundingBox + "; z=" + zoomLevel // oldZoomLevelDouble + " <= " - + mMapView.getBoundingBox() + + oldBoundingBox+ "; z=" + oldZoomLevelDouble + "(" + recalculatedZoom + ")" ); } - setZoomBarZoomLevel(why, mMapView.getZoomLevelDouble()); + setZoomBarZoomLevel(why, recalculatedZoom); } else { // map not initialized yet. do it later. this.mDelayedZoomToBoundingBox = boundingBox; + this.mDelayedZoomLevel = zoomLevel; + if (Global.debugEnabledMap) { + Log.i(Global.LOG_CONTEXT, mDebugPrefix + + "zoomToBoundingBox-enque(" + why + + "; z=" + oldZoomLevelDouble + + ") :" + + boundingBox + + " <= " + + oldBoundingBox + ); + } } } + return recalculatedZoom; } /** all marker clicks will be delegated to LocationMapFragment#onFotoMarkerClicked() */ @@ -650,8 +692,8 @@ private void reloadFotoMarker(BoundingBox latLonArea, double groupingFactor, Lis QueryParameter query = FotoSql.getQueryGroupByPlace(groupingFactor); query.clearWhere(); - if (this.mRootFilter != null) { - TagSql.filter2QueryEx(query, this.mRootFilter, true); + if (this.mRootQuery != null) { + query.getWhereFrom(this.mRootQuery, true); } // delta: make the grouping area a little bit bigger than the viewport @@ -985,14 +1027,14 @@ public boolean onMenuItemClick(MenuItem item) { switch (item.getItemId()) { case R.id.cmd_photo: - return showPhoto(getGeoPointById(markerId, geoPosition)); + return showPhoto(getGeoPointById(markerId, geoPosition, "showPhoto")); case R.id.cmd_gallery: - return showGallery(getGeoPointById(markerId, geoPosition)); + return showGallery(getGeoPointById(markerId, geoPosition,"showGallery")); case R.id.cmd_zoom: - return zoomToFit(getGeoPointById(markerId, geoPosition)); + return zoomToFit(getGeoPointById(markerId, geoPosition,"on cmd zoomToFit")); case R.id.cmd_show_geo_as: { - IGeoPoint _geo = getGeoPointById(markerId, geoPosition); + IGeoPoint _geo = getGeoPointById(markerId, geoPosition,"cmd_show_geo_as"); GeoPointDto geo = new GeoPointDto(_geo.getLatitude(), _geo.getLongitude(), GeoPointDto.NO_ZOOM); geo.setId(""+markerId); geo.setName("#"+markerId); @@ -1022,9 +1064,7 @@ protected void closePopup() { } private boolean showPhoto(IGeoPoint geoPosition) { - GalleryFilterParameter filter = getMarkerFilter(geoPosition); - QueryParameter query = new QueryParameter(); - TagSql.filter2QueryEx(query, filter, false); + QueryParameter query = getQueryForPositionRectangle(geoPosition); FotoSql.setSort(query, FotoSql.SORT_BY_DATE, false); ImageDetailActivityViewPager.showActivity(this.getActivity(), null, 0, query, 0); @@ -1032,18 +1072,19 @@ private boolean showPhoto(IGeoPoint geoPosition) { } private boolean showGallery(IGeoPoint geoPosition) { - GalleryFilterParameter filter = getMarkerFilter(geoPosition); - FotoGalleryActivity.showActivity(this.getActivity(), filter, null, 0); + FotoGalleryActivity.showActivity(this.getActivity(), getQueryForPositionRectangle(geoPosition), 0); return true; } - private boolean zoomToFit(IGeoPoint geoPosition) { + private boolean zoomToFit(IGeoPoint geoCenterPoint, Object... dbgContext) { + QueryParameter baseQuery = getQueryForPositionRectangle(geoCenterPoint); BoundingBox BoundingBox = null; - IGeoRectangle fittingRectangle = FotoSql.execGetGeoRectangle(this.getActivity(), getMarkerFilter(geoPosition), null); + IGeoRectangle fittingRectangle = FotoSql.execGetGeoRectangle(null, this.getActivity(), + baseQuery, null, mDebugPrefix, "zoomToFit", dbgContext); double delta = getDelta(fittingRectangle); - if (delta < 1e-6) { - BoundingBox = getMarkerBoundingBox(geoPosition); + if ((geoCenterPoint != null) && (delta < 1e-6)) { + BoundingBox = getMarkerBoundingBox(geoCenterPoint); } else { double enlarge = delta * 0.2; @@ -1069,10 +1110,18 @@ private double getDelta(IGeoRectangle fittingRectangle) { , Math.abs(fittingRectangle.getLatitudeMax() - fittingRectangle.getLatitudeMin())); } - private GalleryFilterParameter getMarkerFilter(IGeoPoint geoPosition) { + private QueryParameter getQueryForPositionRectangle(IGeoPoint geoPosition) { + QueryParameter result = AndroidAlbumUtils.getAsMergedNewQueryParameter(mRootQuery, null); + + GeoRectangle rect = getRectangleFrom(new GeoRectangle(), geoPosition); + FotoSql.addWhereFilterLatLon(result, rect); + + return result; + } + + private GeoRectangle getRectangleFrom(GeoRectangle result, IGeoPoint geoPosition) { double delta = getMarkerDelta(); - GalleryFilterParameter result = new GalleryFilterParameter().get(mRootFilter); result.setNonGeoOnly(false); result.setLatitude(geoPosition.getLatitude() - delta, geoPosition.getLatitude() + delta); result.setLogitude(geoPosition.getLongitude() - delta, geoPosition.getLongitude() + delta); @@ -1097,9 +1146,10 @@ private double getMarkerDelta() { return 1/groupingFactor/2; } - private IGeoPoint getGeoPointById(int markerId, IGeoPoint notFoundValue) { + private IGeoPoint getGeoPointById(int markerId, IGeoPoint notFoundValue, Object... dbgContext) { if (markerId != NO_MARKER_ID) { - IGeoPoint pos = FotoSql.execGetPosition(this.getActivity(), null, markerId); + IGeoPoint pos = FotoSql.execGetPosition(null, this.getActivity(), + null, markerId, mDebugPrefix, "getGeoPointById", dbgContext); if (pos != null) { return pos; } diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/locationmap/MapGeoPickerActivity.java b/app/src/main/java/de/k3b/android/androFotoFinder/locationmap/MapGeoPickerActivity.java index 6effeca2..545cc290 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/locationmap/MapGeoPickerActivity.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/locationmap/MapGeoPickerActivity.java @@ -21,7 +21,6 @@ import android.app.ActionBar; import android.app.Activity; -import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.net.Uri; @@ -29,27 +28,32 @@ import android.preference.PreferenceManager; import android.util.Log; import android.view.Menu; +import android.view.MenuInflater; import android.view.MenuItem; -import android.view.Window; +import android.view.View; import android.widget.Toast; import org.osmdroid.api.IGeoPoint; +import org.osmdroid.views.MapView; import de.k3b.android.androFotoFinder.AffUtils; -import de.k3b.android.androFotoFinder.BookmarkController; import de.k3b.android.androFotoFinder.Common; import de.k3b.android.androFotoFinder.FotoGalleryActivity; -import de.k3b.android.androFotoFinder.GalleryFilterActivity; import de.k3b.android.androFotoFinder.Global; import de.k3b.android.androFotoFinder.LockScreen; import de.k3b.android.androFotoFinder.R; import de.k3b.android.androFotoFinder.SettingsActivity; import de.k3b.android.androFotoFinder.imagedetail.ImageDetailActivityViewPager; +import de.k3b.android.androFotoFinder.imagedetail.ImageDetailMetaDialogBuilder; +import de.k3b.android.androFotoFinder.queries.AndroidAlbumUtils; import de.k3b.android.androFotoFinder.queries.FotoSql; +import de.k3b.android.androFotoFinder.tagDB.TagSql; import de.k3b.android.osmdroid.OsmdroidUtil; import de.k3b.android.widget.AboutDialogPreference; -import de.k3b.android.widget.LocalizedActivity; +import de.k3b.android.widget.BaseQueryActivity; import de.k3b.database.QueryParameter; +import de.k3b.io.IDirectory; +import de.k3b.io.StringUtils; import de.k3b.io.collections.SelectedFiles; import de.k3b.io.collections.SelectedItems; import de.k3b.geo.api.GeoPointDto; @@ -59,7 +63,8 @@ import de.k3b.io.GeoRectangle; import de.k3b.io.IGeoRectangle; -public class MapGeoPickerActivity extends LocalizedActivity implements Common { +// BaseQueryActivity LocalizedActivity +public class MapGeoPickerActivity extends BaseQueryActivity implements Common { private static final String mDebugPrefix = "GalM-"; private static final String STATE_Filter = "filterMap"; private static final String STATE_LAST_GEO = "geoLastView"; @@ -70,24 +75,23 @@ public class MapGeoPickerActivity extends LocalizedActivity implements Common { private boolean mSaveLastUsedFilterToSharedPrefs = true; private boolean mSaveLastUsedGeoToSharedPrefs = true; - private GalleryFilterParameter mFilter; private GeoUri mGeoUriParser = new GeoUri(GeoUri.OPT_PARSE_INFER_MISSING); - private BookmarkController mBookmarkController = null; + // lockscreen support + private boolean locked = false; // if != Global.locked : must update menu + private boolean mMustReplaceMenue = false; - public static void showActivity(Activity context, SelectedFiles selectedItems, GalleryFilterParameter filter) { + public static void showActivity(Activity context, SelectedFiles selectedItems, + QueryParameter query, int requestCode) { Uri initalUri = null; final Intent intent = new Intent().setClass(context, MapGeoPickerActivity.class); - GalleryFilterParameter localFilter = new GalleryFilterParameter(); - - if (filter != null) { - localFilter.get(filter); - } + AndroidAlbumUtils.saveFilterAndQuery(context, null, intent, null, null, query); if (AffUtils.putSelectedFiles(intent, selectedItems)) { - IGeoPoint initialPoint = FotoSql.execGetPosition(context, null, selectedItems.getId(0)); + IGeoPoint initialPoint = FotoSql.execGetPosition(null, context, + null, selectedItems.getId(0), context, mDebugPrefix, "showActivity"); if (initialPoint != null) { GeoUri PARSER = new GeoUri(GeoUri.OPT_PARSE_INFER_MISSING); @@ -96,15 +100,16 @@ public static void showActivity(Activity context, SelectedFiles selectedItems, G } } - localFilter.setNonGeoOnly(false); - intent.putExtra(EXTRA_FILTER, localFilter.toString()); - intent.setAction(Intent.ACTION_VIEW); if (Global.debugEnabled) { Log.d(Global.LOG_CONTEXT, context.getClass().getSimpleName() + " > MapGeoPickerActivity.showActivity@" + initalUri); } - context.startActivity(intent); + if (requestCode != 0) { + context.startActivityForResult(intent, requestCode); + } else { + context.startActivity(intent); + } } @Override @@ -117,14 +122,12 @@ protected void onCreate(Bundle savedInstanceState) { Log.d(Global.LOG_CONTEXT, mDebugPrefix + "onCreate " + intent.toUri(Intent.URI_INTENT_SCHEME)); } - mBookmarkController = new BookmarkController(this); - mBookmarkController.loadState(intent,savedInstanceState); - - GeoPointDto geoPointFromIntent = getGeoPointDtoFromIntent(intent); + final GeoPointDto geoPointFromIntent = getGeoPointDtoFromIntent(intent); // no geo: from intent: use last used value mSaveLastUsedGeoToSharedPrefs = (geoPointFromIntent == null); - Uri additionalPointsContentUri = ((intent != null) && (geoPointFromIntent == null)) ? intent.getData() : null; + final Uri uriFromIntent = intent.getData(); + final Uri additionalPointsContentUri = ((intent != null) && (geoPointFromIntent == null)) ? uriFromIntent : null; String lastGeoUri = sharedPref.getString(STATE_LAST_GEO, "geo:53,8?z=6"); IGeoPointInfo lastGeo = mGeoUriParser.fromUri(lastGeoUri); @@ -137,23 +140,8 @@ protected void onCreate(Bundle savedInstanceState) { IGeoPointInfo initalZoom = (mSaveLastUsedGeoToSharedPrefs) ? lastGeo : geoPointFromIntent; - String extraTitle = intent.getStringExtra(EXTRA_TITLE); - if (extraTitle == null && (geoPointFromIntent == null)) { - extraTitle = getString(R.string.app_map_title); - } - - if (extraTitle == null) { - this.requestWindowFeature(Window.FEATURE_NO_TITLE); - } - setContentView(R.layout.activity_map_geo_picker); - if (extraTitle != null) { - this.setTitle(extraTitle); - } else { - setNoTitle(); - } - mMap = (PickerLocationMapFragment) getFragmentManager().findFragmentById(R.id.fragment_map); GeoRectangle rectangle = null; @@ -165,81 +153,78 @@ protected void onCreate(Bundle savedInstanceState) { rectangle.setLogituedMax(initalZoom.getLongitude()).setLatitudeMax(initalZoom.getLatitude()); } // else (savedInstanceState != null) restore after rotation. fragment takes care of restoring map pos - SelectedItems selectedItems = AffUtils.getSelectedItems(intent); + final SelectedItems selectedItems = AffUtils.getSelectedItems(intent); // TODO !!! #62 gpx/kml files: wie an LocatonMapFragment übergeben?? String filter = null; // for debugging: where does the filter come from String dbgFilter = null; - if (intent != null) { - filter = intent.getStringExtra(EXTRA_FILTER); - } - this.mSaveLastUsedFilterToSharedPrefs = (filter == null); // false if controlled via intent - if (savedInstanceState != null) { - filter = savedInstanceState.getString(STATE_Filter); - if (filter != null) dbgFilter = "filter from savedInstanceState=" + filter; - } - - if (this.mSaveLastUsedFilterToSharedPrefs) { - filter = sharedPref.getString(STATE_Filter, null); - if (filter != null) dbgFilter = "filter from sharedPref=" + filter; - } - - this.mFilter = new GalleryFilterParameter(); - if (filter != null) { - GalleryFilterParameter.parse(filter, this.mFilter); + final boolean zoom2fit = false; + + onCreateData(savedInstanceState); + + { + // bugfix: first defineNavigation will not work until map is created completely + // so wait until then + // note: delayed params must be final + final GeoRectangle _rectangle = rectangle; + final int _zoom = zoom; + mMap.mMapView.addOnFirstLayoutListener(new MapView.OnFirstLayoutListener() { + @Override + public void onFirstLayout(View v, int left, int top, int right, int bottom) { + mMap.defineNavigation(mGalleryQueryParameter.calculateEffectiveGalleryContentQuery(), + null, geoPointFromIntent, _rectangle, _zoom, selectedItems, additionalPointsContentUri, zoom2fit); + } + }); } + } - if (Global.debugEnabled) { - Log.i(Global.LOG_CONTEXT, mDebugPrefix + dbgFilter + " => " + this.mFilter); + @Override + public void onResume() { + super.onResume(); + + // if lock mode has changed redefine menu + final boolean locked = LockScreen.isLocked(this); + if (this.locked != locked) { + this.locked = locked; + mMustReplaceMenue = true; + invalidateOptionsMenu(); } - - mMap.defineNavigation(this.mFilter, geoPointFromIntent, rectangle, zoom, selectedItems, additionalPointsContentUri); } - @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. - getMenuInflater().inflate(LockScreen.isLocked(this) ? R.menu.menu_map_context_locked : R.menu.menu_map_geo_picker, menu); - AboutDialogPreference.onPrepareOptionsMenu(this, menu); - + mMustReplaceMenue = true; + fixOptionsMenu(menu); return true; } - @Override - protected void onDestroy() { - saveSettings(this); - super.onDestroy(); - } - - @Override - public void onSaveInstanceState(Bundle savedInstanceState) { - super.onSaveInstanceState(savedInstanceState); - saveSettings(this); - mBookmarkController.saveState(null, savedInstanceState); + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + super.onPrepareOptionsMenu(menu); - if ((savedInstanceState != null) && (this.mFilter != null)) { - savedInstanceState.putString(STATE_Filter, this.mFilter.toString()); - } + // if lock mode has changed redefine menu + fixOptionsMenu(menu); + return true; } - private void saveSettings(Context context) { - if (mSaveLastUsedFilterToSharedPrefs || mSaveLastUsedGeoToSharedPrefs) { - // save settings - SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context); - SharedPreferences.Editor edit = sharedPref.edit(); + private void fixOptionsMenu(Menu menu) { + final boolean locked = LockScreen.isLocked(this); + if (mMustReplaceMenue || (locked != this.locked)) { + mMustReplaceMenue = false; + this.locked = locked; + menu.clear(); + MenuInflater inflater = getMenuInflater(); - if (mSaveLastUsedFilterToSharedPrefs && (mFilter != null)) { - edit.putString(STATE_Filter, mFilter.toString()); - } + if (locked) { + inflater.inflate(R.menu.menu_locked, menu); + LockScreen.removeDangerousCommandsFromMenu(menu); + } else { + inflater.inflate(R.menu.menu_map_geo_picker, menu); - if (mSaveLastUsedGeoToSharedPrefs) { - String currentGeoUri = mMap.getCurrentGeoUri(); - edit.putString(STATE_LAST_GEO, currentGeoUri); } - - edit.apply(); + AboutDialogPreference.onPrepareOptionsMenu(this, menu); } } @@ -249,9 +234,6 @@ public boolean onOptionsItemSelected(MenuItem item) { return true; // Handle presses on the action bar items switch (item.getItemId()) { - case R.id.cmd_filter: - openFilter(); - return true; //cmd_lock case R.id.cmd_about: AboutDialogPreference.createAboutDialog(this).show(); @@ -263,8 +245,11 @@ public boolean onOptionsItemSelected(MenuItem item) { return showPhoto(mMap.getCurrentGeoRectangle()); case R.id.cmd_gallery: return showGallery(mMap.getCurrentGeoRectangle()); + case R.id.action_details: + cmdShowDetails(); + return true; default: - return super.onOptionsItemSelected(item); + return onOptionsItemSelected(item, AffUtils.getSelectedItems(getIntent())); } } @@ -279,48 +264,59 @@ private boolean showPhoto(IGeoRectangle geoArea) { } private boolean showGallery(IGeoRectangle geoArea) { + QueryParameter query = getAsMergedQuery(geoArea); + + FotoGalleryActivity.showActivity(this, query, 0); + return true; + } + + private QueryParameter getAsMergedQuery() { + return getAsMergedQuery(mMap.getCurrentGeoRectangle()); + } + + private QueryParameter getAsMergedQuery(IGeoRectangle geoArea) { GalleryFilterParameter filter = new GalleryFilterParameter(); filter.get(geoArea); - FotoGalleryActivity.showActivity(this, filter, null, 0); - return true; + return TagSql.filter2NewQuery(filter); + } + + private void cmdShowDetails() { + final QueryParameter asMergedQuery = getAsMergedQuery(); + + CharSequence subQuerymTitle = getValueAsTitle(true); + ImageDetailMetaDialogBuilder.createImageDetailDialog( + this, + getTitle().toString(), + asMergedQuery.toSqlString(), + StringUtils.appendMessage(null, + getString(R.string.show_photo), + TagSql.getCount(this, asMergedQuery)), + subQuerymTitle + ).show(); } - - /** - * Call back from sub-activities.
- * Process Change StartTime (longpress start), Select StopTime before stop - * (longpress stop) or filter change for detailReport - */ @Override - protected void onActivityResult(final int requestCode, - final int resultCode, final Intent intent) { - super.onActivityResult(requestCode, resultCode, intent); - - switch (requestCode) { - case GalleryFilterActivity.resultID : - mBookmarkController.loadState(intent, null); - onFilterChanged(GalleryFilterActivity.getFilter(intent)); - break; - default: - throw new IllegalStateException(); + protected void reloadGui(String why) { + if (mMap != null) { + QueryParameter query = this.mGalleryQueryParameter.calculateEffectiveGalleryContentQuery(); + mMap.defineNavigation(query, + null,null, IGeoPointInfo.NO_ZOOM, null, null, false); } + } - private void onFilterChanged(GalleryFilterParameter filter) { - if ((mMap != null) && (filter != null)) { - this.mFilter = filter; - mMap.defineNavigation(this.mFilter, null, OsmdroidUtil.NO_ZOOM, null, null); - } + @Override + protected void defineDirectoryNavigation(IDirectory directoryRoot) { } - private void openFilter() { - GalleryFilterActivity.showActivity(this, this.mFilter, null, - mBookmarkController.getlastBookmarkFileName(), GalleryFilterActivity.resultID); + /** allows childclass to have their own sharedPreference names */ + @Override + protected String fixSharedPrefSuffix(String statSuffix) { + return super.fixSharedPrefSuffix(statSuffix) + "-map"; } private GeoPointDto getGeoPointDtoFromIntent(Intent intent) { - final Uri uri = (intent != null) ? intent.getData() : null; - String uriAsString = (uri != null) ? uri.toString() : null; + String uriAsString = (intent != null) ? intent.getDataString() : null; GeoPointDto pointFromIntent = null; if (uriAsString != null) { Toast.makeText(this, getString(R.string.app_name) + ": received " + uriAsString, Toast.LENGTH_LONG).show(); diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/locationmap/PickerLocationMapFragment.java b/app/src/main/java/de/k3b/android/androFotoFinder/locationmap/PickerLocationMapFragment.java index 3c8fa48a..145ce804 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/locationmap/PickerLocationMapFragment.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/locationmap/PickerLocationMapFragment.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2016 by k3b. + * Copyright (c) 2015-2018 by k3b. * * This file is part of AndroFotoFinder. * @@ -41,6 +41,7 @@ import de.k3b.android.androFotoFinder.queries.FotoSql; import de.k3b.android.osmdroid.IconOverlay; import de.k3b.android.util.ResourceUtils; +import de.k3b.database.QueryParameter; import de.k3b.io.collections.SelectedItems; import de.k3b.geo.api.GeoPointDto; import de.k3b.geo.api.IGeoPointInfo; @@ -129,9 +130,18 @@ protected void definteOverlays(MapView mapView) { } /** get all important parameters for displaying the map */ - public void defineNavigation(GalleryFilterParameter rootFilter, IGeoPointInfo selectedItem, + /** + * (re)define map display + * @param rootQuery if not null contain database where to limit the photo data displayed + * @param depricated_rootFilter should be null. if not null contain database where to limit the data displayed + * @param rectangle if nut null the initial visible rectange + * @param zoomlevel the initial zoomlevel + * @param selectedItems if not null: items to be displayed as blue markers + * @param zoomToFit true mean recalculate zoomlevel from rectangle + */ + public void defineNavigation(QueryParameter rootQuery, GalleryFilterParameter depricated_rootFilter, IGeoPointInfo selectedItem, GeoRectangle rectangle, int zoomlevel, - SelectedItems selectedItems, Uri additionalPointsContentUri) { + SelectedItems selectedItems, Uri additionalPointsContentUri, boolean zoomToFit) { IGeoPointInfo currentSelection = selectedItem; if ((currentSelection == null) && (getCurrentSelectionPosition() == null)) { // first call with no geo: take last use from config @@ -141,7 +151,7 @@ public void defineNavigation(GalleryFilterParameter rootFilter, IGeoPointInfo se currentSelection = (lastValue == null) ? null : mGeoUriEngine.fromUri(lastValue); } - super.defineNavigation(rootFilter, rectangle, zoomlevel, selectedItems, additionalPointsContentUri); + super.defineNavigation(rootQuery, depricated_rootFilter, rectangle, zoomlevel, selectedItems, additionalPointsContentUri, zoomToFit); if (currentSelection != null) { updateMarker(null, NO_MARKER_ID, new GeoPoint(currentSelection.getLatitude(), currentSelection.getLongitude()), null); } @@ -160,7 +170,8 @@ protected void onOk() { Activity activity = getActivity(); if (mMarkerId != NO_MARKER_ID) { - result = FotoSql.execGetPosition(activity, null, mMarkerId); + result = FotoSql.execGetPosition(null, activity, null, mMarkerId, + mDebugPrefix,"onOk"); } if (result == null) { diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/media/package-info.java b/app/src/main/java/de/k3b/android/androFotoFinder/media/package-info.java index 7b6030ee..68dc29da 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/media/package-info.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/media/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017 by k3b. + * Copyright (c) 2017-2018 by k3b. * * This file is part of AndroFotoFinder / #APhotoManager. * @@ -113,7 +113,68 @@ [TODO:syncAndroid]-->[TODO:syncPC] : {TODO:z} @enduml +camera-worflow-simple.png +@startuml + title Workflow Photo management Android-PC (simple) + package "PC" { + [Folder pc-in "from-android"] + [DigiKam] + } + + package "Android" { + [Camera] + [Folder "camera"] + [Folder android-out "to-pc"] + ["A Photo Manager"] + } + [Camera] --> [Folder "camera"] #blue : {1-take} + [Folder "camera"] <-- ["A Photo Manager"] #blue : {2-move} + [Folder "camera"] --> [Folder android-out "to-pc"] : {2-Autoprocessing:\nRename+Exif} + ["A Photo Manager"] --> [Folder android-out "to-pc"] #blue : {3-process} + + [Folder android-out "to-pc"] <--> [Folder pc-in "from-android"] : {4-Syncthing} + [Folder pc-in "from-android"] <-- [DigiKam] #blue : {5-process} + +@enduml + + +camera-worflow-huge.png +@startuml + title Workflow Photo management Android-PC (huge) + package "PC" { + [Folder pc-in "from-android"] + [Folder pc "hires"] + [Folder pc-out "lowres"] + [DigiKam] + [IrfanView] + } + + package "Android" { + [Camera] + [Folder "camera"] + [Folder android-out "to-pc"] + [Folder android-in "from-pc"] + ["A Photo Manager"] + } + + [Camera] --> [Folder "camera"] #blue : {1-take} + [Folder "camera"] <-- ["A Photo Manager"] #blue : {2-move} + [Folder "camera"] --> [Folder android-out "to-pc"] : {2-Autoprocessing:\nRename+Exif} + + ["A Photo Manager"] --> [Folder android-out "to-pc"] #blue : {3-process} + + [Folder android-out "to-pc"] --> [Folder pc-in "from-android"] : {4-Syncthing} + [Folder pc-in "from-android"] <-- [DigiKam] #blue : {5-process\nmove} + [Folder pc "hires"] <- [Folder pc-in "from-android"] : {5} + [Folder pc "hires"] <-- [DigiKam] #blue : {6-process} + [Folder pc "hires"] <-- [IrfanView] #blue : {7-resize} + [Folder pc-out "lowres"] <- [Folder pc "hires"] : {7} + + [Folder android-in "from-pc"] <-- [Folder pc-out "lowres"] : {8b-Syncthing} + + ["A Photo Manager"] --> [Folder android-in "from-pc"] #blue : {9-process} +@enduml */ \ No newline at end of file diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/queries/AndroidAlbumUtils.java b/app/src/main/java/de/k3b/android/androFotoFinder/queries/AndroidAlbumUtils.java new file mode 100644 index 00000000..345b3c5a --- /dev/null +++ b/app/src/main/java/de/k3b/android/androFotoFinder/queries/AndroidAlbumUtils.java @@ -0,0 +1,404 @@ +/* + * Copyright (c) 2018 by k3b. + * + * This file is part of AndroFotoFinder / #APhotoManager. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see + */ + +package de.k3b.android.androFotoFinder.queries; + +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.net.Uri; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.Log; +import android.widget.Toast; + +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import de.k3b.android.androFotoFinder.Common; +import de.k3b.android.androFotoFinder.GalleryFilterPathState; +import de.k3b.android.androFotoFinder.Global; +import de.k3b.android.androFotoFinder.R; +import de.k3b.android.androFotoFinder.tagDB.TagSql; +import de.k3b.android.util.IntentUtil; +import de.k3b.android.util.MediaScanner; +import de.k3b.database.QueryParameter; +import de.k3b.io.AlbumFile; +import de.k3b.io.FileUtils; +import de.k3b.io.GalleryFilterParameter; +import de.k3b.io.IGalleryFilter; +import de.k3b.io.StringUtils; +import de.k3b.tagDB.TagProcessor; + +/** + * Handles file io of QueryParameter and GalleryFilterParameter via intent. + * + * Created by k3b on 28.03.2018. + */ +public class AndroidAlbumUtils implements Common { + private static final String mDebugPrefix = AndroidAlbumUtils.class.getSimpleName(); + + /** get from savedInstanceState else intent: inten.data=file-uri else EXTRA_QUERY else EXTRA_FILTER */ + @Deprecated + public static GalleryFilterParameter getFilterAndRestQuery( + @NonNull Context context, @Nullable Bundle savedInstanceState, + @Nullable Intent intent, @Nullable QueryParameter resultQueryWithoutFilter, + boolean ignoreFilter, @Nullable StringBuilder dbgFilter) { + if (savedInstanceState != null) { + return getFilterAndRestQuery( + context, + null, + savedInstanceState.getString(EXTRA_QUERY), + savedInstanceState.getString(EXTRA_FILTER), + resultQueryWithoutFilter, ignoreFilter, dbgFilter); + } else if (intent != null) { + return getFilterAndRestQuery( + context, + IntentUtil.getUri(intent), + intent.getStringExtra(EXTRA_QUERY), + intent.getStringExtra(EXTRA_FILTER), + resultQueryWithoutFilter, ignoreFilter, dbgFilter); + } + return null; + } + + /** get from savedInstanceState else intent else sharedPref: inten.data=file-uri else EXTRA_QUERY else EXTRA_FILTER */ + public static QueryParameter getQuery( + @NonNull Context context, @NonNull String paramNameSuffix, + @Nullable Bundle savedInstanceState, + @Nullable Intent intent, @Nullable SharedPreferences sharedPref, + StringBuilder outQueryFileUri, @Nullable StringBuilder dbgMessageResult) { + QueryParameter result = null; + + if (savedInstanceState == null) { + // onCreate (first call) : if intent is usefull use it else use sharedPref + if (intent != null) { + Uri uri = IntentUtil.getUri(intent); + result = getQueryFromFirstMatching( + "Intent-uri", context, + uri, + null, + null, + dbgMessageResult); + if (result == null) { + result = getQueryFromFirstMatching( + "Intent", context, + uri, + intent.getStringExtra(EXTRA_QUERY), + intent.getStringExtra(EXTRA_FILTER), + dbgMessageResult); + } else if (outQueryFileUri != null) { + outQueryFileUri.append(uri.toString()); + } + } + + if ((result == null) && (sharedPref != null)) { + result = getQueryFromFirstMatching( + "SharedPreferences", context, + null, + sharedPref.getString(EXTRA_QUERY + paramNameSuffix, null), + sharedPref.getString(EXTRA_FILTER + paramNameSuffix, null), + dbgMessageResult); + } + } else { + // (savedInstanceState != null) : onCreate after screen rotation + result = getQueryFromFirstMatching( + "InstanceState", context, + null, + savedInstanceState.getString(EXTRA_QUERY + paramNameSuffix), + savedInstanceState.getString(EXTRA_FILTER + paramNameSuffix), + dbgMessageResult); + } + return result; + } + + /** used by folder picker if album-file is picked instead of folder */ + public static QueryParameter getQueryFromUri(String dbgContext, @NonNull Context context, Uri uri, @Nullable StringBuilder dbgFilter) { + // ignore geo: or area: uri-s + String path = (IntentUtil.isFileOrContentUri(uri, true)) ? uri.getPath() : null; + if (!StringUtils.isNullOrEmpty(path)) { + // #118 app specific content uri convert + // from {content://approvider}//storage/emulated/0/DCIM/... to /storage/emulated/0/DCIM/ + path = FileUtils.fixPath(path); + if (dbgFilter != null) { + dbgFilter.append(dbgContext).append("\n"); + } else if (Global.debugEnabled) { + Log.d(Global.LOG_CONTEXT, dbgContext); + } + + if ((context != null) && AlbumFile.isQueryFile(path)) { + try { + QueryParameter query = QueryParameter.load(context.getContentResolver().openInputStream(uri)); + if (query != null) { + Map found = FotoSql.execGetPathIdMap(context, path); + if ((found == null) || (found.size() == 0)) { + AndroidAlbumUtils.albumMediaScan(dbgContext + " not found mediadb => ", context, new File(path), 1); + } + + if (dbgFilter != null) { + dbgFilter.append("query album from uri ").append(uri).append("\n") + .append(query).append("\n"); + } + return query; + } + + // not found in filesystem. + AndroidAlbumUtils.albumMediaScan(dbgContext + " not found in filesystem => ", context, new File(path), 1); + } catch (IOException e) { + Log.e(Global.LOG_CONTEXT, mDebugPrefix + ".loadFrom(" + uri + + ") failed: " + e.getMessage(), e); + if (dbgFilter != null) { + dbgFilter.append("query album from uri '").append(uri).append("' failed:") + .append(e.getMessage()).append("\n"); + } + } + } + + // replace file wildcards with sql wildcards + path = path.replace("*","%"); + + // non full-path-query becomes contains-path-query + if (!path.startsWith("/")) { + path = "%" + path; + if (!path.endsWith("%")) { + path += "%"; + } + } + if (path.length() > 2) { + if (dbgFilter != null) { + dbgFilter.append("path query from uri '").append(uri).append("' : '") + .append(path).append("'\n"); + } + GalleryFilterParameter filter = new GalleryFilterParameter().setPath(path); + return TagSql.filter2NewQuery(filter); + } + } + return null; + } + + @Deprecated + public static GalleryFilterParameter getGalleryFilterAndRestQueryFromQueryUri( + @NonNull Context context, Uri uri, QueryParameter resultQueryWithoutFilter, + boolean ignoreFilter, @Nullable StringBuilder dbgFilter) { + QueryParameter query = getQueryFromUri(mDebugPrefix, context, uri, null); + if (query != null) { + if ((uri != null) && (dbgFilter != null)) { + dbgFilter.append("query from uri ").append(uri).append("\n"); + } + return getFilterAndRestQuery(query, resultQueryWithoutFilter, ignoreFilter, dbgFilter); + } + return null; + } + + @Deprecated + private static GalleryFilterParameter getFilterAndRestQuery( + @NonNull Context context, Uri uri, String sql, String filter, QueryParameter resultQueryWithoutFilter, + boolean ignoreFilter, @Nullable StringBuilder dbgFilter) { + if (uri != null) { + return getGalleryFilterAndRestQueryFromQueryUri(context, uri, resultQueryWithoutFilter, ignoreFilter, dbgFilter); + } + + if ((sql != null) && (dbgFilter != null)) { + dbgFilter.append("query from ").append(EXTRA_QUERY).append("\n\t").append(sql).append("\n"); + } + QueryParameter query = QueryParameter.parse(sql); + final GalleryFilterParameter result = getFilterAndRestQuery(query, resultQueryWithoutFilter, ignoreFilter, dbgFilter); + + if (filter == null) { + // no own value for filter return parsed filter + return result; + } + + if (filter != null) { + if (dbgFilter != null) { + dbgFilter.append("filter from ").append(EXTRA_FILTER).append("=").append(filter).append("\n"); + } + return GalleryFilterParameter.parse(filter, new GalleryFilterParameter()); + } + return null; + } + + /** + * @param dbgMessagePrefix + * @param context + * @param uri if not null and exist: load from uri + * @param sql else if not null: parse query from this sql + * @param filter else if not null parse query from filter + */ + private static QueryParameter getQueryFromFirstMatching( + String dbgMessagePrefix, @NonNull Context context, @Nullable Uri uri, @Nullable String sql, @Nullable String filter, + @Nullable StringBuilder dbgMessageResult) { + if (uri != null) { + QueryParameter query = getQueryFromUri(dbgMessagePrefix, context, uri, dbgMessageResult); + if (query != null) { + return query; + } + } + + if (sql != null) { + if (dbgMessageResult != null) { + dbgMessageResult.append(dbgMessagePrefix).append(" query from ").append(EXTRA_QUERY).append("\n\t").append(sql).append("\n"); + } + return QueryParameter.parse(sql); + } + + if (filter != null) { + if (dbgMessageResult != null) { + dbgMessageResult.append(dbgMessagePrefix).append(" filter from ").append(EXTRA_FILTER).append("=").append(filter).append("\n"); + } + return TagSql.filter2NewQuery(GalleryFilterParameter.parse(filter, new GalleryFilterParameter())); + } + return null; + } + + @Deprecated + private static GalleryFilterParameter getFilterAndRestQuery( + QueryParameter query, QueryParameter resultQueryWithoutFilter, + boolean ignoreFilter, @Nullable StringBuilder dbgFilter) { + + final GalleryFilterParameter result = (ignoreFilter) ? null : (GalleryFilterParameter) TagSql.parseQueryEx(query, true); + + if (resultQueryWithoutFilter != null) { + resultQueryWithoutFilter.clear(); + resultQueryWithoutFilter.getFrom(query); + } + return result; + } + + /** + * + * @param context + * @param destUri if not null: merged-query-filter will be saved to this uri/file with update mediaDB + * @param destIntent if not null: merged-query-filter will be saved to this intent + * @param destBundle if not null: merged-query-filter will be saved to this intent + * @param srcFilter if not null filter will be appended to query parameter + * @param srcQueryParameter + */ + public static void saveFilterAndQuery( + @NonNull Context context, Uri destUri, Intent destIntent, Bundle destBundle, + final IGalleryFilter srcFilter, final QueryParameter srcQueryParameter) { + final String dbgContext = mDebugPrefix + ".saveFilterAndQuery(" + destUri + ")"; + final QueryParameter mergedQuery = getAsMergedNewQueryParameter(srcQueryParameter, srcFilter); + + if (destUri != null) { + PrintWriter out = null; + try { + mergedQuery.save(context.getContentResolver().openOutputStream(destUri, "w")); + insertToMediaDB(dbgContext, context, destUri); + } catch (IOException e) { + Log.e(Global.LOG_CONTEXT, dbgContext + + " failed: " + e.getMessage(), e); + } finally { + FileUtils.close(out, ""); + } + if (destIntent != null) destIntent.setData(destUri); + } + + if (destBundle != null) { + if (srcQueryParameter != null) + destBundle.putString(EXTRA_QUERY, mergedQuery.toReParseableString()); + else if (srcFilter != null) + destBundle.putString(EXTRA_FILTER, srcFilter.toString()); + } + if (destIntent != null) { + if (srcQueryParameter != null) + destIntent.putExtra(EXTRA_QUERY, mergedQuery.toReParseableString()); + else if (srcFilter != null) + destIntent.putExtra(EXTRA_FILTER, srcFilter.toString()); + } + } + + /** create a copy of srcQueryParameter and add srcFilter */ + public static QueryParameter getAsMergedNewQueryParameter(QueryParameter srcQueryParameter, IGalleryFilter srcFilter) { + final QueryParameter mergedQuery = new QueryParameter(srcQueryParameter); + TagSql.filter2QueryEx(mergedQuery, srcFilter, false); + return mergedQuery; + } + + public static void insertToMediaDB(String dbgContext, @NonNull Context context, Uri uri) { + if (IntentUtil.isFileUri(uri)) { + File f = FileUtils.tryGetCanonicalFile(IntentUtil.getFile(uri)); + insertToMediaDB(dbgContext, context, f); + } + } + + public static void insertToMediaDB(String dbgContext, @NonNull Context context, File fileToBeScannedAndInserted) { + if (fileToBeScannedAndInserted != null) { + ContentValues values = new ContentValues(); + String newAbsolutePath = MediaScanner.setFileFields(values, fileToBeScannedAndInserted); + values.put(FotoSql.SQL_COL_EXT_MEDIA_TYPE, FotoSql.MEDIA_TYPE_ALBUM_FILE); + FotoSql.insertOrUpdateMediaDatabase(dbgContext, context, newAbsolutePath, values, null, 1l); + } + } + + // !!! todo einbauen in filter-edit if item not found in db and/or filesystem und in media scanner + /** return the number of modified(added+deleted) items */ + public static int albumMediaScan(String dbgContext, Context context, File _root, int subDirLevels) { + String dbgMessage = dbgContext + ": albumMediaScan(" + _root + "): "; + + File root = FileUtils.getFirstExistingDir(_root); + int result = 0; + if (root != null) { + List currentFiles = AlbumFile.getFilePaths(null, root, subDirLevels); + List databaseFiles = FotoSql.getAlbumFiles(context, FileUtils.tryGetCanonicalPath(root, null), subDirLevels); + + List added = new ArrayList(); + List removed = new ArrayList(); + TagProcessor.getDiff(databaseFiles, currentFiles, added, removed); + + for (String insert : added) { + insertToMediaDB(dbgMessage + "add-new", context, new File(insert)); + result++; + } + + result += FotoSql.deleteMedia(dbgMessage + "delete-obsolete", context, removed, false); + } + return result; + } + + public static void saveAs(Context context, File outFile, final QueryParameter currentFilter) { + if (Global.debugEnabled) { + Log.d(Global.LOG_CONTEXT, "onSaveAs(" + outFile.getAbsolutePath() + ")"); + } + PrintWriter out = null; + try { + out = new PrintWriter(outFile); + out.println(currentFilter.toReParseableString()); + out.close(); + out = null; + + AndroidAlbumUtils.insertToMediaDB( + ".saveAlbumAs", + context, + outFile); + GalleryFilterPathState.saveAsPreference(context, Uri.fromFile(outFile), null); + } catch (IOException err) { + String errorMessage = context.getString(R.string.mk_err_failed_format, outFile.getAbsoluteFile()); + Toast.makeText(context, errorMessage, Toast.LENGTH_LONG).show(); + Log.e(Global.LOG_CONTEXT, errorMessage, err); + } + } + +} diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/queries/FotoSql.java b/app/src/main/java/de/k3b/android/androFotoFinder/queries/FotoSql.java index e569114f..0e8281d3 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/queries/FotoSql.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/queries/FotoSql.java @@ -47,6 +47,9 @@ import de.k3b.android.androFotoFinder.R; import de.k3b.android.util.DBUtils; import de.k3b.database.QueryParameter; +import de.k3b.io.AlbumFile; +import de.k3b.io.ListUtils; +import de.k3b.io.StringUtils; import de.k3b.io.VISIBILITY; import de.k3b.io.collections.SelectedFiles; import de.k3b.io.collections.SelectedItems; @@ -133,8 +136,9 @@ public class FotoSql extends FotoSqlBase { public static final String SQL_COL_EXT_MEDIA_TYPE = MediaStore.Files.FileColumns.MEDIA_TYPE; - public static final int MEDIA_TYPE_IMAGE = MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE; // 1 + public static final int MEDIA_TYPE_IMAGE = MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE; // 1 public static final int MEDIA_TYPE_IMAGE_PRIVATE = 1000 + MEDIA_TYPE_IMAGE; // 1001 APhoto manager specific + public static final int MEDIA_TYPE_ALBUM_FILE = 0; protected static final String FILTER_EXPR_PRIVATE = "(" + SQL_COL_EXT_MEDIA_TYPE + " = " + MEDIA_TYPE_IMAGE_PRIVATE + ")"; @@ -185,6 +189,17 @@ public class FotoSql extends FotoSqlBase { .addGroupBy(SQL_EXPR_FOLDER) .addOrderBy(SQL_EXPR_FOLDER); + public static final QueryParameter queryVAlbum = new QueryParameter() + .setID(QUERY_TYPE_GROUP_ALBUM) + .addColumn( + SQL_COL_PK, + SQL_COL_PATH + " AS " + SQL_COL_DISPLAY_TEXT, + "0 AS " + SQL_COL_COUNT, + "null AS " + SQL_COL_GPS) + .addFrom(SQL_TABLE_EXTERNAL_CONTENT_URI_FILE_NAME) + .addWhere(SQL_COL_PATH + " like '%" + AlbumFile.SUFFIX_VALBUM + "'") + .addOrderBy(SQL_COL_PATH); + /* image entries may become duplicated if media scanner finds new images that have not been inserted into media database yet * and aFotoSql tries to show the new image and triggers a filescan. */ public static final QueryParameter queryGetDuplicates = new QueryParameter() @@ -307,49 +322,88 @@ public static void filter2Query(QueryParameter resultQuery, IGalleryFilter filte resultQuery.clearWhere(); } - if (filter.isNonGeoOnly()) { - resultQuery.addWhere(FILTER_EXPR_NO_GPS); - } else { - addWhereFilterLatLon(resultQuery, filter); - } + addWhereFilterLatLon(resultQuery, filter); - if (filter.getDateMin() != 0) resultQuery.addWhere(FILTER_EXPR_DATE_MIN, Long.toString(filter.getDateMin())); - if (filter.getDateMax() != 0) resultQuery.addWhere(FILTER_EXPR_DATE_MAX, Long.toString(filter.getDateMax())); + addWhereDateMinMax(resultQuery, filter.getDateMin(), filter.getDateMax()); String path = filter.getPath(); if ((path != null) && (path.length() > 0)) resultQuery.addWhere(FILTER_EXPR_PATH_LIKE, path); } } + public static void addWhereDateMinMax(QueryParameter resultQuery, final long dateMin, final long dateMax) { + + if (dateMin != 0) resultQuery.addWhere(FILTER_EXPR_DATE_MIN, Long.toString(dateMin)); + + if (dateMax != 0) resultQuery.addWhere(FILTER_EXPR_DATE_MAX, Long.toString(dateMax)); + } + /** translates a query back to filter */ - public static IGalleryFilter parseQuery(QueryParameter query, boolean remove) { + public static IGalleryFilter parseQuery(QueryParameter query, boolean removeFromSourceQuery) { if (query != null) { GalleryFilterParameter filter = new GalleryFilterParameter(); - if (null != getParams(query, FILTER_EXPR_NO_GPS, remove)) { - filter.setNonGeoOnly(true); - } else { - filter.setLogitude(getParam(query, FILTER_EXPR_LON_MIN, remove), getParam(query, FILTER_EXPR_LON_MAX, remove)); - filter.setLatitude(getParam(query, FILTER_EXPR_LAT_MIN, remove), getParam(query, FILTER_EXPR_LAT_MAX, remove)); - } + parseQueryGeo(query, filter, removeFromSourceQuery); - filter.setRatingMin(GalleryFilterParameter.parseRating(getParam(query, FILTER_EXPR_RATING_MIN, remove))); - filter.setDate(getParam(query, FILTER_EXPR_DATE_MIN, remove), getParam(query, FILTER_EXPR_DATE_MAX, remove)); - filter.setPath(getParam(query, FILTER_EXPR_PATH_LIKE, remove)); + filter.setRatingMin(GalleryFilterParameter.parseRating(getParam(query, FILTER_EXPR_RATING_MIN, removeFromSourceQuery))); + filter.setDate(getParam(query, FILTER_EXPR_DATE_MIN, removeFromSourceQuery), getParam(query, FILTER_EXPR_DATE_MAX, removeFromSourceQuery)); + filter.setPath(getFilePath(query, removeFromSourceQuery)); return filter; } return null; } + /** extracts geo infos from srcQuery to destFilter */ + public static GeoRectangle parseQueryGeo(QueryParameter srcQuery, GeoRectangle destFilter, boolean removeFromSourceQuery) { + if (null != getParams(srcQuery, FILTER_EXPR_NO_GPS, removeFromSourceQuery)) { + if (destFilter != null) destFilter.setNonGeoOnly(true); + } else { + final String lonMin = getParam(srcQuery, FILTER_EXPR_LON_MIN, removeFromSourceQuery); + final String lonMax = getParam(srcQuery, FILTER_EXPR_LON_MAX, removeFromSourceQuery); + final String latMin = getParam(srcQuery, FILTER_EXPR_LAT_MIN, removeFromSourceQuery); + final String latMax = getParam(srcQuery, FILTER_EXPR_LAT_MAX, removeFromSourceQuery); + if (destFilter != null) { + destFilter.setLogitude(lonMin, lonMax); + destFilter.setLatitude(latMin, latMax); + } + } + return destFilter; + } + + public static String getFilePath(QueryParameter query, boolean removeFromSourceQuery) { + if (query == null) return null; + return getParam(query, FILTER_EXPR_PATH_LIKE, removeFromSourceQuery); + } + + /** append path expressions from src to dest. Return null if unchanged. */ + public static QueryParameter copyPathExpressions(QueryParameter dest, QueryParameter src) { + QueryParameter resultQuery = null; + if (src != null) { + // duplicate will be modified. preserve original + QueryParameter remainingQuery = new QueryParameter(src); + String pathExpr = null; + while (true) { + pathExpr = getFilePath(remainingQuery, true); + if (pathExpr != null) { + if (resultQuery == null) resultQuery = new QueryParameter(dest); + resultQuery.addWhere(FILTER_EXPR_PATH_LIKE, pathExpr); + } else { + break; + } + } + } + return resultQuery; + } + /** @return return param for expression inside query. null if expression is not in query or number of params is not 1. */ - protected static String getParam(QueryParameter query, String expresion, boolean remove) { - final String[] result = getParams(query, expresion, remove); + protected static String getParam(QueryParameter query, String expresion, boolean removeFromSourceQuery) { + final String[] result = getParams(query, expresion, removeFromSourceQuery); return ((result != null) && (result.length > 0)) ? result[0] : null; } /** @return return all params for expression inside query. null if expression is not in query */ - protected static String[] getParams(QueryParameter query, String expresion, boolean remove) { - return query.getWhereParameter(expresion, remove); + protected static String[] getParams(QueryParameter query, String expresion, boolean removeFromSourceQuery) { + return query.getWhereParameter(expresion, removeFromSourceQuery); } public static QueryParameter setWhereSelectionPks(QueryParameter query, SelectedItems selectedItems) { @@ -386,14 +440,18 @@ public static void setWhereFileNames(QueryParameter query, String... fileNames) } public static void addWhereLatLonNotNull(QueryParameter query) { - query.addWhere(FotoSql.SQL_COL_LAT + " is not null and " + FotoSql.SQL_COL_LON + " is not null") - ; + query.addWhere(FotoSql.SQL_COL_LAT + " is not null and " + + FotoSql.SQL_COL_LON + " is not null"); } - public static void addWhereFilterLatLon(QueryParameter parameters, IGeoRectangle filter) { - if ((parameters != null) && (filter != null)) { - addWhereFilterLatLon(parameters, filter.getLatitudeMin(), - filter.getLatitudeMax(), filter.getLogituedMin(), filter.getLogituedMax()); + public static void addWhereFilterLatLon(QueryParameter resultQuery, IGeoRectangle filter) { + if ((resultQuery != null) && (filter != null)) { + if (filter.isNonGeoOnly()) { + resultQuery.addWhere(FILTER_EXPR_NO_GPS); + } else { + addWhereFilterLatLon(resultQuery, filter.getLatitudeMin(), + filter.getLatitudeMax(), filter.getLogituedMin(), filter.getLogituedMax()); + } } } @@ -587,7 +645,7 @@ public static boolean set(GalleryFilterParameter dest, String selectedAbsolutePa public static String execGetFotoPath(Context context, Uri uriWithID) { Cursor c = null; try { - c = createCursorForQuery("execGetFotoPath", context, uriWithID.toString(), null, null, null, FotoSql.SQL_COL_PATH); + c = createCursorForQuery(null, "execGetFotoPath(uri)", context, uriWithID.toString(), null, null, null, FotoSql.SQL_COL_PATH); if (c.moveToFirst()) { return DBUtils.getString(c,FotoSql.SQL_COL_PATH, null); } @@ -605,7 +663,7 @@ public static List execGetFotoPaths(Context context, String pathFilter) Cursor c = null; try { - c = createCursorForQuery("execGetFotoPaths", context,SQL_TABLE_EXTERNAL_CONTENT_URI_FILE_NAME, + c = createCursorForQuery(null, "execGetFotoPaths(pathFilter)", context,SQL_TABLE_EXTERNAL_CONTENT_URI_FILE_NAME, FotoSql.SQL_COL_PATH + " like ? and " + FILTER_EXPR_PRIVATE_PUBLIC, new String[]{pathFilter}, FotoSql.SQL_COL_PATH, FotoSql.SQL_COL_PATH); while (c.moveToNext()) { @@ -623,16 +681,18 @@ public static List execGetFotoPaths(Context context, String pathFilter) return result; } - public static Cursor createCursorForQuery(String dbgContext, final Context context, QueryParameter parameters, VISIBILITY visibility) { + public static Cursor createCursorForQuery( + StringBuilder out_debugMessage, String dbgContext, final Context context, + QueryParameter parameters, VISIBILITY visibility) { if (visibility != null) setWhereVisibility(parameters, visibility); - return createCursorForQuery(dbgContext, context, parameters.toFrom(), parameters.toAndroidWhere(), + return createCursorForQuery(out_debugMessage, dbgContext, context, parameters.toFrom(), parameters.toAndroidWhere(), parameters.toAndroidParameters(), parameters.toOrderBy(), parameters.toColumns() ); } /** every cursor query should go through this. adds logging if enabled */ - private static Cursor createCursorForQuery(String dbgContext, final Context context, final String from, final String sqlWhereStatement, + private static Cursor createCursorForQuery(StringBuilder out_debugMessage, String dbgContext, final Context context, final String from, final String sqlWhereStatement, final String[] sqlWhereParameters, final String sqlSortOrder, final String... sqlSelectColums) { ContentResolver resolver = context.getContentResolver(); @@ -644,18 +704,25 @@ private static Cursor createCursorForQuery(String dbgContext, final Context cont } catch (Exception ex) { excpetion = ex; } finally { - if ((excpetion != null) || Global.debugEnabledSql) { - Log.i(Global.LOG_CONTEXT, dbgContext + ": FotoSql.createCursorForQuery: " + excpetion + - "\n" + + if ((excpetion != null) || Global.debugEnabledSql || (out_debugMessage != null)) { + StringBuilder message = StringUtils.appendMessage(out_debugMessage, excpetion, + dbgContext,"FotoSql.createCursorForQuery:\n" , QueryParameter.toString(sqlSelectColums, null, from, sqlWhereStatement, - sqlWhereParameters, sqlSortOrder, query.getCount()), excpetion); + sqlWhereParameters, sqlSortOrder, query.getCount())); + if (out_debugMessage == null) { + Log.i(Global.LOG_CONTEXT, message.toString(), excpetion); + } // else logging is done by caller } } return query; } - public static IGeoRectangle execGetGeoRectangle(Context context, IGalleryFilter filter, SelectedItems selectedItems) { + public static IGeoRectangle execGetGeoRectangle(StringBuilder out_debugMessage, Context context, QueryParameter baseQuery, + SelectedItems selectedItems, Object... dbgContext) { + StringBuilder debugMessage = (out_debugMessage == null) + ? StringUtils.createDebugMessage(Global.debugEnabledSql, dbgContext) + : out_debugMessage; QueryParameter query = new QueryParameter() .setID(QUERY_TYPE_UNDEFINED) .addColumn( @@ -668,8 +735,8 @@ public static IGeoRectangle execGetGeoRectangle(Context context, IGalleryFilter .addFrom(SQL_TABLE_EXTERNAL_CONTENT_URI_FILE_NAME) ; - if (!GalleryFilterParameter.isEmpty(filter)) { - filter2Query(query, filter, true); + if (baseQuery != null) { + query.getWhereFrom(baseQuery, true); } if (selectedItems != null) { @@ -677,36 +744,42 @@ public static IGeoRectangle execGetGeoRectangle(Context context, IGalleryFilter } FotoSql.addWhereLatLonNotNull(query); + GeoRectangle result = null; Cursor c = null; try { - c = createCursorForQuery("execGetGeoRectangle", context, query, VISIBILITY.PRIVATE_PUBLIC); + c = createCursorForQuery(debugMessage, "execGetGeoRectangle", context, query, VISIBILITY.PRIVATE_PUBLIC); if (c.moveToFirst()) { - GeoRectangle result = new GeoRectangle(); + result = new GeoRectangle(); result.setLatitude(c.getDouble(0), c.getDouble(1)); result.setLogitude(c.getDouble(2), c.getDouble(3)); - if (Global.debugEnabledSql) { - Log.i(Global.LOG_CONTEXT, "FotoSql.execGetGeoRectangle() => " + result + " from " + c.getLong(4) + " via\n\t" + query); - } - return result; } } catch (Exception ex) { Log.e(Global.LOG_CONTEXT, "FotoSql.execGetGeoRectangle(): error executing " + query, ex); } finally { if (c != null) c.close(); + if (debugMessage != null) { + StringUtils.appendMessage(debugMessage, "result", result); + if (out_debugMessage == null) { + Log.i(Global.LOG_CONTEXT, debugMessage.toString()); + } + } } - return null; + return result; } /** gets IGeoPoint either from file if fullPath is not null else from db via id */ - public static IGeoPoint execGetPosition(Context context, String fullPath, long id) { + public static IGeoPoint execGetPosition(StringBuilder out_debugMessage, Context context, + String fullPath, long id, Object... dbgContext) { + StringBuilder debugMessage = (out_debugMessage == null) ? StringUtils.createDebugMessage(Global.debugEnabledSql, dbgContext) : out_debugMessage; QueryParameter query = new QueryParameter() .setID(QUERY_TYPE_UNDEFINED) .addColumn(SQL_COL_LAT, SQL_COL_LON) .addFrom(SQL_TABLE_EXTERNAL_CONTENT_URI_FILE_NAME) .addWhere(SQL_COL_LAT + " IS NOT NULL") - .addWhere(SQL_COL_LON + " IS NOT NULL"); + .addWhere(SQL_COL_LON + " IS NOT NULL") + .addWhere("(" + SQL_COL_LON + " <> 0 OR " + SQL_COL_LAT + " <> 0)"); if (fullPath != null) { query.addWhere(SQL_COL_PATH + "= ?", fullPath); @@ -715,17 +788,24 @@ public static IGeoPoint execGetPosition(Context context, String fullPath, long i query.addWhere(FILTER_COL_PK, "" + id); } + GeoPoint result = null; Cursor c = null; try { - c = createCursorForQuery("execGetPosition", context, query, VISIBILITY.PRIVATE_PUBLIC); + c = createCursorForQuery(debugMessage, "execGetPosition", context, query, VISIBILITY.PRIVATE_PUBLIC); if (c.moveToFirst()) { - GeoPoint result = new GeoPoint(c.getDouble(0),c.getDouble(1)); + result = new GeoPoint(c.getDouble(0),c.getDouble(1)); return result; } } catch (Exception ex) { Log.e(Global.LOG_CONTEXT, "FotoSql.execGetPosition: error executing " + query, ex); } finally { if (c != null) c.close(); + if (debugMessage != null) { + StringUtils.appendMessage(debugMessage, "result", result); + if (out_debugMessage == null) { + Log.i(Global.LOG_CONTEXT, debugMessage.toString()); + } // else logging by caller + } } return null; } @@ -746,7 +826,7 @@ public static Map execGetPathIdMap(Context context, String... file Cursor c = null; try { - c = createCursorForQuery("execGetPathIdMap", context, query, VISIBILITY.PRIVATE_PUBLIC); + c = createCursorForQuery(null, "execGetPathIdMap", context, query, null); while (c.moveToNext()) { result.put(c.getString(1),c.getLong(0)); } @@ -813,7 +893,7 @@ public static int execRenameFolder(Context context, String pathOld, String pathN .addWhere(SQL_COL_EXT_MEDIA_TYPE + " IS NOT NULL") ; - SelectedFiles selectedFiles= getSelectedfiles(context, queryAffectedFiles, sqlColNewPathAlias); + SelectedFiles selectedFiles= getSelectedfiles(context, queryAffectedFiles, sqlColNewPathAlias, null); String[] paths = selectedFiles.getFileNames(); Long[] ids = selectedFiles.getIds(); @@ -859,7 +939,7 @@ private static int _del_execRenameFolder_batch_not_working(Context context, Stri Cursor c = null; try { - c = createCursorForQuery(dbgContext, context, queryAffectedFiles, null); + c = createCursorForQuery(null, dbgContext, context, queryAffectedFiles, null); int pkColNo = c.getColumnIndex(FotoSql.SQL_COL_PK); int pathColNo = c.getColumnIndex(sqlColNewPathAlias); @@ -911,16 +991,22 @@ protected static int exexUpdateImpl(String dbgContext, Context context, ContentV protected static String getFilterExprPathLikeWithVisibility(VISIBILITY visibility) { // visibility VISIBILITY.PRIVATE_PUBLIC - return FotoSql.FILTER_EXPR_PATH_LIKE + " AND " + getFilterExpressionVisibility(visibility); + String resultExpression = FotoSql.FILTER_EXPR_PATH_LIKE; + if (visibility != null) { + resultExpression += " AND " + getFilterExpressionVisibility(visibility); + } + return resultExpression; } + /** return id of inserted item */ public static Long insertOrUpdateMediaDatabase(String dbgContext, Context context, String dbUpdateFilterJpgFullPathName, - ContentValues values, Long updateSuccessValue) { + ContentValues values, VISIBILITY visibility, + Long updateSuccessValue) { Long result = updateSuccessValue; int modifyCount = FotoSql.execUpdate(dbgContext, context, dbUpdateFilterJpgFullPathName, - values, VISIBILITY.PRIVATE_PUBLIC); + values, visibility); if (modifyCount == 0) { // update failed (probably becauce oldFullPathName not found. try insert it. @@ -964,6 +1050,15 @@ public static int execDeleteByPath(String dbgContext, Activity context, String p return delCount; } + public static int deleteMedia(String dbgContext, Context context, List pathsToBeRemoved, + boolean preventDeleteImageFile) { + if ((pathsToBeRemoved != null) && (pathsToBeRemoved.size() > 0)) { + String whereDelete = SQL_COL_PATH + " in ('" + ListUtils.toString("','", pathsToBeRemoved) + "')"; + return deleteMedia(dbgContext, context, whereDelete, null, preventDeleteImageFile); + } + return 0; + } + public static int deleteMediaWithNullPath(Context context) { /// delete where SQL_COL_PATH + " is null" throws null pointer exception QueryParameter wherePathIsNull = new QueryParameter(); @@ -972,7 +1067,7 @@ public static int deleteMediaWithNullPath(Context context) { // return deleteMedia("delete without path (_data = null)", context, wherePathIsNull.toAndroidWhere(), null, false); - SelectedFiles filesWitoutPath = getSelectedfiles(context, wherePathIsNull, FotoSql.SQL_COL_PATH); + SelectedFiles filesWitoutPath = getSelectedfiles(context, wherePathIsNull, FotoSql.SQL_COL_PATH, VISIBILITY.PRIVATE_PUBLIC); String pksAsString = filesWitoutPath.toIdString(); if ((pksAsString != null) && (pksAsString.length() > 0)) { QueryParameter whereInIds = new QueryParameter(); @@ -1060,22 +1155,41 @@ public static String getUriString(long imageID) { return SQL_TABLE_EXTERNAL_CONTENT_URI_FILE_NAME + "/" + imageID; } - public static SelectedFiles getSelectedfiles(Context context, String sqlWhere) { + public static SelectedFiles getSelectedfiles(Context context, String sqlWhere, VISIBILITY visibility) { QueryParameter query = new QueryParameter(FotoSql.queryChangePath); query.addWhere(sqlWhere); query.addOrderBy(FotoSql.SQL_COL_PATH); - return getSelectedfiles(context, query, FotoSql.SQL_COL_PATH); + return getSelectedfiles(context, query, FotoSql.SQL_COL_PATH, visibility); + + } + + @Nullable + public static long getCount(Context context, QueryParameter query) { + QueryParameter queryModified = new QueryParameter(query); + queryModified.clearColumns().addColumn("count(*)"); + Cursor c = null; + try { + c = FotoSql.createCursorForQuery(null, "getCount", context, queryModified, null); + if (c.moveToNext()) { + return c.getLong(0); + } + } catch (Exception ex) { + Log.e(Global.LOG_CONTEXT, "FotoSql.getCount() error :", ex); + } finally { + if (c != null) c.close(); + } + return 0; } @Nullable - private static SelectedFiles getSelectedfiles(Context context, QueryParameter query, String colnameForPath) { + private static SelectedFiles getSelectedfiles(Context context, QueryParameter query, String colnameForPath, VISIBILITY visibility) { SelectedFiles result = null; Cursor c = null; try { - c = FotoSql.createCursorForQuery("getSelectedfiles", context, query, VISIBILITY.PRIVATE_PUBLIC); + c = FotoSql.createCursorForQuery(null, "getSelectedfiles", context, query, visibility); int len = c.getCount(); Long[] ids = new Long[len]; String[] paths = new String[len]; @@ -1149,7 +1263,7 @@ private static List getFileNamesImpl(Context context, QueryParameter par Cursor cursor = null; try { - cursor = createCursorForQuery("getFileNames", context, parameters, VISIBILITY.PRIVATE_PUBLIC); + cursor = createCursorForQuery(null, "getFileNames", context, parameters, VISIBILITY.PRIVATE_PUBLIC); int colPath = cursor.getColumnIndex(SQL_COL_DISPLAY_TEXT); if (colPath == -1) colPath = cursor.getColumnIndex(SQL_COL_PATH); @@ -1193,14 +1307,44 @@ public static QueryParameter setWhereVisibility(QueryParameter parameters, VISIB if (parameters.toFrom() == null) { parameters.addFrom(SQL_TABLE_EXTERNAL_CONTENT_URI_FILE_NAME); } - String sqlWhere = parameters.toAndroidWhere(); - if ((sqlWhere == null) || (parameters.toFrom().contains(SQL_TABLE_EXTERNAL_CONTENT_URI_FILE_NAME) && !sqlWhere.contains(SQL_COL_EXT_MEDIA_TYPE))) { - parameters.addWhere(getFilterExpressionVisibility(visibility)); + + if (visibility != null) { + String sqlWhere = parameters.toAndroidWhere(); + if ((sqlWhere == null) || (parameters.toFrom().contains(SQL_TABLE_EXTERNAL_CONTENT_URI_FILE_NAME) && !sqlWhere.contains(SQL_COL_EXT_MEDIA_TYPE))) { + parameters.addWhere(getFilterExpressionVisibility(visibility)); + } } return parameters; } + public static List getAlbumFiles(Context context, String path, int subDirLevels) { + SelectedFiles databaseFiles = FotoSql.getSelectedfiles(context, + SQL_COL_PATH +" like '" + path + "/%" + AlbumFile.SUFFIX_VALBUM + "' OR " + + SQL_COL_PATH +" like '" + path + "/%" + AlbumFile.SUFFIX_QUERY + "'", null); + String[] fileNames = (databaseFiles == null) ? null : databaseFiles.getFileNames(); + List paths = new ArrayList(); + + // copy all items that are not deeper that subDirLevels + int numSegs = StringUtils.charCount(path,'/') + subDirLevels; + if (fileNames != null) { + for (int i = fileNames.length - 1; i >= 0; i--) { + String fileName = fileNames[i]; + if ((fileName != null) && (StringUtils.charCount(fileName, '/') <= numSegs)) { + paths.add(fileName); + } + } + } + return paths; + } + + public static int execRename(Context context, String oldFullPath, String newFullPath) { + ContentValues values = new ContentValues(); + values.put(SQL_COL_PATH, newFullPath); + return FotoSql.execUpdate("rename file", context, oldFullPath, + values, null); + } + public static class CursorLoaderWithException extends CursorLoader { private final QueryParameter query; private Exception mException; diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/queries/FotoThumbSql.java b/app/src/main/java/de/k3b/android/androFotoFinder/queries/FotoThumbSql.java index 4bbfa6a5..137eb258 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/queries/FotoThumbSql.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/queries/FotoThumbSql.java @@ -25,7 +25,6 @@ import de.k3b.android.androFotoFinder.Global; import de.k3b.database.QueryParameter; -import de.k3b.io.IGalleryFilter; import de.k3b.io.VISIBILITY; /** @@ -67,7 +66,7 @@ private static String getStatistic(Context context, QueryParameter query, String Cursor c = null; try { - c = FotoSql.createCursorForQuery(mDebugPrefix + "getStatistic", context, query, VISIBILITY.PRIVATE_PUBLIC); + c = FotoSql.createCursorForQuery(null, mDebugPrefix + "getStatistic", context, query, VISIBILITY.PRIVATE_PUBLIC); if (Global.debugEnabledSql) { Log.i(Global.LOG_CONTEXT, mDebugPrefix + "getStatistic " + c.getCount() + "\n\t" + query.toSqlString()); diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/tagDB/TagSql.java b/app/src/main/java/de/k3b/android/androFotoFinder/tagDB/TagSql.java index d8e77f31..b15fd3c4 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/tagDB/TagSql.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/tagDB/TagSql.java @@ -35,6 +35,7 @@ import de.k3b.FotoLibGlobal; import de.k3b.android.androFotoFinder.Global; import de.k3b.android.androFotoFinder.media.MediaContentValues; +import de.k3b.android.androFotoFinder.queries.AndroidAlbumUtils; import de.k3b.android.androFotoFinder.queries.FotoSql; import de.k3b.android.util.MediaScanner; import de.k3b.io.GalleryFilterParameter; @@ -94,9 +95,16 @@ public static IGalleryFilter parseQueryEx(QueryParameter query, boolean remove) // from more complex to less complex String[] params; - if ((params = getParams(query, FILTER_EXPR_ANY_LIKE, remove)) != null) { - resultFilter.setInAnyField(params[0]); + StringBuilder any = null; + while ((params = getParams(query, FILTER_EXPR_ANY_LIKE, remove)) != null) { + if (any == null) { + any = new StringBuilder().append(params[0]); + } else { + any.append(" ").append(params[0]); + + } } + if (any != null) resultFilter.setInAnyField(any.toString()); parseTagsFromQuery(query, remove, resultFilter); @@ -152,17 +160,17 @@ private static void parseTagsFromQuery(QueryParameter query, boolean remove, Gal } + public static QueryParameter filter2NewQuery(IGalleryFilter filter) { + return AndroidAlbumUtils.getAsMergedNewQueryParameter(null, filter); + } + public static void filter2QueryEx(QueryParameter resultQuery, IGalleryFilter filter, boolean clearWhereBefore) { if ((resultQuery != null) && (!GalleryFilterParameter.isEmpty(filter))) { filter2Query(resultQuery, filter, clearWhereBefore); if (Global.Media.enableIptcMediaScanner) { - String any = filter.getInAnyField(); - if ((any != null) && (any.length() > 0)) { - if (!any.contains("%")) { - any = "%" + any + "%"; - } - resultQuery.addWhere(FILTER_EXPR_ANY_LIKE, any, any, any, any); - } + String allAny = filter.getInAnyField(); + + addFilterAny(resultQuery, allAny); List includes = ListUtils.emptyAsNull(filter.getTagsAllIncluded()); @@ -193,6 +201,19 @@ public static void filter2QueryEx(QueryParameter resultQuery, IGalleryFilter fil } } + public static void addFilterAny(QueryParameter resultQuery, String allAny) { + if (allAny != null) { + for (String any : allAny.split(" ")) { + if ((any != null) && (any.length() > 0)) { + if (!any.contains("%")) { + any = "%" + any + "%"; + } + resultQuery.addWhere(FILTER_EXPR_ANY_LIKE, any, any, any, any); + } + } + } + } + private static QueryParameter addWhereTagExcluded(QueryParameter resultQuery, String tag, boolean withNoTags) { return resultQuery.addWhere((withNoTags) ? FILTER_EXPR_TAG_NONE_OR_EXCLUDED : FILTER_EXPR_TAG_EXCLUDED, "%;" + tag + ";%"); } @@ -381,7 +402,7 @@ public static int getTagRefCount(Context context, List tags) { if (addWhereAnyOfTags(query, tags) > 0) { Cursor c = null; try { - c = createCursorForQuery("getTagRefCount", context, query, VISIBILITY.PRIVATE_PUBLIC); + c = createCursorForQuery(null, "getTagRefCount", context, query, VISIBILITY.PRIVATE_PUBLIC); if (c.moveToFirst()) { return c.getInt(0); } @@ -435,7 +456,7 @@ public static List loadTagWorflowItems(Context context, String s if (filterCount > 0) { try { - c = createCursorForQuery("loadTagWorflowItems", context, query, VISIBILITY.PRIVATE_PUBLIC); + c = createCursorForQuery(null, "loadTagWorflowItems", context, query, VISIBILITY.PRIVATE_PUBLIC); if (c.moveToFirst()) { do { result.add(new TagWorflowItem(c.getLong(0), c.getString(1), TagConverter.fromString(c.getString(2)), diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/tagDB/TagsPickerFragment.java b/app/src/main/java/de/k3b/android/androFotoFinder/tagDB/TagsPickerFragment.java index 48b5185b..3c1608c0 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/tagDB/TagsPickerFragment.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/tagDB/TagsPickerFragment.java @@ -490,7 +490,7 @@ public static boolean handleMenuShow(int menuItemItemId, Tag selectedTag, Activi ImageDetailActivityViewPager.showActivity(context, null, 0, createSubQueryByTag(parentFilter, selectedTag), 0); return true; case R.id.cmd_gallery: - FotoGalleryActivity.showActivity(context, createSubFilterByTag(parentFilter, selectedTag), null, 0); + FotoGalleryActivity.showActivity(context, createSubQueryByTag(parentFilter, selectedTag), 0); return true; default:break; } @@ -500,8 +500,7 @@ public static boolean handleMenuShow(int menuItemItemId, Tag selectedTag, Activi @NonNull private static QueryParameter createSubQueryByTag(IGalleryFilter parentFilter, Tag selectedTag) { GalleryFilterParameter filter = createSubFilterByTag(parentFilter, selectedTag); - QueryParameter query = new QueryParameter(); - TagSql.filter2QueryEx(query, filter, false); + QueryParameter query = TagSql.filter2NewQuery(filter); FotoSql.setSort(query, FotoSql.SORT_BY_DATE, false); return query; } diff --git a/app/src/main/java/de/k3b/android/osmdroid/OsmdroidUtil.java b/app/src/main/java/de/k3b/android/osmdroid/OsmdroidUtil.java index 75e7e63f..cde37568 100644 --- a/app/src/main/java/de/k3b/android/osmdroid/OsmdroidUtil.java +++ b/app/src/main/java/de/k3b/android/osmdroid/OsmdroidUtil.java @@ -29,59 +29,71 @@ import org.osmdroid.api.IMapController; import org.osmdroid.tileprovider.MapTileProviderBase; import org.osmdroid.util.GeoPoint; +import org.osmdroid.util.TileSystem; import org.osmdroid.views.MapView; -import microsoft.mappoint.TileSystem; - /** * Helper for OsmDroid lib. * * Created by k3b on 16.03.2015. */ public class OsmdroidUtil { + // osmdroid supports 0..29.0 but this app only 0..22 + private static int MAX_ZOOM_LEVEL = 22; public static final int NO_ZOOM = -1; // GeoPointDto.NO_ZOOM; + public static final int RECALCULATE_ZOOM = NO_ZOOM; // GeoPointDto.NO_ZOOM; /** * Similar to MapView.zoomToBoundingBox that seems to be to inexact. * @param zoom if NO_ZOOM (-1) zoom is calculated from min and max */ - public static void zoomTo(MapView mapView, int zoom, IGeoPoint min, IGeoPoint max) { + public static int zoomTo(MapView mapView, int zoom, IGeoPoint min, IGeoPoint max) { int calculatedZoom = zoom; - MapTileProviderBase tileProvider = mapView.getTileProvider(); - IMapController controller = mapView.getController(); - IGeoPoint center = min; - - if (max != null) { - center = new GeoPoint((max.getLatitudeE6() + min.getLatitudeE6()) / 2, (max.getLongitudeE6() + min.getLongitudeE6()) / 2); - - if (calculatedZoom == NO_ZOOM) { - // int pixels = Math.min(mapView.getWidth(), mapView.getHeight()); - double pixels = Math.sqrt((mapView.getWidth() * mapView.getWidth()) + (mapView.getHeight() * mapView.getHeight())); - final double requiredMinimalGroundResolutionInMetersPerPixel - = ((double) new GeoPoint(min.getLatitude(), min.getLongitude()).distanceToAsDouble(max)) / pixels; - calculatedZoom = calculateZoom(center.getLatitude(), requiredMinimalGroundResolutionInMetersPerPixel, tileProvider.getMaximumZoomLevel(), tileProvider.getMinimumZoomLevel()); + if (mapView != null) { + MapTileProviderBase tileProvider = mapView.getTileProvider(); + IMapController controller = mapView.getController(); + IGeoPoint center = min; + + if (max != null) { + center = new GeoPoint((max.getLatitude() + min.getLatitude()) / 2, (max.getLongitude() + min.getLongitude()) / 2); + + if (calculatedZoom == NO_ZOOM) { + // int pixels = Math.min(mapView.getWidth(), mapView.getHeight()); + double pixels = Math.sqrt((mapView.getWidth() * mapView.getWidth()) + (mapView.getHeight() * mapView.getHeight())); + final double requiredMinimalGroundResolutionInMetersPerPixel + = ((double) new GeoPoint(min.getLatitude(), min.getLongitude()).distanceToAsDouble(max)) / pixels; + calculatedZoom = calculateZoom(center.getLatitude(), requiredMinimalGroundResolutionInMetersPerPixel, getMaximumZoomLevel(tileProvider), tileProvider.getMinimumZoomLevel()); + } + } + if (calculatedZoom != NO_ZOOM) { + controller.setZoom((double) calculatedZoom); } - } - if (calculatedZoom != NO_ZOOM) { - controller.setZoom(calculatedZoom); - } - if (center != null) { - controller.setCenter(center); - } -/* - if (logger.isDebugEnabled()) { - logger.debug("DelayedSetCenterZoom.execute({}: ({}) .. ({}),z={}) => ({}), z={} => {}", - debugContext, - min, max, mZoomLevel, center, zoom, getStatusForDebug()); + if (center != null) { + controller.setCenter(center); } -*/ + } + return calculatedZoom; + } + + public static double getMaximumZoomLevel(MapView map) { + final double maxZoomLevel = map.getMaxZoomLevel(); + return (maxZoomLevel > MAX_ZOOM_LEVEL) ? MAX_ZOOM_LEVEL : maxZoomLevel; + } + + public static int getMaximumZoomLevel(MapTileProviderBase tileProvider) { + final int maxZoomLevel = tileProvider.getMaximumZoomLevel(); + return (maxZoomLevel > MAX_ZOOM_LEVEL) ? MAX_ZOOM_LEVEL : maxZoomLevel; } - private static int calculateZoom(double latitude, double requiredMinimalGroundResolutionInMetersPerPixel, int maximumZoomLevel, int minimumZoomLevel) { - for (int zoom = maximumZoomLevel; zoom >= minimumZoomLevel; zoom--) { - if (TileSystem.GroundResolution(latitude, zoom) > requiredMinimalGroundResolutionInMetersPerPixel) + private static int calculateZoom(double latitude, double requiredMinimalGroundResolutionInMetersPerPixel, + int maximumZoomLevel, int minimumZoomLevel) { + double groundResolution; + for (int zoom = Math.min(maximumZoomLevel, 15); zoom >= minimumZoomLevel; zoom--) { + // groundResolution = TileSystemFix_978.GroundResolution(latitude, zoom); + groundResolution = TileSystem.GroundResolution(latitude, zoom); + if (groundResolution > requiredMinimalGroundResolutionInMetersPerPixel) return zoom; } diff --git a/app/src/main/java/de/k3b/android/util/AndroidFileCommands.java b/app/src/main/java/de/k3b/android/util/AndroidFileCommands.java index ee5bd8b3..67c026c1 100644 --- a/app/src/main/java/de/k3b/android/util/AndroidFileCommands.java +++ b/app/src/main/java/de/k3b/android/util/AndroidFileCommands.java @@ -215,8 +215,13 @@ public int execRename(File srcDirFile, String newFolderName) { if (destDirFile != null) { destDirFile.getParentFile().mkdirs(); + boolean isDir = srcDirFile.isDirectory(); if (srcDirFile.renameTo(destDirFile)) { - modifyCount = FotoSql.execRenameFolder(this.mContext, srcDirFile.getAbsolutePath() + "/", destDirFile.getAbsolutePath() + "/"); + if (isDir) { + modifyCount = FotoSql.execRenameFolder(this.mContext, srcDirFile.getAbsolutePath() + "/", destDirFile.getAbsolutePath() + "/"); + } else { + modifyCount = FotoSql.execRename(mContext, srcDirFile.getAbsolutePath(), destDirFile.getAbsolutePath()); + } if (modifyCount < 0) { destDirFile.renameTo(srcDirFile); // error: undo change return -1; @@ -329,17 +334,41 @@ public int deleteFiles(SelectedFiles fotos, IProgessListener progessListener) { } @SuppressLint("ValidFragment") - class MediaScannerDirectoryPickerFragment extends DirectoryPickerFragment { + private static class MediaScannerDirectoryPickerFragment extends DirectoryPickerFragment { + private AndroidFileCommands mParent = null; + /** do not use activity callback */ @Override protected void setDirectoryListener(Activity activity) {} @Override protected void onDirectoryPick(IDirectory selection) { dismiss(); - if (selection != null) { - onMediaScannerAnswer(selection.getAbsolute()); + if ((mParent != null) && (selection != null)) { + mParent.onMediaScannerAnswer(selection.getAbsolute()); } } + + @Override + public void onPause() { + super.onPause(); + + // else the java.lang.InstantiationException: can't instantiate + // class de.k3b.android.util.AndroidFileCommands$MediaScannerDirectoryPickerFragment; + // no empty constructor + // on orientation change + dismiss(); + } + + public void setParent(AndroidFileCommands parent) { + this.mParent = parent; + } + + @Override + public void dismiss() { + setParent(null); + super.dismiss(); + } + } public boolean cmdMediaScannerWithQuestion() { @@ -352,35 +381,14 @@ public boolean cmdMediaScannerWithQuestion() { return true; } else if (AndroidFileCommands.canProcessFile(mContext, this.isInBackground)) { // show dialog to get start parameter - DirectoryPickerFragment destDir = new MediaScannerDirectoryPickerFragment() { - /** do not use activity callback */ - @Override protected void setDirectoryListener(Activity activity) {} - - @Override - protected void onDirectoryPick(IDirectory selection) { - dismiss(); - if (selection != null) { - onMediaScannerAnswer(selection.getAbsolute()); - } - } - - @Override - public void onPause() { - super.onPause(); - - // else the java.lang.InstantiationException: can't instantiate - // class de.k3b.android.util.AndroidFileCommands$MediaScannerDirectoryPickerFragment; - // no empty constructor - // on orientation change - dismiss(); - } - }; + MediaScannerDirectoryPickerFragment destDir = new MediaScannerDirectoryPickerFragment(); + destDir.setParent(this); destDir.setTitleId(R.string.scanner_dir_question); - destDir.defineDirectoryNavigation(OsUtils.getRootOSDirectory(), + destDir.defineDirectoryNavigation(OsUtils.getRootOSDirectory(null), FotoSql.QUERY_TYPE_UNDEFINED, getLastCopyToPath()); - destDir.setContextMenuId(LockScreen.isLocked(mContext) ? 0 : R.menu.menu_context_osdir); + destDir.setContextMenuId(LockScreen.isLocked(mContext) ? 0 : R.menu.menu_context_osdir); destDir.show(mContext.getFragmentManager(), "scannerPick"); return true; diff --git a/app/src/main/java/de/k3b/android/util/IntentUtil.java b/app/src/main/java/de/k3b/android/util/IntentUtil.java index 2dfe1d4f..313d3985 100644 --- a/app/src/main/java/de/k3b/android/util/IntentUtil.java +++ b/app/src/main/java/de/k3b/android/util/IntentUtil.java @@ -58,6 +58,7 @@ public static String getFilePath(Context context, Uri uri) { if ((scheme == null) || ("file".equals(scheme))) { path = uri.getPath(); } else if ("content".equals(scheme)) { + // try to translate via media db path = FotoSql.execGetFotoPath(context, uri); if (path != null) { if (Global.debugEnabled) { @@ -65,10 +66,21 @@ public static String getFilePath(Context context, Uri uri) { "' to '" + path + "'"); } } else { - Log.i(Global.LOG_CONTEXT, "Cannot translate from '" + uri + - "' to local file"); + // #118 try to translate from app specific content uri + // i.e. "OI file manager" uses "content://org.openintents.filemanager/%2Fstorage%2Femulated%2F0%2FDCIM%2F%F0%9F%93%B8test%2F180122mytest001.jpg" + path = uri.getPath(); } } + + // #118 app specific content uri convert from //storage/emulated/0/DCIM/... to /storage/emulated/0/DCIM/ + if ((path != null) && (path.startsWith("//"))) path = path.substring(1); + final File file = getExistingFileOrNull(path); + + if (file == null) { + path = null; + Log.i(Global.LOG_CONTEXT, "Cannot translate from '" + uri + + "' to local file"); + } } return path; } @@ -88,20 +100,39 @@ public static Uri getUri(Intent intent) { /** return null if uri is not a valid file scheam */ @Nullable public static File getFile(Uri uri) { + // #118: opened from "OI file manager" with app specific content uri-s i.e. + // "content://org.openintents.filemanager/%2Fstorage%2Femulated%2F0%2FDCIM%2F%F0%9F%93%B8test%2F180122mytest001.jpg" if (isFileUri(uri)) { + final File file = getExistingFileOrNull(uri.getPath()); + if (file != null) return file; + } + return null; + } + + private static File getExistingFileOrNull(String fullPath) { + if (fullPath != null) { try { - return new File(uri.getPath()); + final File file = new File(fullPath); + if ((file != null) && (file.exists())) { + return file; + } } catch (Exception ex) { - ; // i.e. contain illegal chars + Log.d(Global.LOG_CONTEXT, + "Cannot open " + fullPath + " as file "); } } return null; } public static boolean isFileUri(Uri uri) { + return isFileOrContentUri(uri,false); + } + + public static boolean isFileOrContentUri(Uri uri, boolean allowContent) { if (uri == null) return false; String scheme = uri.getScheme(); - return StringUtils.isNullOrEmpty(scheme) || (0 == "file".compareTo(scheme)); + return StringUtils.isNullOrEmpty(scheme) || (0 == "file".compareTo(scheme) + || (allowContent && (0 == "content".compareTo(scheme)))); } public static boolean isFileUri(String initalFileUrl) { diff --git a/app/src/main/java/de/k3b/android/util/MediaScanner.java b/app/src/main/java/de/k3b/android/util/MediaScanner.java index 4b36504f..002f97f4 100644 --- a/app/src/main/java/de/k3b/android/util/MediaScanner.java +++ b/app/src/main/java/de/k3b/android/util/MediaScanner.java @@ -235,7 +235,9 @@ public Long insertOrUpdateMediaDatabase(String dbgContext, Context context, if ((currentJpgFile != null) && currentJpgFile.exists() && currentJpgFile.canRead()) { ContentValues values = createDefaultContentValues(); getExifFromFile(values, currentJpgFile); - Long result = FotoSql.insertOrUpdateMediaDatabase(dbgContext, context, dbUpdateFilterJpgFullPathName, values, updateSuccessValue); + Long result = FotoSql.insertOrUpdateMediaDatabase( + dbgContext, context, dbUpdateFilterJpgFullPathName, + values, VISIBILITY.PRIVATE_PUBLIC, updateSuccessValue); return result; } @@ -301,7 +303,7 @@ private int renameInMediaDatabase(Context context, Map old2NewFi Cursor c = null; try { - c = FotoSql.createCursorForQuery("renameInMediaDatabase", context, query, VISIBILITY.PRIVATE_PUBLIC); + c = FotoSql.createCursorForQuery(null, "renameInMediaDatabase", context, query, VISIBILITY.PRIVATE_PUBLIC); int pkColNo = c.getColumnIndex(FotoSql.SQL_COL_PK); int pathColNo = c.getColumnIndex(FotoSql.SQL_COL_PATH); while (c.moveToNext()) { @@ -436,14 +438,24 @@ public int updatePathRelatedFields(Context context, Cursor cursor, String newAbs } /** sets the path related fields */ - private void setPathRelatedFieldsIfNeccessary(ContentValues values, String newAbsolutePath, String oldAbsolutePath) { + private static void setPathRelatedFieldsIfNeccessary(ContentValues values, String newAbsolutePath, String oldAbsolutePath) { setFieldIfNeccessary(values, DB_TITLE, generateTitleFromFilePath(newAbsolutePath), generateTitleFromFilePath(oldAbsolutePath)); setFieldIfNeccessary(values, DB_DISPLAY_NAME, generateDisplayNameFromFilePath(newAbsolutePath), generateDisplayNameFromFilePath(oldAbsolutePath)); values.put(DB_DATA, newAbsolutePath); } + /** sets the path related fields */ + public static String setFileFields(ContentValues values, File file) { + String newAbsolutePath = FileUtils.tryGetCanonicalPath(file, file.getAbsolutePath()); + setPathRelatedFieldsIfNeccessary(values, newAbsolutePath, null); + values.put(DB_DATE_MODIFIED, file.lastModified() / 1000); + values.put(DB_SIZE, file.length()); + return newAbsolutePath; + } + + /** values[fieldName]=newCalculatedValue if current not set or equals oldCalculatedValue */ - private void setFieldIfNeccessary(ContentValues values, String fieldName, String newCalculatedValue, String oldCalculatedValue) { + private static void setFieldIfNeccessary(ContentValues values, String fieldName, String newCalculatedValue, String oldCalculatedValue) { String currentValue = values.getAsString(fieldName); if ((currentValue == null) || (TextUtils.isEmpty(currentValue.trim())) || (currentValue.equals(oldCalculatedValue))) { values.put(fieldName, newCalculatedValue); @@ -485,7 +497,7 @@ private int insert_Android42(String dbgContext, Context context, File file) { @NonNull // generates a title based on file name - protected String generateTitleFromFilePath(String _filePath) { + protected static String generateTitleFromFilePath(String _filePath) { String filePath = generateDisplayNameFromFilePath(_filePath); if (filePath != null) { diff --git a/app/src/main/java/de/k3b/android/util/MenuUtils.java b/app/src/main/java/de/k3b/android/util/MenuUtils.java index 560541da..07d7c41e 100644 --- a/app/src/main/java/de/k3b/android/util/MenuUtils.java +++ b/app/src/main/java/de/k3b/android/util/MenuUtils.java @@ -68,6 +68,19 @@ public static void setShowAsActionFlags(Menu menu, int actionEnum, int... menuId if (sub != null) sub.setShowAsActionFlags(SHOW_AS_ACTION_NEVER); } } + /** + * For api convinience: remove 0, 1 or more menuitems by id + * @param menu where items are removed from + * @param menuIds 0 or more menu-ids to be modified. + */ + public static void removeItems(Menu menu, int... menuIds) { + if ((menu != null) && (menuIds != null)) { + for (int menuId: menuIds) { + menu.removeItem(menuId); + } + } + + } public static void changeShowAsActionFlags(EditText edit, final int actionEnum, final int... menuIds) { ActionMode.Callback callback = new ActionMode.Callback() { diff --git a/app/src/main/java/de/k3b/android/util/OsUtils.java b/app/src/main/java/de/k3b/android/util/OsUtils.java index 175c7c01..b206c0f3 100644 --- a/app/src/main/java/de/k3b/android/util/OsUtils.java +++ b/app/src/main/java/de/k3b/android/util/OsUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2016 by k3b. + * Copyright (c) 2015-2018 by k3b. * * This file is part of AndroFotoFinder. * @@ -23,6 +23,7 @@ import java.io.File; +import de.k3b.io.FileUtils; import de.k3b.io.OSDirectory; /** @@ -60,20 +61,32 @@ public static File buildPath(File base, String... segments) { return cur; } - public static OSDirectory getRootOSDirectory() { + /** + * create android specific dir root. + * + * @param factory null or factory that creates OSDirectory or subclass of OSDirectory. + */ + public static OSDirectory getRootOSDirectory(OSDirectory factory) { // #103: bugfix // this works for android-4.4 an earlier and on rooted devices - OSDirectory root = new OSDirectory("/", null); + OSDirectory root = createOsDirectory(FileUtils.tryGetCanonicalFile("/"), factory); if (root.getChildren().size() == 0) { // on android-5.0 an newer root access is not allowed. // i.e. /storage/emulated/0 File externalRoot = Environment.getExternalStorageDirectory(); if (externalRoot != null) { - root = new OSDirectory(externalRoot.getAbsolutePath(), null); + root = createOsDirectory(externalRoot, factory); } } return root; } + private static OSDirectory createOsDirectory(File file, OSDirectory factory) { + if (factory != null) return factory.createOsDirectory(file, null, null); + return new OSDirectory(file, null, null); + } + public static File getDefaultPhotoRoot() { + return new File(Environment.getExternalStorageDirectory(),Environment.DIRECTORY_DCIM); + } } diff --git a/app/src/main/java/de/k3b/android/widget/BaseQueryActivity.java b/app/src/main/java/de/k3b/android/widget/BaseQueryActivity.java new file mode 100644 index 00000000..da4e9c62 --- /dev/null +++ b/app/src/main/java/de/k3b/android/widget/BaseQueryActivity.java @@ -0,0 +1,1176 @@ +/* + * Copyright (c) 2015-2018 by k3b. + * + * This file is part of AndroFotoFinder / #APhotoManager. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see + */ + +package de.k3b.android.widget; + +import android.app.Activity; +import android.app.FragmentManager; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.preference.PreferenceManager; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; +import android.widget.Toast; + +import java.io.File; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import de.k3b.FotoLibGlobal; +import de.k3b.android.androFotoFinder.Common; +import de.k3b.android.androFotoFinder.GalleryFilterActivity; +import de.k3b.android.androFotoFinder.GalleryFilterPathState; +import de.k3b.android.androFotoFinder.Global; +import de.k3b.android.androFotoFinder.LockScreen; +import de.k3b.android.androFotoFinder.R; +import de.k3b.android.androFotoFinder.directory.DirectoryLoaderTask; +import de.k3b.android.androFotoFinder.directory.DirectoryPickerFragment; +import de.k3b.android.androFotoFinder.locationmap.LocationMapFragment; +import de.k3b.android.androFotoFinder.queries.AndroidAlbumUtils; +import de.k3b.android.androFotoFinder.queries.FotoSql; +import de.k3b.android.androFotoFinder.tagDB.TagSql; +import de.k3b.android.androFotoFinder.tagDB.TagsPickerFragment; +import de.k3b.android.osmdroid.OsmdroidUtil; +import de.k3b.android.util.MediaScanner; + +import de.k3b.database.QueryParameter; +import de.k3b.io.AlbumFile; +import de.k3b.io.Directory; +import de.k3b.io.DirectoryFormatter; +import de.k3b.io.GalleryFilterParameter; +import de.k3b.io.IDirectory; +import de.k3b.io.IGalleryFilter; +import de.k3b.io.ListUtils; +import de.k3b.io.StringUtils; +import de.k3b.io.collections.SelectedItems; +import de.k3b.tagDB.Tag; + +/** + * All that is is needed for to have a base-filter plus a sub-filter + * + * Created by k3b on 10.07.2018. + */ +public abstract class BaseQueryActivity extends ActivityWithAutoCloseDialogs implements Common, DirectoryPickerFragment.OnDirectoryInteractionListener, + LocationMapFragment.OnDirectoryInteractionListener, + TagsPickerFragment.ITagsPicker { + protected static final String mDebugPrefix = "GalleryA-"; + + private static final String DLG_NAVIGATOR_TAG = "navigator"; + private static final String DEFAULT_BOOKMARKNAME_PICK_GEO = "pickGeoFromPhoto"; + public static final int resultID = 522; + + protected GalleryQueryParameter mGalleryQueryParameter = new GalleryQueryParameter(); + + protected String mTitleResultCount = ""; + + protected boolean mHasEmbeddedDirPicker = false; + + /** + * every thing that belongs to search. + * visible gallery items are mGalleryContentBaseQuery + expression(mCurrentSubFilterMode) + */ + protected class GalleryQueryParameter { + + /** picker has different set of filter parameters as ordenary gallery view */ + private static final String PICK_NONE_SUFFIX = ""; + private static final String PICK_GEO_SUFFIX = "-pick-geo"; + private static final String PICK_IMAGE_SUFFIX = "-pick-image"; + + /** + * one of the PICK_XXXX_SUFFIX constants + * view/pick-image/pick-geo have different state persistence. + * naem=STATE_XXXXX + mSharedPrefKeySuffix + * ""==view; "-pick-image"; "-pick-geo" + */ + private String mSharedPrefKeySuffix = PICK_NONE_SUFFIX; + + /** + * STATE_... to persist current filter + */ + private static final String STATE_DirQueryID = "DirQueryID"; + private static final String STATE_SortID = "SortID"; + private static final String STATE_SortAscending = "SortAscending"; + + private static final String STATE_SUB_FILTER = "subFilter"; + + private static final String STATE_SUB_FILTR_MODE = "currentSubFilterMode"; + + /** + * mCurrentSubFilterMode = SUB_FILTER_MODE_XXX: which filter addon is currently active + */ + private static final int SUB_FILTER_MODE_NONE = -1; + private static final int SUB_FILTER_MODE_PATH = 0; + private static final int SUB_FILTER_MODE_GEO = 1; + private static final int SUB_FILTER_MODE_TAG = 2; + private static final int SUB_FILTER_MODE_SEARCH_BAR = 3; + private static final int SUB_FILTER_MODE_ALBUM = 4; + private static final int SUB_FILTER_MODE_DATE = 5; + + /** + * mCurrentSubFilterMode = SUB_FILTER_MODE_XXX: which filter addon is currently active: + * Filter = basefilter + mCurrentSubFilterMode + */ + private int mCurrentSubFilterMode = SUB_FILTER_MODE_NONE; + private String mAlbumName = null; + + /** + * mCurrentSubFilterMode defines which of the Filter parameters define the current visible items + */ + private GalleryFilterParameter mCurrentSubFilterSettings = new GalleryFilterParameter(); + + /** + * sql defines current visible items with optional sort order + */ + protected QueryParameter mGalleryContentBaseQuery = null; + + /** + * one of the FotoSql.QUERY_TYPE_xxx values + */ + protected int mDirQueryID = FotoSql.QUERY_TYPE_GROUP_DEFAULT; + + /** + * current sort order + */ + private int mCurrentSortID = FotoSql.SORT_BY_DEFAULT; + /** + * current sort order + */ + private boolean mCurrentSortAscending = false; + + /** + * true: if activity started without special intent-parameters, + * the last mCurrentSubFilterSettings is saved/loaded for next use + */ + private boolean mSaveLastUsedFilterToSharedPrefs = true; + + /** + * one of the FotoSql.QUERY_TYPE_xxx values. if undefined use default + */ + public int getDirQueryID() { + if (this.mDirQueryID == FotoSql.QUERY_TYPE_UNDEFINED) + return FotoSql.QUERY_TYPE_GROUP_DEFAULT; + + return this.mDirQueryID; + } + + public int getSortID() { + return mCurrentSortID; + } + + public void setSortID(int sortID) { + if (sortID == mCurrentSortID) { + mCurrentSortAscending = !mCurrentSortAscending; + } else { + mCurrentSortAscending = true; + mCurrentSortID = sortID; + } + } + + public String getSortDisplayName(Context context) { + return FotoSql.getName(context, this.mCurrentSortID) + " " + ((mCurrentSortAscending) ? IGalleryFilter.SORT_DIRECTION_ASCENDING : IGalleryFilter.SORT_DIRECTION_DESCENDING); + } + + public boolean clearPathIfActive() { + if ((mCurrentSubFilterMode == SUB_FILTER_MODE_PATH) && (mCurrentSubFilterSettings.getPath() != null)) { + mCurrentSubFilterSettings.setPath(null); + return true; + } + return false; + } + + /** + * combine root-query plus current selected directoryRoot/geo/tags + */ + public QueryParameter calculateEffectiveGalleryContentQuery() { + return calculateEffectiveGalleryContentQuery(mGalleryContentBaseQuery); + } + + /** + * combine root-query plus current selected directoryRoot + */ + private QueryParameter calculateEffectiveGalleryContentQuery(QueryParameter rootQuery) { + QueryParameter result = new QueryParameter(rootQuery); + + final IGalleryFilter currentSubFilterSettings = this.getCurrentSubFilterSettings(); + if (currentSubFilterSettings != null) { + + if (result == null) return null; + + switch (mCurrentSubFilterMode) { + case SUB_FILTER_MODE_SEARCH_BAR: + TagSql.addFilterAny(result, currentSubFilterSettings.getInAnyField()); + break; + case SUB_FILTER_MODE_GEO: + FotoSql.addWhereFilterLatLon(result, currentSubFilterSettings); + break; + case SUB_FILTER_MODE_TAG: + TagSql.addWhereTagsIncluded(result, currentSubFilterSettings.getTagsAllIncluded(), false); + break; + case SUB_FILTER_MODE_PATH: + case SUB_FILTER_MODE_ALBUM: { + final String path = currentSubFilterSettings.getPath(); + if (!StringUtils.isNullOrEmpty(path)) { + Uri uri = Uri.fromFile(new File(path)); + QueryParameter albumQuery = AndroidAlbumUtils.getQueryFromUri( + mDebugPrefix + " calculateEffectiveGalleryContentQuery ", + BaseQueryActivity.this, uri, null); + if (albumQuery != null) { + result.getWhereFrom(albumQuery, true); + } else if (MediaScanner.isNoMedia(path, MediaScanner.DEFAULT_SCAN_DEPTH)) { + // do not show (parent-)directories that contain ".nomedia" + return null; + } else { + FotoSql.addPathWhere(result, path, this.getDirQueryID()); + } + } + } + break; + + case SUB_FILTER_MODE_DATE: + FotoSql.addWhereDateMinMax(result, currentSubFilterSettings.getDateMin(), currentSubFilterSettings.getDateMax()); + break; + } + } + + if (mCurrentSortID != IGalleryFilter.SORT_BY_NONE) { + FotoSql.setSort(result, mCurrentSortID, mCurrentSortAscending); + } + return result; + } + + public boolean isGeoPick() { + return (mSharedPrefKeySuffix != null) && mSharedPrefKeySuffix.equals(PICK_GEO_SUFFIX); + } + + // load from intent/ savedInstanceState/ SharedPrefs + // Use cases + + /** + * load from savedInstanceState/ intent/ SharedPrefs + * Use cases + * * stand alone gallery (i.e. from file manager with intent-uri of image.jpg/virtual-album/directory) + * * sub gallery drill down from other apm activity with intent containing file-uri/query-extra and/or filter-extra + * * as picker for + * * * ACTION_PICK geo via image + * * * ACTION_PICK pick image + * * * ACTION_GET_CONTENT + * + * @param context + * @param savedInstanceState + */ + private void loadSettingsAndInstanceState(Activity context, Bundle savedInstanceState) { + + Intent intent = context.getIntent(); + + // for debugging: where does the filter come from + StringBuilder dbgMessageResult = (Global.debugEnabled) ? new StringBuilder() : null; + + // special name handling for pickers + String action = (intent != null) ? intent.getAction() : null; + if ((action != null) && ((Intent.ACTION_PICK.compareTo(action) == 0) || (Intent.ACTION_GET_CONTENT.compareTo(action) == 0))) { + String schema = intent.getScheme(); + if ((schema != null) && ("geo".compareTo(schema) == 0)) { + this.mSharedPrefKeySuffix = PICK_GEO_SUFFIX; + if (dbgMessageResult != null) dbgMessageResult.append("pick geo "); + } else { + this.mSharedPrefKeySuffix = PICK_IMAGE_SUFFIX; + if (dbgMessageResult != null) dbgMessageResult.append("pick photo "); + } + this.mSaveLastUsedFilterToSharedPrefs = true; + } else { + this.mSharedPrefKeySuffix = PICK_NONE_SUFFIX; + // save only if no intent-uri is involved + this.mSaveLastUsedFilterToSharedPrefs = StringUtils.isNullOrEmpty(intent.getDataString()); + } + this.mSharedPrefKeySuffix = fixSharedPrefSuffix(this.mSharedPrefKeySuffix); + SharedPreferences sharedPref = + (this.mSaveLastUsedFilterToSharedPrefs) + ? PreferenceManager.getDefaultSharedPreferences(context) + : null; + + StringBuilder uriQueryFile = new StringBuilder(); + this.mGalleryContentBaseQuery = AndroidAlbumUtils.getQuery( + BaseQueryActivity.this, mSharedPrefKeySuffix, + savedInstanceState, intent, sharedPref, uriQueryFile, dbgMessageResult); + + if (dbgMessageResult != null) dbgMessageResult.append("SubFilter "); + boolean found = false; + if (savedInstanceState == null) { + // onCreate (first call) : if intent is usefull use it else use sharedPref + if (!found && (intent != null)) { + + found = setState(dbgMessageResult, " from-Intent: ", + intent.getStringExtra(STATE_SUB_FILTER), + intent.getIntExtra(STATE_DirQueryID, this.getDirQueryID()), + intent.getIntExtra(STATE_SortID, this.mCurrentSortID), + intent.getBooleanExtra(STATE_SortAscending, this.mCurrentSortAscending), + intent.getIntExtra(STATE_SUB_FILTR_MODE, this.mCurrentSubFilterMode)); + } + if (!found && (sharedPref != null)) { + found = setState(dbgMessageResult, " from-SharedPrefs: ", + sharedPref.getString(STATE_SUB_FILTER + mSharedPrefKeySuffix, null), + sharedPref.getInt(STATE_DirQueryID + mSharedPrefKeySuffix, this.getDirQueryID()), + sharedPref.getInt(STATE_SortID + mSharedPrefKeySuffix, this.mCurrentSortID), + sharedPref.getBoolean(STATE_SortAscending + mSharedPrefKeySuffix, this.mCurrentSortAscending), + sharedPref.getInt(STATE_SUB_FILTR_MODE, this.mCurrentSubFilterMode)); + } + } else { + // (savedInstanceState != null) : onCreate after screen rotation + + found = setState(dbgMessageResult, " from-InstanceState: ", + savedInstanceState.getString(STATE_SUB_FILTER, null), + savedInstanceState.getInt(STATE_DirQueryID, this.getDirQueryID()), + savedInstanceState.getInt(STATE_SortID, this.mCurrentSortID), + savedInstanceState.getBoolean(STATE_SortAscending, this.mCurrentSortAscending), + savedInstanceState.getInt(STATE_SUB_FILTR_MODE, this.mCurrentSubFilterMode)); + } + + // all parameters loaded: either album, filter or path + if (this.mGalleryContentBaseQuery == null) { + this.mGalleryContentBaseQuery = FotoSql.getQuery(FotoSql.QUERY_TYPE_DEFAULT); + if (dbgMessageResult != null) dbgMessageResult.append(" no query in parameters-use defaults "); + } + + if (uriQueryFile.length() > 0) { + this.setAlbum(uriQueryFile.toString()); + } + + if (dbgMessageResult != null) { + Log.i(Global.LOG_CONTEXT, mDebugPrefix + dbgMessageResult.toString()); + } + } + + private void setAlbum(String uriQueryFile) { + Uri uri = (uriQueryFile == null) ? null : Uri.parse(uriQueryFile); + if (uri != null) { + this.mAlbumName = uri.getLastPathSegment(); + this.mCurrentSubFilterMode = SUB_FILTER_MODE_ALBUM; + } + } + + private boolean setState(StringBuilder dbgMessageResult, String dbgContext, + String subFilterSettingsAsString, int dirQueryID, int sortID, + boolean sortAscending, int subFilterMode) { + if (subFilterSettingsAsString != null) { + // SubFilterSettings, DirQueryID, SortID, SortAscending, SubFilterMode + GalleryFilterParameter.parse(subFilterSettingsAsString, mCurrentSubFilterSettings); + if (dbgMessageResult != null) + dbgMessageResult.append(dbgContext).append(subFilterSettingsAsString); + this.mDirQueryID = dirQueryID; + this.mCurrentSortID = sortID; + this.mCurrentSortAscending = sortAscending; + this.mCurrentSubFilterMode = subFilterMode; + return true; + } + return false; + } + + private void saveToInstanceState(Context context, Bundle savedInstanceState) { + saveToSharedPrefs(context); + + // SubFilterSettings, DirQueryID, SortID, SortAscending, SubFilterMode + if (mCurrentSubFilterSettings != null) { + savedInstanceState.putString(STATE_SUB_FILTER, mCurrentSubFilterSettings.toString()); + } + savedInstanceState.putInt(STATE_DirQueryID, this.getDirQueryID()); + savedInstanceState.putInt(STATE_SortID, this.mCurrentSortID); + savedInstanceState.putBoolean(STATE_SortAscending, this.mCurrentSortAscending); + savedInstanceState.putInt(STATE_SUB_FILTR_MODE, this.mCurrentSubFilterMode); + + if ((mGalleryQueryParameter != null) && (mGalleryQueryParameter.mGalleryContentBaseQuery != null)) { + savedInstanceState.putString(EXTRA_QUERY, mGalleryQueryParameter.mGalleryContentBaseQuery.toReParseableString()); + } + + } + + private void saveToSharedPrefs(Context context) { + if (mSaveLastUsedFilterToSharedPrefs) { + // SubFilterSettings, DirQueryID, SortID, SortAscending, SubFilterMode + SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context); + SharedPreferences.Editor edit = sharedPref.edit(); + + if (getCurrentSubFilterSettings() != null) { + edit.putString(STATE_SUB_FILTER + mSharedPrefKeySuffix, getCurrentSubFilterSettings().toString()); + } + + edit.putInt(STATE_DirQueryID + mSharedPrefKeySuffix, this.getDirQueryID()); + edit.putInt(STATE_SortID + mSharedPrefKeySuffix, this.mCurrentSortID); + edit.putBoolean(STATE_SortAscending + mSharedPrefKeySuffix, this.mCurrentSortAscending); + edit.putInt(STATE_SUB_FILTR_MODE, this.mCurrentSubFilterMode); + + if ((mGalleryQueryParameter != null) && (mGalleryQueryParameter.mGalleryContentBaseQuery != null)) { + edit.putString(EXTRA_QUERY + mSharedPrefKeySuffix, mGalleryQueryParameter.mGalleryContentBaseQuery.toReParseableString()); + } + + edit.apply(); + } + } + + public GalleryFilterParameter getCurrentSubFilterSettings() { + return mCurrentSubFilterSettings; + } + + // ...path, {album, +tag, ?search, #date, lat+long + protected CharSequence getValueAsTitle(boolean longName) { + final int subFilterMode = this.mCurrentSubFilterMode; + GalleryFilterParameter v = mCurrentSubFilterSettings; + + if (v != null) { + switch (subFilterMode) { + case SUB_FILTER_MODE_PATH: { + File f = v.getPathFile(); + if (f == null) break; + StringBuilder result = new StringBuilder(); + result.insert(0, f.getName()); + f = f.getParentFile(); + if (f != null) { + result.insert(0, "/"); + result.insert(0, f.getName()); + } + result.insert(0, "..."); + if (longName) result.insert(0, getString(R.string.folder_menu_title)); + + return result; + } + + case SUB_FILTER_MODE_ALBUM: { + if (this.mAlbumName != null) return getValueAsTitle(longName, "{", R.string.bookmark, this.mAlbumName); + return null; + } + + case SUB_FILTER_MODE_GEO: + final int lat = (int) v.getLatitudeMin(); + final int lon = (int) v.getLogituedMin(); + return ((longName) ? getString(R.string.area_menu_title) : "") + lat + " " + lon; + case SUB_FILTER_MODE_TAG: + return getValueAsTitle(longName, "+", R.string.tags_activity_title, ListUtils.toString(v.getTagsAllIncluded())); + case SUB_FILTER_MODE_SEARCH_BAR: + return getValueAsTitle(longName, "?", R.string.searchbar_menu_title, v.getInAnyField()); + case SUB_FILTER_MODE_DATE: + return getValueAsTitle(longName, "#", R.string.date_picker_menu_title, v.getDatePath()); + } + } + return null; + } + + private CharSequence getValueAsTitle(boolean longName, String prefix, int stringResourceId, String value) { + StringBuilder result = new StringBuilder(); + if (longName && (stringResourceId != 0)) result.append(getString(stringResourceId)).append(": "); + if (!StringUtils.isNullOrEmpty(value)) result.append(prefix); + result.append(value); + return result; + } + } + + // ...path, {album, +tag, ?search, #date, lat+long + public CharSequence getValueAsTitle(boolean longName) { + if (mGalleryQueryParameter == null) return null; + return mGalleryQueryParameter.getValueAsTitle(longName); + } + + /** allows childclass to have their own sharedPreference names */ + protected String fixSharedPrefSuffix(String statSuffix) { + return statSuffix; + } + + + private FolderApi mFolderApi = null; + private FolderApi getFolderApi() { + if (mFolderApi == null) { + mFolderApi = new FolderApi(); + } + return mFolderApi; + } + + protected class FolderApi { + // either folder picker or date picker + private static final int QUERY_TYPE_GROUP_ALBUM = FotoSql.QUERY_TYPE_GROUP_ALBUM; + // either folder picker or date picker + private static final int QUERY_TYPE_GROUP_DATE = FotoSql.QUERY_TYPE_GROUP_DATE; + + private IDirectory mDirectoryRoot = null; + private IDirectory mDateRoot = null; + + /** + * true if activity should show navigator dialog after loading mDirectoryRoot is complete + */ + private boolean mMustShowNavigator = false; + + /** + * set while dir picker is active + */ + private DirectoryPickerFragment mDirPicker = null; + + private void openDatePicker() { + openPicker(BaseQueryActivity.GalleryQueryParameter.SUB_FILTER_MODE_DATE, QUERY_TYPE_GROUP_DATE); + } + + private void openFolderPicker() { + openPicker(BaseQueryActivity.GalleryQueryParameter.SUB_FILTER_MODE_PATH, QUERY_TYPE_GROUP_ALBUM); + } + + private void openPicker(final int filterMode, int _dirQueryID) { + mGalleryQueryParameter.mCurrentSubFilterMode = filterMode; + final Activity context = BaseQueryActivity.this; + + /** if wrong datatype was saved: gallery is not allowed for dirPicker */ + final int dirQueryID = + (FotoSql.QUERY_TYPE_GALLERY == _dirQueryID) + ? QUERY_TYPE_GROUP_ALBUM + : _dirQueryID; + + mGalleryQueryParameter.mDirQueryID = dirQueryID; + + final boolean loadDate = (dirQueryID == QUERY_TYPE_GROUP_DATE); + final IDirectory currentDirectoryRoot = loadDate ? this.mDateRoot : this.mDirectoryRoot; + if (currentDirectoryRoot == null) { + // not loaded yet. load directoryRoot in background + ; + final QueryParameter mergedBaseQuery = FotoSql.getQuery(dirQueryID); + mergedBaseQuery.getWhereFrom(mGalleryQueryParameter.mGalleryContentBaseQuery, false); + if (mergedBaseQuery != null) { + this.mMustShowNavigator = true; + mergedBaseQuery.setID(dirQueryID); + + DirectoryLoaderTask loader = new DirectoryLoaderTask(context, loadDate ? FotoLibGlobal.datePickerUseDecade : false, + mDebugPrefix + " from openPicker(loadDate=" + + loadDate + ")") { + @Override + protected void onPostExecute(IDirectory directoryRoot) { + onDirectoryDataLoadComplete(loadDate, directoryRoot); + } + }; + + if (!loadDate) { + // limit valbums to matching parent-path query + QueryParameter vAlbumQueryWithPathExpr = FotoSql.copyPathExpressions(FotoSql.queryVAlbum, mergedBaseQuery); + if (vAlbumQueryWithPathExpr == null) + vAlbumQueryWithPathExpr = FotoSql.queryVAlbum; + + // load dir-s + "*.album" + loader.execute(mergedBaseQuery, vAlbumQueryWithPathExpr); + } else { + loader.execute(mergedBaseQuery); + } + } else { + Log.e(Global.LOG_CONTEXT, mDebugPrefix + " this.mDirQueryID undefined " + + mGalleryQueryParameter.mDirQueryID); + } + // if not loaded yet + } else { + // if loaded + mMustShowNavigator = false; + final FragmentManager manager = getFragmentManager(); + DirectoryPickerFragment dirDialog = new DirectoryPickerFragment(); + + dirDialog.setContextMenuId(LockScreen.isLocked(context) ? 0 : R.menu.menu_context_dirpicker); + + String initialPath = mGalleryQueryParameter.getCurrentSubFilterSettings().getPath(); + + if ((initialPath == null) && (!loadDate)) { + initialPath = new GalleryFilterPathState() + .load(BaseQueryActivity.this, + null,null) + .getPathDefault(null); + } + + if ((initialPath != null) && (initialPath.endsWith("%"))) { + initialPath = initialPath.substring(0,initialPath.length() - 1); + } + dirDialog.defineDirectoryNavigation(currentDirectoryRoot, dirQueryID, + initialPath); + + mDirPicker = dirDialog; + setAutoClose(mDirPicker, null, null); + dirDialog.show(manager, DLG_NAVIGATOR_TAG); + } + } + + private void onDirectoryDataLoadComplete(final boolean loadDate, IDirectory directoryRoot) { + if (directoryRoot == null) { + final String message = getString(R.string.folder_err_load_failed_format, FotoSql.getName(BaseQueryActivity.this, mGalleryQueryParameter.getDirQueryID())); + Toast.makeText(BaseQueryActivity.this, message, Toast.LENGTH_LONG).show(); + } else { + boolean mustDefineNavigation; + if (loadDate) { + mustDefineNavigation= (mGalleryQueryParameter.getCurrentSubFilterSettings().getDatePath() != null); + this.mDateRoot = directoryRoot; + } else { + mustDefineNavigation= (mGalleryQueryParameter.getCurrentSubFilterSettings().getPath() != null); + this.mDirectoryRoot = directoryRoot; + } + + final boolean mustShowFolderPicker = (directoryRoot != null) && (this.mMustShowNavigator); + + if (Global.debugEnabled) { + StringBuilder name = new StringBuilder(directoryRoot.getAbsolute()); + Directory.appendCount(name, directoryRoot, Directory.OPT_DIR | Directory.OPT_SUB_DIR); + Log.i(Global.LOG_CONTEXT, mDebugPrefix + "onDirectoryDataLoadComplete(" + + "mustDefineNavigation=" + mustDefineNavigation + + ", mustShowFolderPicker=" + mustShowFolderPicker + + ", content=" + name + ",loadDate=" + + loadDate + ")"); + } + + if (mustDefineNavigation) { + defineDirectoryNavigation(directoryRoot); + } + Global.debugMemory(mDebugPrefix, "onDirectoryDataLoadComplete"); + + if (mustShowFolderPicker) { + if (loadDate) { + openDatePicker(); + } else { + openFolderPicker(); + } + } + } + } + + private void refreshSelection() { + IDirectory lastPopUpSelection = (mDirPicker == null) ? null : mDirPicker.getLastPopUpSelection(); + if (lastPopUpSelection != null) lastPopUpSelection.refresh(); + } + + private void invalidateDirectories(String why) { + mDirectoryRoot = invalidateDirectories(why, mDirectoryRoot); + mDateRoot = invalidateDirectories(why, mDateRoot); + } + + private IDirectory invalidateDirectories(String why, IDirectory directoryRoot) { + if (directoryRoot != null) { + if (Global.debugEnabled) { + StringBuilder name = new StringBuilder(directoryRoot.getAbsolute()); + Directory.appendCount(name, directoryRoot, Directory.OPT_DIR | Directory.OPT_SUB_DIR); + Log.i(Global.LOG_CONTEXT, mDebugPrefix + "invalidateDirectories(" + name + ") because of " + why); + } + if (mDirPicker == null) { + directoryRoot.destroy(); + directoryRoot = null; // must refreshLocal next time + } + } + return directoryRoot; + } + } + + abstract protected void defineDirectoryNavigation(IDirectory directoryRoot); + + private void openLatLonPicker(SelectedItems selectedItems) { + this.mGalleryQueryParameter.mCurrentSubFilterMode = BaseQueryActivity.GalleryQueryParameter.SUB_FILTER_MODE_GEO; + + final FragmentManager manager = getFragmentManager(); + LocationMapFragment dialog = new LocationMapFragment(); + dialog.defineNavigation(this.mGalleryQueryParameter.mGalleryContentBaseQuery, + null, + this.mGalleryQueryParameter.getCurrentSubFilterSettings(), OsmdroidUtil.NO_ZOOM, selectedItems, null, false); + + dialog.show(manager, DLG_NAVIGATOR_TAG); + } + + private void openFilter() { + GalleryFilterActivity.showActivity(this, + null, + this.mGalleryQueryParameter.mGalleryContentBaseQuery, + null, BaseQueryActivity.resultID); + } + + private void openTagPicker() { + mGalleryQueryParameter.mCurrentSubFilterMode = BaseQueryActivity.GalleryQueryParameter.SUB_FILTER_MODE_TAG; + + final FragmentManager manager = getFragmentManager(); + TagsPickerFragment dlg = new TagsPickerFragment(); + dlg.setFragmentOnwner(this); + dlg.setTitleId(R.string.tags_activity_title); + List included = this.mGalleryQueryParameter.getCurrentSubFilterSettings().getTagsAllIncluded(); + if (included == null) included = new ArrayList(); + dlg.setAddNames(included); + dlg.show(manager, DLG_NAVIGATOR_TAG); + } + + /** + * called by {@link TagsPickerFragment} + */ + @Override + public boolean onCancel(String msg) { + return true; + } + + /** + * called by {@link TagsPickerFragment} + */ + @Override + public boolean onOk(List addNames, List removeNames) { + Log.d(Global.LOG_CONTEXT, "BaseQueryActivity.navigateTo " + ListUtils.toString(addNames) + " from " + + ListUtils.toString(mGalleryQueryParameter.getCurrentSubFilterSettings().getTagsAllIncluded())); + mGalleryQueryParameter.mCurrentSubFilterMode = BaseQueryActivity.GalleryQueryParameter.SUB_FILTER_MODE_TAG; + mGalleryQueryParameter.getCurrentSubFilterSettings().setTagsAllIncluded(new ArrayList(addNames)); + reloadGui("navigate to tags"); + return true; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + Global.debugMemory(mDebugPrefix, "onCreate"); + super.onCreate(savedInstanceState); + this.getContentResolver().registerContentObserver(FotoSql.SQL_TABLE_EXTERNAL_CONTENT_URI, true, mMediaObserverDirectory); + this.getContentResolver().registerContentObserver(FotoSql.SQL_TABLE_EXTERNAL_CONTENT_URI_FILE, true, mMediaObserverDirectory); + } + + protected void onCreateData(Bundle savedInstanceState) { + final Intent intent = getIntent(); + + this.mGalleryQueryParameter.loadSettingsAndInstanceState(this, savedInstanceState); + } + + /** + * OnDirectoryInteractionListener: called when user selects a new directoryRoot + */ + @Override + public void onDirectoryPick(String selectedAbsolutePath, int queryTypeId) { + if (!this.mHasEmbeddedDirPicker) { + navigateTo(selectedAbsolutePath, queryTypeId); + } + closeDialogIfNeeded(); + + } + + /** + * after media db change cached Directories must be recalculated + */ + private final ContentObserver mMediaObserverDirectory = new ContentObserver(null) { + @Override + public void onChange(boolean selfChange) { + invalidateDirectories(mDebugPrefix + "#onChange from mMediaObserverDirectory"); + } + }; + + /* OnDirectoryInteractionListener */ + @Override + public void invalidateDirectories(String why) { + getFolderApi().invalidateDirectories(why); + } + + /** + * DirectoryPickerFragment#OnDirectoryInteractionListener: called when user cancels selection of a new directoryRoot + */ + @Override + public void onDirectoryCancel(int queryTypeId) { + closeDialogIfNeeded(); + } + + @Override + protected void closeDialogIfNeeded() { + super.closeDialogIfNeeded(); + getFolderApi().mDirPicker = null; + } + + /** DirectoryPickerFragment#OnDirectoryInteractionListener: called after the selection in tree has changed */ + @Override + public void onDirectorySelectionChanged(String selectedAbsolutePath, int queryTypeId) { + if (this.mHasEmbeddedDirPicker) { + navigateTo(selectedAbsolutePath, queryTypeId); + } + } + + private void navigateTo(String selectedAbsolutePath, int queryTypeId) { + + if (selectedAbsolutePath != null) { + final GalleryFilterParameter currentSubFilterSettings = this.mGalleryQueryParameter.getCurrentSubFilterSettings(); + if (mGalleryQueryParameter.mCurrentSubFilterMode == GalleryQueryParameter.SUB_FILTER_MODE_GEO) { + final String why = "FotoGalleryActivity.navigateTo tags geo "; + Log.d(Global.LOG_CONTEXT, why + selectedAbsolutePath + " from " + + DirectoryFormatter.formatLatLon(currentSubFilterSettings.getLatitudeMin() + ,currentSubFilterSettings.getLogituedMin() + ,currentSubFilterSettings.getLatitudeMax() + ,currentSubFilterSettings.getLogituedMax())); + currentSubFilterSettings.get(DirectoryFormatter.parseLatLon(selectedAbsolutePath)); + + reloadGui(why); + + } else if (mGalleryQueryParameter.mCurrentSubFilterMode == GalleryQueryParameter.SUB_FILTER_MODE_TAG) { + final String why = "FotoGalleryActivity.navigateTo tags "; + Log.d(Global.LOG_CONTEXT, why + selectedAbsolutePath + " from " + + ListUtils.toString(this.mGalleryQueryParameter.getCurrentSubFilterSettings().getTagsAllIncluded())); + currentSubFilterSettings.setTagsAllIncluded(new ArrayList<>(ListUtils.fromString(selectedAbsolutePath))); + + reloadGui(why); + } else if (mGalleryQueryParameter.mCurrentSubFilterMode == GalleryQueryParameter.SUB_FILTER_MODE_DATE) { + final String why = "FotoGalleryActivity.navigateTo date "; + Log.d(Global.LOG_CONTEXT, why + selectedAbsolutePath + " from " + currentSubFilterSettings.getDatePath()); + + Date from = new Date(); + Date to = new Date(); + DirectoryFormatter.getDates(selectedAbsolutePath, from, to); + + currentSubFilterSettings.setDate(from.getTime(), to.getTime()); + this.mGalleryQueryParameter.mCurrentSubFilterMode = GalleryQueryParameter.SUB_FILTER_MODE_DATE; + this.mGalleryQueryParameter.mDirQueryID = queryTypeId; + setTitle(); + + reloadGui(why); + } else if (mGalleryQueryParameter.mCurrentSubFilterMode == GalleryQueryParameter.SUB_FILTER_MODE_PATH) { + GalleryFilterPathState state = new GalleryFilterPathState() + .load(BaseQueryActivity.this, + null,null); + File queryFile = AlbumFile.getExistingQueryFileOrNull(selectedAbsolutePath); + if (queryFile != null) { + final String why = "FotoGalleryActivity.navigate to virtual album "; + Log.d(Global.LOG_CONTEXT, why + selectedAbsolutePath); + + QueryParameter albumQuery = AndroidAlbumUtils.getQueryFromUri(mDebugPrefix + " navigateTo ", this, Uri.fromFile(queryFile), null); + if (albumQuery != null) { + this.mGalleryQueryParameter.mGalleryContentBaseQuery = albumQuery; + this.mGalleryQueryParameter.mCurrentSubFilterMode = GalleryQueryParameter.SUB_FILTER_MODE_ALBUM; + currentSubFilterSettings.setPath(selectedAbsolutePath); + + state.setLastPath(queryFile.getParent()); + state.setAlbum(Uri.fromFile(queryFile)); + + reloadGui(why); + } + } else { + final String why = "FotoGalleryActivity.navigateTo dir "; + Log.d(Global.LOG_CONTEXT, why + selectedAbsolutePath + " from " + currentSubFilterSettings.getPath()); + + currentSubFilterSettings.setPath(selectedAbsolutePath + "/%"); + this.mGalleryQueryParameter.mCurrentSubFilterMode = GalleryQueryParameter.SUB_FILTER_MODE_PATH; + this.mGalleryQueryParameter.mDirQueryID = queryTypeId; + state.setLastPath(selectedAbsolutePath); + setTitle(); + + reloadGui(why); + } + state.save(this, null); + + } // if SUB_FILTER_MODE_PATH + } + } + + + @Override + public void onSaveInstanceState(Bundle savedInstanceState) { + this.mGalleryQueryParameter.saveToInstanceState(this, savedInstanceState); + super.onSaveInstanceState(savedInstanceState); + } + + @Override + protected void onPause() { + Global.debugMemory(mDebugPrefix, "onPause"); + this.mGalleryQueryParameter.saveToSharedPrefs(this); + super.onPause(); + } + + @Override + public void onLowMemory() { + super.onLowMemory(); + invalidateDirectories(mDebugPrefix + "#onLowMemory"); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + this.getContentResolver().unregisterContentObserver(mMediaObserverDirectory); + this.mGalleryQueryParameter.mGalleryContentBaseQuery = null; + invalidateDirectories(mDebugPrefix + "#onDestroy"); + } + + @Override + protected void onResume() { + Global.debugMemory(mDebugPrefix, "onResume"); + super.onResume(); + } + + /** + * Call back from sub-activities.
+ * Process Change StartTime (longpress start), Select StopTime before stop + * (longpress stop) or filter change for detailReport + */ + @Override + protected void onActivityResult(final int requestCode, + final int resultCode, final Intent intent) { + super.onActivityResult(requestCode, resultCode, intent); + + getFolderApi().refreshSelection(); + + switch (requestCode) { + case BaseQueryActivity.resultID: + onBaseFilterChanged(AndroidAlbumUtils.getQuery( + this, "", null, intent, null, null, null) + , mDebugPrefix + "#onActivityResult from GalleryFilterActivity"); + break; + default: + break; + } + } + + + protected abstract void reloadGui(String why); + + /** + * called by {@link TagsPickerFragment} + */ + @Override + public boolean onTagPopUpClick(int menuItemItemId, Tag selectedTag) { + return TagsPickerFragment.handleMenuShow(menuItemItemId, selectedTag, this, this.mGalleryQueryParameter.getCurrentSubFilterSettings()); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + final boolean result = super.onCreateOptionsMenu(menu); + initSearchView(menu.findItem(R.id.cmd_searchbar)); + return result; + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + final boolean result = super.onPrepareOptionsMenu(menu); + initSearchView(menu.findItem(R.id.cmd_searchbar)); + return result; + } + + protected boolean onOptionsItemSelected(MenuItem item, SelectedItems selectedItems) { + // Handle presses on the action bar items + switch (item.getItemId()) { + case R.id.cmd_select_date: + getFolderApi().openDatePicker(); + return true; + + case R.id.cmd_select_folder: + getFolderApi().openFolderPicker(); + return true; + + case R.id.cmd_select_lat_lon: + openLatLonPicker(selectedItems); + return true; + case R.id.cmd_select_tag: + openTagPicker(); + return true; + case R.id.cmd_filter: + openFilter(); + return true; + case R.id.cmd_sort_date: + this.mGalleryQueryParameter.setSortID(FotoSql.SORT_BY_DATE); + reloadGui("sort date"); + return true; + case R.id.cmd_sort_directory: + this.mGalleryQueryParameter.setSortID(FotoSql.SORT_BY_NAME); + reloadGui("sort dir"); + return true; + case R.id.cmd_sort_path_len: + this.mGalleryQueryParameter.setSortID(FotoSql.SORT_BY_NAME_LEN); + reloadGui("sort len"); + return true; + case R.id.cmd_sort_file_len: + this.mGalleryQueryParameter.setSortID(FotoSql.SORT_BY_FILE_LEN); + reloadGui("sort size"); + return true; + + case R.id.cmd_sort_width: + this.mGalleryQueryParameter.setSortID(FotoSql.SORT_BY_WIDTH); + reloadGui("sort width"); + return true; + + case R.id.cmd_sort_location: + this.mGalleryQueryParameter.setSortID(FotoSql.SORT_BY_LOCATION); + reloadGui("sort geo"); + return true; + + case R.id.cmd_sort_rating: + this.mGalleryQueryParameter.setSortID(FotoSql.SORT_BY_RATING); + reloadGui("sort rating"); + return true; + + case R.id.cmd_sort_modification: + this.mGalleryQueryParameter.setSortID(FotoSql.SORT_BY_MODIFICATION); + reloadGui("sort modification"); + return true; + + default: + return super.onOptionsItemSelected(item); + } + } + + /** + * redefine base filter and refresh gui + */ + protected void onBaseFilterChanged(QueryParameter query, String why) { + if (query != null) { + this.mGalleryQueryParameter.mGalleryContentBaseQuery = query; + this.mGalleryQueryParameter.mCurrentSubFilterMode = GalleryQueryParameter.SUB_FILTER_MODE_NONE; + + invalidateDirectories(mDebugPrefix + "#filter changed " + why); + + reloadGui("basefilter changed " + why); + setTitle(); + } + } + + /** GalleryFragment tells the Owning Activity that querying data has finisched */ + public void setResultCount(int count) { + this.mTitleResultCount = (count > 0) ? ("(" + count + ")") : ""; + setTitle(); + + // current path does not contain photo => refreshLocal witout current path + if ((count == 0) &&(mGalleryQueryParameter.clearPathIfActive())) { + setTitle(); + reloadGui("query changed"); + } + } + + protected void setTitle() { + Intent intent = getIntent(); + CharSequence title = (intent == null) ? null : intent.getStringExtra(EXTRA_TITLE); + + if (title == null) { + title = mGalleryQueryParameter.getValueAsTitle(false); + if (StringUtils.isNullOrEmpty(title)) title = getString(R.string.gallery_title); + } + if (title != null) { + this.setTitle(mTitleResultCount + title); + } + } + /*********************** search view *******************/ + private SearchViewWithHistory searchView = null; + + protected void initSearchView(MenuItem item) { + if (item != null) { + final SearchViewWithHistory searchView = (SearchViewWithHistory) item.getActionView(); + this.searchView = searchView; + if (searchView != null) { + searchView.setMenuItem(item); + // searchView.setCursorDrawable(R.drawable.custom_cursor); + searchView.setEllipsize(true); + searchView.setOnQueryTextListener(new SearchViewWithHistory.OnQueryTextListener() { + @Override + public boolean onQueryTextSubmit(String query) { + showSearchbarResult(query, "search bar submit"); + // Toast.makeText(FotoGalleryActivity.this, "Query: " + query, Toast.LENGTH_LONG).show(); + return false; + } + + @Override + public boolean onQueryTextChange(String newText) { + sendDelayed(HANDLER_FILTER_TEXT_CHANGED, HANDLER_FILTER_TEXT_DELAY); + return false; + } + }); + + searchView.setOnSearchViewListener(new SearchViewWithHistory.SearchViewListener() { + @Override + public void onSearchViewShown() { + showSearchbarResult("onSearchViewShown"); + } + + @Override + public void onSearchViewClosed() { + + showSearchbarResult("onSearchViewClosed"); + searchView.hideKeyboard(BaseQueryActivity.this.searchView.getRootView()); + } + }); + if (Global.debugEnabledViewItem) { + Log.i(Global.LOG_CONTEXT, mDebugPrefix + " initSearchView " + searchView); + } + + } + } + } + + private void showSearchbarResult(String why) { + if ((searchView != null) && (searchView.isSearchOpen()) ) { + showSearchbarResult(searchView.getFilterValue(), why); + } + } + private void showSearchbarResult(String query, String why) { + if (Global.debugEnabledViewItem) { + Log.i(Global.LOG_CONTEXT, mDebugPrefix + " showSearchbarResult(" + + why + ") " + searchView); + } + final GalleryFilterParameter currentSubFilterSettings = mGalleryQueryParameter.getCurrentSubFilterSettings(); + if ((mGalleryQueryParameter.mCurrentSubFilterMode != GalleryQueryParameter.SUB_FILTER_MODE_SEARCH_BAR) + || (0 != StringUtils.compare(query, currentSubFilterSettings.getInAnyField()))) { + + mGalleryQueryParameter.mCurrentSubFilterMode = GalleryQueryParameter.SUB_FILTER_MODE_SEARCH_BAR; + currentSubFilterSettings.setInAnyField(query); + if (Global.debugEnabledSql) { + Log.i(Global.LOG_CONTEXT, why + ": search " + query); + } + + reloadGui(why); + } else { + if (Global.debugEnabledSql) { + Log.i(Global.LOG_CONTEXT, why + ": ignore " + query); + } + + } + } + + // char(s) typing in filter is active + private static final int HANDLER_FILTER_TEXT_CHANGED = 0; + private static final int HANDLER_FILTER_TEXT_DELAY = 500; + + private final Handler delayProcessor = new Handler() { + @Override + public void handleMessage(final Message msg) { + clearDelayProcessor(); + switch (msg.what) { + case HANDLER_FILTER_TEXT_CHANGED: + showSearchbarResult( "onQueryTextChange"); + break; + default: + // not implemented + throw new IllegalStateException(); + } + } + + }; + + private void clearDelayProcessor() { + this.delayProcessor + .removeMessages(HANDLER_FILTER_TEXT_CHANGED); + } + + private void sendDelayed(final int messageID, final int delayInMilliSec) { + this.clearDelayProcessor(); + + final Message msg = Message + .obtain(this.delayProcessor, messageID, null); + delayProcessor.sendMessageDelayed(msg, + delayInMilliSec); + } + @Override + public void onBackPressed() { + if ((searchView != null) && searchView.isSearchOpen()) { + searchView.closeSearch(); + + // ??bug?? : with back-key on my android-4.2 the soft keyboard does not close + } else { + super.onBackPressed(); + } + } + +} diff --git a/app/src/main/java/de/k3b/android/widget/HistoryEditText.java b/app/src/main/java/de/k3b/android/widget/HistoryEditText.java index de01a613..289ad0f9 100644 --- a/app/src/main/java/de/k3b/android/widget/HistoryEditText.java +++ b/app/src/main/java/de/k3b/android/widget/HistoryEditText.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2017 by k3b. + * Copyright (c) 2015-2018 by k3b. * * This file is part of AndroFotoFinder. * @@ -20,11 +20,10 @@ package de.k3b.android.widget; import android.annotation.TargetApi; -import android.app.Activity; +import android.content.Context; import android.content.SharedPreferences; import android.os.Build; import android.preference.PreferenceManager; -import android.support.annotation.NonNull; import android.view.Menu; import android.view.MenuItem; import android.view.View; @@ -39,9 +38,10 @@ import de.k3b.io.ListUtils; /** - * Add history-popup to EditText. - * invoke via Clipboard ContextActionBar: star (history) to open a popupmenu with previous values. + * Add history-popup to a list of EditText+ImageButton pairs. + * The ImageButton opens a popupmenu with previous values. * Long-Press if no selection => popup with previous values. + * Use HistoryEditText.saveHistory() to persist the previous values in shared preferences. * * Popup-menu requires at least api 11 (HONEYCOMB) * Created by k3b on 26.08.2015. @@ -49,7 +49,7 @@ @TargetApi(Build.VERSION_CODES.HONEYCOMB) public class HistoryEditText { private static final int NO_ID = -1; - private final Activity mContext; + private final Context mContext; private final String mDelimiter; private final int mMaxHisotrySize; private final EditorHandler[] mEditorHandlers; @@ -60,11 +60,11 @@ protected class EditorHandler implements View.OnLongClickListener, View.OnClickL private final ImageButton mCmd; private final String mId; - public EditorHandler(String id, EditText editor, int cmdId) { + public EditorHandler(String id, EditText editor, int imageButtonResourceId) { mId = id; mEditor = editor; - mCmd = (cmdId != NO_ID) ? (ImageButton) mContext.findViewById(cmdId) : null; + mCmd = (imageButtonResourceId != NO_ID) ? (ImageButton) editor.getRootView().findViewById(imageButtonResourceId) : null; if (mCmd == null) { mEditor.setOnLongClickListener(this); @@ -107,7 +107,6 @@ public boolean onMenuItemClick(MenuItem item) { popup.show(); } - @NonNull private List getHistoryItems() { SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(mContext); return getHistory(sharedPref); @@ -176,12 +175,12 @@ public boolean onLongClick(View v) { } /** define history function for these editors */ - public HistoryEditText(Activity context, int[] cmdIds, EditText... editors) { + public HistoryEditText(Context context, int[] cmdIds, EditText... editors) { this(context, context.getClass().getSimpleName() + "_history_","';'", 8, cmdIds, editors); } /** define history function for these editors */ - public HistoryEditText(Activity context, String settingsPrefix, String delimiter, int maxHisotrySize, int[] cmdIds, EditText... editors) { + public HistoryEditText(Context context, String settingsPrefix, String delimiter, int maxHisotrySize, int[] cmdIds, EditText... editors) { this.mContext = context; this.mDelimiter = delimiter; diff --git a/app/src/main/java/de/k3b/android/widget/LocalizedActivity.java b/app/src/main/java/de/k3b/android/widget/LocalizedActivity.java index c47f2882..fdba14db 100644 --- a/app/src/main/java/de/k3b/android/widget/LocalizedActivity.java +++ b/app/src/main/java/de/k3b/android/widget/LocalizedActivity.java @@ -106,4 +106,7 @@ public static void recreate(Activity child) { } } + @Override public String toString() { + return getClass().getSimpleName(); + } } diff --git a/app/src/main/java/de/k3b/android/widget/SearchViewWithHistory.java b/app/src/main/java/de/k3b/android/widget/SearchViewWithHistory.java new file mode 100644 index 00000000..4e11f25f --- /dev/null +++ b/app/src/main/java/de/k3b/android/widget/SearchViewWithHistory.java @@ -0,0 +1,629 @@ +/* + * Copyright (c) 2015-2016 by Miguel Catalan Bañuls + * Copyright (c) 2018 by k3b. + * + * This file is part of AndroFotoFinder / #APhotoManager. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see + */ + +package de.k3b.android.widget; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.Editable; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.util.AttributeSet; +import android.util.Log; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.inputmethod.InputMethodManager; +import android.widget.EditText; +import android.widget.Filter; +import android.widget.FrameLayout; +import android.widget.ImageButton; +import android.widget.TextView; + +import java.lang.reflect.Field; + +import de.k3b.android.androFotoFinder.R; +import de.k3b.io.StringUtils; + +/** + * This is the implementation of a Searchview based on + * code from com.miguelcatalan.materialsearchview.MaterialSearchView + * from https://github.com/MiguelCatalan/MaterialSearchView/ + * + * originally created (c) 2015-2016 by Miguel Catalan Bañuls via "Apache License" + * + * ------ + * + * Changes: (c) 2018 by k3b + * * removed voice, styling, ListView with corresponding Adapter; + * * No more dependency to AppCmpat lib + * * Removed animation (required AppCompat) + * * Can be used with MenuItem (even without AppCompatActivity) + * + * + * + * without embedded toolbar in client qui. + * + */ +public class SearchViewWithHistory extends FrameLayout implements Filter.FilterListener, MenuItem.OnActionExpandListener { + private MenuItem mMenuItem; + private boolean mIsSearchOpen = false; + private boolean mClearingFocus; + + //Views + private View mSearchLayout; + private View mTintView; + private EditText mSearchSrcTextView; + private ImageButton mBackBtn; + private ImageButton mEmptyBtn; + private View mSearchTopBar; + private HistoryEditText mHistory; + + private CharSequence mOldQueryText; + private CharSequence mUserQuery; + + private OnQueryTextListener mOnQueryChangeListener; + private SearchViewListener mSearchViewListener; + + private SavedState mSavedState; + private boolean submit = false; + + private boolean ellipsize = false; + + private Context mContext; + + public SearchViewWithHistory(Context context) { + this(context, null); + } + + public SearchViewWithHistory(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public SearchViewWithHistory(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs); + + mContext = context; + + initiateView(); + + initStyle(attrs, defStyleAttr); + } + + // for debugging + @Override + public String toString() { + return StringUtils.appendMessage(null, + this.getClass().getSimpleName(),": open", + mIsSearchOpen, mUserQuery, mSavedState).toString(); + } + + private void initStyle(AttributeSet attrs, int defStyleAttr) { + TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.SearchViewWithHistory, defStyleAttr, 0); + + if (a != null) { + if (a.hasValue(R.styleable.SearchViewWithHistory_searchBackground)) { + setBackground(a.getDrawable(R.styleable.SearchViewWithHistory_searchBackground)); + } + + if (a.hasValue(R.styleable.SearchViewWithHistory_searchCloseIcon)) { + setCloseIcon(a.getDrawable(R.styleable.SearchViewWithHistory_searchCloseIcon)); + } + + if (a.hasValue(R.styleable.SearchViewWithHistory_searchBackIcon)) { + setBackIcon(a.getDrawable(R.styleable.SearchViewWithHistory_searchBackIcon)); + } + + a.recycle(); + } + } + + private void initiateView() { + LayoutInflater.from(mContext).inflate(R.layout.search_view, this, true); + mSearchLayout = findViewById(R.id.search_layout); + + mSearchTopBar = mSearchLayout.findViewById(R.id.search_top_bar); + mSearchSrcTextView = (EditText) mSearchLayout.findViewById(R.id.searchTextView); + mBackBtn = (ImageButton) mSearchLayout.findViewById(R.id.action_up_btn); + mEmptyBtn = (ImageButton) mSearchLayout.findViewById(R.id.action_empty_btn); + mTintView = mSearchLayout.findViewById(R.id.transparent_view); + + + mSearchSrcTextView.setOnClickListener(mOnClickListener); + mBackBtn.setOnClickListener(mOnClickListener); + mEmptyBtn.setOnClickListener(mOnClickListener); + mTintView.setOnClickListener(mOnClickListener); + + initSearchView(); + + mHistory = new HistoryEditText(mContext, new int[] { + R.id.action_history_btn}, + mSearchSrcTextView ); + } + + private void initSearchView() { + mSearchSrcTextView.setOnEditorActionListener(new TextView.OnEditorActionListener() { + @Override + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + onSubmitQuery(); + return true; + } + }); + + mSearchSrcTextView.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + mUserQuery = s; + startFilter(s); + SearchViewWithHistory.this.onTextChanged(s); + } + + @Override + public void afterTextChanged(Editable s) { + + } + }); + + mSearchSrcTextView.setOnFocusChangeListener(new OnFocusChangeListener() { + @Override + public void onFocusChange(View v, boolean hasFocus) { + if (hasFocus) { + showKeyboard(mSearchSrcTextView); + } + } + }); + } + + /** called every time the text changes */ + private void startFilter(CharSequence s) { + /* + if (mAdapter != null && mAdapter instanceof Filterable) { + ((Filterable) mAdapter).getFilter().filter(s, SearchViewWithHistory.this); + } + */ + } + + private final OnClickListener mOnClickListener = new OnClickListener() { + + public void onClick(View v) { + if (v == mBackBtn) { + closeSearch(); + } else if (v == mEmptyBtn) { + mSearchSrcTextView.setText(null); + } else if (v == mTintView) { + closeSearch(); + } + } + }; + + private void onTextChanged(CharSequence newText) { + CharSequence text = mSearchSrcTextView.getText(); + mUserQuery = text; + boolean hasText = !TextUtils.isEmpty(text); + if (hasText) { + mEmptyBtn.setVisibility(VISIBLE); + } else { + mEmptyBtn.setVisibility(GONE); + } + + if (mOnQueryChangeListener != null && !TextUtils.equals(newText, mOldQueryText)) { + mOnQueryChangeListener.onQueryTextChange(newText.toString()); + } + mOldQueryText = newText.toString(); + } + + private void onSubmitQuery() { + CharSequence query = mSearchSrcTextView.getText(); + if (query != null && TextUtils.getTrimmedLength(query) > 0) { + if (mOnQueryChangeListener == null || !mOnQueryChangeListener.onQueryTextSubmit(query.toString())) { + closeSearch(); + mSearchSrcTextView.setText(null); + } + } + } + + public void hideKeyboard(View view) { + InputMethodManager imm = (InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(view.getWindowToken(), 0); + } + + public void showKeyboard(View view) { + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1 && view.hasFocus()) { + view.clearFocus(); + } + view.requestFocus(); + InputMethodManager imm = (InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + imm.showSoftInput(view, 0); + } + + //Public Attributes + + @Override + public void setBackground(Drawable background) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + mSearchTopBar.setBackground(background); + } else { + mSearchTopBar.setBackgroundDrawable(background); + } + } + + @Override + public void setBackgroundColor(int color) { + mSearchTopBar.setBackgroundColor(color); + } + + public void setTextColor(int color) { + mSearchSrcTextView.setTextColor(color); + } + + public void setHintTextColor(int color) { + mSearchSrcTextView.setHintTextColor(color); + } + + public void setHint(CharSequence hint) { + mSearchSrcTextView.setHint(hint); + } + + public void setCloseIcon(Drawable drawable) { + mEmptyBtn.setImageDrawable(drawable); + } + + public void setBackIcon(Drawable drawable) { + mBackBtn.setImageDrawable(drawable); + } + + public void setCursorDrawable(int drawable) { + try { + // https://github.com/android/platform_frameworks_base/blob/kitkat-release/core/java/android/widget/TextView.java#L562-564 + Field f = TextView.class.getDeclaredField("mCursorDrawableRes"); + f.setAccessible(true); + f.set(mSearchSrcTextView, drawable); + } catch (Exception ignored) { + Log.e("MaterialSearchView", ignored.toString()); + } + } + + //Public Methods + + /** + * Submit the query as soon as the user clicks the item. + * + * @param submit submit state + */ + public void setSubmitOnClick(boolean submit) { + this.submit = submit; + } + + /** + * Calling this will set the query to search text box. if submit is true, it'll submit the query. + * + * @param query + * @param submit + */ + public void setQuery(CharSequence query, boolean submit) { + mSearchSrcTextView.setText(query); + if (query != null) { + mSearchSrcTextView.setSelection(mSearchSrcTextView.length()); + mUserQuery = query; + } + if (submit && !TextUtils.isEmpty(query)) { + onSubmitQuery(); + } + } + + /** + * Call this method and pass the menu item so this class can handle click events for the Menu Item. + * + * @param menuItem + */ + public void setMenuItem(final MenuItem menuItem) { + this.mMenuItem = menuItem; + mMenuItem.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + if (menuItem.getActionView() != null) { + menuItem.expandActionView(); + } + showSearch(); + return true; + } + }); + } + + /** + * Return true if search is open + * + * @return + */ + public boolean isSearchOpen() { + return mIsSearchOpen; + } + + /** + * Open Search View. This will animate the showing of the view. + */ + public void showSearch() { + showSearch(false); + } + + /** + * Open Search View. If animate is true, Animate the showing of the view. + * + * @param animate true for animate + */ + public void showSearch(boolean animate) { + if (isSearchOpen()) { + return; + } + + //Request Focus + mSearchSrcTextView.setText(null); + mSearchSrcTextView.requestFocus(); + + if (animate) { + setVisibleWithAnimation(); + + } else { + mSearchLayout.setVisibility(VISIBLE); + if (mSearchViewListener != null) { + mSearchViewListener.onSearchViewShown(); + } + } + mIsSearchOpen = true; + } + + private void setVisibleWithAnimation() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + mSearchLayout.setVisibility(View.VISIBLE); + // AnimationUtil.reveal(mSearchTopBar, animationListener); + + } else { + // AnimationUtil.fadeInView(mSearchLayout, mAnimationDuration, animationListener); + } + } + + /** + * Close search view. + */ + public void closeSearch() { + if (mHistory != null) mHistory.saveHistory(); + + clearFocus(); + if (!isSearchOpen()) { + return; + } + + mIsSearchOpen = false; + if (mSearchViewListener != null) { + mSearchViewListener.onSearchViewClosed(); + } + // mSearchSrcTextView.setText(null); + + if ((mMenuItem != null) && (mMenuItem.getActionView() != null)) { + mMenuItem.collapseActionView(); + } + + mSearchLayout.setVisibility(GONE); + + } + + /** + * Set this listener to listen to Query Change events. + * + * @param listener + */ + public void setOnQueryTextListener(OnQueryTextListener listener) { + mOnQueryChangeListener = listener; + } + + /** + * Set this listener to listen to Search View open and close events + * + * @param listener + */ + public void setOnSearchViewListener(SearchViewListener listener) { + mSearchViewListener = listener; + } + + /** + * Ellipsize suggestions longer than one line. + * + * @param ellipsize + */ + public void setEllipsize(boolean ellipsize) { + this.ellipsize = ellipsize; + } + + @Override + public void onFilterComplete(int count) { + if (count > 0) { + //showSuggestions(); + } else { + //dismissSuggestions(); + } + } + + @Override + public boolean requestFocus(int direction, Rect previouslyFocusedRect) { + // Don't accept focus if in the middle of clearing focus + if (mClearingFocus) return false; + // Check if SearchView is focusable. + if (!isFocusable()) return false; + return mSearchSrcTextView.requestFocus(direction, previouslyFocusedRect); + } + + @Override + public void clearFocus() { + mClearingFocus = true; + hideKeyboard(this); + super.clearFocus(); + mSearchSrcTextView.clearFocus(); + mClearingFocus = false; + } + + @Override + public Parcelable onSaveInstanceState() { + Parcelable superState = super.onSaveInstanceState(); + + mSavedState = new SavedState(superState); + mSavedState.query = mUserQuery != null ? mUserQuery.toString() : null; + mSavedState.isSearchOpen = this.mIsSearchOpen; + + return mSavedState; + } + + @Override + public void onRestoreInstanceState(Parcelable state) { + if (!(state instanceof SavedState)) { + super.onRestoreInstanceState(state); + return; + } + + mSavedState = (SavedState) state; + + if (mSavedState.isSearchOpen) { + showSearch(false); + setQuery(mSavedState.query, false); + } + + super.onRestoreInstanceState(mSavedState.getSuperState()); + } + + /** + * Called when a menu item with {@link MenuItem#SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW} + * is expanded. + * + * @param item Item that was expanded + * @return true if the item should expand, false if expansion should be suppressed. + */ + @Override + public boolean onMenuItemActionExpand(MenuItem item) { + return true; + } + + /** + * Called when a menu item with {@link MenuItem#SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW} + * is collapsed. + * + * @param item Item that was collapsed + * @return true if the item should collapse, false if collapsing should be suppressed. + */ + @Override + public boolean onMenuItemActionCollapse(MenuItem item) { + return true; + } + + public String getFilterValue() { + return (mSearchSrcTextView == null) ? "" : mSearchSrcTextView.getText().toString(); + } + + static class SavedState extends BaseSavedState { + String query; + boolean isSearchOpen; + + SavedState(Parcelable superState) { + super(superState); + } + + private SavedState(Parcel in) { + super(in); + this.query = in.readString(); + this.isSearchOpen = in.readInt() == 1; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + super.writeToParcel(out, flags); + out.writeString(query); + out.writeInt(isSearchOpen ? 1 : 0); + } + + //required field that makes Parcelables from a Parcel + public static final Creator CREATOR = + new Creator() { + public SavedState createFromParcel(Parcel in) { + return new SavedState(in); + } + + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + + // for debugging + @Override + public String toString() { + return StringUtils.appendMessage(null, + this.getClass().getSimpleName(),": open",isSearchOpen, query).toString(); + } + + } + + public interface OnQueryTextListener { + + /** + * Called when the user submits the query. This could be due to a key press on the + * keyboard or due to pressing a submit button. + * The listener can override the standard behavior by returning true + * to indicate that it has handled the submit request. Otherwise return false to + * let the SearchView handle the submission by launching any associated intent. + * + * @param query the query text that is to be submitted + * @return true if the query has been handled by the listener, false to let the + * SearchView perform the default action. + */ + boolean onQueryTextSubmit(String query); + + /** + * Called when the query text is changed by the user. + * + * @param newText the new content of the query text field. + * @return false if the SearchView should perform the default action of showing any + * suggestions if available, true if the action was handled by the listener. + */ + boolean onQueryTextChange(String newText); + } + + public interface SearchViewListener { + void onSearchViewShown(); + + void onSearchViewClosed(); + } + + +} \ No newline at end of file diff --git a/app/src/main/res/drawable-hdpi/album.png b/app/src/main/res/drawable-hdpi/album.png new file mode 100644 index 00000000..a7a44345 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/album.png differ diff --git a/app/src/main/res/drawable-hdpi/exif_edit.png b/app/src/main/res/drawable-hdpi/exif_edit.png index ba88b32d..724a8b01 100644 Binary files a/app/src/main/res/drawable-hdpi/exif_edit.png and b/app/src/main/res/drawable-hdpi/exif_edit.png differ diff --git a/app/src/main/res/drawable-hdpi/foto_gallery.png b/app/src/main/res/drawable-hdpi/foto_gallery.png index d6bf8a3e..ec30a2c2 100644 Binary files a/app/src/main/res/drawable-hdpi/foto_gallery.png and b/app/src/main/res/drawable-hdpi/foto_gallery.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_action_navigation_arrow_back.png b/app/src/main/res/drawable-hdpi/ic_action_navigation_arrow_back.png new file mode 100644 index 00000000..ffc68b4d Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_action_navigation_arrow_back.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_menu_find_holo_dark.png b/app/src/main/res/drawable-hdpi/ic_menu_find_holo_dark.png new file mode 100644 index 00000000..b981a4da Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_menu_find_holo_dark.png differ diff --git a/app/src/main/res/drawable-hdpi/rule.png b/app/src/main/res/drawable-hdpi/rule.png new file mode 100644 index 00000000..6e7348d9 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/rule.png differ diff --git a/app/src/main/res/drawable-mdpi/album.png b/app/src/main/res/drawable-mdpi/album.png new file mode 100644 index 00000000..d68ed22b Binary files /dev/null and b/app/src/main/res/drawable-mdpi/album.png differ diff --git a/app/src/main/res/drawable-mdpi/exif_edit.png b/app/src/main/res/drawable-mdpi/exif_edit.png index 1b89aa16..727b4065 100644 Binary files a/app/src/main/res/drawable-mdpi/exif_edit.png and b/app/src/main/res/drawable-mdpi/exif_edit.png differ diff --git a/app/src/main/res/drawable-mdpi/foto_gallery.png b/app/src/main/res/drawable-mdpi/foto_gallery.png index c0ed8506..1d502de0 100644 Binary files a/app/src/main/res/drawable-mdpi/foto_gallery.png and b/app/src/main/res/drawable-mdpi/foto_gallery.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_action_navigation_arrow_back.png b/app/src/main/res/drawable-mdpi/ic_action_navigation_arrow_back.png new file mode 100644 index 00000000..b2948bf0 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_action_navigation_arrow_back.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_menu_find_holo_dark.png b/app/src/main/res/drawable-mdpi/ic_menu_find_holo_dark.png new file mode 100644 index 00000000..45f8fd3f Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_menu_find_holo_dark.png differ diff --git a/app/src/main/res/drawable-mdpi/rule.png b/app/src/main/res/drawable-mdpi/rule.png new file mode 100644 index 00000000..ade07834 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/rule.png differ diff --git a/app/src/main/res/drawable-xhdpi/album.png b/app/src/main/res/drawable-xhdpi/album.png new file mode 100644 index 00000000..2f5f52eb Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/album.png differ diff --git a/app/src/main/res/drawable-xhdpi/exif_edit.png b/app/src/main/res/drawable-xhdpi/exif_edit.png index 3dc4f36f..d2a6e5f9 100644 Binary files a/app/src/main/res/drawable-xhdpi/exif_edit.png and b/app/src/main/res/drawable-xhdpi/exif_edit.png differ diff --git a/app/src/main/res/drawable-xhdpi/foto_gallery.png b/app/src/main/res/drawable-xhdpi/foto_gallery.png index ca88f4a3..f1189a49 100644 Binary files a/app/src/main/res/drawable-xhdpi/foto_gallery.png and b/app/src/main/res/drawable-xhdpi/foto_gallery.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_action_navigation_arrow_back.png b/app/src/main/res/drawable-xhdpi/ic_action_navigation_arrow_back.png new file mode 100644 index 00000000..42fb563e Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_action_navigation_arrow_back.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_menu_find_holo_dark.png b/app/src/main/res/drawable-xhdpi/ic_menu_find_holo_dark.png new file mode 100644 index 00000000..3ede9e23 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_menu_find_holo_dark.png differ diff --git a/app/src/main/res/drawable-xhdpi/rule.png b/app/src/main/res/drawable-xhdpi/rule.png new file mode 100644 index 00000000..c9d33dcc Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/rule.png differ diff --git a/app/src/main/res/drawable-xxhdpi/album.png b/app/src/main/res/drawable-xxhdpi/album.png new file mode 100644 index 00000000..790a79a7 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/album.png differ diff --git a/app/src/main/res/drawable-xxhdpi/exif_edit.png b/app/src/main/res/drawable-xxhdpi/exif_edit.png index 0be28450..1441b26d 100644 Binary files a/app/src/main/res/drawable-xxhdpi/exif_edit.png and b/app/src/main/res/drawable-xxhdpi/exif_edit.png differ diff --git a/app/src/main/res/drawable-xxhdpi/foto_gallery.png b/app/src/main/res/drawable-xxhdpi/foto_gallery.png index 4c8591bb..ea18e5df 100644 Binary files a/app/src/main/res/drawable-xxhdpi/foto_gallery.png and b/app/src/main/res/drawable-xxhdpi/foto_gallery.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_navigation_arrow_back.png b/app/src/main/res/drawable-xxhdpi/ic_action_navigation_arrow_back.png new file mode 100644 index 00000000..5f11c473 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_action_navigation_arrow_back.png differ diff --git a/app/src/main/res/drawable-xxhdpi/rule.png b/app/src/main/res/drawable-xxhdpi/rule.png new file mode 100644 index 00000000..734d19de Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/rule.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_action_navigation_arrow_back.png b/app/src/main/res/drawable-xxxhdpi/ic_action_navigation_arrow_back.png new file mode 100644 index 00000000..877f43e5 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_action_navigation_arrow_back.png differ diff --git a/app/src/main/res/drawable/color_cursor_white.xml b/app/src/main/res/drawable/color_cursor_white.xml new file mode 100644 index 00000000..c752e965 --- /dev/null +++ b/app/src/main/res/drawable/color_cursor_white.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_geo_edit.png b/app/src/main/res/drawable/ic_geo_edit.png index afa0be32..c9c711ed 100644 Binary files a/app/src/main/res/drawable/ic_geo_edit.png and b/app/src/main/res/drawable/ic_geo_edit.png differ diff --git a/app/src/main/res/layout-sw600dp/fragment_directory.xml b/app/src/main/res/layout-sw600dp/fragment_directory.xml index 0bdec786..d03fd0d4 100644 --- a/app/src/main/res/layout-sw600dp/fragment_directory.xml +++ b/app/src/main/res/layout-sw600dp/fragment_directory.xml @@ -1,7 +1,7 @@ + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_context_dirpicker.xml b/app/src/main/res/menu/menu_context_dirpicker.xml index cede6a66..12f2e79e 100644 --- a/app/src/main/res/menu/menu_context_dirpicker.xml +++ b/app/src/main/res/menu/menu_context_dirpicker.xml @@ -25,6 +25,10 @@ android:visible="true" android:orderInCategory="10" android:showAsAction="never" /> + + + + + - - + diff --git a/app/src/main/res/menu/menu_gallery_locked.xml b/app/src/main/res/menu/menu_gallery_locked.xml deleted file mode 100644 index 6c8fb653..00000000 --- a/app/src/main/res/menu/menu_gallery_locked.xml +++ /dev/null @@ -1,102 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/menu/menu_gallery_non_multiselect.xml b/app/src/main/res/menu/menu_gallery_non_multiselect.xml index 473451f0..aa23a469 100644 --- a/app/src/main/res/menu/menu_gallery_non_multiselect.xml +++ b/app/src/main/res/menu/menu_gallery_non_multiselect.xml @@ -19,16 +19,29 @@ * this program. If not, see */ --> - + + + android:showAsAction="ifRoom" + /> - - + + android:orderInCategory="2" android:showAsAction="always" android:visible="true" /> + android:orderInCategory="2" android:showAsAction="always" android:visible="true" /> + + + + + + + + + diff --git a/app/src/main/res/menu/menu_map_context.xml b/app/src/main/res/menu/menu_map_context.xml index 97cc2fa0..391e96f3 100644 --- a/app/src/main/res/menu/menu_map_context.xml +++ b/app/src/main/res/menu/menu_map_context.xml @@ -1,7 +1,7 @@ + + + A Photo Managerwith 'A Photo Map' in 'AndroFotoFinder' +
+ +

+ +مدير للصور المحلية يقوم بـ: بحث ونسخ وتعديل الصور ووضعها في معرض صور او في خريطة +

+ + +Features: + + + +يتطلب صلاحيات النظام الاتية: + +
    +
  • الانترنت: لتحميل ملفات الخرائط من سيرفر openstreetmap +
  • ACCESS_NETWORK_STATE and ACCESS_WIFI_STATE: لمعرفة امكانية اتصال الجهاز +
  • WRITE_EXTERNAL_STORAGE لتخزين ملفات الخريطة لفرز الصور لها +
  • READ_LOGS لقراءة وتخزين ملف سجلات الانهيار. +
+
+"]]>
+
diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 0ea47a74..180a35b2 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -2,9 +2,7 @@ - \"A Photo Manager%1$s\" الم + عن \"A Photo Manager%1$s\" مرشح الخريطة الخلفية - علامات مرجعية تمت إزالتها %1$s. + الألبوم + علامة مرجعية تمت إزالتها %1$s. لا يمكن إزالة العلامة المرجعية %s$1. هل تريد إزالة هذه العلامة المرجعية؟ تم توليده في %3$s بـ %1$s إصدار %2$s شاهد https://github.com/k3b/APhotoManager/wiki/intentapi#sql لمزيد من التفاصيل - تحميل العلامات المرجعية من… حفظ العلامة المرجعية كـ … تغيير ايقاف مؤقت تم نسخ %1$d من %2$d ملفات إزالة "هل أنت متأكد من أنك تود إزالة هذه الملفات كليًا؟ -%1$s" - إزالة صورة(صور)؟ +%1$s +هذه العملية لا يمكن استرجاعها +" + إزالة الصورة(الصور)؟ إزالة %1$d من %2$d ملفات نسخ المكان المقصود تحريك المكان المقصود @@ -63,7 +63,7 @@ https://github.com/k3b/APhotoManager/wiki/intentapi#sql لمزيد من التف تغير المعلومات الجغرافية للصور المختارة لم يتم إيجاد المنتقي الجغرافي اختيار جغرافي جديد للصور - "خطأ SQL %1$s\n\n%2$s" + خطأ SQL %1$s\n\n%2$s Sql غير صالح. إعادة تحميل الافتراضي خطأ في النظام. موجود الملف %1$s موجود بالفعل. .\n هل بالفعل تريد استبداله؟ @@ -73,10 +73,10 @@ https://github.com/k3b/APhotoManager/wiki/intentapi#sql لمزيد من التف "تحميل (%1$d) …" صور %1$d المحدثة. التاريخ - لات - لون + خط العرض + خط الطول المسار - بدون معلومات جيو + بدون معلومات جغرافية مجلد_جديد إنشاء مجلد لا يمكن إنشاء %1$s @@ -97,11 +97,12 @@ https://github.com/k3b/APhotoManager/wiki/intentapi#sql لمزيد من التف المختارة فقط الاختيار: أضف كل المرئي الاختيار: إزالة كل المرئي - مجلد العلامات المرجعية التشخيصات ... محتويات SQL التي تم تنفيذها. ... محتويات عناصر gridview/listview. استهلاك ذاكرة LogCat + قراءة وكتابة بيانات xmp و jpg بواسطة LogCat + … of 3rd party libraries. متفرقات LogCat. ... محتويات رسائل السجل الأخرى. محو LogCat @@ -114,8 +115,8 @@ https://github.com/k3b/APhotoManager/wiki/intentapi#sql لمزيد من التف اللغة افتراضيات النظام الحد الأقصي لعلامات التحديد بالخريطة - تعيين عداد تاريخ الجيو - تعيين ملف تاريخ الجيو + تعيين عداد التاريخ الجغرافي + تعيين ملف التاريخ الجغرافي إزالة الاختيارات المتعددة بعد الإجراء نسخ، تحريك، إعادة تسمية، إلخ. الإعدادات @@ -169,4 +170,58 @@ https://github.com/k3b/APhotoManager/wiki/intentapi#sql لمزيد من التف تحديث الوسوم في الصور المتأثرة الشريط-الطويل في قائمة فتح اسماء الوسوم النشاطات، الاشخاص، الاماكن، الموضوعات، المشروعات + كتابة التغييرات إلى + jpg و xmp (إنشاء إذا لم يتم العثور عليه) + jpg (و xmp ان لم يجد) + ملف jpg فقط + ملف xmp فقط + اسم ملف بيانات Xmp طويل؟ + طويل: ملف.jpg.xmp; قصير: ملف.xmp + + تحميل السياق ... + + تعديل Exif + العنوان + الوصف + التقييم + + التقييم + آخر تعديل + + تعديل معالجة الصور التلقائية + نمط اسم الملف + Exif + + "%1$s %2$d Photos\n\t%3$s (%4$s), …\nTo %5$s\n\t%6$s" + + الحجم + العرض + + الوضوح + + التطبيق محمي/مثبت + التطبيق غير محمي/مثبت + + اعادة تسمية الصور الخاصة من .jpg الى .jpg-p + مسح + فتح في مدير الملفات + + شريط البحث + منتقي التاريخ: استخدام العقود + أي وضع 2010، 2011،... 2019 اسفل 2010 * + مرشح التاريخ + + تحرير الألبوم الافتراضي + + مجلد العلامات المرجعية diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 5bf52bfa..e0e2f8d4 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -24,12 +24,13 @@ Über \"A Photo Manager%1$s\" Karten-Filter Hintergrund - Lesezeichen %1$s gelöscht. - Kann Lesezeichen %1$s nicht löschen. - Lesezeichen löschen? + Album + Album %1$s gelöscht. + Kann Album %1$s nicht löschen. + Album löschen? Erstellt am %3$s mit \'%1$s\' Version %2$s. weitere Informationen finden Sie unter https://github.com/k3b/APhotoManager/wiki/intentapi#sql - Lade Lesezeichen aus… - Lesezeichen speichern unter... + Lade Album aus… + Album speichern unter... Ändern Anhalten %1$d/%2$d Dateien kopiert @@ -94,7 +95,6 @@ Nur ausgewählte Auswahl: alle sichtbaren Auswahl: alle sichtbaren abwählen - Ordner für Lesezeichen … Fremdkomponenten. LogCat Speicherverbrauch @@ -234,13 +234,30 @@ Verstecken kann über das 'Media-Scanner' Gallery-Menü rückgängig gemacht wer Schutz/Fixierung aufheben - PRIVATE ...jpg wird zu ....jpg-p + Private *.jpg wird zu *.jpg-p Leeren In Filemanager öffnen + + Schnellsuche + + Datum Picker: nach Jahrzehnten gruppieren. + Bsp: 2010, 2011,...2019 unterhalb von 2010* + Datum Filter + + + Virtelles Album bearbeiten - + + + Ordner für Lesezeichen + diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 5dbc3930..f173eb01 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -2,7 +2,7 @@ Aplicación protegida/marcada Aplicación desprotegida/desmarcada - + + Cambiar extensión *.jpg a *.jpg-p + Borrar + Abrir en Administrador de archivos + + Buscador + Fechas: use décadas + Ejemplo escriba 2010, 2011,...2019 a continuación 2010* + Filtro de fechas + + Editar Album Virtual + + Carpeta de marcadores diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 31c79cc4..eee80cb8 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -24,11 +24,11 @@ À propos de \"A Photo Manager%1$s\" Sélectionnez une zone sur la carte Arrière plan + Album Supprimer le signet %1$s. Impossible de supprimer le signet %1$s. Supprimer ce signet ? Générer sur %3$s avec \'%1$s\' Version %2$s. Allez sur https://github.com/k3b/APhotoManager/wiki/intentapi#sql pour plus de détails - Charger le Signet de … Enregistrer le signet sous … Modifier Suspendre @@ -93,7 +93,6 @@ Uniquement les Sélectionnée(s) Sélection : Ajouter tous les visibles Sélection : Supprimer tous les visibles - Dossier de signet Diagnostiques Enregistrer les requêtes SQL. Enregistrer les évènements de la grille et des vues. @@ -167,7 +166,7 @@ jpg (et xmp si existant) fichier jpg uniquement fichier xmp uniquement - Taille du fichier pour les métadonnées Xmp ? + Longueur du nom de fichier Xmp? Long : file.jpg.xmp; Court : file.xmp Charger le Contexte... @@ -198,10 +197,22 @@ Appli protégée/épinglée Appli non-protégée/non-épinglée - Renommez les images JPG privés de .jpg à jpg-p - + Barre de recherche + Sélecteur de dates : utilisez des décennies + Par ex. mettez 2010, 2011,...2019 dessous 2010* + Filtre par date + + Modifier l’Album Virtuel + - Effacer - Ouvert dans le gestionnaire de fichiers + Dossier de signet diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index bc516dbf..9b1497db 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -1,8 +1,8 @@ - + - Info su \"A Photo Manager%1$s\" - Seleziona l\'area sulla mappa - Sfondo - Segnalibro eliminato %1$s. - Impossibile eliminare il segnalibro %1$s. - Eliminare questo segnalibro? - Generato su %3$s con \'%1$s\' Versione %2$s. Vedi https://github.com/k3b/APhotoManager/wiki/intentapi#sql per dettagli - Carica segnalibro da … - Salva segnalibro come … - Cambia - Pausa - Copiati %1$d/%2$d file - Elimina - "Sei sicuro di volere eliminare in modo definitivo questi file? %1$sQuesta operazione non può essere annullata." - Eliminare la/le foto? - Eliminati %1$d/%2$d file - Copia destinazione - Sposta destinazione - Dettagli - Scegli un editor foto - Nessun editor foto trovato - Modifica - protezione da scrittura per \'%1$s\'.\n\n \'%2$s\' non è possibile. - \'%1$s\' non è una data valida. - \'%1$s\' non è una coordinata valida. - Filtro - Seleziona %1$s - Caricamento cartella %1$s fallito - Filtra cartella - Foto - Mostra sulla mappa - vedi in app geo esterna - Imposta geo - Modifica delle informazioni geo sulle foto selezionate - Nessun selettore geo trovato - Seleziona nuova geo per la foto - Errore SQL \'%1$s\'\n\n%2$s - SQL non valido. Ripristino - Errore di sistema. Uscita - Il file \'%1$s\' esiste già.\n Vuoi veramente sovrascriverlo? - Impossibile rinominare il file \'%1$s\'. - Nessuna foto trovata per %1$s. - Foto non ancora trovata nel database %1$s. Scansione di %2$d nuovi file. La foto mancante sarà disponibile a breve. - "Caricamento (%1$d) …" - Aggiornate %1$d foto. - Data - Lat - Lon - Percorso - Senza info geo - nuova_cartella - Crea cartella - Impossibile creare %1$s - Creato %1$s - Altro ... - Sposta - Spostati %1$d/%2$d file - Tile Mappe (c) + Info su \"A Photo Manager%1$s\" + Seleziona l\'area sulla mappa + Sfondo + Album + Segnalibro eliminato %1$s. + Impossibile eliminare il segnalibro %1$s. + Eliminare questo segnalibro? + Generato su %3$s con \'%1$s\' Versione %2$s. Vedi https://github.com/k3b/APhotoManager/wiki/intentapi#sql per dettagli + Salva segnalibro come … + Cambia + Pausa + Copiati %1$d/%2$d file + Elimina + Sei sicuro di volere eliminare in modo definitivo questi file? %1$sQuesta operazione non può essere annullata. + Eliminare la/le foto? + Eliminati %1$d/%2$d file + Copia destinazione + Sposta destinazione + Dettagli + Scegli un editor foto + Nessun editor foto trovato + Modifica + protezione da scrittura per \'%1$s\'.\n\n \'%2$s\' non è possibile. + \'%1$s\' non è una data valida. + \'%1$s\' non è una coordinata valida. + Filtro + Seleziona %1$s + Caricamento cartella %1$s fallito + Filtra cartella + Foto + Mostra sulla mappa + vedi in app geo esterna + Imposta geo + Modifica delle informazioni geo sulle foto selezionate + Nessun selettore geo trovato + Seleziona nuova geo per la foto + Errore SQL \'%1$s\'\n\n%2$s + SQL non valido. Ripristino + Errore di sistema. Uscita + Il file \'%1$s\' esiste già.\n Vuoi veramente sovrascriverlo? + Impossibile rinominare il file \'%1$s\'. + Nessuna foto trovata per %1$s. + Foto non ancora trovata nel database %1$s. Scansione di %2$d nuovi file. La foto mancante sarà disponibile a breve. + "Caricamento (%1$d) …" + Aggiornate %1$d foto. + Data + Lat + Lon + Percorso + Senza info geo + nuova_cartella + Crea cartella + Impossibile creare %1$s + Creato %1$s + Altro ... + Sposta + Spostati %1$d/%2$d file + Tile Mappe (c) OpenStreetMap - Sovrascrivere? - Rinomina - Rinominati %1$d/%2$d file - Dove inizio la scansione? - Impossibile elaborare le foto mentre lo Scanner Multimediale di Android è occupato. Riprova più tardi. - Scanner multimediale - Aggiornati %1$d elementi del Database multimediale - %1$d selezionati - Seleziona una cartella con immagini per attivare l\'OK - Solo selezionati - Selezione: aggiungi tutte quelle visibili - Selezione: rimuovi tutte quelle visibili - Cartella di segnalibro - Diagnostica - … contenente SQL eseguito. - ... contenente elementi gridview/listview. - LogCat consumo di memoria - LogCat misc. - … contenente altri messaggi di log. - Svuota LogCat - Salva LogCat in un file - Cartella Errorlog - Auto-nascondi barra azioni dopo tot millisecondi - Visualizzazione foto iniziale con qualità migliore (lento, usa più memoria) - Migliora qualità vista foto iniziale - Intervallo presentazione in millisecondi - Language - (device language) - Max selettori sulla mappa - Imposta conteggio cronologia geo - Imposta file cronologia geo - Cancella la selezione - … dopo Copia/Sposta/Rinomina/… . - Impostazioni - Nessun provider Invia/Condividi trovato - Invio multiplo: numero di elementi eccessivo - Condividi/Invia - Mostra in nuova galleria - Presentazione - Data - Cartella - Nome - Lunghezza - Non ordinato - Luogo - Ordina - Aggiornati %1$d/%2$d file - Adatta alla finestra - Foto grande larghezza/altezza - se la larghezza-altezza della foto è maggiore di questo, mostra miniatura nella visualizzazione dettagliata. (migliore memoria, veloce, ma scarsa qualità) - Cartella miniatura - Usa i file di mappa locali *.map - che hai scaricato manualmente tramite pc - Cartella file di mappa *.map - Mostra foto - Ripara duplicati - Scegli geo dalla foto - Scegli geo dalla mappa - Nascondi immagini - "Sei sicuro?Vuoi rendere tutti i file multimediali (foto, video, audio) sotto %1$sinvisibili ai database, app galleria e media scanner?Puoi annullare l\'azione avviando il media scanner dal menu galleria." - Parte della cartella o nome file. % = Carattere jolly - Trova - Parte del tag, titolo, descrizione o percorso - Immagini pubbliche - Immagini private - Senza tag - + Tag - -Tag - Tag - Filtro tag - Digita qui per filtrare... - Nessun tag corrispondente trovato - Imposta tag - Crea nuovo Tag - nuovo_tag - Eliminazione dei contenuti - Aggiorna i tag nelle foto coinvolte - Premendo a lungo il nome del tag apre il menu - Attività,Persone,Luoghi,Temi,Progetti + Sovrascrivere? + Rinomina + Rinominati %1$d/%2$d file + Dove inizio la scansione? + Impossibile elaborare le foto mentre lo Scanner Multimediale di Android è occupato. Riprova più tardi. + Scanner multimediale + Aggiornati %1$d elementi del Database multimediale + %1$d selezionati + Seleziona una cartella con immagini per attivare l\'OK + Solo selezionati + Selezione: aggiungi tutte quelle visibili + Selezione: rimuovi tutte quelle visibili + Diagnostica + … contenente SQL eseguito. + ... contenente elementi gridview/listview. + LogCat consumo di memoria + LogCat jpg/xmp metadata read/write + … of 3rd party libraries. + LogCat misc. + … contenente altri messaggi di log. + Svuota LogCat + Salva LogCat in un file + Cartella Errorlog + Auto-nascondi barra azioni dopo tot millisecondi + Visualizzazione foto iniziale con qualità migliore (lento, usa più memoria) + Migliora qualità vista foto iniziale + Intervallo presentazione in millisecondi + Language + (device language) + Max selettori sulla mappa + Imposta conteggio cronologia geo + Imposta file cronologia geo + Cancella la selezione + … dopo Copia/Sposta/Rinomina/… . + Impostazioni + Nessun provider Invia/Condividi trovato + Invio multiplo: numero di elementi eccessivo + Condividi/Invia + Mostra in nuova galleria + Presentazione + Data + Cartella + Nome + Lunghezza + Non ordinato + Luogo + Ordina + Aggiornati %1$d/%2$d file + Adatta alla finestra + Foto grande larghezza/altezza + se la larghezza-altezza della foto è maggiore di questo, mostra miniatura nella visualizzazione dettagliata. (migliore memoria, veloce, ma scarsa qualità) + Cartella miniatura + Usa i file di mappa locali *.map + che hai scaricato manualmente tramite pc + Cartella file di mappa *.map + Mostra foto + Ripara duplicati + Scegli geo dalla foto + Scegli geo dalla mappa + Nascondi immagini + Sei sicuro?Vuoi rendere tutti i file multimediali (foto, video, audio) sotto %1$sinvisibili ai database, app galleria e media scanner?Puoi annullare l\'azione avviando il media scanner dal menu galleria. + Parte della cartella o nome file. % = Carattere jolly + Trova + Parte del tag, titolo, descrizione o percorso + Immagini pubbliche + Immagini private + Senza tag + + Tag + -Tag + Tag + Filtro tag + Digita qui per filtrare... + Nessun tag corrispondente trovato + Imposta tag + Crea nuovo Tag + nuovo_tag + Eliminazione dei contenuti + Aggiorna i tag nelle foto coinvolte + Premendo a lungo il nome del tag apre il menu + Attività,Persone,Luoghi,Temi,Progetti + Write changes to + jpg and xmp (Create if not found) + jpg (and xmp if exists) + jpg file only + xmp file only + Long Xmp Sidecar File Name? + Long: file.jpg.xmp; Short: file.xmp + + Load Context ... + + Edit Exif + Title + Description + Rating + + Rating + Last Modified + + Edit Photo Autoprocessing + Filename Pattern + Exif + + "%1$s %2$d Photos\n\t%3$s (%4$s), …\nTo %5$s\n\t%6$s" + + Size + Width + + Visibility + + App protected/pinned + App unprotect/unpinned + + Rename private *.jpg to *.jpg-p + Clear + Open in Filemanager + + Searchbar + Date Picker: use decades + I.E. Put 2010, 2011,...2019 below 2010* + Date Filter + + Edit Virtual Album + + Cartella di segnalibro diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 35e73890..4cf38310 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -2,7 +2,7 @@ + Рейтинг + Последнее изменение + + Размер + + Видимость + + Очистить diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index c4a50868..92d2bae2 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -2,7 +2,9 @@ 固定应用窗口 应用窗口取消保护/固定 - + + 重命名私人 *.jpg 至 *.jpg-p + 清除 + 在文件管理器中打开 + + 搜索栏 + 日期选取器: 使用十年 + 例如: 将 2010、2011···2019 放置在 2010* 下 + 日期筛选 + + 编辑虚拟相册 + + 书签文件夹 diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index 7ce840eb..efd38f41 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -8,5 +8,13 @@ + + + + + + + + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 8b56346d..985a132a 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -24,4 +24,5 @@ #ffffff #70ffffff @android:color/black + #50000000 \ No newline at end of file diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml index 1c2776ef..7a6a85d6 100644 --- a/app/src/main/res/values/donottranslate.xml +++ b/app/src/main/res/values/donottranslate.xml @@ -105,11 +105,12 @@ this program. If not, see

The code uses


History

@@ -178,8 +173,8 @@ this program. If not, see

Legal stuff

A Photo Manager (A(ndro)FotoFinder)
-
Copyright (c) 2015-2016 by k3b -
Licensed under GPL, Version 3.0 or later. +
Copyright (c) 2015-2018 by k3b +
Licensed under GPL, Verion 3.0 or later.

Translations supported crowdin.com diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a0faaa45..045453af 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -22,12 +22,12 @@ About \"A Photo Manager%1$s\" Map Area Filter Background - Deleted bookmark %1$s. - Cannot delete bookmark %1$s. - Delete this bookmark? + Album + Deleted album %1$s. + Cannot delete album %1$s. + Delete this album? Generated on %3$s with \'%1$s\' Version %2$s. See https://github.com/k3b/APhotoManager/wiki/intentapi#sql for details - Load bookmark from … - Save bookmark as … + Save album as … Change Pause Copied %1$d/%2$d files @@ -92,11 +92,10 @@ Media scanner Updated %1$d Media Database Items %1$d Selected - Select folder with images to enable OK + Select folder to load pictures from and click ok Selected only Selection: Add all visible Selection: Remove all visible - Bookmark folder Diagnostics … containing executed SQL. @@ -118,7 +117,7 @@ Slideshow interval in millisecs Language System default - Max. sel-markers in map + Max. selected markers in map Set geo history count Set geo history file Clear multi-selection after action @@ -238,14 +237,30 @@ You can undo hiding by calling the mediascanner from gallery-menu." App unprotect/unpinned - Rename PRIVATE ...jpg to ....jpg-p + Rename private *.jpg to *.jpg-p Clear Open in Filemanager + + Searchbar + + Date Picker: use decades + I.E. Put 2010, 2011,...2019 below 2010* + Date Filter + + + Edit Virtual Album + + Bookmark folder + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 846fc91b..a7f42402 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -25,5 +25,10 @@ + diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index ce0020fc..0fe13098 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -66,6 +66,10 @@ this program. If not, see + diff --git a/app/src/test/java/de/k3b/android/androFotoFinder/tagDB/TagSqlQueryParserTests.java b/app/src/test/java/de/k3b/android/androFotoFinder/tagDB/TagSqlQueryParserTests.java index 1e21505c..6d0988d9 100644 --- a/app/src/test/java/de/k3b/android/androFotoFinder/tagDB/TagSqlQueryParserTests.java +++ b/app/src/test/java/de/k3b/android/androFotoFinder/tagDB/TagSqlQueryParserTests.java @@ -67,6 +67,11 @@ public void shouldParsePublic() throws Exception { assertFilterQueryFilter(VISIBILITY.PUBLIC); } + @Test + public void shouldFilterFind() throws Exception { + assertFilterFind("hello world", "shouldFilterFind"); + } + // assert that input-string==output-string in input-string -> filter -> query -> filter -> output-string private QueryParameter assertFilterQueryFilter(VISIBILITY visibility) { String FILTER_STRING = ";;;;;;;;;" + visibility.value; @@ -79,29 +84,75 @@ private QueryParameter assertFilterQueryFilter(String filterString) { } // assert that input-string==output-string in input-string -> filter -> query -> filter -> output-string - private QueryParameter assertFilterQueryFilter(String filterString, String printSql) { - GalleryFilterParameter initialFilter = GalleryFilterParameter.parse(filterString, new GalleryFilterParameter()); + private QueryParameter assertFilterQueryFilter(String expectedFilterString, String printSql) { + GalleryFilterParameter initialFilter = GalleryFilterParameter.parse(expectedFilterString, new GalleryFilterParameter()); + + QueryParameter query = new QueryParameter(); + GalleryFilterParameter parsedFilter = getParsedGalleryFilterParameter(query, initialFilter, printSql); + + assertEquals(expectedFilterString, parsedFilter.toString()); + return query; + } + + @Test + public void assertGFilterQueryGFilter() { + assertGFilterQueryGFilter("InAnyField", createPublicGalleryFilterParameter().setInAnyField("%1% %2%")); + assertGFilterQueryGFilter("Date", createPublicGalleryFilterParameter().setDate("1997-12-24","2005-11-30")); + assertGFilterQueryGFilter("Path", createPublicGalleryFilterParameter().setPath("%1%")); + GalleryFilterParameter gfLL = createPublicGalleryFilterParameter(); + gfLL.setLatitude("12.34", "34.56").setLogitude("45.67", "56.78"); + assertGFilterQueryGFilter("Latitude Logitude", gfLL); + assertGFilterQueryGFilter("Rating", createPublicGalleryFilterParameter().setRatingMin(4)); + } + private GalleryFilterParameter createPublicGalleryFilterParameter() { + return new GalleryFilterParameter().setVisibility(VISIBILITY.PUBLIC); + } + + static private void assertGFilterQueryGFilter(String msg, GalleryFilterParameter original) { QueryParameter query = new QueryParameter(); - TagSql.filter2QueryEx(query, initialFilter, true); + TagSql.filter2QueryEx(query, original, true); + String sql = query.toReParseableString(); + + // query is destroyed by parse so use a clone + QueryParameter queryToParse = QueryParameter.parse(sql); // new QueryParameter(query); + GalleryFilterParameter parsedFilter = (GalleryFilterParameter) TagSql.parseQueryEx(queryToParse, true); - if (printSql != null) { - String sql = query.toSqlString(); + assertEquals(msg, original.toString(), parsedFilter.toString()); + } + + // assert that input-string==output-string in input-string -> filter -> query -> filter -> output-string + private QueryParameter assertFilterFind(String expectedFilterFindValue, String debugPrefixPrintSql) { + GalleryFilterParameter initialFilter = new GalleryFilterParameter().setInAnyField(expectedFilterFindValue); + + QueryParameter query = new QueryParameter(); + GalleryFilterParameter parsedFilter = getParsedGalleryFilterParameter(query, initialFilter, debugPrefixPrintSql); + + assertEquals(query.toSqlString(), expectedFilterFindValue, parsedFilter.getInAnyField().replaceAll("%","")); + return query; + } + + private GalleryFilterParameter getParsedGalleryFilterParameter(QueryParameter resultQuery, GalleryFilterParameter initialFilter, String debugPrefixPrintSql) { + TagSql.filter2QueryEx(resultQuery, initialFilter, true); + + if (debugPrefixPrintSql != null) { + String sql = resultQuery.toSqlString(); int start = sql.indexOf("WHERE"); - System.out.println(printSql + ": " + sql.substring(start)); + System.out.println(debugPrefixPrintSql + ": " + sql.substring(start)); } - GalleryFilterParameter parsedFilter = (GalleryFilterParameter) TagSql.parseQueryEx(query, true); + // query is destroyed by parse so use a clone + QueryParameter queryToParse = new QueryParameter(resultQuery); + GalleryFilterParameter parsedFilter = (GalleryFilterParameter) TagSql.parseQueryEx(queryToParse, true); parsedFilter.setSort(initialFilter.getSortID(), initialFilter.isSortAscending()); // compensate that query might automatically add visibility if (initialFilter.getVisibility() == VISIBILITY.DEFAULT) { parsedFilter.setVisibility(VISIBILITY.DEFAULT); } - - assertEquals(filterString, parsedFilter.toString()); - return query; + return parsedFilter; } + //################ tag filter support @Test public void shouldTagsNoneOnly() throws Exception { diff --git a/fastlane/metadata/android/ar-SA/full_description.txt b/fastlane/metadata/android/ar-SA/full_description.txt new file mode 100644 index 00000000..e3cbec04 --- /dev/null +++ b/fastlane/metadata/android/ar-SA/full_description.txt @@ -0,0 +1,23 @@ +مدير للصور المحلية يقوم بـ: بحث ونسخ وتعديل الصور ووضعها في معرض صور او في خريطة + + المميزات: + + * سرعة البحث على الصور بالعناوين (الكلمات الرئيسية) + * عرض النتائج في معرض او خريطة جغرافية من openstreetmap. + * عرض بالتفاصيل. يحتوي على ميزة تكبير الصور, والسحب للصورة التالية والسابقة. + * مدير ملفات مدمج للبحث وفرز, عرض, نسخ, ارسال, وحذف الصور, ... . + * تعديل ملف بيانات exif :التاريخ, والعنوان, والوصف, والعناوين (الكلمات الرئيسية), والمكان الجغرافي, والتقييم + * القيام بمعالجة الصور باعادة التسمية, وضع العلامات, والموقع الجغرافي, والعنوان تلقائيا ... عند نسخهم وقصهم . + * وضع "خاص" في الصور لاخفائهم من بقية برامج عرض الصور. + * في الوضع "المحمي والمثبت" الأوامر الحساسة مثل تعديل ونسخ, حذف, مشاركة, الاعدادات, وتغيير اختيار الصور, موقفة فبامكانك اعطاء هاتفك للغير بدون خوف. + * يمكنه تحمل مجموعة كبيرة من الصور (فوق 20000 صورة و 1000 مجلد). + * يستخدم مزود محتويات Android. لا يحتاج الى العثور على الصور . + * كاشف اضافي على ملفات Exif, IPTC, XMP + + يتطلب صلاحيات النظام الاتية: + + * الانترنت: لتحميل ملفات الخرائط من سيرفر openstreetmap + * ACCESS_NETWORK_STATE and ACCESS_WIFI_STATE: لمعرفة امكانية اتصال الجهاز + * WRITE_EXTERNAL_STORAGE لتخزين ملفات الخريطة لفرز الصور لها + * READ_LOGS لقراءة وتخزين ملف سجلات الانهيار.. +. \ No newline at end of file diff --git a/fastlane/metadata/android/ar-SA/short_description.txt b/fastlane/metadata/android/ar-SA/short_description.txt new file mode 100644 index 00000000..9be09f54 --- /dev/null +++ b/fastlane/metadata/android/ar-SA/short_description.txt @@ -0,0 +1 @@ +مدير للصور المحلية يقوم بـ: بحث ونسخ وتعديل الصور ووضعها في معرض صور او في خريطة. \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/changelogs/38.txt b/fastlane/metadata/android/en-US/changelogs/38.txt new file mode 100644 index 00000000..850d2802 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/38.txt @@ -0,0 +1,7 @@ +* New Logo/Icons by Md Nazmul Hasan. +* New Virutal Albums/".album" files: edit with Filter-View; open with Dirpicker for Gallery-View and Geographic-Map. +* Gallery-View: Added "Filter by Date"; Added search bar. +* Openstreemap-Display: added optional path/date/tag-filter; fixed: works with zoomfactor > 19; Fixed initial zoom region. +* Filter-View: In field "Find": allow to search for subexpressions seperated by " " +* Fixed: Open image via filemanager with app specific content urls (i.e. OI File Manager) +* Translation updates: ar,de,en,es,fr,it,ja,ru,tr,zh-CN (7 = 100%, 5 > 90%, 5 < 65% \ No newline at end of file diff --git a/fotolib2/src/main/java/de/k3b/FotoLibGlobal.java b/fotolib2/src/main/java/de/k3b/FotoLibGlobal.java index 14d3e386..ab81f6ac 100644 --- a/fotolib2/src/main/java/de/k3b/FotoLibGlobal.java +++ b/fotolib2/src/main/java/de/k3b/FotoLibGlobal.java @@ -41,6 +41,9 @@ public class FotoLibGlobal { /** false do not follow symlinks when scanning Directories. */ public static final boolean ignoreSymLinks = false; + /** datePickerUseDecade true add decade in date picker */ + public static boolean datePickerUseDecade = false; + /** #100: true: private images get the extension ".jpg-p" which hides them from other gallery-apps and image pickers. */ public static boolean renamePrivateJpg = true; diff --git a/fotolib2/src/main/java/de/k3b/database/QueryParameter.java b/fotolib2/src/main/java/de/k3b/database/QueryParameter.java index ac8b8a40..5963f9d3 100644 --- a/fotolib2/src/main/java/de/k3b/database/QueryParameter.java +++ b/fotolib2/src/main/java/de/k3b/database/QueryParameter.java @@ -19,9 +19,16 @@ package de.k3b.database; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; +import de.k3b.io.FileUtils; +import de.k3b.io.StringUtils; + /** * Utility to collect query parameters for content-provider. * No dependencies to android so it can be unittested @@ -33,6 +40,9 @@ * Created by k3b on 04.06.2015. */ public class QueryParameter { + public static final String SUFFIX_VALBUM = ".album"; + public static final String SUFFIX_QUERY = ".query"; + /** added to every serialized item if != null. Example "Generated on 2015-10-19 with myApp Version 0815." */ public static String sFileComment = null; @@ -140,6 +150,22 @@ public QueryParameter clearWhere() { return this; } + public QueryParameter clear() { + clearWhere(); + clearColumns(); + mFrom.clear(); + mGroupBy.clear(); + mHaving.clear(); + mOrderBy.clear(); + mHavingParameters.clear(); + return this; + } + + public QueryParameter clearColumns() { + mColumns.clear(); + return this; + } + public QueryParameter addWhere(String where, String... parameters) { mWhere.add(where); return addToList(mParameters, true, parameters); @@ -201,7 +227,7 @@ private static int getParamCount(String sqlWhereWithParameters, List par * Therefore this sql is added to the WHERE part. * [select ... from ... where (] [[mWhere][) GROUP BY (mGroupBy][) HAVING (mHaving]] [) ORDER BY ] [mOrderBy]*/ public String toAndroidWhere() { - boolean hasWhere = Helper.isNotEmpty(mWhere); + boolean hasWhere = hasWhere(); boolean hasGroup = Helper.isNotEmpty(mGroupBy); boolean hasHaving = Helper.isNotEmpty(mHaving); if (!hasWhere && !hasGroup && !hasHaving) return null; @@ -218,6 +244,10 @@ public String toAndroidWhere() { return result.toString(); } + public boolean hasWhere() { + return Helper.isNotEmpty(mWhere); + } + public String[] toAndroidParameters() { return Helper.toList(mParameters, mHavingParameters); } @@ -256,6 +286,27 @@ public String toOrderBy() { } /************************** end properties *********************/ + + public void save(OutputStream _out) throws IOException { + PrintWriter writer = null; + try { + writer = new PrintWriter(_out); + writer.println(this.toReParseableString()); + writer.flush(); + } finally { + writer.close(); + } + writer = null; + } + + public static QueryParameter load(InputStream input) throws IOException { + String sql = FileUtils.readFile(input); + if (!StringUtils.isNullOrEmpty(sql)) { + return QueryParameter.parse(sql); + } + return null; + } + public String toReParseableString() { StringBuilder result = new StringBuilder(); if (sFileComment != null) result.append("# ").append(sFileComment).append("\n"); diff --git a/fotolib2/src/main/java/de/k3b/io/AlbumFile.java b/fotolib2/src/main/java/de/k3b/io/AlbumFile.java new file mode 100644 index 00000000..1c51cbb3 --- /dev/null +++ b/fotolib2/src/main/java/de/k3b/io/AlbumFile.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2018 by k3b. + * + * This file is part of AndroFotoFinder / #APhotoManager + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see + */ +package de.k3b.io; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +/** + * Created by k3b on 17.04.2018. + */ + +public class AlbumFile { + public static final String SUFFIX_VALBUM = ".album"; + public static final String SUFFIX_QUERY = ".query"; + + public static boolean isQueryFile(String uri) { + if (uri != null) { + return uri.endsWith(SUFFIX_VALBUM) || uri.endsWith(SUFFIX_QUERY); + } + return false; + } + public static boolean isQueryFile(File uri) { + if (uri != null) { + return isQueryFile(uri.getName()); + } + return false; + } + public static File getExistingQueryFileOrNull(String uri) { + if (isQueryFile(uri)) { + File result = new File(FileUtils.fixPath(uri)); + if ((result != null) && result.isFile() && result.exists()) return result; + } + return null; + } + + /** return all album files as absolute path */ + public static List getFilePaths(List result, File root, int subDirLevels) { + if (result == null) result = new ArrayList(); + + if ((root != null) && !FileUtils.isSymlinkDir(root,false) && root.isDirectory()) { + for (File file : root.listFiles()) { + if (file.isDirectory() && (subDirLevels > 1)) { + getFilePaths(result, file, subDirLevels - 1); + } else if (isQueryFile((file))) { + String path = FileUtils.tryGetCanonicalPath(file, null); + result.add(path); + } + } + } + return result; + } +} diff --git a/fotolib2/src/main/java/de/k3b/io/DateUtil.java b/fotolib2/src/main/java/de/k3b/io/DateUtil.java index 086b44f0..fc7af937 100644 --- a/fotolib2/src/main/java/de/k3b/io/DateUtil.java +++ b/fotolib2/src/main/java/de/k3b/io/DateUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2017 by k3b. + * Copyright (c) 2016-2018 by k3b. * * This file is part of AndroFotoFinder / #APhotoManager. * @@ -57,11 +57,13 @@ public static Date parseIsoDate(String dateString) { private static Date parseDateTime(String dateString, DateFormat... formatCandidates) { Date result = null; - for (DateFormat formatCandidate : formatCandidates) { - try { - result = formatCandidate.parse(dateString); - if (result != null) break; - } catch (ParseException e) { + if (dateString != null) { + for (DateFormat formatCandidate : formatCandidates) { + try { + result = formatCandidate.parse(dateString); + if (result != null) break; + } catch (ParseException e) { + } } } return result; diff --git a/fotolib2/src/main/java/de/k3b/io/Directory.java b/fotolib2/src/main/java/de/k3b/io/Directory.java index 42998a0b..4f05ce8d 100644 --- a/fotolib2/src/main/java/de/k3b/io/Directory.java +++ b/fotolib2/src/main/java/de/k3b/io/Directory.java @@ -42,7 +42,7 @@ public class Directory implements IDirectory { private String relPath = null; private Boolean apmDir = null; - private IDirectory parent = null; + private Directory parent = null; private List children = null; private int nonDirItemCount = 0; @@ -52,16 +52,28 @@ public class Directory implements IDirectory { private int iconID = 0; - public Directory(String relPath, Directory parent, int nonDirItemCount) { + public Directory(String relPath, IDirectory parent, int nonDirItemCount) { this.setRelPath(relPath); this.setParent(parent); // this.setHasNonDirElements(hasNonDirElements); if (parent != null) { - parent.addChild(this); + ((Directory) parent).addChild(this); } setNonDirItemCount(nonDirItemCount); } + /** factory method to be overwrittern by derived classes, if tree should consist of derived classes. */ + public IDirectory createOsDirectory(File file, IDirectory parent, List children) { + final Directory result = new Directory(file.getName(), parent, 0); + + if (children != null) { + for (IDirectory child : children) { + addChild(child); + } + } + return result; + } + @Override public void destroy() { if (children != null) { @@ -103,8 +115,8 @@ public IDirectory getParent() { return parent; } - public void setParent(Directory parent) { - this.parent = parent; + public void setParent(IDirectory parent) { + this.parent = (Directory) parent; } @Override @@ -270,6 +282,7 @@ public int getSelectionIconID() { @Override public int getDirFlags() { + if (AlbumFile.isQueryFile(this.getRelPath())) return IDirectory.DIR_FLAG_VIRTUAL_DIR; return isApmDir() ? IDirectory.DIR_FLAG_APM_DIR : IDirectory.DIR_FLAG_NONE; } diff --git a/fotolib2/src/main/java/de/k3b/io/DirectoryFormatter.java b/fotolib2/src/main/java/de/k3b/io/DirectoryFormatter.java index f56fc63a..375815b3 100644 --- a/fotolib2/src/main/java/de/k3b/io/DirectoryFormatter.java +++ b/fotolib2/src/main/java/de/k3b/io/DirectoryFormatter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 by k3b. + * Copyright (c) 2015-2018 by k3b. * * This file is part of AndroFotoFinder. * @@ -21,14 +21,20 @@ import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; +import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import java.util.Locale; +import java.util.TimeZone; + +import de.k3b.FotoLibGlobal; /** * Created by k3b on 12.07.2015. */ public class DirectoryFormatter { + private static final int INTDECADE_LEN = 3; + /** * "/2001/01/16" => 2001-01-16 - 2001-01-17 * "/2001/01/" => 2001-01-01 - 2001-02-01 @@ -38,14 +44,19 @@ public static void getDates(String selectedAbsolutePath, Date from, Date to) { Integer year = null; Integer month = null; Integer day = null; + Integer decade = (FotoLibGlobal.datePickerUseDecade) ? null : Integer.MIN_VALUE; String parts[] = selectedAbsolutePath.split(Directory.PATH_DELIMITER); for (String part : parts) { if ((part != null) && ((part.length() > 0))) { try { + if (decade == null) { + part = part.substring(0,part.length() - 1); // remove trailing "*" + } Integer value = Integer.parseInt(part); - if (year == null) year = value; + if (decade == null) decade = value; + else if (year == null) year = value; else if (month == null) month = value; else if (day == null) day = value; } catch (NumberFormatException ex) { @@ -54,27 +65,29 @@ public static void getDates(String selectedAbsolutePath, Date from, Date to) { } } - if (year != null) { - int yearFrom = year.intValue(); + int yearFrom = 0; + if ((FotoLibGlobal.datePickerUseDecade) && (decade != null)) yearFrom = decade.intValue(); + if (year != null) yearFrom = year.intValue(); - if (yearFrom == 1970) { + if (yearFrom != 0) { + if ((year != null) && (yearFrom == 1970)) { from.setTime(0); to.setTime(0); } else { int monthFrom = (month != null) ? month.intValue() : 1; int dayFrom = (day != null) ? day.intValue() : 1; - GregorianCalendar _from = new GregorianCalendar(yearFrom, monthFrom - 1, dayFrom, 0, 0, 0); - _from.setTimeInMillis(_from.getTimeInMillis()); + GregorianCalendar cal = new GregorianCalendar(yearFrom, monthFrom - 1, dayFrom, 0, 0, 0); + from.setTime(cal.getTimeInMillis()); + int field = GregorianCalendar.YEAR; + int increment = 10; + if (year != null) increment = 1; if (month != null) field = GregorianCalendar.MONTH; if (day != null) field = GregorianCalendar.DAY_OF_MONTH; - GregorianCalendar _to = new GregorianCalendar(); - _to.setTimeInMillis(_from.getTimeInMillis()); - _to.add(field, 1); - to.setTime(_to.getTimeInMillis()); - from.setTime(_from.getTimeInMillis()); + cal.add(field, increment); + to.setTime(cal.getTimeInMillis()); } } } @@ -104,6 +117,12 @@ public static String formatLatLon(Double latOrLon) { return formatLatLon(latOrLon.doubleValue()); } + public static CharSequence formatLatLon(Double... latOrLons) { + StringBuilder result = new StringBuilder(); + for (Double latOrLon : latOrLons) + result.append(formatLatLon(latOrLon)).append(" "); + return result; + } public static String formatLatLon(double latOrLon) { if ((latOrLon <= 0.0000005) && (latOrLon >= -0.0000005)) return "0"; @@ -183,4 +202,54 @@ private static double getLatLon(String latOrLon) { return Double.parseDouble(latOrLon); } + public static String getDecade(String stringWithYear, int posOfYear) { + if ((stringWithYear == null) || (stringWithYear.length() - posOfYear < INTDECADE_LEN)) { + return null; + } + return stringWithYear.substring(posOfYear, posOfYear + INTDECADE_LEN) + "0*"; + } + + public static String getDatePath(final boolean withDecade, long dateFrom, long dateTo) { + // special cases if null, empty or only one value + if (dateFrom == 0) dateFrom = dateTo; + if (dateTo == 0) dateTo = dateFrom; + if (dateTo == 0) return null; + + final long diffTage = Math.abs (dateTo - dateFrom) / (1000 * 60 * 60 * 24); + + final Calendar date = Calendar.getInstance(); // TimeZone.getTimeZone("UTC")); + date.setTimeInMillis(dateFrom); + final int year = date.get(Calendar.YEAR); + + final StringBuilder result = new StringBuilder(); + if ((withDecade) && (diffTage < 3800)) { + result.append("/").append(year / 10).append("0*"); + } + if (diffTage < 380) { + result.append("/").append(year); + } + if (diffTage < 40) { + final int month = date.get(Calendar.MONTH) + 1; + result.append("/").append(n2(month) ); + } + if (diffTage <= 2) { + final int day = date.get(Calendar.DAY_OF_MONTH); + result.append("/").append(n2(day)); + } + if (result.length() == 0) return null; + return result.toString(); + } + + private static int getYear(Date dateTo) { + int year = dateTo.getYear(); + if ((year >= 0) && (year < 1000)) year += 1900; + return year; + } + + + private static String n2(int i) { + if (i < 10) return "0"+i; + + return ""+i; + } } diff --git a/fotolib2/src/main/java/de/k3b/io/FileUtils.java b/fotolib2/src/main/java/de/k3b/io/FileUtils.java index b367f8ca..8f2b2103 100644 --- a/fotolib2/src/main/java/de/k3b/io/FileUtils.java +++ b/fotolib2/src/main/java/de/k3b/io/FileUtils.java @@ -29,6 +29,7 @@ import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; import java.io.OutputStream; import java.util.regex.Pattern; @@ -52,8 +53,15 @@ public static InputStream streamFromStringContent(String data) { return s; } + public static String readFile(InputStream file) throws IOException { + return internalReadFile(new BufferedReader(new InputStreamReader(file)), file); + } + public static String readFile(File file) throws IOException { - BufferedReader br = new BufferedReader(new FileReader(file)); + return internalReadFile(new BufferedReader(new FileReader(file)), file); + } + + public static String internalReadFile(BufferedReader br, Object source) throws IOException { StringBuilder sb = new StringBuilder(); String line = br.readLine(); @@ -62,11 +70,11 @@ public static String readFile(File file) throws IOException { sb.append("\n"); line = br.readLine(); } - close(br, file); + close(br, source); return sb.toString(); } - public static void close(Closeable stream, Object source) { + public static void close(Closeable stream, Object source) { if (stream != null) { try { stream.close(); @@ -286,4 +294,33 @@ public static void copy(InputStream is, OutputStream os) throws IOException { } + // #118 app specific content uri convert + // from {content://approvider}//storage/emulated/0/DCIM/... to /storage/emulated/0/DCIM/ + public static String fixPath(String path) { + if (path != null) { + while (path.startsWith("//")) { + path = path.substring(1); + } + } + return path; + } + + public static File getFirstExistingDir(File root) { + while ((root != null) && (!root.exists() || !root.isDirectory())) { + root = root.getParentFile(); + } + return root; + } + + public static File getFirstNonExistingFile(File parentDir, String newFilePrefix, int number, String newFileSuffix) { + if (parentDir == null) return null; + + parentDir.mkdirs(); + File candidate = new File(parentDir, newFilePrefix + newFileSuffix); + while (candidate.exists()) { + number ++; + candidate = new File(parentDir, newFilePrefix + number + newFileSuffix); + } + return candidate; + } } diff --git a/fotolib2/src/main/java/de/k3b/io/GalleryFilterParameter.java b/fotolib2/src/main/java/de/k3b/io/GalleryFilterParameter.java index 1203f497..22104b72 100644 --- a/fotolib2/src/main/java/de/k3b/io/GalleryFilterParameter.java +++ b/fotolib2/src/main/java/de/k3b/io/GalleryFilterParameter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2017 by k3b. + * Copyright (c) 2015-2018 by k3b. * * This file is part of AndroFotoFinder / #APhotoManager. * @@ -19,12 +19,14 @@ package de.k3b.io; +import java.io.File; import java.sql.Date; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import de.k3b.FotoLibGlobal; import de.k3b.io.collections.SelectedItems; /** @@ -39,12 +41,13 @@ public class GalleryFilterParameter extends GeoRectangle implements IGalleryFilt private List tagsAllIncluded; private List tagsAllExcluded; + + /** match if the text is in path, filename, title, description, tags. Wildcard "%" is allowed. Sub-expressions are seperated by " " */ private String inAnyField; private long dateMin = 0; private long dateMax = 0; - private boolean nonGeoOnly = false; private boolean withNoTags = false; /** one of the VISIBILITY_.XXXX values */ @@ -60,7 +63,6 @@ public GalleryFilterParameter get(IGalleryFilter src) { this.setDateMax(src.getDateMax()); this.setDateMin(src.getDateMin()); this.setPath(src.getPath()); - this.setNonGeoOnly(src.isNonGeoOnly()); this.setWithNoTags(src.isWithNoTags()); this.setVisibility(src.getVisibility()); @@ -83,6 +85,11 @@ public GalleryFilterParameter setPath(String path) { this.path = path;return this; } + public File getPathFile() { + if (StringUtils.isNullOrEmpty(getPath())) return null; + return new File(getPath()); + } + @Override public long getDateMin() { return dateMin; @@ -109,16 +116,6 @@ public GalleryFilterParameter setDate(long min, long max) { return setDateMin(min).setDateMax(max); } - @Override - public boolean isNonGeoOnly() { - return nonGeoOnly; - } - - public GalleryFilterParameter setNonGeoOnly(boolean nonGeoOnly) { - this.nonGeoOnly = nonGeoOnly; - return this; - } - @Override public boolean isWithNoTags() { return withNoTags; @@ -152,12 +149,13 @@ public GalleryFilterParameter setTagsAllExcluded(List tagsAllExcluded) { return this; } - /** match if the text is in path, filename, title, description, tags */ + /** match if the text is in path, filename, title, description, tags. Wildcard "%" is allowed. Sub-expressions are seperated by " " */ @Override public String getInAnyField() { return inAnyField; } + /** match if the text is in path, filename, title, description, tags. Wildcard "%" is allowed. Sub-expressions are seperated by " " */ public GalleryFilterParameter setInAnyField(String inAnyField) { this.inAnyField = inAnyField; return this; @@ -389,12 +387,18 @@ public static String convertList(List strings) { return SelectedItems.toString(strings.iterator()); } - public void setRatingMin(int ratingMin) { + public GalleryFilterParameter setRatingMin(int ratingMin) { this.ratingMin = ratingMin; + return this; } @Override public int getRatingMin() { return ratingMin; } + + /** get Date Min/Max in date picker compatible format */ + public String getDatePath() { + return DirectoryFormatter.getDatePath(FotoLibGlobal.datePickerUseDecade, getDateMin(), getDateMax()); + } } diff --git a/fotolib2/src/main/java/de/k3b/io/GeoRectangle.java b/fotolib2/src/main/java/de/k3b/io/GeoRectangle.java index b0204c93..5bc6b062 100644 --- a/fotolib2/src/main/java/de/k3b/io/GeoRectangle.java +++ b/fotolib2/src/main/java/de/k3b/io/GeoRectangle.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2017 by k3b. + * Copyright (c) 2015-2018 by k3b. * * This file is part of AndroFotoFinder. * @@ -33,6 +33,8 @@ public class GeoRectangle implements IGeoRectangle { private double logituedMin = Double.NaN; private double logituedMax = Double.NaN; + private boolean nonGeoOnly = false; + protected static double parseLatLon(String value) { if ((value == null) || value.isEmpty()) return Double.NaN; try { @@ -48,6 +50,7 @@ public GeoRectangle get(IGeoRectangle src) { this.setLatitudeMin(src.getLatitudeMin()); this.setLatitudeMax(src.getLatitudeMax()); this.setLogituedMax(src.getLogituedMax()); + this.setNonGeoOnly(src.isNonGeoOnly()); } return this; } @@ -198,4 +201,22 @@ private static String format(double d) { return Double.toString(d); } + @Override + public boolean isNonGeoOnly() { + return nonGeoOnly; + } + + public GeoRectangle setNonGeoOnly(boolean nonGeoOnly) { + this.nonGeoOnly = nonGeoOnly; + return this; + } + + public void setHasGeo() { + if (isNonGeoOnly() || isEmpty((IGeoRectangle) this)) { + setNonGeoOnly(false); + setLogitude(-180.0, +180); + setLatitude(-90.0, +90.0); + } + } + } diff --git a/fotolib2/src/main/java/de/k3b/io/IDirectory.java b/fotolib2/src/main/java/de/k3b/io/IDirectory.java index 8d5438c4..63c497a6 100644 --- a/fotolib2/src/main/java/de/k3b/io/IDirectory.java +++ b/fotolib2/src/main/java/de/k3b/io/IDirectory.java @@ -19,6 +19,7 @@ package de.k3b.io; +import java.io.File; import java.util.List; /** @@ -32,11 +33,14 @@ public interface IDirectory { public static final int DIR_FLAG_NOMEDIA_ROOT = 2; // containing ".nomedia" public static final int DIR_FLAG_APM_DIR = 8; // containing ".apm" + public static final int DIR_FLAG_VIRTUAL_DIR = 9; // containing "*.album" String APM_DIR_PREFIX = "§ "; String getRelPath(); String getAbsolute(); + IDirectory createOsDirectory(File file, IDirectory parent, List children); + IDirectory getParent(); List getChildren(); diff --git a/fotolib2/src/main/java/de/k3b/io/IGalleryFilter.java b/fotolib2/src/main/java/de/k3b/io/IGalleryFilter.java index 842c0c23..6f27d8fb 100644 --- a/fotolib2/src/main/java/de/k3b/io/IGalleryFilter.java +++ b/fotolib2/src/main/java/de/k3b/io/IGalleryFilter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2017 by k3b. + * Copyright (c) 2015-2018 by k3b. * * This file is part of AndroFotoFinder / #APhotoManager. * @@ -38,9 +38,6 @@ public interface IGalleryFilter extends IGeoRectangle { long getDateMax(); - /** true: only photos whith no geo info (lat==lon==null) */ - boolean isNonGeoOnly(); - /** number defining current sorting */ int getSortID(); @@ -57,7 +54,7 @@ public interface IGalleryFilter extends IGeoRectangle { /** None of the Tags/Keywords/Categories/VirtualAlbum that the image must NOT contain. ("AND NOT") */ List getTagsAllExcluded(); - /** match if the text is in path, filename, title, description, tags */ + /** match if the text is in path, filename, title, description, tags. Wildcard "%" is allowed. Sub-expressions are seperated by " " */ String getInAnyField(); /** one of the VISIBILITY_XXXX values for public/private images */ diff --git a/fotolib2/src/main/java/de/k3b/io/IGeoRectangle.java b/fotolib2/src/main/java/de/k3b/io/IGeoRectangle.java index 5e054045..752fdad3 100644 --- a/fotolib2/src/main/java/de/k3b/io/IGeoRectangle.java +++ b/fotolib2/src/main/java/de/k3b/io/IGeoRectangle.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 by k3b. + * Copyright (c) 2015-2018 by k3b. * * This file is part of AndroFotoFinder. * @@ -35,5 +35,8 @@ public interface IGeoRectangle { /** maximum longitude, in degrees east. -180..+180 */ double getLogituedMax(); + /** true: only photos whith no geo info (lat==lon==null) */ + boolean isNonGeoOnly(); + IGeoRectangle get(IGeoRectangle src); } diff --git a/fotolib2/src/main/java/de/k3b/io/ListUtils.java b/fotolib2/src/main/java/de/k3b/io/ListUtils.java index af4b8b96..b7fb4009 100644 --- a/fotolib2/src/main/java/de/k3b/io/ListUtils.java +++ b/fotolib2/src/main/java/de/k3b/io/ListUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017 by k3b. + * Copyright (c) 2017-2018 by k3b. * * This file is part of AndroFotoFinder / #APhotoManager. * @@ -52,8 +52,11 @@ public static List toStringList(Iterable list) { public static List toStringList(Object... list) { ArrayList result = new ArrayList(); - for (Object item : list) { - if (item != null) result.add(item.toString()); + + if (list != null) { + for (Object item : list) { + if (item != null) result.add(item.toString()); + } } return result; } diff --git a/fotolib2/src/main/java/de/k3b/io/OSDirOrVirtualAlbumFile.java b/fotolib2/src/main/java/de/k3b/io/OSDirOrVirtualAlbumFile.java new file mode 100644 index 00000000..fe091b43 --- /dev/null +++ b/fotolib2/src/main/java/de/k3b/io/OSDirOrVirtualAlbumFile.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2018 by k3b. + * + * This file is part of AndroFotoFinder / #APhotoManager + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see + */ + +package de.k3b.io; + +import java.io.File; +import java.util.List; + +/** + * Also add *.album files to sub-directories. + * + * Created by k3b on 17.04.2018. + */ + +public class OSDirOrVirtualAlbumFile extends OSDirectory { + public OSDirOrVirtualAlbumFile(File current, OSDirectory parent, List childen) { + super(current, parent, childen); + if (isAlbum(current)) { + setDirFlags(DIR_FLAG_VIRTUAL_DIR); + } + } + + private boolean isAlbum(File candidate) { + return (candidate != null) && AlbumFile.isQueryFile(candidate.getName()); + } + + @Override + protected int getCalculateFlags(File directory) { + int result; + if (isAlbum(directory)) { + result = DIR_FLAG_VIRTUAL_DIR; + } else { + result = super.getCalculateFlags(directory); + } + return result; + } + + @Override + protected boolean isDirectory(File candidate) { + if (super.isDirectory(candidate)) return true; + return isAlbum(candidate); + } + + /** factory method to be overwrittern by derived classes, if tree should consist of derived classes. */ + @Override + public OSDirectory createOsDirectory(File file, IDirectory parent, List children) { + return new OSDirOrVirtualAlbumFile(file, (OSDirectory) parent, children); + } +} \ No newline at end of file diff --git a/fotolib2/src/main/java/de/k3b/io/OSDirectory.java b/fotolib2/src/main/java/de/k3b/io/OSDirectory.java index 8fe8fa19..5c19ed3f 100644 --- a/fotolib2/src/main/java/de/k3b/io/OSDirectory.java +++ b/fotolib2/src/main/java/de/k3b/io/OSDirectory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2017 by k3b. + * Copyright (c) 2015-2018 by k3b. * * This file is part of AndroFotoFinder / #APhotoManager * @@ -43,16 +43,8 @@ public class OSDirectory implements IDirectory { private int mDirFlags = DIR_FLAG_NONE; - public OSDirectory(String current, OSDirectory parent) { - this(FileUtils.tryGetCanonicalFile(current), parent); - } - - protected OSDirectory(File current, OSDirectory parent) { - this(current, parent, null); - } - // protected constructor to allow unittesting with fake children - protected OSDirectory(File current, OSDirectory parent, List childen) { + public OSDirectory(File current, OSDirectory parent, List childen) { setCurrent(current); mParent = parent; mChilden = childen; @@ -64,6 +56,11 @@ protected OSDirectory(File current, OSDirectory parent, List childen } } + /** factory method to be overwrittern by derived classes, if tree should consist of derived classes. */ + public OSDirectory createOsDirectory(File file, IDirectory parent, List children) { + return new OSDirectory(file, (OSDirectory) parent, children); + } + public OSDirectory setCurrent(File current) { destroy(); mCurrent = current; @@ -71,7 +68,7 @@ public OSDirectory setCurrent(File current) { return this; } - private int getCalculateFlags(File directory) { + protected int getCalculateFlags(File directory) { int result = 0; if ((directory != null) && (directory.isDirectory())) { if (new File(directory, FileUtils.MEDIA_IGNORE_FILENAME).exists()) { @@ -133,12 +130,14 @@ public List getChildren() { if (files != null) { for (File file : files) { if ((file != null) - && file.isDirectory() && !file.isHidden() && !file.getName().startsWith(".") + && !FileUtils.isSymlinkDir(file,true) // && file.canWrite() // bugfix: must be visible because writeprotected parentdir may contain writeenabled subdirs - && !FileUtils.isSymlinkDir(file,true)) { - mChilden.add(new OSDirectory(file, this)); + ) { + if (isDirectory(file)) { + mChilden.add(createOsDirectory(file, this, null)); + } // } else if (FotoLibGlobal.debugEnabled) { // logger.debug(FileUtils.getDebugString("OSDirectory.getChildren() rejected ", file)); } @@ -148,6 +147,10 @@ public List getChildren() { return mChilden; } + protected boolean isDirectory(File file) { + return file.isDirectory(); + } + // package to allow unit testing protected IDirectory find(OSDirectory root, String path) { return find(root, FileUtils.tryGetCanonicalFile(path)); @@ -168,7 +171,7 @@ protected static IDirectory find(OSDirectory root, File file) { OSDirectory result = (OSDirectory) findChildByRelPath(children, name); if (result == null) { - result = new OSDirectory(file, (OSDirectory) parentDir); + result = root.createOsDirectory(file, (OSDirectory) parentDir, null); children.add(result); } return result; @@ -270,7 +273,7 @@ private OSDirectory addChildFolder(String newCildFolderName, List gr if (result == null) { File newChildFile = FileUtils.tryGetCanonicalFile(new File(mCurrent, newCildFolderName), null); - result = new OSDirectory(newChildFile, this, grandChilden); + result = createOsDirectory(newChildFile, this, grandChilden); if (result != null) { children.add(result); } diff --git a/fotolib2/src/main/java/de/k3b/io/StringUtils.java b/fotolib2/src/main/java/de/k3b/io/StringUtils.java index 371722bb..1bff81aa 100644 --- a/fotolib2/src/main/java/de/k3b/io/StringUtils.java +++ b/fotolib2/src/main/java/de/k3b/io/StringUtils.java @@ -35,6 +35,7 @@ public static boolean equals(Object lhs, Object rhs) { return (rhs == null); } + /** return pos of '#' before {start} or {-1} if not found */ public static int getTagStart(CharSequence s, int start) { if (start <= s.length()) { int i = start -1; @@ -44,6 +45,8 @@ public static int getTagStart(CharSequence s, int start) { } return -1; } + + /** return first char after tagword or {-1} if not a tag-word-end */ public static int getTagEnd(CharSequence s, int start) { int len = s.length(); int i = start; @@ -55,10 +58,12 @@ public static int getTagEnd(CharSequence s, int start) { private static boolean isTagChar(char c) { return Character.isJavaIdentifierPart(c) || (c == '-'); } + private static boolean isTagDelimiterChar(char c) { return Character.isSpaceChar(c) || (",;(){}".indexOf(c) >= 0); } - /** words surrounded by blank and starting with '#' */ + + /** a tag is a word surrounded by blank and starting with '#' */ public static CharSequence getTag(CharSequence s, int start) { int tagStart = getTagStart(s,start); int tagEnd = getTagEnd(s,start); @@ -68,10 +73,29 @@ public static CharSequence getTag(CharSequence s, int start) { return null; } + public static String trim(CharSequence str) { + if (str == null) return null; + return str.toString().trim(); + + } public static int length(CharSequence str) { return (str != null) ? str.length() : 0; } + public static int charCount(CharSequence str, char c) { + int result = 0; + if (str != null) { + int len = length(str); + for (int i=0; i < len;i++) { + if (str.charAt(i) == c) { + result++; + } + } + } + + return result; + } + public static boolean isNullOrEmpty(CharSequence str) { return (0 == length(str)); } @@ -89,4 +113,43 @@ public static String merge(String lhs, String rhs) { return result.toString(); } + + public static StringBuilder createDebugMessage(boolean enabled, final Object... parameters) { + if (enabled) return appendMessage(null, parameters); + return null; + } + + /** + * append 0..n parameters to stringbuilder + * + * @param resultOrNull where the data is appended to. if null a StringBuilder is created + * @param parameters all non null param-values will be appended seperated by " " + * @return either result or newly created StringBuilder if result was null + */ + public static StringBuilder appendMessage(StringBuilder resultOrNull, final Object... parameters) { + StringBuilder result = (resultOrNull == null) ? new StringBuilder() : resultOrNull; + + append(result, parameters); + return result; + } + + private static void append(StringBuilder result, Object[] parameters) { + if ((result != null) && (parameters != null) && (parameters.length > 0)) { + result.append("("); + for (final Object param : parameters) { + if (param != null) { + if (param instanceof Object[]) { + append(result, (Object[]) param); + } else if (param instanceof Exception) { + Exception ex = (Exception) param; + result.append(ex.getClass().getSimpleName()).append("=").append(ex.getMessage()).append(" "); + } else { + result.append(param.toString()).append(" "); + } + } + } + result.append(") "); + } + } + } diff --git a/fotolib2/src/main/java/de/k3b/io/VISIBILITY.java b/fotolib2/src/main/java/de/k3b/io/VISIBILITY.java index a9674666..39e57db2 100644 --- a/fotolib2/src/main/java/de/k3b/io/VISIBILITY.java +++ b/fotolib2/src/main/java/de/k3b/io/VISIBILITY.java @@ -28,9 +28,13 @@ import de.k3b.FotoLibGlobal; public enum VISIBILITY { + /** take from current settings */ DEFAULT(0), + /** private only */ PRIVATE(1), + /** public only */ PUBLIC(2), + /** private and public images but not other files like album-files */ PRIVATE_PUBLIC(3); // #100: if photo has this tag it has visibility PRIVATE diff --git a/fotolib2/src/main/java/de/k3b/media/MetaWriterExifXml.java b/fotolib2/src/main/java/de/k3b/media/MetaWriterExifXml.java index 423bf487..8f1fa239 100644 --- a/fotolib2/src/main/java/de/k3b/media/MetaWriterExifXml.java +++ b/fotolib2/src/main/java/de/k3b/media/MetaWriterExifXml.java @@ -99,17 +99,32 @@ public static MetaWriterExifXml create(String absoluteJpgInPath, String absolute startTimestamp = new Date().getTime(); } MediaXmpSegment xmp = MediaXmpSegment.loadXmpSidecarContentOrNull(absoluteJpgInPath, dbg_context); - if ((createXmpIfNotExist) && (xmp == null)) { + if ((xmp == null) && (createXmpIfNotExist || MediaUtil.isImage(absoluteJpgInPath,MediaUtil.IMG_TYPE_NON_JPG))) { ImageMetaReader jpg = new ImageMetaReader().load(absoluteJpgInPath,null,null, dbg_context + " xmp-file not found. create/extract from jpg "); - xmp = jpg.getImternalXmp(); + // #124: fix can be null for gif/png + xmp = (jpg == null) ? null : jpg.getImternalXmp(); // jpg has no embedded xmp create - if (xmp == null) xmp = new MediaXmpSegment(); + if (xmp == null) { + xmp = new MediaXmpSegment(); + } // xmp should have the same data as exif/iptc MediaUtil.copyNonEmpty(xmp, jpg); + if ((absoluteJpgInPath != null) && (xmp.getDateTimeTaken() == null)) { + File in = new File(absoluteJpgInPath); + if (in.exists() && in.isFile()) { + long lastModified = in.lastModified(); + if (lastModified != 0) { + final Date newDate = new Date(lastModified); + xmp.setDateTimeTaken(newDate); + if (xmp.getFilelastModified() == 0) xmp.setFilelastModified(in); + } + } + + } } ExifInterfaceEx exif = new ExifInterfaceEx(absoluteJpgInPath, null, xmp, dbg_context); if (exif.isValidJpgExifFormat()) { diff --git a/fotolib2/src/main/java/de/k3b/tagDB/TagProcessor.java b/fotolib2/src/main/java/de/k3b/tagDB/TagProcessor.java index eb0118e3..6d672c37 100644 --- a/fotolib2/src/main/java/de/k3b/tagDB/TagProcessor.java +++ b/fotolib2/src/main/java/de/k3b/tagDB/TagProcessor.java @@ -110,7 +110,7 @@ public static int getDiff(List _original, List _changed, List _original, List _changed, List { + // +2 lowercase letters sorunded by non-word-char or begine-of-line/end-of-line + Pattern languagePattern = Pattern.compile("[\\W^]([a-z][a-z])[$\\W]") ; + + public int add(String timmed) { + int found = 0; + Matcher match = languagePattern.matcher(timmed); + + if (match.find()) { + String lan = match.group(1); + if ("by".compareTo(lan) != 0) { + StringBuilder buff = this.get(lan); + if (buff != null) { + buff.append(nl).append(timmed); + } else { + buff = new StringBuilder(); + buff.append(timmed); + this.put(lan,buff); + } + } + found++; + } + + return found; + } + + @Override public String toString() { + String[] keys = this.keySet().toArray(new String[this.size()]); + Arrays.sort(keys); + StringBuilder result = new StringBuilder(); + + for(String lan : keys) { + result.append(lan).append(nl).append(this.get(lan)).append(nl).append(nl); + } + + return result.toString(); + } + } + private static String nl = "\n"; + private static int dateExampleLen = "2018-07-28".length(); + public int readCountryTimeStatistics(Country2History country2History, File file) throws IOException { + int found = 0; + BufferedReader br = null; + String firstDate = "#"; + + try { + br = new BufferedReader(new FileReader(file)); + for (String line; (line = br.readLine()) != null; ) { + String timmed = line.trim(); + int length = timmed.length(); + if (length == dateExampleLen) { + firstDate = timmed; + } else if ((length > dateExampleLen) && ( timmed.compareTo(firstDate) > 0)) { + found += country2History.add(timmed); + } + } + return found; + } finally { + FileUtils.close(br, file); + } + } + protected String getCommentProperty(String name) { Object val = (lastLocales == null) ? null : lastLocales.get(name); String[] parts = (val != null) ? val.toString().split(";") : null; diff --git a/fotolib2/src/test/java/de/k3b/translations/TranslationStatisticsTests.java b/fotolib2/src/test/java/de/k3b/translations/TranslationStatisticsTests.java index f5ca8781..2d9dfd21 100644 --- a/fotolib2/src/test/java/de/k3b/translations/TranslationStatisticsTests.java +++ b/fotolib2/src/test/java/de/k3b/translations/TranslationStatisticsTests.java @@ -22,9 +22,12 @@ import org.junit.Test; import java.io.File; +import java.util.Date; + +import de.k3b.io.DateUtil; /** - * Created by EVE on 15.01.2018. + * Created by k3b on 15.01.2018. */ public class TranslationStatisticsTests { @@ -41,15 +44,20 @@ public void shouldMatchString_de() { @Test public void dumpAsMD() { final TranslationStatistics translationStatistics = new TranslationStatistics(); - System.out.println("\n" + + System.out.println("\n" + translationStatistics.formatterMarkdown.toString(translationStatistics.getLocaleInfos(), translationStatistics.english)); } @Test public void dupmpAsIni() { final TranslationStatistics translationStatistics = new TranslationStatistics(); - System.out.println("# generated with TranslationStatisticsTests#dupmpAsIni\n" + + System.out.println("# generated on " + DateUtil.toIsoDateString(new Date()) + + " with de.k3b.translations.TranslationStatisticsTests#dupmpAsIni\n" + translationStatistics.formatterIni.toString(translationStatistics.getLocaleInfos(), translationStatistics.english)); + + System.out.println("\n\n\n" + + translationStatistics.country2History.toString()); } diff --git a/translation-git-entries.lis b/translation-git-entries.lis new file mode 100644 index 00000000..6c21c3d0 --- /dev/null +++ b/translation-git-entries.lis @@ -0,0 +1,53 @@ +# translation-git-entries.lis +# textfile containing git history for ticket #21 (language update) +# generated by +# git log "--grep=#21" "--pretty=format:%ci %s" +# sort by date desc + +2011-08-21 + +2018-08-20 08:21:36 +0200 #21: ar by Vitality +2018-08-20 08:18:22 +0200 #21: zh-CN) by Forbidden (cptbl00dra1n) +2018-08-02 11:18:20 +0200 #21: es by Andreaevangelina +2018-08-02 11:16:24 +0200 #21: it .Rogue. +2018-08-02 11:13:34 +0200 #21: fr Poussinou +2018-07-28 10:50:52 +0200 #21: fr by Tuuux and Poussinou +2018-07-28 10:48:26 +0200 #21: ru by Dmitry Zhgun (trupizzza) +2018-03-12 11:29:35 +0100 #21: tr Boyut, pt-BR Lucas Magalhães (whoisroot), ja naofu, fr Tuuux, fr Poussinou, de k3b +2018-02-08 15:36:44 +0100 #21: ja by naofum +2018-01-22 22:41:11 +0100 #21: ru by ku ku(2ku) +2018-01-15 08:39:31 +0100 #21: es (100%) by Dani Certad (daniconejito) +2018-01-09 22:49:15 +0100 #21: es, fr, ja, zh-CN +2017-11-22 00:52:26 +0100 #21: zh by Liu Feng (pitumaomao) +2017-11-22 00:48:42 +0100 #21: zh by Liu Feng (pitumaomao) +2017-11-19 15:44:15 +0100 #21: in by "isaideureka" +2017-11-16 20:38:56 +0100 #21: ja (by naofum" ) +2017-11-13 21:29:25 +0100 #21: zh by rosatravels . +2017-09-11 22:52:13 +0200 #21: nl by ookikgavertalen +2017-07-27 16:02:16 +0200 #21: : zh (zh) by Liu Feng (pitumaomao) +2017-07-22 09:20:33 +0200 #21: ja / tr +2017-05-15 08:09:58 +0200 #21 #86: tr srkns stringlate +2017-04-01 16:10:21 +0200 #21: for 0.6.0 : ar by medowill +2017-03-27 08:15:03 +0200 #21: : it by random_r, fr by Tuuux +2017-03-18 14:32:03 +0100 #21: for 0.6.0 : tr by erdenerr +2017-03-18 13:34:14 +0100 #21: for 0.6.0 : pt-BR by Nana13 +2017-03-06 08:44:38 +0100 #21: for 0.5.5 : ja by "naofum" +2017-02-27 23:40:11 +0100 #21: translations: ru by "divizdev" and special translators with ids +2017-01-19 20:00:17 +0100 #21: more fr by tuuux +2016-12-08 14:42:05 +0100 #21: for 0.5.5 : it by "random_r", ja by "naofum" +2016-12-05 19:42:28 +0100 #21: more fr by tuuux +2016-11-28 23:06:25 +0100 #21: more fr by tuuux +2016-09-23 10:25:09 +0200 #21: more : it by "random_r", ja by "naofum",nl by keunes +2016-09-10 18:53:39 +0200 #21: more nl by keunes +2016-08-09 18:20:34 +0200 #21 added pl "Maselkowicz". +2016-07-12 20:16:47 +0200 #21: merged app from crowdwin. it by "random_r"; ja by naofum +2016-06-20 15:03:09 +0200 #21 it "random_r" +2016-05-18 18:58:06 +0200 #21 d ja and pt-BR +2016-05-17 16:22:35 +0200 #21 added nl keunes +2016-05-09 10:00:14 +0200 #21 added it "random_r" +2016-03-04 12:11:17 +0100 #21: merged app with crowdwin. Added es +2016-01-20 17:26:49 +0100 #21 added ro language by mironeasav +2016-01-11 16:14:22 +0100 #21: fr by tuuux +2016-01-08 10:46:11 +0100 #21: fr by tuuux +2016-01-07 17:23:00 +0100 #21 ja by naofum + diff --git a/translation-history.ini b/translation-history.ini index 16e88bd6..d7c8da07 100644 --- a/translation-history.ini +++ b/translation-history.ini @@ -6,22 +6,26 @@ # format: locale=lastUpdate;comments;strings.xml;about.xml;fdrod;missing-translations # # ignore filedates before this. -ignore=2018-02-12 +ignore=2099-08-20 + +# generated on 2018-08-21 with de.k3b.translations.TranslationStatisticsTests#dupmpAsIni +# language;changed;translated by;app;fdroid;aboutbox;missing +ar=2018-08-20;Vitality, medowill;100%;100%;1; +de=2018-04-08;k3b;100%;100%;1; +en=2018-08-13;;100%;100%;1; +es=2018-08-02;Andreaevangelina, Dani Certad (daniconejito);100%;100%;1; +fr=2018-08-02;Poussinou, tuuux;100%;100%;1; +hi=2018-03-12;jznsamuel (jasonsamuel88);3% (6/170);0% (0/3);0;about_summary, area_menu_title, background, bookmark, bookmark_delete_answer_format, bookmark_delete_error_format, bookmark_delete_question, bookmark_file_comment_format, bookmark_save_as_menu_title, btn_change, btn_pause, clear_menu_title, copy_result_format, date_picker_menu_title, delete_menu_title, delete_question_message_format, delete_question_title, delete_result_format, destination_copy, destination_move, details_menu_title, edit_chooser_title, edit_err_editor_not_found, edit_menu_title, exif_menu_title, file_err_writeprotected, filter_any_hint, filter_err_invalid_date_format, filter_err_invalid_location_format, filter_menu_title, filter_menu_title_album, filter_path_hint, fix_link_menu_title, folder_dialog_title_format, folder_err_load_failed_format, folder_hide_images_menu_title, folder_hide_images_question_message_format, folder_menu_title, gallery_title, geo_picker_err_not_found, geo_picker_title, geo_show_as_menu_title, global_err_sql_message_format, global_err_sql_title_reload, global_err_system, image_err_file_exists_format, image_err_file_rename_format, image_err_not_found_format, image_err_not_in_db_format, image_loading_at_position_format, image_success_update_format, lbl_any, lbl_date, lbl_description, lbl_exif, lbl_file_name_pattern, lbl_image_visibility, lbl_images_private, lbl_images_public, lbl_latitude_short, lbl_longitude_short, lbl_path, lbl_rating, lbl_tag, lbl_tags_exclude, lbl_tags_include, lbl_title, lbl_with_no_tags, menu_title_app_pinnend, menu_title_app_unpinnend, mk_dir_default, mk_dir_menu_title, mk_err_failed_format, mk_success_format, more_menu_title, move_menu_title, move_result_format, osm_cright_title, overwrite_question_title, photo_autoprocessing_edit_menu_title, preview_message_format, rename_menu_title, rename_result_format, scanner_dir_question, scanner_err_busy, scanner_menu_title, scanner_update_result_format, searchbar_menu_title, selected_only_menu_title, selection_add_all_menu_title, selection_none_hint, selection_remove_menu_title, selection_status_format, settings_bookmark_dir_title, settings_debug_clear_title, settings_debug_libs_summary, settings_debug_memory_title, settings_debug_meta_io, settings_debug_save_title, settings_debug_sql_summary, settings_debug_summary, settings_debug_title, settings_debug_view_item_summary, settings_geo_history_file_title, settings_geo_history_max_title, settings_group_debug_title, settings_image_hide_time_title, settings_image_initialImageDetailResolutionHigh_summary, settings_image_initialImageDetailResolutionHigh_title, settings_image_slideshow_intervall_title, settings_image_thumb_dir_title, settings_image_thumb_if_bigger_than_summary, settings_image_thumb_if_bigger_than_title, settings_locale_os_language, settings_locale_title, settings_log_folder_title, settings_map_selmarker_max_title, settings_maps_forge_dir_title, settings_maps_forge_enable_summary, settings_maps_forge_enable_tile, settings_media_update_strategy_jpg, settings_media_update_strategy_jpg_xmp_create, settings_media_update_strategy_jpg_xmp_update, settings_media_update_strategy_title, settings_media_update_strategy_xmp, settings_multisel_clear_summary, settings_multisel_clear_title, settings_pick_date_decade4year_summary, settings_pick_date_decade4year_title, settings_rename_private_jpg_title, settings_title, settings_xmp_file_schema_summary, settings_xmp_file_schema_title, share_err_not_found, share_err_to_many, share_menu_title, show_in_filemanager_menu_title, show_in_gallery_menu_title, show_photo, slideshow_menu_title, sort_by_date, sort_by_file_size, sort_by_folder, sort_by_modification, sort_by_name, sort_by_name_len, sort_by_none, sort_by_place, sort_by_rating, sort_by_width, sort_menu_title, tags_activity_title, tags_add_default, tags_add_menu_title, tags_defaults, tags_delete_children_title, tags_edit_menu_title, tags_hint, tags_search_hint, tags_search_no_matching_items_found, tags_update_photos, update_result_format, view_context_menu_title, zoom_to_fit_menu_title +in=2017-11-19;isaideureka;92% (157/170);100%;1;bookmark, clear_menu_title, date_picker_menu_title, filter_menu_title_album, lbl_image_visibility, menu_title_app_pinnend, menu_title_app_unpinnend, searchbar_menu_title, settings_pick_date_decade4year_summary, settings_pick_date_decade4year_title, settings_rename_private_jpg_title, show_in_filemanager_menu_title, sort_by_file_size, sort_by_width +it=2018-08-02;.Rogue.,random_r;100%;0% (0/3);1; +ja=2018-03-12;naofum;97% (165/170);100%;1;bookmark, date_picker_menu_title, filter_menu_title_album, searchbar_menu_title, settings_pick_date_decade4year_summary, settings_pick_date_decade4year_title +nl=2017-09-11;ookikgavertalen, keunes;90% (153/170);0% (0/3);0;bookmark, clear_menu_title, date_picker_menu_title, filter_menu_title_album, lbl_exif, lbl_file_name_pattern, lbl_image_visibility, menu_title_app_pinnend, menu_title_app_unpinnend, photo_autoprocessing_edit_menu_title, preview_message_format, searchbar_menu_title, settings_pick_date_decade4year_summary, settings_pick_date_decade4year_title, settings_rename_private_jpg_title, show_in_filemanager_menu_title, sort_by_file_size, sort_by_width +pl=2018-03-12;Maselkowicz;64% (109/170);0% (0/3);0;bookmark, clear_menu_title, date_picker_menu_title, exif_menu_title, filter_any_hint, filter_menu_title_album, filter_path_hint, fix_link_menu_title, folder_hide_images_menu_title, folder_hide_images_question_message_format, geo_picker_from_map_title, geo_picker_from_photo_title, lbl_any, lbl_description, lbl_exif, lbl_file_name_pattern, lbl_image_visibility, lbl_images_private, lbl_images_public, lbl_rating, lbl_tag, lbl_tags_exclude, lbl_tags_include, lbl_title, lbl_with_no_tags, menu_title_app_pinnend, menu_title_app_unpinnend, photo_autoprocessing_edit_menu_title, preview_message_format, searchbar_menu_title, settings_debug_libs_summary, settings_debug_meta_io, settings_maps_forge_dir_title, settings_maps_forge_enable_summary, settings_maps_forge_enable_tile, settings_media_update_strategy_jpg, settings_media_update_strategy_jpg_xmp_create, settings_media_update_strategy_jpg_xmp_update, settings_media_update_strategy_title, settings_media_update_strategy_xmp, settings_pick_date_decade4year_summary, settings_pick_date_decade4year_title, settings_rename_private_jpg_title, settings_xmp_file_schema_summary, settings_xmp_file_schema_title, show_in_filemanager_menu_title, show_photo, sort_by_file_size, sort_by_modification, sort_by_rating, sort_by_width, tags_activity_title, tags_add_default, tags_add_menu_title, tags_defaults, tags_delete_children_title, tags_edit_menu_title, tags_hint, tags_search_hint, tags_search_no_matching_items_found, tags_update_photos, view_context_menu_title +pt-rBR=2017-03-18;thiagonkami, Nana13;22% (38/170);0% (0/3);0;bookmark, clear_menu_title, date_picker_menu_title, exif_menu_title, filter_any_hint, filter_menu_title_album, filter_path_hint, fix_link_menu_title, folder_hide_images_menu_title, folder_hide_images_question_message_format, geo_picker_from_map_title, geo_picker_from_photo_title, geo_picker_title, global_err_sql_message_format, global_err_sql_title_reload, image_err_file_exists_format, image_err_not_found_format, image_err_not_in_db_format, image_success_update_format, lbl_any, lbl_date, lbl_description, lbl_exif, lbl_file_name_pattern, lbl_image_visibility, lbl_images_private, lbl_images_public, lbl_latitude_short, lbl_longitude_short, lbl_path, lbl_rating, lbl_tag, lbl_tags_exclude, lbl_tags_include, lbl_title, lbl_with_no_geo, lbl_with_no_tags, menu_title_app_pinnend, menu_title_app_unpinnend, mk_dir_default, mk_dir_menu_title, mk_err_failed_format, mk_success_format, more_menu_title, move_menu_title, move_result_format, osm_cright_title, overwrite_question_title, photo_autoprocessing_edit_menu_title, preview_message_format, rename_menu_title, rename_result_format, scanner_dir_question, scanner_err_busy, scanner_menu_title, scanner_update_result_format, searchbar_menu_title, selected_only_menu_title, selection_add_all_menu_title, selection_none_hint, selection_remove_menu_title, selection_status_format, settings_bookmark_dir_title, settings_debug_clear_title, settings_debug_libs_summary, settings_debug_memory_title, settings_debug_meta_io, settings_debug_save_title, settings_debug_sql_summary, settings_debug_summary, settings_debug_title, settings_debug_view_item_summary, settings_geo_history_file_title, settings_geo_history_max_title, settings_group_debug_title, settings_image_hide_time_title, settings_image_initialImageDetailResolutionHigh_summary, settings_image_initialImageDetailResolutionHigh_title, settings_image_slideshow_intervall_title, settings_image_thumb_dir_title, settings_image_thumb_if_bigger_than_summary, settings_image_thumb_if_bigger_than_title, settings_locale_os_language, settings_locale_title, settings_log_folder_title, settings_map_selmarker_max_title, settings_maps_forge_dir_title, settings_maps_forge_enable_summary, settings_maps_forge_enable_tile, settings_media_update_strategy_jpg, settings_media_update_strategy_jpg_xmp_create, settings_media_update_strategy_jpg_xmp_update, settings_media_update_strategy_title, settings_media_update_strategy_xmp, settings_multisel_clear_summary, settings_multisel_clear_title, settings_pick_date_decade4year_summary, settings_pick_date_decade4year_title, settings_rename_private_jpg_title, settings_title, settings_xmp_file_schema_summary, settings_xmp_file_schema_title, share_err_not_found, share_err_to_many, share_menu_title, show_in_filemanager_menu_title, show_in_gallery_menu_title, show_photo, slideshow_menu_title, sort_by_date, sort_by_file_size, sort_by_folder, sort_by_modification, sort_by_name, sort_by_name_len, sort_by_none, sort_by_place, sort_by_rating, sort_by_width, sort_menu_title, tags_activity_title, tags_add_default, tags_add_menu_title, tags_defaults, tags_delete_children_title, tags_edit_menu_title, tags_hint, tags_search_hint, tags_search_no_matching_items_found, tags_update_photos, update_result_format, view_context_menu_title, zoom_to_fit_menu_title +ro=2016-01-20;mironeasav;30% (52/170);0% (0/3);1;bookmark, clear_menu_title, date_picker_menu_title, exif_menu_title, file_err_writeprotected, filter_any_hint, filter_menu_title_album, filter_path_hint, fix_link_menu_title, folder_hide_images_menu_title, folder_hide_images_question_message_format, geo_picker_from_map_title, geo_picker_from_photo_title, geo_show_as_menu_title, lbl_any, lbl_description, lbl_exif, lbl_file_name_pattern, lbl_image_visibility, lbl_images_private, lbl_images_public, lbl_latitude_short, lbl_longitude_short, lbl_path, lbl_rating, lbl_tag, lbl_tags_exclude, lbl_tags_include, lbl_title, lbl_with_no_geo, lbl_with_no_tags, menu_title_app_pinnend, menu_title_app_unpinnend, mk_dir_default, mk_dir_menu_title, mk_err_failed_format, mk_success_format, more_menu_title, move_menu_title, move_result_format, osm_cright_title, overwrite_question_title, photo_autoprocessing_edit_menu_title, preview_message_format, rename_menu_title, rename_result_format, scanner_dir_question, scanner_err_busy, scanner_menu_title, scanner_update_result_format, searchbar_menu_title, selected_only_menu_title, selection_add_all_menu_title, selection_none_hint, selection_remove_menu_title, selection_status_format, settings_bookmark_dir_title, settings_debug_clear_title, settings_debug_libs_summary, settings_debug_memory_title, settings_debug_meta_io, settings_debug_save_title, settings_debug_sql_summary, settings_debug_summary, settings_debug_title, settings_debug_view_item_summary, settings_geo_history_file_title, settings_geo_history_max_title, settings_group_debug_title, settings_image_hide_time_title, settings_image_initialImageDetailResolutionHigh_summary, settings_image_initialImageDetailResolutionHigh_title, settings_image_slideshow_intervall_title, settings_image_thumb_dir_title, settings_image_thumb_if_bigger_than_summary, settings_image_thumb_if_bigger_than_title, settings_locale_os_language, settings_locale_title, settings_log_folder_title, settings_map_selmarker_max_title, settings_maps_forge_dir_title, settings_maps_forge_enable_summary, settings_maps_forge_enable_tile, settings_media_update_strategy_jpg, settings_media_update_strategy_jpg_xmp_create, settings_media_update_strategy_jpg_xmp_update, settings_media_update_strategy_title, settings_media_update_strategy_xmp, settings_multisel_clear_summary, settings_multisel_clear_title, settings_pick_date_decade4year_summary, settings_pick_date_decade4year_title, settings_rename_private_jpg_title, settings_title, settings_xmp_file_schema_summary, settings_xmp_file_schema_title, share_err_not_found, share_err_to_many, share_menu_title, show_in_filemanager_menu_title, show_in_gallery_menu_title, show_photo, slideshow_menu_title, sort_by_date, sort_by_file_size, sort_by_modification, sort_by_rating, sort_by_width, tags_activity_title, tags_add_default, tags_add_menu_title, tags_defaults, tags_delete_children_title, tags_edit_menu_title, tags_hint, tags_search_hint, tags_search_no_matching_items_found, tags_update_photos, view_context_menu_title +ru=2018-07-28;Dmitry Zhgun (trupizzza), ku ku(2ku), "divizdev";34% (58/170);0% (0/3);0;bookmark, date_picker_menu_title, destination_copy, destination_move, exif_menu_title, file_err_writeprotected, filter_any_hint, filter_err_invalid_date_format, filter_err_invalid_location_format, filter_menu_title_album, filter_path_hint, fix_link_menu_title, folder_dialog_title_format, folder_err_load_failed_format, folder_hide_images_menu_title, folder_hide_images_question_message_format, folder_menu_title, geo_edit_menu_title, geo_edit_update_in_progress, geo_picker_err_not_found, geo_picker_from_map_title, geo_picker_from_photo_title, geo_picker_title, geo_show_as_menu_title, global_err_sql_title_reload, global_err_system, image_err_not_found_format, image_err_not_in_db_format, image_success_update_format, lbl_description, lbl_exif, lbl_file_name_pattern, lbl_images_private, lbl_images_public, lbl_path, lbl_tag, lbl_tags_exclude, lbl_tags_include, lbl_title, lbl_with_no_geo, menu_title_app_pinnend, menu_title_app_unpinnend, mk_err_failed_format, mk_success_format, more_menu_title, move_result_format, osm_cright_title, photo_autoprocessing_edit_menu_title, preview_message_format, rename_result_format, scanner_dir_question, scanner_err_busy, scanner_menu_title, scanner_update_result_format, searchbar_menu_title, selected_only_menu_title, selection_add_all_menu_title, selection_none_hint, selection_remove_menu_title, selection_status_format, settings_debug_libs_summary, settings_debug_meta_io, settings_debug_sql_summary, settings_debug_summary, settings_debug_title, settings_debug_view_item_summary, settings_geo_history_file_title, settings_geo_history_max_title, settings_image_hide_time_title, settings_image_initialImageDetailResolutionHigh_summary, settings_image_initialImageDetailResolutionHigh_title, settings_image_slideshow_intervall_title, settings_image_thumb_dir_title, settings_image_thumb_if_bigger_than_summary, settings_image_thumb_if_bigger_than_title, settings_map_selmarker_max_title, settings_maps_forge_dir_title, settings_media_update_strategy_jpg, settings_media_update_strategy_jpg_xmp_create, settings_media_update_strategy_jpg_xmp_update, settings_media_update_strategy_title, settings_media_update_strategy_xmp, settings_multisel_clear_summary, settings_multisel_clear_title, settings_pick_date_decade4year_summary, settings_pick_date_decade4year_title, settings_rename_private_jpg_title, settings_xmp_file_schema_summary, settings_xmp_file_schema_title, share_err_not_found, share_err_to_many, share_menu_title, show_in_filemanager_menu_title, show_in_gallery_menu_title, show_photo, sort_by_folder, sort_by_name, sort_by_name_len, sort_by_none, sort_by_place, sort_by_width, sort_menu_title, tags_activity_title, tags_defaults, tags_delete_children_title, tags_edit_menu_title, tags_hint, tags_search_hint, tags_search_no_matching_items_found, tags_update_photos, update_result_format, view_context_menu_title, zoom_to_fit_menu_title +tr=2018-03-12;Boyut, srkns, erdenerr;90% (154/170);0% (0/3);1;bookmark, clear_menu_title, date_picker_menu_title, filter_menu_title_album, lbl_exif, lbl_file_name_pattern, lbl_image_visibility, menu_title_app_pinnend, menu_title_app_unpinnend, photo_autoprocessing_edit_menu_title, preview_message_format, searchbar_menu_title, settings_pick_date_decade4year_summary, settings_pick_date_decade4year_title, settings_rename_private_jpg_title, show_in_filemanager_menu_title, sort_by_width +zh-rCN=2018-08-20;Forbidden (cptbl00dra1n), Liu Feng (pitumaomao);100%;100%;1; +zh-rTW=2017-11-22;Liu Feng (pitumaomao), rosatravels;92% (157/170);100%;1;bookmark, clear_menu_title, date_picker_menu_title, filter_menu_title_album, lbl_image_visibility, menu_title_app_pinnend, menu_title_app_unpinnend, searchbar_menu_title, settings_pick_date_decade4year_summary, settings_pick_date_decade4year_title, settings_rename_private_jpg_title, show_in_filemanager_menu_title, sort_by_file_size, sort_by_width +zz=2018-03-12;k3b;95% (163/170);0% (0/3);0;bookmark, clear_menu_title, date_picker_menu_title, filter_menu_title_album, searchbar_menu_title, settings_pick_date_decade4year_summary, settings_pick_date_decade4year_title, show_in_filemanager_menu_title -ar=2017-04-01;medowill -de=2018-01-31;k3b -es=2018-01-15;Dani Certad (daniconejito) -en=2018-01-31;k3b -fr=2018-01-31;Tuuux, Poussinou -hi=2018-01-09;jznsamuel (jasonsamuel88) -in=2017-11-19;isaideureka -it=2017-03-27;random_r -ja=2018-02-08;naofum -nl=2017-09-11;keunes, ookikgavertalen -pl=2016-08-09;Maselkowicz -pt-rBR=2017-03-18;thiagonkami, Nana13 -ro=2016-01-20;mironeasav -ru=2018-01-22;divizdev, ku ku(2ku) -tr=2017-07-22;erdenerr, srkns -zh-rCN=2018-01-09;Liu Feng (pitumaomao) -zh-rTW=2017-11-13;Liu Feng (pitumaomao), rosatravels diff --git a/wiki/table-of-content.md b/wiki/table-of-content.md deleted file mode 100644 index c2ccff06..00000000 --- a/wiki/table-of-content.md +++ /dev/null @@ -1,52 +0,0 @@ -[table-of-content](table-of-content) - - - -![](https://raw.githubusercontent.com/k3b/AndroFotoFinder/master/wiki/png/s_unchecked.png) -![](https://raw.githubusercontent.com/k3b/AndroFotoFinder/master/wiki/png/s_cancel.png) -![](https://raw.githubusercontent.com/k3b/AndroFotoFinder/master/wiki/png/s_checked.png) -![](https://raw.githubusercontent.com/k3b/AndroFotoFinder/master/wiki/png/s_filter.png) -![](https://raw.githubusercontent.com/k3b/AndroFotoFinder/master/wiki/png/s_folder.png) -![](https://raw.githubusercontent.com/k3b/AndroFotoFinder/master/wiki/png/s_map.png) -![](https://raw.githubusercontent.com/k3b/AndroFotoFinder/master/wiki/png/s_share.png) - -![](https://raw.githubusercontent.com/k3b/AndroFotoFinder/master/wiki/png/Gallery.png) - - -* [Features](https://github.com/k3b/AndroFotoFinder/wiki/features) -* [Geographic-Map](https://github.com/k3b/AndroFotoFinder/wiki/geographic-map) - * ["geo:" picker](https://github.com/k3b/AndroFotoFinder/wiki/geographic-map#picker) -* [Gallery-View](https://github.com/k3b/AndroFotoFinder/wiki/Gallery-View) - * [multi selection mode](Gallery-View#Multiselection) - * [Gallery Navigation](Gallery-View#Navigation) - * [Current Visible Photos](Gallery-View#CurrentSet) -* [Image-View](https://github.com/k3b/AndroFotoFinder/wiki/Image-View) - * [Image-View Intent-API](Image-View#api) -* [Folder-Picker](https://github.com/k3b/AndroFotoFinder/wiki/Folder-Picker) -* [Intent API](https://github.com/k3b/AndroFotoFinder/wiki/intentapi) - * [Uri formats](intentapi#uri) - * [geo: uri format](intentapi#uri-geo) - * [android.intent.extra.TITLE string](intentapi#EXTRA_TITLE) - * [Intent-Extra parameter](intentapi#extra) - * [de.k3b.extra.FILTER string](intentapi#filter) - * [de.k3b.extra.SQL string string](intentapi#EXTRA_SQL) - * [de.k3b.extra.SELECTED_ITEMS string](intentapi#SelectedItems) - * [de.k3b.extra.SELECTED_ITEMS_PATH string](intentapi#SelectedPaths) - * [Internal sql format of .query files](intentapi#sql) - -* [Filter-View](https://github.com/k3b/AndroFotoFinder/wiki/Filter-View) -* [Bookmarks](Bookmarks) -* [Settings](https://github.com/k3b/AndroFotoFinder/wiki/settings) -* [pc android meta sync](https://github.com/k3b/AndroFotoFinder/wiki/sync) -* [Metadata](https://github.com/k3b/AndroFotoFinder/wiki/Metadata) -* [Tags](Tags) - -