com.romanenco.gitt.BrowserActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.romanenco.gitt.BrowserActivity.java

Source

/*
 * Copyright 2013 Andrew Romanenco.
 * 
 * This file is part of Gitt.
 * 
 * Gitt 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.
 * 
 * Gitt 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 Gitt.  If not, see <http://www.gnu.org/licenses/>. 
 */

package com.romanenco.gitt;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.io.comparator.NameFileComparator;

import com.romanenco.gitt.dao.DAO;
import com.romanenco.gitt.dao.Repo;
import com.romanenco.gitt.git.GitHelper;
import com.romanenco.gitt.R;

import android.os.Bundle;
import android.app.AlertDialog;
import android.app.ListActivity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.text.Editable;
import android.text.InputType;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;

/**
 * Browse repo checked out to local file system.
 * 
 * Use the same activity instance to browse source tree.
 * Open code viewer on file touch.
 * 
 * @author Andrew Romanenco
 *
 */
public class BrowserActivity extends ListActivity {

    static final String TAG = "Browser";

    public static final String REPO = "key_repo";
    public static final String PATH = "key_path";

    private static final String FILTER = "filterbar.visibility";

    /**
     * We cache last viewed folder for each repo. With no persistence.
     * Use this values to handle back button and return to the repo.
     */
    private static Map<String, String> browseCache = new HashMap<String, String>();

    /**
     * Current repo, sent to every next step
     */
    private Repo current;

    /**
     * Path to folder to show content from.
     * It's relative to current repo's root.
     */
    private String path;

