Эх сурвалжийг харах

Search inside text files

Signed-off-by: tobiasKaminsky <tobias@kaminsky.me>
Mario Danic 8 жил өмнө
parent
commit
b6c528763c

+ 2 - 0
src/main/java/com/owncloud/android/ui/activity/FileActivity.java

@@ -90,6 +90,8 @@ public abstract class FileActivity extends DrawerActivity
     public static final String EXTRA_ACCOUNT = "com.owncloud.android.ui.activity.ACCOUNT";
     public static final String EXTRA_FROM_NOTIFICATION = "com.owncloud.android.ui.activity.FROM_NOTIFICATION";
     public static final String APP_OPENED_COUNT = "APP_OPENED_COUNT";
+    public static final String EXTRA_SEARCH = "com.owncloud.android.ui.activity.SEARCH";
+    public static final String EXTRA_SEARCH_QUERY = "com.owncloud.android.ui.activity.SEARCH_QUERY";
 
     public static final String TAG = FileActivity.class.getSimpleName();
 

+ 27 - 13
src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java

@@ -156,7 +156,7 @@ public class FileDisplayActivity extends HookActivity
         SendShareDialog.SendShareDialogDownloader {
 
     public static final String RESTART = "RESTART";
- 
+
     private SyncBroadcastReceiver mSyncBroadcastReceiver;
     private UploadFinishReceiver mUploadFinishReceiver;
     private DownloadFinishReceiver mDownloadFinishReceiver;
@@ -172,7 +172,6 @@ public class FileDisplayActivity extends HookActivity
     private static final String KEY_WAITING_TO_PREVIEW = "WAITING_TO_PREVIEW";
     private static final String KEY_SYNC_IN_PROGRESS = "SYNC_IN_PROGRESS";
     private static final String KEY_WAITING_TO_SEND = "WAITING_TO_SEND";
-    private static final String KEY_SEARCH_QUERY = "KEY_SEARCH_QUERY";
 
     public static final String ACTION_DETAILS = "com.owncloud.android.ui.activity.action.DETAILS";
 
@@ -205,7 +204,11 @@ public class FileDisplayActivity extends HookActivity
     private MediaServiceBinder mMediaServiceBinder;
     private MediaServiceConnection mMediaServiceConnection;
 
-    private String searchQuery;
+    public static final String KEY_IS_SEARCH_OPEN = "IS_SEARCH_OPEN";
+    public static final String KEY_SEARCH_QUERY = "SEARCH_QUERY";
+
+    private String searchQuery = "";
+    private boolean searchOpen;
 
     private SearchView searchView;
 
@@ -223,6 +226,7 @@ public class FileDisplayActivity extends HookActivity
             mSyncInProgress = savedInstanceState.getBoolean(KEY_SYNC_IN_PROGRESS);
             mWaitingToSend = savedInstanceState.getParcelable(FileDisplayActivity.KEY_WAITING_TO_SEND);
             searchQuery = savedInstanceState.getString(KEY_SEARCH_QUERY);
+            searchOpen = savedInstanceState.getBoolean(FileDisplayActivity.KEY_IS_SEARCH_OPEN, false);
         } else {
             mWaitingToPreview = null;
             mSyncInProgress = false;
@@ -368,7 +372,7 @@ public class FileDisplayActivity extends HookActivity
 
             if (MainApp.getVersionCode() > lastSeenVersion) {
                 OwnCloudVersion serverVersion = AccountUtils.getServerVersionForAccount(account, this);
-                
+
                 if (serverVersion == null) {
                     OCCapability capability = getCapabilities();
                     serverVersion = new OwnCloudVersion(capability.getVersionMayor() + VERSION_DOT +
@@ -627,7 +631,7 @@ public class FileDisplayActivity extends HookActivity
             searchView.post(new Runnable() {
                 @Override
                 public void run() {
-                    searchView.setQuery("", true);
+                    searchView.setQuery(searchQuery, true);
                 }
             });
         }
@@ -736,7 +740,7 @@ public class FileDisplayActivity extends HookActivity
                     if (success) {
                         // update the file from database, for the local storage path
                         mWaitingToPreview = getStorageManager().getFileById(mWaitingToPreview.getFileId());
-                        
+
                         if (PreviewMediaFragment.canBePreviewed(mWaitingToPreview)) {
                             boolean streaming = AccountUtils.getServerVersionForAccount(getAccount(), this)
                                     .isMediaStreamingSupported();
@@ -779,8 +783,9 @@ public class FileDisplayActivity extends HookActivity
         menu.findItem(R.id.action_create_dir).setVisible(false);
 
         menu.findItem(R.id.action_select_all).setVisible(false);
-        final MenuItem item = menu.findItem(R.id.action_search);
-        searchView = (SearchView) MenuItemCompat.getActionView(item);
+        MenuItem searchMenuItem = menu.findItem(R.id.action_search);
+        searchView = (SearchView) MenuItemCompat.getActionView(menu.findItem(R.id.action_search));
+        searchMenuItem.setVisible(false);
 
         // hacky as no default way is provided
         int fontColor = ThemeUtils.fontColor(this);
@@ -857,7 +862,7 @@ public class FileDisplayActivity extends HookActivity
             }
         });
 
-        return true;
+        return super.onCreateOptionsMenu(menu);
     }
 
 
@@ -1195,8 +1200,10 @@ public class FileDisplayActivity extends HookActivity
         // mRefreshSharesInProgress);
         outState.putParcelable(FileDisplayActivity.KEY_WAITING_TO_SEND, mWaitingToSend);
         if (searchView != null) {
-            outState.putString(KEY_SEARCH_QUERY, searchView.getQuery().toString());
+            outState.putBoolean(KEY_IS_SEARCH_OPEN, !searchView.isIconified());
         }
+        outState.putString(KEY_SEARCH_QUERY, searchQuery);
+
         Log_OC.v(TAG, "onSaveInstanceState() end");
     }
 
