Fixed line breaks
[pub/Android/ownCloud.git] / src / com / owncloud / android / ui / preview / PreviewTextFragment.java
1 package com.owncloud.android.ui.preview;
2
3 import android.accounts.Account;
4 import android.annotation.SuppressLint;
5 import android.content.Context;
6 import android.graphics.Paint;
7 import android.graphics.Rect;
8 import android.os.AsyncTask;
9 import android.os.Bundle;
10 import android.support.v4.app.Fragment;
11 import android.support.v4.app.FragmentManager;
12 import android.support.v4.app.FragmentTransaction;
13 import android.util.DisplayMetrics;
14 import android.util.Log;
15 import android.view.LayoutInflater;
16 import android.view.View;
17 import android.view.ViewGroup;
18 import android.widget.BaseAdapter;
19 import android.widget.ListView;
20 import android.widget.TextView;
21
22 import com.actionbarsherlock.view.Menu;
23 import com.actionbarsherlock.view.MenuInflater;
24 import com.actionbarsherlock.view.MenuItem;
25 import com.owncloud.android.R;
26 import com.owncloud.android.datamodel.OCFile;
27 import com.owncloud.android.files.FileMenuFilter;
28 import com.owncloud.android.lib.common.utils.Log_OC;
29 import com.owncloud.android.ui.activity.FileDisplayActivity;
30 import com.owncloud.android.ui.dialog.ConfirmationDialogFragment;
31 import com.owncloud.android.ui.dialog.LoadingDialog;
32 import com.owncloud.android.ui.dialog.RemoveFileDialogFragment;
33 import com.owncloud.android.ui.fragment.FileFragment;
34
35 import java.io.BufferedWriter;
36 import java.io.FileInputStream;
37 import java.io.IOException;
38 import java.io.StringWriter;
39 import java.util.ArrayList;
40 import java.util.LinkedList;
41 import java.util.List;
42 import java.util.Queue;
43 import java.util.Scanner;
44
45 public class PreviewTextFragment extends FileFragment {
46 private static final String EXTRA_FILE = "FILE";
47 private static final String EXTRA_ACCOUNT = "ACCOUNT";
48 private static final String TAG = PreviewTextFragment.class.getSimpleName();
49
50 private Account mAccount;
51 private ListView mTextPreviewList;
52
53 /**
54 * Creates an empty fragment for previews.
55 * <p/>
56 * MUST BE KEPT: the system uses it when tries to reinstantiate a fragment automatically
57 * (for instance, when the device is turned a aside).
58 * <p/>
59 * DO NOT CALL IT: an {@link OCFile} and {@link Account} must be provided for a successful
60 * construction
61 */
62 public PreviewTextFragment() {
63 super();
64 mAccount = null;
65 }
66
67 /**
68 * {@inheritDoc}
69 */
70 @Override
71 public View onCreateView(LayoutInflater inflater, ViewGroup container,
72 Bundle savedInstanceState) {
73 super.onCreateView(inflater, container, savedInstanceState);
74 Log_OC.e(TAG, "onCreateView");
75
76
77 View ret = inflater.inflate(R.layout.text_file_preview, container, false);
78
79 mTextPreviewList = (ListView) ret.findViewById(R.id.text_preview_list);
80 mTextPreviewList.setAdapter(new TextLineAdapter());
81
82 return ret;
83 }
84
85 /**
86 * {@inheritDoc}
87 */
88 @Override
89 public void onCreate(Bundle savedInstanceState) {
90 super.onCreate(savedInstanceState);
91
92 OCFile file = getFile();
93
94 Bundle args = getArguments();
95
96 if (file == null)
97 file = args.getParcelable(FileDisplayActivity.EXTRA_FILE);
98
99 if (mAccount == null)
100 mAccount = args.getParcelable(FileDisplayActivity.EXTRA_ACCOUNT);
101
102
103 if (savedInstanceState == null) {
104 if (file == null) {
105 throw new IllegalStateException("Instanced with a NULL OCFile");
106 }
107 if (mAccount == null) {
108 throw new IllegalStateException("Instanced with a NULL ownCloud Account");
109 }
110 } else {
111 file = savedInstanceState.getParcelable(EXTRA_FILE);
112 mAccount = savedInstanceState.getParcelable(EXTRA_ACCOUNT);
113 }
114 setFile(file);
115 setHasOptionsMenu(true);
116 }
117
118 /**
119 * {@inheritDoc}
120 */
121 @Override
122 public void onSaveInstanceState(Bundle outState) {
123 super.onSaveInstanceState(outState);
124 outState.putParcelable(PreviewImageFragment.EXTRA_FILE, getFile());
125 outState.putParcelable(PreviewImageFragment.EXTRA_ACCOUNT, mAccount);
126 }
127
128 @Override
129 public void onStart() {
130 super.onStart();
131 Log_OC.e(TAG, "onStart");
132 }
133
134 private void loadAndShowTextPreview() {
135 new TextLoadAsyncTask().execute(getFile().getStoragePath());
136 }
137
138 /**
139 * Reads the file to preview and shows its contents. Too critical to be anonymous.
140 */
141 private class TextLoadAsyncTask extends AsyncTask<Object, StringWriter, Void> {
142 private int TEXTVIEW_WIDTH;
143 private float TEXTVIEW_SIZE;
144 private final Queue<Character> accumulatedText = new LinkedList<Character>();
145 private final String DIALOG_WAIT_TAG = "DIALOG_WAIT";
146 private final Rect bounds = new Rect();
147 private final Paint paint = new Paint();
148
149 @SuppressLint("InflateParams")
150 @Override
151 protected void onPreExecute() {
152 ((TextLineAdapter) mTextPreviewList.getAdapter()).clear();
153 DisplayMetrics displaymetrics = new DisplayMetrics();
154 getActivity().getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
155 TEXTVIEW_WIDTH = displaymetrics.widthPixels;
156 TEXTVIEW_SIZE = ((TextView) ((LayoutInflater) getActivity().getApplicationContext()
157 .getSystemService(Context.LAYOUT_INFLATER_SERVICE))
158 .inflate(
159 R.layout.text_file_preview_list_item, null)).getTextSize();
160 showLoadingDialog();
161 paint.setTextSize(TEXTVIEW_SIZE);
162 }
163
164 @Override
165 protected Void doInBackground(java.lang.Object... params) {
166 if (params.length != 1)
167 throw new IllegalArgumentException("The parameter to " + TextLoadAsyncTask.class.getName() + " must be the file location only");
168 final String location = (String) params[0];
169
170 FileInputStream inputStream = null;
171 Scanner sc = null;
172 try {
173 inputStream = new FileInputStream(location);
174 sc = new Scanner(inputStream);
175 while (sc.hasNextLine()) {
176 StringWriter target = new StringWriter();
177 BufferedWriter bufferedWriter = new BufferedWriter(target);
178 if (sc.hasNextLine())
179 bufferedWriter.write(sc.nextLine());
180 bufferedWriter.close();
181 publishProgress(target);
182 }
183 IOException exc = sc.ioException();
184 if (exc != null) throw exc;
185 } catch (IOException e) {
186 finish();
187 } finally {
188 if (inputStream != null) {
189 try {
190 inputStream.close();
191 } catch (IOException e) {
192 finish();
193 }
194 }
195 if (sc != null) {
196 sc.close();
197 }
198 }
199 //Add the remaining text, if any
200 while (!accumulatedText.isEmpty()) {
201 addLine();
202 }
203 return null;
204 }
205
206 @Override
207 protected void onProgressUpdate(StringWriter... values) {
208 super.onProgressUpdate(values);
209 final char[] newTextAsCharArray = values[0].toString().toCharArray();
210 for (char c : newTextAsCharArray) {
211 accumulatedText.add(c);
212 }
213 addLine();
214 }
215
216 private synchronized void addLine() {
217 StringBuilder textForThisLine = new StringBuilder();
218 do {
219 Character polled = accumulatedText.poll();
220 textForThisLine.append(polled);
221 }
222 while (!isTooLarge(textForThisLine.toString()) && !accumulatedText.isEmpty());
223 String line = textForThisLine.toString();
224 ((TextLineAdapter) mTextPreviewList.getAdapter()).add(line.contentEquals("null") ? "" : line);
225 }
226
227 private boolean isTooLarge(String text) {
228 paint.getTextBounds(text, 0, text.length(), bounds);
229 int lineWidth = (int) Math.ceil(bounds.width());
230 return lineWidth / TEXTVIEW_WIDTH > 1;
231 }
232
233 @Override
234 protected void onPostExecute(Void aVoid) {
235 super.onPostExecute(aVoid);
236 mTextPreviewList.setVisibility(View.VISIBLE);
237 dismissLoadingDialog();
238 }
239
240 /**
241 * Show loading dialog
242 */
243 public void showLoadingDialog() {
244 // Construct dialog
245 LoadingDialog loading = new LoadingDialog(getResources().getString(R.string.wait_a_moment));
246 FragmentManager fm = getActivity().getSupportFragmentManager();
247 FragmentTransaction ft = fm.beginTransaction();
248 loading.show(ft, DIALOG_WAIT_TAG);
249 }
250
251 /**
252 * Dismiss loading dialog
253 */
254 public void dismissLoadingDialog() {
255 Fragment frag = getActivity().getSupportFragmentManager().findFragmentByTag(DIALOG_WAIT_TAG);
256 if (frag != null) {
257 LoadingDialog loading = (LoadingDialog) frag;
258 loading.dismiss();
259 }
260 }
261 }
262
263 private class TextLineAdapter extends BaseAdapter {
264 private static final int LIST_ITEM_LAYOUT = R.layout.text_file_preview_list_item;
265 private final List<String> items = new ArrayList<String>();
266
267 private void add(String line) {
268 items.add(line);
269 getActivity().runOnUiThread(new Runnable() {
270 @Override
271 public void run() {
272 notifyDataSetChanged();
273 }
274 });
275 }
276
277 private void clear() {
278 items.clear();
279 getActivity().runOnUiThread(new Runnable() {
280 @Override
281 public void run() {
282 notifyDataSetChanged();
283 }
284 });
285 }
286
287 @Override
288 public int getCount() {
289 return items.size();
290 }
291
292 @Override
293 public String getItem(int position) {
294 if (position >= items.size())
295 throw new IllegalArgumentException();
296 return items.get(position);
297 }
298
299 @Override
300 public long getItemId(int position) {
301 return position;
302 }
303
304 @Override
305 public View getView(int position, View convertView, ViewGroup parent) {
306 ViewHolder viewHolder;
307 if (convertView == null) {
308 convertView =
309 ((LayoutInflater) getActivity().getApplicationContext()
310 .getSystemService(Context.LAYOUT_INFLATER_SERVICE))
311 .inflate(
312 LIST_ITEM_LAYOUT, null);
313 viewHolder = new ViewHolder();
314 viewHolder.setLineView((TextView) convertView.findViewById(R.id.text_preview));
315 convertView.setTag(viewHolder);
316 } else {
317 viewHolder = (ViewHolder) convertView.getTag();
318 }
319
320 viewHolder.getLineView().setText(items.get(position));
321
322 return convertView;
323 }
324 }
325
326 private static class ViewHolder {
327 private TextView lineView;
328
329 private ViewHolder() {
330 }
331
332 public TextView getLineView() {
333 return lineView;
334 }
335
336 public void setLineView(TextView lineView) {
337 this.lineView = lineView;
338 }
339 }
340
341 /**
342 * {@inheritDoc}
343 */
344 @Override
345 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
346 super.onCreateOptionsMenu(menu, inflater);
347 inflater.inflate(R.menu.file_actions_menu, menu);
348 }
349
350 /**
351 * {@inheritDoc}
352 */
353 @Override
354 public void onPrepareOptionsMenu(Menu menu) {
355 super.onPrepareOptionsMenu(menu);
356
357 if (mContainerActivity.getStorageManager() != null) {
358 FileMenuFilter mf = new FileMenuFilter(
359 getFile(),
360 mContainerActivity.getStorageManager().getAccount(),
361 mContainerActivity,
362 getSherlockActivity()
363 );
364 mf.filter(menu);
365 }
366
367 // additional restriction for this fragment
368 MenuItem item = menu.findItem(R.id.action_rename_file);
369 if (item != null) {
370 item.setVisible(false);
371 item.setEnabled(false);
372 }
373
374 // additional restriction for this fragment
375 item = menu.findItem(R.id.action_move);
376 if (item != null) {
377 item.setVisible(false);
378 item.setEnabled(false);
379 }
380
381 // this one doesn't make sense since the file has to be down in order to be previewed
382 item = menu.findItem(R.id.action_download_file);
383 if (item != null) {
384 item.setVisible(false);
385 item.setEnabled(false);
386 }
387 }
388
389 /**
390 * {@inheritDoc}
391 */
392 @Override
393 public boolean onOptionsItemSelected(MenuItem item) {
394 switch (item.getItemId()) {
395 case R.id.action_share_file: {
396 mContainerActivity.getFileOperationsHelper().shareFileWithLink(getFile());
397 return true;
398 }
399 case R.id.action_unshare_file: {
400 mContainerActivity.getFileOperationsHelper().unshareFileWithLink(getFile());
401 return true;
402 }
403 case R.id.action_open_file_with: {
404 openFile();
405 return true;
406 }
407 case R.id.action_remove_file: {
408 RemoveFileDialogFragment dialog = RemoveFileDialogFragment.newInstance(getFile());
409 dialog.show(getFragmentManager(), ConfirmationDialogFragment.FTAG_CONFIRMATION);
410 return true;
411 }
412 case R.id.action_see_details: {
413 seeDetails();
414 return true;
415 }
416 case R.id.action_send_file: {
417 sendFile();
418 return true;
419 }
420 case R.id.action_sync_file: {
421 mContainerActivity.getFileOperationsHelper().syncFile(getFile());
422 return true;
423 }
424
425 default:
426 return false;
427 }
428 }
429
430 /**
431 * Update the file of the fragment with file value
432 *
433 * @param file The new file to set
434 */
435 public void updateFile(OCFile file) {
436 setFile(file);
437 }
438
439 private void sendFile() {
440 mContainerActivity.getFileOperationsHelper().sendDownloadedFile(getFile());
441 }
442
443 private void seeDetails() {
444 mContainerActivity.showDetails(getFile());
445 }
446
447 @Override
448 public void onPause() {
449 Log_OC.e(TAG, "onPause");
450 super.onPause();
451 }
452
453 @Override
454 public void onResume() {
455 super.onResume();
456 Log_OC.e(TAG, "onResume");
457
458 loadAndShowTextPreview();
459 }
460
461 @Override
462 public void onDestroy() {
463 Log_OC.e(TAG, "onDestroy");
464 super.onDestroy();
465 }
466
467 @Override
468 public void onStop() {
469 super.onStop();
470 Log_OC.e(TAG, "onStop");
471 }
472
473 /**
474 * Opens the previewed file with an external application.
475 */
476 private void openFile() {
477 mContainerActivity.getFileOperationsHelper().openFile(getFile());
478 finish();
479 }
480
481 /**
482 * Helper method to test if an {@link OCFile} can be passed to a {@link PreviewTextFragment} to be previewed.
483 *
484 * @param file File to test if can be previewed.
485 * @return 'True' if the file can be handled by the fragment.
486 */
487 public static boolean canBePreviewed(OCFile file) {
488 return (file != null && file.isDown() && file.isText());
489 }
490
491 /**
492 * Finishes the preview
493 */
494 private void finish() {
495 getSherlockActivity().onBackPressed();
496 }
497 }