    private FileListAdapter adapter;
    private View filterBar;
    private EditText filterText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_browse);

        filterBar = findViewById(R.id.filter_bar);

        findViewById(R.id.filter_close).setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                showFilterBar(false);
            }
        });

        filterText = (EditText) findViewById(R.id.filter_text);
        filterText.addTextChangedListener(new TextWatcher() {

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
            }

            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            }

            @Override
            public void afterTextChanged(Editable s) {
                adapter.reFilter(s.toString());
            }

        });

        if (savedInstanceState == null) {
            current = (Repo) getIntent().getSerializableExtra(REPO);
            path = browseCache.get(current.getFolder());
            if (path == null) {
                path = ".";
            }
        } else {
            current = (Repo) savedInstanceState.getSerializable(REPO);
            path = savedInstanceState.getString(PATH);
            filterBar.setVisibility(savedInstanceState.getInt(FILTER));
        }

        updateTitleWithPath();
        adapter = new FileListAdapter(this, current, path);
        getListView().setAdapter(adapter);
    }

    private void updateTitleWithPath() {
        int index = path.lastIndexOf("/");
        if (index == -1) {
            //reading branch name in main thread...
            String branch = GitHelper.currentBranchName(this.getFilesDir() + "/" + current.getFolder());
            this.setTitle("(" + branch + ")/.");
        } else {
            this.setTitle(".." + path.substring(index));
        }
    }

    @Override
    protected void onListItemClick(ListView l, View v, int position, long id) {
        if (adapter.isFolder(position)) {
            String step = adapter.getItem(position);
            if (step.equals("..")) { // browse up
                int index = path.lastIndexOf("/");
                path = path.substring(0, index);
            } else {
                path += "/" + step;
            }
            updateTitleWithPath();
            showFilterBar(false);
            adapter = new FileListAdapter(this, current, path);
            getListView().setAdapter(adapter);
        } else {
            Intent next = new Intent(this, CodeViewActivity.class);
            File file = new File(this.getFilesDir(), current.getFolder());
            file = new File(file, path);
            file = new File(file, adapter.getItem(position));
            next.putExtra(CodeViewActivity.FILE_KEY, file);
            startActivity(next);
        }
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putSerializable(REPO, current);
        outState.putSerializable(PATH, path);
        outState.putInt(FILTER, filterBar.getVisibility());
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.browser, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case R.id.browser_menu_delete:
            deleteThisRepo();
            break;
        case R.id.browser_switch_branch:
            Intent co = new Intent(this, CheckoutActivity.class);
            co.putExtra(CheckoutActivity.REPO, current);
            startActivity(co);
            break;
        case R.id.browser_menu_pull:
            pullFromOrigin();
            break;
        case R.id.browser_filter:
            showFilterBar(true);
            break;
        case R.id.browser_repo_log:
            Intent logView = new Intent(this, LogViewActivity.class);
            logView.putExtra(LogViewActivity.REPO, current);
            startActivity(logView);
            break;
        }
        return super.onOptionsItemSelected(item);
    }

    private void deleteThisRepo() {
        AlertDialog dlg = new AlertDialog.Builder(this).setMessage(R.string.confirm_repo_delete)
                .setPositiveButton(getString(android.R.string.ok), new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        DAO dao = new DAO(BrowserActivity.this);
                        dao.open(true);
                        dao.delete(current.getFolder());
                        dao.close();
                        Intent delete = new Intent(BrowserActivity.this, GitService.class);
                        delete.putExtra(GitService.COMMAND, GitService.Command.Delete);
                        delete.putExtra(GitService.REPO, current);
                        startService(delete);
                        Intent main = new Intent(BrowserActivity.this, MainActivity.class);
                        main.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
                        startActivity(main);
                    }
                }).setNegativeButton(getString(android.R.string.cancel), null).create();
        dlg.setCanceledOnTouchOutside(false);
        dlg.show();
    }

    /**
     * When pull from origin there are several case.
     * 1. We are in TAG (detached head): will inform user to
     * checkout a branch first.
     * 2. Authentication required: ask for password (no passwd save).
     * 3. Anonymous user: just poll.
     */
    private void pullFromOrigin() {
        if (current.getUserName() == null) {
            //no authentication required
            current.setState(Repo.State.Busy);
            DAO dao = new DAO(BrowserActivity.this);
            dao.open(true);
            dao.update(current);
            dao.close();
            Intent pull = new Intent(this, GitService.class);
            pull.putExtra(GitService.COMMAND, GitService.Command.Pull);
            pull.putExtra(GitService.REPO, current);
            startService(pull);
            Intent main = new Intent(this, MainActivity.class);
            main.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
            startActivity(main);
        } else {
            String req = getString(R.string.passwd_request, current.getUserName());
            final EditText passwd = new EditText(this);
            passwd.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
            AlertDialog dlg = new AlertDialog.Builder(this).setMessage(req)
                    .setPositiveButton(getString(android.R.string.ok), new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int which) {
                            String password = passwd.getText().toString();
                            if (TextUtils.isEmpty(password)) {
                                pullFromOrigin();
                            } else {
                                current.setState(Repo.State.Busy);
                                DAO dao = new DAO(BrowserActivity.this);
                                dao.open(true);
                                dao.update(current);
                                dao.close();
                                Intent pull = new Intent(BrowserActivity.this, GitService.class);
                                pull.putExtra(GitService.COMMAND, GitService.Command.Pull);
                                pull.putExtra(GitService.REPO, current);
                                pull.putExtra(GitService.AUTH_PASSWD, password);
                                startService(pull);
                                Intent main = new Intent(BrowserActivity.this, MainActivity.class);
                                main.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
                                startActivity(main);
                            }
                        }
                    }).setNegativeButton(getString(android.R.string.cancel), null).create();
            dlg.setCanceledOnTouchOutside(false);
            dlg.setView(passwd);
            dlg.show();
        }

    }

    private void showFilterBar(boolean show) {
        if (show) {
            filterBar.setVisibility(View.VISIBLE);
            filterText.setText("");
        } else {
            filterBar.setVisibility(View.GONE);
            if (adapter != null)
                adapter.reFilter(null);
        }
    }

    /**
     * List data source.
     * Each item has name and type file/fodler.
     * 
     * @author Andrew Romanenco
     *
     */
    class FileListAdapter extends BaseAdapter {

        private Context context;
        private List<Item> allItems;
        private Map<String, String> fileSizes; //size is cached

        private List<Item> filteredList;

        /**
         * @param context
         * @param repo
         * @param folder - relative to repo root.
         */
        public FileListAdapter(Context context, Repo repo, String folder) {
            Log.d(TAG, "Reading: " + folder);
            this.context = context;
            browseCache.put(repo.getFolder(), folder);
            File repoDir = new File(context.getFilesDir(), repo.getFolder());
            File dir = new File(repoDir, folder);
            File[] files = dir.listFiles();
            Arrays.sort(files, NameFileComparator.NAME_COMPARATOR);
            allItems = new ArrayList<Item>();
            fileSizes = new HashMap<String, String>();
            if (!folder.equals(".")) {
                allItems.add(new Item("..", true));
            }
            for (File f : files) {
                if (".git".equals(f.getName()))
                    continue;
                boolean isDir = f.isDirectory();
                allItems.add(new Item(f.getName(), isDir));
                if (!isDir) {
                    fileSizes.put(f.getName(), Utils.formatFileSize(BrowserActivity.this, f.length()));
                }
            }
            filteredList = allItems;
        }

        private void reFilter(String pattern) {
            if (TextUtils.isEmpty(pattern)) {
                filteredList = allItems;
            } else {
                filteredList = new ArrayList<Item>();
                if ((allItems.size() > 0) && allItems.get(0).name.equals("..")) {
                    filteredList.add(allItems.get(0));
                }
                pattern = pattern.toLowerCase();
                for (Item item : allItems) {
                    if (item.name.toLowerCase().indexOf(pattern) > -1) {
                        filteredList.add(item);
                    }
                }
            }
            this.notifyDataSetChanged();
        }

        @Override
        public int getCount() {
            return filteredList.size();
        }

        @Override
        public String getItem(int position) {
            return filteredList.get(position).name;
        }

        public String getItemSize(int position) {
            return fileSizes.get(position);
        }

        @Override
        public long getItemId(int position) {
            return filteredList.get(position).name.hashCode();
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            Item item = filteredList.get(position);
            if (convertView == null) {
                LayoutInflater inflater = LayoutInflater.from(context);
                convertView = inflater.inflate(R.layout.item_row, parent, false);
            }
            TextView name = (TextView) convertView.findViewById(R.id.item_name);
            TextView size = (TextView) convertView.findViewById(R.id.item_size);
            ImageView type = (ImageView) convertView.findViewById(R.id.item_icon);
            name.setText(item.name);
            if (item.isFolder) {
                type.setVisibility(View.VISIBLE);
                size.setText("");
            } else {
                type.setVisibility(View.INVISIBLE);
                size.setText(fileSizes.get(item.name));
            }
            return convertView;
        }

        public boolean isFolder(int index) {
            return filteredList.get(index).isFolder;
        }

        private class Item {
            String name;
            boolean isFolder;

            public Item(String name, boolean isFolder) {
                this.name = name;
                this.isFolder = isFolder;
            }
        }

    }

}