@@ -1215,7 +1222,7 @@ public class FileDisplayActivity extends HookActivity
 
         // refresh list of files
         if (searchView != null && !TextUtils.isEmpty(searchQuery)) {
-            searchView.setQuery(searchQuery, true);
+            searchView.setQuery(searchQuery, false);
         } else if (getListOfFilesFragment() != null && !getListOfFilesFragment().isSearchFragment()
                 && startFile == null) {
             refreshListOfFilesFragment(false);
@@ -1261,7 +1268,7 @@ public class FileDisplayActivity extends HookActivity
         } else {
             setDrawerMenuItemChecked(menuItemId);
         }
-        
+
         Log_OC.v(TAG, "onResume() end");
     }
 
@@ -2301,7 +2308,7 @@ public class FileDisplayActivity extends HookActivity
         } else {
             Log_OC.e(TAG, "Trying to send a NULL OCFile");
         }
-        
+
         mWaitingToSend = null;
     }
 
@@ -2395,6 +2402,8 @@ public class FileDisplayActivity extends HookActivity
             Bundle args = new Bundle();
             args.putParcelable(EXTRA_FILE, file);
             args.putParcelable(EXTRA_ACCOUNT, getAccount());
+            args.putBoolean(EXTRA_SEARCH, searchOpen);
+            args.putString(EXTRA_SEARCH_QUERY, searchQuery);
             Fragment textPreviewFragment = Fragment.instantiate(getApplicationContext(),
                     PreviewTextFragment.class.getName(), args);
             setSecondFragment(textPreviewFragment);
@@ -2541,4 +2550,9 @@ public class FileDisplayActivity extends HookActivity
 
         checkForNewDevVersionNecessary(findViewById(R.id.root_layout), getApplicationContext());
     }
+
+    public void setSearchQuery(String query) {
+        searchQuery = query;
+    }
+
 }

+ 81 - 4
src/main/java/com/owncloud/android/ui/preview/PreviewTextFragment.java

@@ -22,7 +22,11 @@ package com.owncloud.android.ui.preview;
 import android.accounts.Account;
 import android.os.AsyncTask;
 import android.os.Bundle;
+import android.os.Handler;
 import android.support.annotation.NonNull;
+import android.support.v4.view.MenuItemCompat;
+import android.support.v7.widget.SearchView;
+import android.text.Html;
 import android.view.LayoutInflater;
 import android.view.Menu;
 import android.view.MenuInflater;
@@ -45,6 +49,7 @@ import com.owncloud.android.ui.dialog.RemoveFilesDialogFragment;
 import com.owncloud.android.ui.fragment.FileFragment;
 import com.owncloud.android.utils.DisplayUtils;
 import com.owncloud.android.utils.MimeTypeUtil;
+import com.owncloud.android.utils.StringUtils;
 
 import org.mozilla.universalchardet.ReaderFactory;
 
@@ -58,7 +63,7 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.Scanner;
 
-public class    PreviewTextFragment extends FileFragment {
+public class PreviewTextFragment extends FileFragment implements SearchView.OnQueryTextListener {
     private static final String EXTRA_FILE = "FILE";
     private static final String EXTRA_ACCOUNT = "ACCOUNT";
     private static final String TAG = PreviewTextFragment.class.getSimpleName();
@@ -67,6 +72,10 @@ public class    PreviewTextFragment extends FileFragment {
     private TextView mTextPreview;
     private TextLoadAsyncTask mTextLoadTask;
 
+    private String mOriginalText;
+
+    private Handler mHandler;
+    private SearchView mSearchView;
     private RelativeLayout mMultiView;
 
     protected LinearLayout mMultiListContainer;
@@ -76,6 +85,9 @@ public class    PreviewTextFragment extends FileFragment {
     protected ProgressBar mMultiListProgress;
 
 
+    private String mSearchQuery = "";
+    private boolean mSearchOpen;
+
     /**
      * Creates an empty fragment for previews.
      *
@@ -101,6 +113,7 @@ public class    PreviewTextFragment extends FileFragment {
 
 
         View ret = inflater.inflate(R.layout.text_file_preview, container, false);
+        mTextPreview = ret.findViewById(R.id.text_preview);
 
         mTextPreview = ret.findViewById(R.id.text_preview);
 
@@ -137,6 +150,7 @@ public class    PreviewTextFragment extends FileFragment {
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+        setHasOptionsMenu(true);
 
         OCFile file = getFile();
 
@@ -150,6 +164,11 @@ public class    PreviewTextFragment extends FileFragment {
             mAccount = args.getParcelable(FileDisplayActivity.EXTRA_ACCOUNT);
         }
 
+        if (args.containsKey(FileDisplayActivity.EXTRA_SEARCH_QUERY)) {
+            mSearchQuery = args.getString(FileDisplayActivity.EXTRA_SEARCH_QUERY);
+        }
+        mSearchOpen = args.getBoolean(FileDisplayActivity.EXTRA_SEARCH, false);
+
         if (savedInstanceState == null) {
             if (file == null) {
                 throw new IllegalStateException("Instanced with a NULL OCFile");
@@ -161,8 +180,9 @@ public class    PreviewTextFragment extends FileFragment {
             file = savedInstanceState.getParcelable(EXTRA_FILE);
             mAccount = savedInstanceState.getParcelable(EXTRA_ACCOUNT);
         }
+
+        mHandler = new Handler();
         setFile(file);
-        setHasOptionsMenu(true);
     }
 
     /**
@@ -170,9 +190,10 @@ public class    PreviewTextFragment extends FileFragment {
      */
     @Override
     public void onSaveInstanceState(@NonNull Bundle outState) {
-        super.onSaveInstanceState(outState);
         outState.putParcelable(PreviewTextFragment.EXTRA_FILE, getFile());
         outState.putParcelable(PreviewTextFragment.EXTRA_ACCOUNT, mAccount);
+
+        super.onSaveInstanceState(outState);
     }
 
     @Override
@@ -189,6 +210,45 @@ public class    PreviewTextFragment extends FileFragment {
     }
 
 
+    @Override
+    public boolean onQueryTextSubmit(String query) {
+        performSearch(query, 0);
+        return true;
+    }
+
+    @Override
+    public boolean onQueryTextChange(final String newText) {
+        performSearch(newText, 500);
+        return true;
+    }
+
+
+    private void performSearch(final String query, int delay) {
+        mHandler.removeCallbacksAndMessages(null);
+
+        if (mOriginalText != null) {
+            if (getActivity() != null && getActivity() instanceof FileDisplayActivity) {
+                FileDisplayActivity fileDisplayActivity = (FileDisplayActivity) getActivity();
+                fileDisplayActivity.setSearchQuery(query);
+            }
+            mHandler.postDelayed(() -> {
+                if (query != null && !query.isEmpty()) {
+                    if (getContext() != null && getContext().getResources() != null) {
+                        String coloredText = StringUtils.searchAndColor(mOriginalText, query,
+                            getContext().getResources().getColor(R.color.primary));
+                        mTextPreview.setText(Html.fromHtml(coloredText.replace("\n", "<br \\>")));
+                    }
+                } else {
+                    mTextPreview.setText(mOriginalText);
+                }
+            }, delay);
+        }
+
+        if (delay == 0 && mSearchView != null) {
+            mSearchView.clearFocus();
+        }
+    }
+
     /**
      * Reads the file to preview and shows its contents. Too critical to be anonymous.
      */
@@ -258,7 +318,13 @@ public class    PreviewTextFragment extends FileFragment {
             final TextView textView = mTextViewReference.get();
 
             if (textView != null) {
-                textView.setText(new String(stringWriter.getBuffer()));
+                mOriginalText = stringWriter.toString();
+                mSearchView.setOnQueryTextListener(PreviewTextFragment.this);
+                textView.setText(mOriginalText);
+
+                if (mSearchOpen) {
+                    mSearchView.setQuery(mSearchQuery, true);
+                }
                 textView.setVisibility(View.VISIBLE);
             }
 
@@ -277,6 +343,17 @@ public class    PreviewTextFragment extends FileFragment {
     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
         super.onCreateOptionsMenu(menu, inflater);
         inflater.inflate(R.menu.file_actions_menu, menu);
+
+        MenuItem menuItem = menu.findItem(R.id.action_search);
+        menuItem.setVisible(true);
+        mSearchView = (SearchView) MenuItemCompat.getActionView(menuItem);
+        mSearchView.setMaxWidth(Integer.MAX_VALUE);
+
+        if (mSearchOpen) {
+            mSearchView.setIconified(false);
+            mSearchView.setQuery(mSearchQuery, false);
+            mSearchView.clearFocus();
+        }
     }
 
     /**

+ 33 - 33
src/main/java/com/owncloud/android/ui/preview/PreviewVideoActivity.java

@@ -42,9 +42,9 @@ import com.owncloud.android.utils.MimeTypeUtil;
 
 /**
  *  Activity implementing a basic video player.
- * 
+ *
  *  Used as an utility to preview video files contained in an ownCloud account.
- *  
+ *
  *  Currently, it always plays in landscape mode, full screen. When the playback ends,
  *  the activity is finished.
  */
@@ -52,12 +52,12 @@ public class PreviewVideoActivity extends FileActivity implements OnCompletionLi
 
     /** Key to receive a flag signaling if the video should be started immediately */
     public static final String EXTRA_AUTOPLAY = "AUTOPLAY";
-    
+
     /** Key to receive the position of the playback where the video should be put at start */
     public static final String EXTRA_START_POSITION = "START_POSITION";
 
     public static final String EXTRA_STREAM_URL = "STREAM_URL";
-    
+
     private static final String TAG = PreviewVideoActivity.class.getSimpleName();
 
     private int mSavedPlaybackPosition;         // in the unit time handled by MediaPlayer.getCurrentPosition()
@@ -65,24 +65,24 @@ public class PreviewVideoActivity extends FileActivity implements OnCompletionLi
     private VideoView mVideoPlayer;             // view to play the file; both performs and show the playback
     private MediaController mMediaController;   // panel control used by the user to control the playback
     private Uri mStreamUri;
-          
-    /** 
+
+    /**
      *  Called when the activity is first created.
-     *  
+     *
      *  Searches for an {@link OCFile} and ownCloud {@link Account} holding it in the starting {@link Intent}.
-     *  
-     *  The {@link Account} is unnecessary if the file is downloaded; else, the {@link Account} is used to 
+     *
+     *  The {@link Account} is unnecessary if the file is downloaded; else, the {@link Account} is used to
      *  try to stream the remote file - TODO get the streaming works
-     * 
+     *
      *  {@inheritDoc}
      */
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         Log_OC.v(TAG, "onCreate");
-        
+
         setContentView(R.layout.video_layout);
-    
+
         if (savedInstanceState == null) {
             Bundle extras = getIntent().getExtras();
             mSavedPlaybackPosition = extras.getInt(EXTRA_START_POSITION);
@@ -93,14 +93,14 @@ public class PreviewVideoActivity extends FileActivity implements OnCompletionLi
             mAutoplay = savedInstanceState.getBoolean(EXTRA_AUTOPLAY);
             mStreamUri = (Uri) savedInstanceState.get(EXTRA_STREAM_URL);
         }
-          
-        mVideoPlayer = (VideoView) findViewById(R.id.videoPlayer);
+
+        mVideoPlayer = findViewById(R.id.videoPlayer);
 
         // set listeners to get more control on the playback
         mVideoPlayer.setOnPreparedListener(this);
         mVideoPlayer.setOnCompletionListener(this);
         mVideoPlayer.setOnErrorListener(this);
-          
+
         // keep the screen on while the playback is performed (prevents screen off by battery save)
         mVideoPlayer.setKeepScreenOn(true);
 
@@ -120,7 +120,7 @@ public class PreviewVideoActivity extends FileActivity implements OnCompletionLi
         outState.putParcelable(PreviewVideoActivity.EXTRA_STREAM_URL, mStreamUri);
     }
 
-    
+
     @Override
     public void onBackPressed() {
         Log_OC.v(TAG, "onBackPressed");
@@ -131,41 +131,41 @@ public class PreviewVideoActivity extends FileActivity implements OnCompletionLi
         super.onBackPressed();
     }
 
-    
-    /** 
+
+    /**
      * Called when the file is ready to be played.
-     * 
+     *
      * Just starts the playback.
-     * 
+     *
      * @param   mp    {@link MediaPlayer} instance performing the playback.
      */
     @Override
     public void onPrepared(MediaPlayer mp) {
         Log_OC.v(TAG, "onPrepare");
         mVideoPlayer.seekTo(mSavedPlaybackPosition);
-        if (mAutoplay) { 
+        if (mAutoplay) {
             mVideoPlayer.start();
         }
-        mMediaController.show(5000);  
+        mMediaController.show(5000);
     }
-    
-    
+
+
     /**
      * Called when the file is finished playing.
-     *  
+     *
      * Rewinds the video
-     * 
+     *
      * @param   mp    {@link MediaPlayer} instance performing the playback.
      */
     @Override
     public void onCompletion(MediaPlayer  mp) {
         mVideoPlayer.seekTo(0);
     }
-    
-    
+
+
     /**
      * Called when an error in playback occurs.
-     * 
+     *
      * @param   mp      {@link MediaPlayer} instance performing the playback.
      * @param   what    Type of error
      * @param   extra   Extra code specific to the error
@@ -173,11 +173,11 @@ public class PreviewVideoActivity extends FileActivity implements OnCompletionLi
     @Override
     public boolean onError(MediaPlayer mp, int what, int extra) {
         Log_OC.e(TAG, "Error in video playback, what = " + what + ", extra = " + extra);
-        
+
         if (mMediaController != null) {
             mMediaController.hide();
         }
-        
+
         if (mVideoPlayer.getWindowToken() != null) {
             String message = MediaService.getMessageForMediaError(this, what, extra);
             new AlertDialog.Builder(this)
@@ -193,7 +193,7 @@ public class PreviewVideoActivity extends FileActivity implements OnCompletionLi
         }
         return true;
     }
-    
+
     @Override
     protected void onAccountSet(boolean stateWasRecovered) {
         super.onAccountSet(stateWasRecovered);
@@ -206,7 +206,7 @@ public class PreviewVideoActivity extends FileActivity implements OnCompletionLi
             if (!MimeTypeUtil.isVideo(file)) {
                 throw new IllegalArgumentException("Non-video file passed as argument");
             }
-            file = getStorageManager().getFileById(file.getFileId()); 
+            file = getStorageManager().getFileById(file.getFileId());
             if (file != null) {
                 if (file.isDown()) {
                     mVideoPlayer.setVideoURI(file.getStorageUri());

+ 55 - 0
src/main/java/com/owncloud/android/utils/StringUtils.java

@@ -0,0 +1,55 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Mario Danic
+ * Copyright (C) 2017 Nextcloud GmbH.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.owncloud.android.utils;
+
+import android.support.annotation.ColorInt;
+import android.text.TextUtils;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Helper class for handling and manipulating strings.
+ */
+public class StringUtils {
+
+    public static String searchAndColor(String text, String searchText, @ColorInt int color) {
+
+        if (TextUtils.isEmpty(text) || TextUtils.isEmpty(searchText)) {
+            return text;
+        }
+
+        Matcher matcher = Pattern.compile(searchText, Pattern.CASE_INSENSITIVE | Pattern.LITERAL).matcher(text);
+
+        StringBuffer stringBuffer = new StringBuffer();
+
+        while (matcher.find()) {
+            String replacement = matcher.group().replace(
+                matcher.group(),
+                "<font color='" + color + "'><b>" + matcher.group() + "</b></font>"
+            );
+            matcher.appendReplacement(stringBuffer, Matcher.quoteReplacement(replacement));
+        }
+        matcher.appendTail(stringBuffer);
+
+        return stringBuffer.toString();
+    }
+}