├── app
├── .gitignore
├── src
│ └── main
│ │ ├── res
│ │ ├── drawable
│ │ │ ├── kodis.png
│ │ │ ├── menu_right.xml
│ │ │ ├── vector_add.xml
│ │ │ ├── vector_menu.xml
│ │ │ ├── vector_file.xml
│ │ │ ├── vector_close.xml
│ │ │ ├── vector_java.xml
│ │ │ ├── vector_open.xml
│ │ │ ├── vector_css.xml
│ │ │ ├── vector_save.xml
│ │ │ ├── vector_html.xml
│ │ │ ├── vector_xml.xml
│ │ │ ├── vector_txt.xml
│ │ │ ├── vector_cpp.xml
│ │ │ ├── vector_js.xml
│ │ │ ├── vector_python.xml
│ │ │ └── vector_php.xml
│ │ ├── mipmap-hdpi
│ │ │ └── ic_launcher.png
│ │ ├── mipmap-mdpi
│ │ │ └── ic_launcher.png
│ │ ├── mipmap-xhdpi
│ │ │ └── ic_launcher.png
│ │ ├── mipmap-xxhdpi
│ │ │ └── ic_launcher.png
│ │ ├── mipmap-xxxhdpi
│ │ │ └── ic_launcher.png
│ │ ├── values
│ │ │ ├── strings.xml
│ │ │ ├── dimens.xml
│ │ │ ├── colors.xml
│ │ │ └── styles.xml
│ │ ├── values-v21
│ │ │ └── styles.xml
│ │ ├── values-w820dp
│ │ │ └── dimens.xml
│ │ ├── menu
│ │ │ ├── menu_editor.xml
│ │ │ └── menu_main.xml
│ │ └── layout
│ │ │ ├── item_tree_view.xml
│ │ │ ├── nav_header.xml
│ │ │ ├── dialog_new_file.xml
│ │ │ ├── activity_main.xml
│ │ │ ├── fragment_main.xml
│ │ │ └── fragment_editor.xml
│ │ ├── assets
│ │ └── fonts
│ │ │ └── Consolas.ttf
│ │ ├── java
│ │ └── com
│ │ │ └── kodis
│ │ │ ├── listener
│ │ │ ├── OnBottomReachedListener.java
│ │ │ ├── OnScrollListener.java
│ │ │ ├── FileChangeListener.java
│ │ │ └── OnDirectoryBuiltListener.java
│ │ │ ├── utils
│ │ │ ├── PermissionManager.java
│ │ │ ├── DirectoryBuilder.java
│ │ │ └── ExtensionManager.java
│ │ │ ├── adapter
│ │ │ └── ViewPagerAdapter.java
│ │ │ ├── holder
│ │ │ └── IconTreeItemHolder.java
│ │ │ └── ui
│ │ │ ├── component
│ │ │ ├── InteractiveScrollView.java
│ │ │ ├── CodeEditText.java
│ │ │ └── ShaderEditor.java
│ │ │ ├── fragment
│ │ │ ├── EditorFragment.java
│ │ │ └── MainFragment.java
│ │ │ └── MainActivity.java
│ │ └── AndroidManifest.xml
├── proguard-rules.pro
└── build.gradle
├── settings.gradle
├── ic_web.png
├── .idea
├── copyright
│ └── profiles_settings.xml
├── encodings.xml
├── vcs.xml
├── modules.xml
├── gradle.xml
├── compiler.xml
└── misc.xml
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── LICENSE
├── README.md
├── gradle.properties
├── gradlew.bat
└── gradlew
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------
/ic_web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iamareebjamal/Kodis/HEAD/ic_web.png
--------------------------------------------------------------------------------
/.idea/copyright/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iamareebjamal/Kodis/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/res/drawable/kodis.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iamareebjamal/Kodis/HEAD/app/src/main/res/drawable/kodis.png
--------------------------------------------------------------------------------
/app/src/main/assets/fonts/Consolas.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iamareebjamal/Kodis/HEAD/app/src/main/assets/fonts/Consolas.ttf
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iamareebjamal/Kodis/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iamareebjamal/Kodis/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iamareebjamal/Kodis/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iamareebjamal/Kodis/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iamareebjamal/Kodis/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Kodis
3 | Settings
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/java/com/kodis/listener/OnBottomReachedListener.java:
--------------------------------------------------------------------------------
1 | package com.kodis.listener;
2 |
3 | public interface OnBottomReachedListener {
4 | void onBottomReached();
5 | }
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/java/com/kodis/listener/OnScrollListener.java:
--------------------------------------------------------------------------------
1 | package com.kodis.listener;
2 |
3 | public interface OnScrollListener {
4 | void onScrolled();
5 |
6 | void onScrolledUp();
7 |
8 | void onScrolledDown();
9 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/kodis/listener/FileChangeListener.java:
--------------------------------------------------------------------------------
1 | package com.kodis.listener;
2 |
3 | public interface FileChangeListener {
4 | void onFileOpen();
5 |
6 | void onFileChanged(boolean save);
7 |
8 | void onFileSave();
9 | }
10 |
--------------------------------------------------------------------------------
/app/src/main/java/com/kodis/listener/OnDirectoryBuiltListener.java:
--------------------------------------------------------------------------------
1 | package com.kodis.listener;
2 |
3 | import com.unnamed.b.atv.view.AndroidTreeView;
4 |
5 | public interface OnDirectoryBuiltListener {
6 |
7 | void onDirectoryBuilt(AndroidTreeView treeView);
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Oct 21 11:34:03 PDT 2015
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/menu_right.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 | 16dp
6 | 10sp
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/vector_add.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/vector_menu.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values-v21/styles.xml:
--------------------------------------------------------------------------------
1 | >
2 |
3 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/vector_file.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/vector_close.xml:
--------------------------------------------------------------------------------
1 |
6 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/vector_java.xml:
--------------------------------------------------------------------------------
1 |
6 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/vector_open.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_editor.xml:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/vector_css.xml:
--------------------------------------------------------------------------------
1 |
6 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/vector_save.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/vector_html.xml:
--------------------------------------------------------------------------------
1 |
6 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/vector_xml.xml:
--------------------------------------------------------------------------------
1 |
6 |
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2016 Areeb Jamal
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
--------------------------------------------------------------------------------
/app/src/main/res/drawable/vector_txt.xml:
--------------------------------------------------------------------------------
1 |
6 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #00BCD4
4 | #0097A7
5 | #FFC107
6 | #4f5b66
7 | #eff1f5
8 | #f99157
9 | #7E57C2
10 | #ef5350
11 | #66cccc
12 | #a7adba
13 |
14 |
15 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
18 |
19 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /home/iamareebjamal/Android/SDK/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 | -keep class com.kodis.holder.** { *; }
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Kodis
2 | > Kodis is a basic Android Code and Text Editor
3 |
4 | 
5 |
6 | ### Features
7 | - Basic file management like file and folder creation
8 | - File picker
9 | - Automatic Project View of tree style hierarchy
10 | - Syntax Highlighting
11 | - Open various files via tabs
12 | - Use common programming symbols
13 | - Intelligent state management between app closures
14 |
15 | ### Screenshots
16 | 
17 | 
18 | 
19 |
20 | ### Authors
21 | Areeb Jamal [@iamareebjamal](https://github.com/iamareebjamal)
22 | Divya Prakash Varshney [@divs4debu](https://github.com/divs4debu)
23 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/vector_cpp.xml:
--------------------------------------------------------------------------------
1 |
6 |
8 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 25
5 | buildToolsVersion "25"
6 |
7 | defaultConfig {
8 | applicationId "com.kodis"
9 | minSdkVersion 11
10 | targetSdkVersion 25
11 | versionCode 1
12 | versionName "1.0"
13 | vectorDrawables.useSupportLibrary = true
14 | }
15 | buildTypes {
16 | release {
17 | minifyEnabled true
18 | shrinkResources true
19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
20 | }
21 | }
22 | }
23 |
24 |
25 |
26 |
27 | dependencies {
28 | compile fileTree(dir: 'libs', include: ['*.jar'])
29 | compile 'com.android.support:appcompat-v7:25.0.0'
30 | compile 'com.android.support:design:25.0.0'
31 | compile 'com.github.angads25:filepicker:1.0.2'
32 | compile 'com.github.bmelnychuk:atv:1.2.9'
33 | }
34 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/vector_js.xml:
--------------------------------------------------------------------------------
1 |
6 |
8 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/vector_python.xml:
--------------------------------------------------------------------------------
1 |
6 |
8 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
14 |
15 |
16 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_tree_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
18 |
19 |
23 |
24 |
31 |
32 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/nav_header.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
17 |
18 |
27 |
28 |
35 |
36 |
--------------------------------------------------------------------------------
/app/src/main/java/com/kodis/utils/PermissionManager.java:
--------------------------------------------------------------------------------
1 | package com.kodis.utils;
2 |
3 | import android.Manifest;
4 | import android.app.Activity;
5 | import android.content.pm.PackageManager;
6 | import android.support.v4.app.ActivityCompat;
7 |
8 | public class PermissionManager {
9 | // Storage Permissions
10 | private static final int REQUEST_EXTERNAL_STORAGE = 1;
11 | private static String[] PERMISSIONS_STORAGE = {
12 | Manifest.permission.READ_EXTERNAL_STORAGE,
13 | Manifest.permission.WRITE_EXTERNAL_STORAGE
14 | };
15 |
16 | /**
17 | * Checks if the app has permission to write to device storage
18 | *
19 | * If the app does not has permission then the user will be prompted to grant permissions
20 | *
21 | * @param activity
22 | */
23 | public static void verifyStoragePermissions(Activity activity) {
24 | // Check if we have write permission
25 | int permission = ActivityCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE);
26 |
27 | if (permission != PackageManager.PERMISSION_GRANTED) {
28 | // We don't have permission so prompt the user
29 | ActivityCompat.requestPermissions(
30 | activity,
31 | PERMISSIONS_STORAGE,
32 | REQUEST_EXTERNAL_STORAGE
33 | );
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/vector_php.xml:
--------------------------------------------------------------------------------
1 |
6 |
8 |
--------------------------------------------------------------------------------
/app/src/main/java/com/kodis/adapter/ViewPagerAdapter.java:
--------------------------------------------------------------------------------
1 | package com.kodis.adapter;
2 |
3 | import android.support.v4.app.Fragment;
4 | import android.support.v4.app.FragmentManager;
5 | import android.support.v4.app.FragmentStatePagerAdapter;
6 | import com.kodis.ui.fragment.EditorFragment;
7 |
8 | import java.util.ArrayList;
9 | import java.util.List;
10 |
11 | public class ViewPagerAdapter extends FragmentStatePagerAdapter {
12 | private List mFragmentList = new ArrayList<>();
13 |
14 | public ViewPagerAdapter(FragmentManager fm) {
15 | super(fm);
16 | }
17 |
18 | @Override
19 | public Fragment getItem(int position) {
20 | return mFragmentList.get(position);
21 | }
22 |
23 | @Override
24 | public int getCount() {
25 | return mFragmentList.size();
26 | }
27 |
28 | public void addFragment(Fragment fragment) {
29 | mFragmentList.add(fragment);
30 | }
31 |
32 | @Override
33 | public int getItemPosition(Object object) {
34 | // refresh all fragments when data set changed
35 | return ViewPagerAdapter.POSITION_NONE;
36 | }
37 |
38 | public void removeTabPage(int position) {
39 | mFragmentList.remove(position);
40 | }
41 |
42 | @Override
43 | public CharSequence getPageTitle(int position) {
44 | return ((EditorFragment) mFragmentList.get(position)).getFileName();
45 | }
46 |
47 | public List getFragmentList() {
48 | return mFragmentList;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/app/src/main/java/com/kodis/holder/IconTreeItemHolder.java:
--------------------------------------------------------------------------------
1 | package com.kodis.holder;
2 |
3 | import android.content.Context;
4 | import android.media.Image;
5 | import android.support.v4.view.ViewCompat;
6 | import android.view.LayoutInflater;
7 | import android.view.View;
8 | import android.widget.ImageView;
9 | import android.widget.TextView;
10 | import com.kodis.R;
11 | import com.unnamed.b.atv.model.TreeNode;
12 |
13 | public class IconTreeItemHolder extends TreeNode.BaseNodeViewHolder {
14 |
15 | ImageView arrow;
16 |
17 | public IconTreeItemHolder(Context context) {
18 | super(context);
19 | }
20 |
21 | @Override
22 | public View createNodeView(final TreeNode node, FileTreeItem value) {
23 | LayoutInflater inflater = LayoutInflater.from(context);
24 | View view = inflater.inflate(R.layout.item_tree_view, null, false);
25 | TextView fileName = (TextView) view.findViewById(R.id.file_browser_name);
26 | ImageView fileIcon = (ImageView) view.findViewById(R.id.file_browser_icon);
27 | arrow = (ImageView) view.findViewById(R.id.file_browser_arrow);
28 |
29 | fileName.setText(value.text);
30 | fileIcon.setImageResource(value.icon);
31 |
32 | if (node.isLeaf()) {
33 | arrow.setVisibility(View.INVISIBLE);
34 | }
35 |
36 | if (!node.isExpanded()) {
37 | arrow.setRotation(0);
38 | }
39 |
40 | return view;
41 | }
42 |
43 | @Override
44 | public void toggle(boolean active) {
45 | ViewCompat.animate(arrow).rotation(active?90:0);
46 | }
47 |
48 | public static class FileTreeItem {
49 |
50 | public int icon;
51 |
52 | public String text;
53 | public String path;
54 |
55 | public FileTreeItem(int icon, String text, String path) {
56 | this.icon = icon;
57 | this.text = text;
58 | this.path = path;
59 | }
60 | }
61 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/kodis/ui/component/InteractiveScrollView.java:
--------------------------------------------------------------------------------
1 | package com.kodis.ui.component;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 | import android.view.View;
6 | import android.widget.ScrollView;
7 | import com.kodis.listener.OnBottomReachedListener;
8 | import com.kodis.listener.OnScrollListener;
9 |
10 | public class InteractiveScrollView extends ScrollView {
11 | OnBottomReachedListener onBottomReachedListener;
12 | OnScrollListener onScrollListener;
13 |
14 | public InteractiveScrollView(Context context, AttributeSet attrs,
15 | int defStyle) {
16 | super(context, attrs, defStyle);
17 | }
18 |
19 | public InteractiveScrollView(Context context, AttributeSet attrs) {
20 | super(context, attrs);
21 | }
22 |
23 | public InteractiveScrollView(Context context) {
24 | super(context);
25 | }
26 |
27 | @Override
28 | protected void onScrollChanged(int l, int t, int oldl, int oldt) {
29 | if (onScrollListener == null || onBottomReachedListener == null)
30 | return;
31 |
32 | onScrollListener.onScrolled();
33 |
34 | if (t > oldt)
35 | onScrollListener.onScrolledDown();
36 | else
37 | onScrollListener.onScrolledUp();
38 |
39 | View view = getChildAt(getChildCount() - 1);
40 | int diff = (view.getBottom() - (getHeight() + getScrollY()));
41 |
42 | if (diff <= 20 && onBottomReachedListener != null) {
43 | onBottomReachedListener.onBottomReached();
44 | }
45 |
46 | super.onScrollChanged(l, t, oldl, oldt);
47 | }
48 |
49 | public OnBottomReachedListener getOnBottomReachedListener() {
50 | return onBottomReachedListener;
51 | }
52 |
53 | public void setOnBottomReachedListener(
54 | OnBottomReachedListener onBottomReachedListener) {
55 | this.onBottomReachedListener = onBottomReachedListener;
56 | }
57 |
58 | public OnScrollListener getOnScrollListener() {
59 | return onScrollListener;
60 | }
61 |
62 | public void setOnScrollListener(OnScrollListener onScrollListener) {
63 | this.onScrollListener = onScrollListener;
64 | }
65 |
66 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_new_file.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
15 |
16 |
21 |
22 |
32 |
33 |
37 |
38 |
45 |
46 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
11 |
12 |
16 |
17 |
24 |
25 |
26 |
27 |
34 |
38 |
45 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/app/src/main/java/com/kodis/utils/DirectoryBuilder.java:
--------------------------------------------------------------------------------
1 | package com.kodis.utils;
2 |
3 | import android.content.Context;
4 | import android.os.AsyncTask;
5 | import com.kodis.R;
6 | import com.kodis.holder.IconTreeItemHolder;
7 | import com.kodis.listener.OnDirectoryBuiltListener;
8 | import com.unnamed.b.atv.model.TreeNode;
9 | import com.unnamed.b.atv.view.AndroidTreeView;
10 |
11 | import java.io.File;
12 | import java.io.FilenameFilter;
13 |
14 | public class DirectoryBuilder {
15 | private Context context;
16 | private String path;
17 | private OnDirectoryBuiltListener onDirectoryBuiltListener;
18 |
19 | public DirectoryBuilder(Context context, String path){
20 | this.context = context;
21 | this.path = path;
22 | }
23 |
24 | public void setOnDirectoryBuiltListener(OnDirectoryBuiltListener onDirectoryBuiltListener) {
25 | this.onDirectoryBuiltListener = onDirectoryBuiltListener;
26 | }
27 |
28 | public void start(){
29 | new DirectoryBuild().execute();
30 | }
31 |
32 | private class DirectoryBuild extends AsyncTask{
33 |
34 | private void buildDirectory(TreeNode root, File path){
35 | File[] files = path.listFiles(new FilenameFilter() {
36 | @Override
37 | public boolean accept(File file, String name) {
38 | return !name.startsWith(".");
39 | }
40 | });
41 |
42 | if(files == null)
43 | return;
44 |
45 | for(File file : files) {
46 | if(file.isDirectory()){
47 | TreeNode folderNode = new TreeNode(new IconTreeItemHolder.FileTreeItem(R.drawable.vector_open, file.getName(), file.getAbsolutePath()));
48 | buildDirectory(folderNode, file);
49 | root.addChild(folderNode);
50 | } else {
51 | TreeNode folderNode = new TreeNode(new IconTreeItemHolder.FileTreeItem(ExtensionManager.getIcon(file.getName()), file.getName(), file.getAbsolutePath()));
52 | root.addChild(folderNode);
53 | }
54 | }
55 |
56 | }
57 |
58 | @Override
59 | protected AndroidTreeView doInBackground(Void... voids) {
60 | TreeNode root = TreeNode.root();
61 | buildDirectory(root, new File(path).getParentFile());
62 | AndroidTreeView treeView = new AndroidTreeView(context, root);
63 |
64 | return treeView;
65 | }
66 |
67 | @Override
68 | protected void onPostExecute(AndroidTreeView androidTreeView) {
69 | if(onDirectoryBuiltListener!=null)
70 | onDirectoryBuiltListener.onDirectoryBuilt(androidTreeView);
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
15 |
16 |
22 |
23 |
29 |
30 |
31 |
32 |
39 |
40 |
44 |
45 |
51 |
52 |
53 |
54 |
59 |
60 |
68 |
69 |
--------------------------------------------------------------------------------
/app/src/main/java/com/kodis/ui/component/CodeEditText.java:
--------------------------------------------------------------------------------
1 | package com.kodis.ui.component;
2 |
3 | import android.content.Context;
4 | import android.graphics.Canvas;
5 | import android.graphics.Color;
6 | import android.graphics.Paint;
7 | import android.graphics.Typeface;
8 | import android.text.Layout;
9 | import android.util.AttributeSet;
10 | import android.util.TypedValue;
11 | import android.view.ViewTreeObserver;
12 |
13 | import java.io.Serializable;
14 |
15 | public class CodeEditText extends ShaderEditor {
16 | private Context context;
17 | private transient Paint paint = new Paint();
18 | private transient Paint bgPaint = new Paint();
19 | private Layout layout;
20 |
21 | public CodeEditText(Context context, AttributeSet attrs) {
22 | super(context, attrs);
23 |
24 | this.context = context;
25 | bgPaint.setStyle(Paint.Style.FILL);
26 | bgPaint.setColor(Color.parseColor("#eeeeee"));
27 |
28 | paint.setStyle(Paint.Style.FILL);
29 | paint.setAntiAlias(true);
30 | paint.setTypeface(Typeface.createFromAsset(context.getAssets(), "fonts/Consolas.ttf"));
31 | paint.setColor(Color.parseColor("#bbbbbb"));
32 | paint.setTextSize(getPixels(14));
33 | getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
34 | @Override
35 | public void onGlobalLayout() {
36 | layout = getLayout();
37 | }
38 | });
39 | }
40 |
41 | private int getDigitCount() {
42 | int count = 0;
43 | int len = getLineCount();
44 | while (len > 0) {
45 | count++;
46 | len /= 10;
47 | }
48 | return count;
49 | }
50 |
51 | @Override
52 | protected void onDraw(Canvas canvas) {
53 | int padding = (int) getPixels(getDigitCount() * 10 + 10);
54 | setPadding(padding, 0, 0, 0);
55 |
56 |
57 | int scrollY = getScrollY();
58 | int firstLine = layout.getLineForVertical(scrollY), lastLine;
59 |
60 | try {
61 | lastLine = layout.getLineForVertical(scrollY + (getHeight() - getExtendedPaddingTop() - getExtendedPaddingBottom()));
62 | } catch (NullPointerException npe) {
63 | lastLine = layout.getLineForVertical(scrollY + (getHeight() - getPaddingTop() - getPaddingBottom()));
64 | }
65 |
66 | //canvas.drawRect(0, 0, padding-getPixels(2), layout.getLineBottom(lastLine), bgPaint);
67 |
68 | //the y position starts at the baseline of the first line
69 | int positionY = getBaseline() + (layout.getLineBaseline(firstLine) - layout.getLineBaseline(0));
70 | drawLineNumber(canvas, layout, positionY, firstLine);
71 | for (int i = firstLine + 1; i <= lastLine; i++) {
72 | //get the next y position using the difference between the current and last baseline
73 | positionY += layout.getLineBaseline(i) - layout.getLineBaseline(i - 1);
74 | drawLineNumber(canvas, layout, positionY, i);
75 | }
76 |
77 | super.onDraw(canvas);
78 |
79 | }
80 |
81 | private void drawLineNumber(Canvas canvas, Layout layout, int positionY, int line) {
82 | int positionX = (int) layout.getLineLeft(line);
83 | canvas.drawText(String.valueOf(line + 1), positionX + getPixels(2), positionY, paint);
84 |
85 | }
86 |
87 | private float getPixels(int dp) {
88 | return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, context.getResources().getDisplayMetrics());
89 | }
90 |
91 |
92 | }
93 |
--------------------------------------------------------------------------------
/app/src/main/java/com/kodis/utils/ExtensionManager.java:
--------------------------------------------------------------------------------
1 | package com.kodis.utils;
2 |
3 | import android.util.Log;
4 | import com.kodis.R;
5 |
6 | import java.io.File;
7 | import java.io.FileInputStream;
8 |
9 | public class ExtensionManager {
10 |
11 | public static String getExtension(String fileName) {
12 | int index = fileName.lastIndexOf('.');
13 | if (index > 0) {
14 | return fileName.substring(index + 1);
15 | }
16 |
17 | return null;
18 | }
19 |
20 | public static int getIcon(String fileName) {
21 | Language language = getLanguage(getExtension(fileName));
22 |
23 | switch (language) {
24 | case C:
25 | return R.drawable.vector_cpp;
26 | case PYTHON:
27 | return R.drawable.vector_python;
28 | case JAVA:
29 | return R.drawable.vector_java;
30 | case XML:
31 | return R.drawable.vector_xml;
32 | case HTML:
33 | return R.drawable.vector_html;
34 | case CSS:
35 | return R.drawable.vector_css;
36 | case JAVASCRIPT:
37 | return R.drawable.vector_js;
38 | case PHP:
39 | return R.drawable.vector_php;
40 | case TEXT:
41 | return R.drawable.vector_txt;
42 | case NONE:
43 | return R.drawable.vector_file;
44 | default:
45 | return R.drawable.vector_file;
46 | }
47 | }
48 |
49 | public static Language getLanguage(String extension) {
50 | if (extension == null) return Language.NONE;
51 |
52 | String normalized = extension.toLowerCase();
53 | if (normalized.equals("c") || normalized.equals("cpp") || normalized.equals("c++") || normalized.equals("h") || normalized.equals("hpp") || normalized.equals("h++")) {
54 | return Language.C;
55 | } else if (normalized.equals("py")) {
56 | return Language.PYTHON;
57 | } else if (normalized.equals("java")) {
58 | return Language.JAVA;
59 | } else if (normalized.equals("xml")) {
60 | return Language.XML;
61 | } else if (normalized.equals("html") || normalized.equals("htm")) {
62 | return Language.HTML;
63 | } else if (normalized.equals("css") || normalized.equals("sass") || normalized.equals("less")) {
64 | return Language.CSS;
65 | } else if (normalized.equals("js")) {
66 | return Language.JAVASCRIPT;
67 | } else if (normalized.equals("php")) {
68 | return Language.PHP;
69 | } else if (normalized.equals("txt") || normalized.equals("text")) {
70 | return Language.TEXT;
71 | } else {
72 | return Language.NONE;
73 | }
74 | }
75 |
76 | public static boolean isBinaryFile(File f) {
77 | int result = 0;
78 | try {
79 | FileInputStream in = new FileInputStream(f);
80 | int size = in.available();
81 | if (size > 1024) size = 1024;
82 | byte[] data = new byte[size];
83 | result = in.read(data);
84 | in.close();
85 |
86 | int ascii = 0;
87 | int other = 0;
88 |
89 | for (byte b : data) {
90 | if (b < 0x09) return true;
91 |
92 | if (b == 0x09 || b == 0x0A || b == 0x0C || b == 0x0D) ascii++;
93 | else if (b >= 0x20 && b <= 0x7E) ascii++;
94 | else other++;
95 | }
96 |
97 | return other != 0 && 100 * other / (ascii + other) > 95;
98 |
99 | } catch (Exception e) {
100 | Log.e("Kodis", e.getMessage() + String.valueOf(result));
101 | }
102 |
103 | return true;
104 | }
105 |
106 | public enum Language {
107 | C, PYTHON, JAVA, XML, HTML, CSS, JAVASCRIPT, PHP, TEXT, NONE
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_editor.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
20 |
21 |
27 |
28 |
35 |
36 |
46 |
47 |
48 |
54 |
55 |
66 |
67 |
68 |
73 |
79 |
87 |
88 |
96 |
97 |
105 |
106 |
114 |
115 |
123 |
124 |
132 |
133 |
141 |
142 |
150 |
151 |
159 |
160 |
168 |
169 |
177 |
178 |
186 |
187 |
195 |
203 |
204 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
--------------------------------------------------------------------------------
/app/src/main/java/com/kodis/ui/fragment/EditorFragment.java:
--------------------------------------------------------------------------------
1 | package com.kodis.ui.fragment;
2 |
3 | import android.content.Context;
4 | import android.graphics.Typeface;
5 | import android.os.AsyncTask;
6 | import android.os.Bundle;
7 | import android.os.Handler;
8 | import android.support.annotation.Nullable;
9 | import android.support.v4.app.Fragment;
10 | import android.text.Editable;
11 | import android.text.TextWatcher;
12 | import android.view.LayoutInflater;
13 | import android.view.View;
14 | import android.view.ViewGroup;
15 | import android.widget.LinearLayout;
16 | import android.widget.TextView;
17 | import android.widget.Toast;
18 | import com.kodis.R;
19 | import com.kodis.listener.FileChangeListener;
20 | import com.kodis.listener.OnBottomReachedListener;
21 | import com.kodis.listener.OnScrollListener;
22 | import com.kodis.ui.component.CodeEditText;
23 | import com.kodis.ui.component.InteractiveScrollView;
24 | import com.kodis.utils.ExtensionManager;
25 |
26 | import java.io.*;
27 |
28 | public class EditorFragment extends Fragment implements TextWatcher {
29 | public static final String FILE_KEY = "FILE";
30 |
31 | private Context context;
32 | private File file;
33 | private FileChangeListener fileChangeListener;
34 |
35 | private int CHUNK = 20000;
36 | private String FILE_CONTENT;
37 | private String currentBuffer;
38 | private StringBuilder loaded;
39 |
40 | private View rootView;
41 |
42 | private CodeEditText contentView;
43 | private View hidden;
44 | private InteractiveScrollView scrollView;
45 |
46 | public EditorFragment() {
47 |
48 | }
49 |
50 | public void setArguments(Bundle arguments) {
51 | if (arguments.containsKey(FILE_KEY))
52 | file = (File) arguments.getSerializable(FILE_KEY);
53 | }
54 |
55 | public void setFileChangeListener(FileChangeListener fileChangeListener) {
56 | this.fileChangeListener = fileChangeListener;
57 | }
58 | @Override
59 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
60 | View rootView = inflater.inflate(R.layout.fragment_editor, container, false);
61 | this.rootView = rootView;
62 |
63 | setupViews();
64 | return rootView;
65 | }
66 |
67 | @Override
68 | public void onAttach(Context context) {
69 | super.onAttach(context);
70 |
71 | this.context = context;
72 | }
73 |
74 | public void setupViews() {
75 | contentView = (CodeEditText) rootView.findViewById(R.id.fileContent);
76 | hidden = rootView.findViewById(R.id.hidden);
77 |
78 | LinearLayout symbolLayout = (LinearLayout) rootView.findViewById(R.id.symbolLayout);
79 | View.OnClickListener symbolClickListener = new View.OnClickListener() {
80 | @Override
81 | public void onClick(View view) {
82 | contentView.getText().insert(contentView.getSelectionStart(), ((TextView) view).getText().toString());
83 | }
84 | };
85 | for (int i = 0; i < symbolLayout.getChildCount(); i++) {
86 | symbolLayout.getChildAt(i).setOnClickListener(symbolClickListener);
87 | }
88 |
89 | if (file != null) {
90 | contentView.setVisibility(View.GONE);
91 | contentView.setTypeface(Typeface.createFromAsset(getContext().getAssets(), "fonts/Consolas.ttf"));
92 |
93 | hidden.setVisibility(View.VISIBLE);
94 | scrollView = (InteractiveScrollView) rootView.findViewById(R.id.scrollView);
95 | scrollView.setOnBottomReachedListener(null);
96 | scrollView.setOnScrollListener((OnScrollListener) fileChangeListener);
97 |
98 | new DocumentLoader().execute();
99 | }
100 | }
101 |
102 | private String getFileSize() {
103 | String modifiedFileSize = null;
104 | double fileSize = 0.0;
105 | if (file.isFile()) {
106 | fileSize = (double) file.length();//in Bytes
107 |
108 | if (fileSize < 1024) {
109 | modifiedFileSize = String.valueOf(fileSize).concat("B");
110 | } else if (fileSize > 1024 && fileSize < (1024 * 1024)) {
111 | modifiedFileSize = String.valueOf(Math.round((fileSize / 1024 * 100.0)) / 100.0).concat("KB");
112 | } else {
113 | modifiedFileSize = String.valueOf(Math.round((fileSize / (1024 * 1204) * 100.0)) / 100.0).concat("MB");
114 | }
115 | } else {
116 | modifiedFileSize = "Unknown";
117 | }
118 |
119 | return modifiedFileSize;
120 | }
121 |
122 | public String getFilePath() {
123 | return file.getPath();
124 | }
125 |
126 | public String getFileName() {
127 | return file.getName();
128 | }
129 |
130 | public String getFileInfo() {
131 | StringBuilder sb = new StringBuilder();
132 | sb.append("Size : " + getFileSize() + "\n");
133 | sb.append("Path : " + file.getPath() + "\n");
134 |
135 | return sb.toString();
136 | }
137 |
138 | public boolean isChanged() {
139 | if (FILE_CONTENT == null) {
140 | return false;
141 | }
142 |
143 | if (FILE_CONTENT.length() >= CHUNK && FILE_CONTENT.substring(0, loaded.length()).equals(currentBuffer))
144 | return false;
145 | else if (FILE_CONTENT.equals(currentBuffer))
146 | return false;
147 |
148 | return true;
149 | }
150 |
151 | private void loadInChunks(InteractiveScrollView scrollView, final String bigString) {
152 | loaded.append(bigString.substring(0, CHUNK));
153 | contentView.setTextHighlighted(loaded);
154 | scrollView.setOnBottomReachedListener(new OnBottomReachedListener() {
155 | @Override
156 | public void onBottomReached() {
157 | if (loaded.length() >= bigString.length())
158 | return;
159 | else if (loaded.length() + CHUNK > bigString.length()) {
160 | String buffer = bigString.substring(loaded.length(), bigString.length());
161 | loaded.append(buffer);
162 | } else {
163 | String buffer = bigString.substring(loaded.length(), loaded.length() + CHUNK);
164 | loaded.append(buffer);
165 | }
166 |
167 | contentView.setTextHighlighted(loaded);
168 | }
169 | });
170 | }
171 |
172 | private void loadDocument(final String fileContent) {
173 | scrollView.smoothScrollTo(0, 0);
174 |
175 | contentView.setFocusable(false);
176 | contentView.setOnClickListener(new View.OnClickListener() {
177 | @Override
178 | public void onClick(View view) {
179 | contentView.setFocusableInTouchMode(true);
180 | }
181 | });
182 |
183 | loaded = new StringBuilder();
184 | if (fileContent.length() > CHUNK)
185 | loadInChunks(scrollView, fileContent);
186 | else {
187 | loaded.append(fileContent);
188 | contentView.setTextHighlighted(loaded);
189 | }
190 |
191 |
192 | hidden.setVisibility(View.GONE);
193 | contentView.setVisibility(View.VISIBLE);
194 | contentView.addTextChangedListener(this);
195 | currentBuffer = contentView.getText().toString();
196 |
197 | if (isFileChangeListenerAttached()) fileChangeListener.onFileOpen();
198 | }
199 |
200 | public void save() {
201 | if (isChanged())
202 | new DocumentSaver().execute();
203 | else
204 | Toast.makeText(getContext(), "No change in file", Toast.LENGTH_SHORT).show();
205 | }
206 |
207 | private void onPostSave() {
208 | Toast.makeText(context, "Saved", Toast.LENGTH_SHORT).show();
209 |
210 | if (isFileChangeListenerAttached()) fileChangeListener.onFileSave();
211 | }
212 |
213 | private boolean isFileChangeListenerAttached() {
214 | return fileChangeListener != null;
215 | }
216 |
217 | @Override
218 | public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
219 |
220 | }
221 |
222 | @Override
223 | public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
224 | }
225 |
226 | @Override
227 | public void afterTextChanged(Editable editable) {
228 | new Handler().postDelayed(new Runnable() {
229 | @Override
230 | public void run() {
231 | currentBuffer = contentView.getText().toString();
232 |
233 | if (isFileChangeListenerAttached()) fileChangeListener.onFileChanged(isChanged());
234 | }
235 | }, 1000);
236 | }
237 |
238 |
239 | private class DocumentLoader extends AsyncTask {
240 |
241 | @Override
242 | protected String doInBackground(Void... paths) {
243 |
244 | try {
245 | BufferedReader br = new BufferedReader(new FileReader(file.getAbsoluteFile()));
246 | try {
247 | StringBuilder sb = new StringBuilder();
248 | String line = br.readLine();
249 |
250 | while (line != null) {
251 | sb.append(line);
252 | sb.append("\n");
253 | line = br.readLine();
254 | }
255 | FILE_CONTENT = sb.toString();
256 | return FILE_CONTENT;
257 | } catch (IOException ioe) {
258 | } finally {
259 | try {
260 | br.close();
261 | } catch (IOException ioe) {
262 | }
263 | }
264 | } catch (FileNotFoundException fnfe) {
265 | }
266 |
267 | return null;
268 | }
269 |
270 | @Override
271 | protected void onPostExecute(String s) {
272 | loadDocument(s);
273 | }
274 | }
275 |
276 | private class DocumentSaver extends AsyncTask {
277 |
278 | @Override
279 | protected Void doInBackground(Void... voids) {
280 | BufferedWriter output = null;
281 | String toSave = currentBuffer;
282 | try {
283 | output = new BufferedWriter(new FileWriter(file));
284 | if (FILE_CONTENT.length() > CHUNK) {
285 | toSave = currentBuffer + FILE_CONTENT.substring(loaded.length(), FILE_CONTENT.length());
286 | }
287 | output.write(toSave);
288 | } catch (IOException e) {
289 | e.printStackTrace();
290 | } finally {
291 | if (output != null) {
292 | try {
293 | output.close();
294 | FILE_CONTENT = toSave;
295 | } catch (IOException ioe) {
296 | }
297 | }
298 | }
299 |
300 | return null;
301 | }
302 |
303 | @Override
304 | protected void onPostExecute(Void v) {
305 | onPostSave();
306 | }
307 | }
308 | }
309 |
--------------------------------------------------------------------------------
/app/src/main/java/com/kodis/ui/fragment/MainFragment.java:
--------------------------------------------------------------------------------
1 | package com.kodis.ui.fragment;
2 |
3 | import android.content.DialogInterface;
4 | import android.os.Bundle;
5 | import android.support.annotation.Nullable;
6 | import android.support.design.widget.FloatingActionButton;
7 | import android.support.design.widget.Snackbar;
8 | import android.support.design.widget.TabLayout;
9 | import android.support.v4.app.Fragment;
10 | import android.support.v4.view.ViewPager;
11 | import android.support.v7.app.AlertDialog;
12 | import android.support.v7.app.AppCompatActivity;
13 | import android.support.v7.widget.Toolbar;
14 | import android.util.Log;
15 | import android.view.*;
16 | import android.widget.Toast;
17 | import com.github.angads25.filepicker.controller.DialogSelectionListener;
18 | import com.github.angads25.filepicker.model.DialogConfigs;
19 | import com.github.angads25.filepicker.model.DialogProperties;
20 | import com.github.angads25.filepicker.view.FilePickerDialog;
21 | import com.kodis.R;
22 | import com.kodis.listener.FileChangeListener;
23 | import com.kodis.listener.OnScrollListener;
24 | import com.kodis.ui.MainActivity;
25 | import com.kodis.adapter.ViewPagerAdapter;
26 | import com.kodis.utils.PermissionManager;
27 |
28 | import java.io.File;
29 | import java.io.Serializable;
30 | import java.util.List;
31 |
32 | import static android.content.ContentValues.TAG;
33 |
34 | public class MainFragment extends Fragment implements FileChangeListener, OnScrollListener, Serializable {
35 |
36 | private View rootView;
37 | private FloatingActionButton fab;
38 |
39 | private ViewPager viewPager;
40 | private TabLayout tabLayout;
41 | private ViewPagerAdapter viewPagerAdapter;
42 |
43 | private List retainedPages;
44 |
45 | @Override
46 | public void onSaveInstanceState(Bundle outState) {
47 | retainedPages = viewPagerAdapter.getFragmentList();
48 | if (retainedPages.size() > 0)
49 | outState.putSerializable("pages", (Serializable) retainedPages);
50 | super.onSaveInstanceState(outState);
51 | }
52 |
53 | @Override
54 | public void onCreate(@Nullable Bundle savedInstanceState) {
55 | super.onCreate(savedInstanceState);
56 | setRetainInstance(true);
57 |
58 | if (savedInstanceState != null) {
59 | retainedPages = (List) savedInstanceState.getSerializable("pages");
60 | }
61 | }
62 |
63 | @Override
64 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
65 | View rootView = inflater.inflate(R.layout.fragment_main, container, false);
66 | this.rootView = rootView;
67 |
68 | setupViews();
69 | setHasOptionsMenu(true);
70 | return rootView;
71 | }
72 |
73 | @Override
74 | public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
75 | inflater.inflate(R.menu.menu_editor, menu);
76 |
77 | super.onCreateOptionsMenu(menu, inflater);
78 | }
79 |
80 | @Override
81 | public boolean onOptionsItemSelected(MenuItem item) {
82 | switch (item.getItemId()) {
83 |
84 | case R.id.action_close:
85 | closeTab();
86 | return true;
87 | default:
88 | break;
89 | }
90 |
91 | return false;
92 | }
93 |
94 | private void setupViews() {
95 | Toolbar toolbar = (Toolbar) rootView.findViewById(R.id.toolbar);
96 | ((AppCompatActivity) getActivity()).setSupportActionBar(toolbar);
97 |
98 | setInitialFAB();
99 |
100 | viewPager = (ViewPager) rootView.findViewById(R.id.viewpager);
101 | viewPagerAdapter = new ViewPagerAdapter(getActivity().getSupportFragmentManager());
102 | if (retainedPages != null && retainedPages.size() > 0) {
103 | for (Fragment fragment : retainedPages) {
104 | viewPagerAdapter.addFragment(fragment);
105 | }
106 | }
107 | viewPager.setAdapter(viewPagerAdapter);
108 |
109 | tabLayout = (TabLayout) rootView.findViewById(R.id.tabs);
110 | tabLayout.setupWithViewPager(viewPager);
111 |
112 | if(viewPagerAdapter.getCount()<=0)
113 | tabLayout.setVisibility(View.GONE);
114 | PermissionManager.verifyStoragePermissions(getActivity());
115 | }
116 |
117 | @Override
118 | public void onActivityCreated(@Nullable Bundle savedInstanceState) {
119 | super.onActivityCreated(savedInstanceState);
120 |
121 | String[] files = ((MainActivity) getActivity()).getSavedFiles();
122 | if (files != null && files.length > 0) {
123 | for (String file : files) {
124 | addTab(file);
125 | }
126 | }
127 | }
128 |
129 | public void addTab(String path) {
130 | File file = new File(path);
131 | if(!file.exists())
132 | return;
133 | addTab(file);
134 | }
135 |
136 | public void addTab(File file) {
137 | if(!file.exists() || isOpen(file))
138 | return;
139 | EditorFragment fragment = new EditorFragment();
140 | Bundle bundle = new Bundle();
141 | bundle.putSerializable(EditorFragment.FILE_KEY, file);
142 | fragment.setArguments(bundle);
143 | fragment.setFileChangeListener(this);
144 |
145 | viewPagerAdapter.addFragment(fragment);
146 | viewPagerAdapter.notifyDataSetChanged();
147 | }
148 |
149 |
150 | private void setInitialFAB() {
151 | fab = (FloatingActionButton) rootView.findViewById(R.id.fab);
152 | setOpenFAB();
153 | }
154 |
155 | private void setSaveFAB() {
156 | fab.setImageResource(R.drawable.vector_save);
157 | fab.setOnClickListener(new View.OnClickListener() {
158 | @Override
159 | public void onClick(View view) {
160 | EditorFragment editorFragment = (EditorFragment) viewPagerAdapter.getItem(tabLayout.getSelectedTabPosition());
161 | if (editorFragment != null)
162 | editorFragment.save();
163 | }
164 | });
165 | fab.setOnLongClickListener(new View.OnLongClickListener() {
166 | @Override
167 | public boolean onLongClick(View view) {
168 | closeTab();
169 |
170 | if (!((EditorFragment) viewPagerAdapter.getItem(tabLayout.getSelectedTabPosition())).isChanged())
171 | setOpenFAB();
172 | return true;
173 | }
174 | });
175 | }
176 |
177 | private void setOpenFAB() {
178 | fab.setImageResource(R.drawable.vector_open);
179 | fab.setOnClickListener(new View.OnClickListener() {
180 | @Override
181 | public void onClick(View view) {
182 | openFilePicker();
183 | }
184 | });
185 | fab.setOnLongClickListener(new View.OnLongClickListener() {
186 | @Override
187 | public boolean onLongClick(View view) {
188 | Toast.makeText(getContext(), "Open File", Toast.LENGTH_SHORT).show();
189 | if (tabLayout.getTabCount() > 0) //added this
190 | closeTab();
191 | return true;
192 | }
193 | });
194 |
195 | }
196 |
197 | private void openFilePicker() {
198 | DialogProperties properties = new DialogProperties();
199 | properties.selection_mode = DialogConfigs.MULTI_MODE;
200 | properties.selection_type = DialogConfigs.FILE_SELECT;
201 | properties.root = new File(DialogConfigs.DEFAULT_DIR);
202 | properties.extensions = null;
203 |
204 | FilePickerDialog dialog = new FilePickerDialog(getActivity(), properties);
205 | dialog.setDialogSelectionListener(new DialogSelectionListener() {
206 | @Override
207 | public void onSelectedFilePaths(String[] files) {
208 | if (files.length == 0) {
209 | Toast.makeText(getContext(), "No file selected", Toast.LENGTH_SHORT).show();
210 | return;
211 | }
212 |
213 | for(String filePath : files) {
214 | File file = new File(filePath);
215 | if (isOpen(file)) {
216 | Snackbar.make(rootView, "File " + file.getName() + " already open", Snackbar.LENGTH_SHORT).show();
217 | return;
218 | }
219 | addTab(file);
220 | }
221 | }
222 | });
223 | dialog.show();
224 | }
225 |
226 | private boolean isOpen(File file) {
227 | for (int i = 0; i < tabLayout.getTabCount(); i++) {
228 | if (((EditorFragment) viewPagerAdapter.getItem(i)).getFilePath().equals(file.getPath())) {
229 | return true;
230 | }
231 | }
232 | return false;
233 | }
234 |
235 | private void removeTab(int position) {
236 | if (tabLayout.getTabCount() < 1 || position >= tabLayout.getTabCount())
237 | return;
238 |
239 | tabLayout.removeTabAt(position);
240 | viewPagerAdapter.removeTabPage(position);
241 | viewPager.setAdapter(viewPagerAdapter);
242 | if (tabLayout.getTabCount() <= 0) {
243 | tabLayout.setVisibility(View.GONE);
244 | rootView.findViewById(R.id.intro).setVisibility(View.VISIBLE);
245 | }
246 | setInitialFAB();
247 | }
248 |
249 | private void closeTab() {
250 | if (tabLayout.getTabCount() <= 0) {
251 | Toast.makeText(getContext(), "No file is open", Toast.LENGTH_SHORT).show();
252 | return;
253 | }
254 |
255 | final int position = tabLayout.getSelectedTabPosition();
256 | final EditorFragment editorFragment = (EditorFragment) viewPagerAdapter.getItem(position);
257 |
258 | if (editorFragment != null && editorFragment.isChanged()) {
259 | new AlertDialog.Builder(getActivity())
260 | .setTitle("File is changed")
261 | .setMessage("Do you want to save file before quitting?")
262 | .setPositiveButton("Save", new DialogInterface.OnClickListener() {
263 | @Override
264 | public void onClick(DialogInterface dialogInterface, int i) {
265 | editorFragment.save();
266 | removeTab(position);
267 |
268 | viewPagerAdapter.notifyDataSetChanged();
269 |
270 | }
271 | })
272 | .setNegativeButton("Quit", new DialogInterface.OnClickListener() {
273 | @Override
274 | public void onClick(DialogInterface dialogInterface, int i) {
275 | removeTab(position);
276 | viewPagerAdapter.notifyDataSetChanged();
277 |
278 | }
279 | })
280 | .setNeutralButton("Cancel", new DialogInterface.OnClickListener() {
281 | @Override
282 | public void onClick(DialogInterface dialogInterface, int i) {
283 | dialogInterface.dismiss();
284 | }
285 | }).show();
286 | } else {
287 | removeTab(position);
288 | }
289 |
290 | }
291 |
292 | public String[] getOpenFiles() {
293 | String[] files = new String[viewPagerAdapter.getCount()];
294 | for (int i = 0; i < viewPagerAdapter.getCount(); i++) {
295 | EditorFragment editorFragment = (EditorFragment) viewPagerAdapter.getItem(i);
296 | files[i] = editorFragment.getFilePath();
297 | }
298 |
299 | return files;
300 | }
301 |
302 | public EditorFragment getSelectedTab(){
303 | if(viewPagerAdapter.getCount() > 0)
304 | return (EditorFragment) viewPagerAdapter.getItem(tabLayout.getSelectedTabPosition());
305 |
306 | return null;
307 | }
308 |
309 | @Override
310 | public void onFileOpen() {
311 | tabLayout.setVisibility(View.VISIBLE);
312 | rootView.findViewById(R.id.intro).setVisibility(View.GONE);
313 | }
314 |
315 | @Override
316 | public void onFileChanged(boolean save) {
317 | if (save)
318 | setSaveFAB();
319 | else
320 | setOpenFAB();
321 | }
322 |
323 | @Override
324 | public void onFileSave() {
325 | setOpenFAB();
326 | }
327 |
328 | @Override
329 | public void onScrolled() {
330 |
331 | }
332 |
333 | @Override
334 | public void onScrolledUp() {
335 | fab.show();
336 | }
337 |
338 | @Override
339 | public void onScrolledDown() {
340 | fab.hide();
341 | }
342 |
343 | @Override
344 | public void onDestroy() {
345 | Log.d(TAG, "onDestroy");
346 | try {
347 | super.onDestroy();
348 | } catch (NullPointerException npe) {
349 | Log.e(TAG, "NPE: Bug workaround");
350 | }
351 | }
352 |
353 | }
354 |
--------------------------------------------------------------------------------
/app/src/main/java/com/kodis/ui/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.kodis.ui;
2 |
3 | import android.content.Context;
4 | import android.content.DialogInterface;
5 | import android.content.SharedPreferences;
6 | import android.os.Bundle;
7 | import android.support.design.widget.FloatingActionButton;
8 | import android.support.design.widget.NavigationView;
9 | import android.support.v4.view.GravityCompat;
10 | import android.support.v4.widget.DrawerLayout;
11 | import android.support.v7.app.ActionBar;
12 | import android.support.v7.app.AlertDialog;
13 | import android.support.v7.app.AppCompatActivity;
14 | import android.support.v7.app.AppCompatDelegate;
15 | import android.support.v7.widget.AppCompatCheckBox;
16 | import android.view.Menu;
17 | import android.view.MenuItem;
18 | import android.view.View;
19 | import android.widget.*;
20 | import com.github.angads25.filepicker.controller.DialogSelectionListener;
21 | import com.github.angads25.filepicker.model.DialogConfigs;
22 | import com.github.angads25.filepicker.model.DialogProperties;
23 | import com.github.angads25.filepicker.view.FilePickerDialog;
24 | import com.kodis.R;
25 | import com.kodis.holder.IconTreeItemHolder;
26 | import com.kodis.listener.OnDirectoryBuiltListener;
27 | import com.kodis.ui.fragment.EditorFragment;
28 | import com.kodis.ui.fragment.MainFragment;
29 | import com.kodis.utils.DirectoryBuilder;
30 | import com.kodis.utils.ExtensionManager;
31 | import com.unnamed.b.atv.model.TreeNode;
32 | import com.unnamed.b.atv.view.AndroidTreeView;
33 | import org.json.JSONArray;
34 | import org.json.JSONException;
35 |
36 | import java.io.File;
37 | import java.io.FilenameFilter;
38 | import java.io.IOException;
39 | import java.util.regex.Pattern;
40 |
41 |
42 | public class MainActivity extends AppCompatActivity {
43 | private TextView projectStructure, headerProject;
44 | private DrawerLayout drawerLayout;
45 | private MainFragment mainFragment;
46 |
47 | @Override
48 | protected void onCreate(Bundle savedInstanceState) {
49 | super.onCreate(savedInstanceState);
50 | setContentView(R.layout.activity_main);
51 | AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
52 |
53 | if (savedInstanceState == null)
54 | mainFragment = (MainFragment) getSupportFragmentManager().findFragmentById(R.id.mainFragment);
55 | else
56 | mainFragment = (MainFragment) getSupportFragmentManager().getFragment(savedInstanceState, "mainFragment");
57 |
58 | drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
59 | NavigationView navigationView = (NavigationView) findViewById(R.id.navView);
60 | headerProject = (TextView) navigationView.getHeaderView(0).findViewById(R.id.header_project_name);
61 | projectStructure = (TextView) findViewById(R.id.project_info);
62 |
63 | ActionBar actionBar = getSupportActionBar();
64 | actionBar.setHomeAsUpIndicator(R.drawable.vector_menu);
65 | actionBar.setDisplayHomeAsUpEnabled(true);
66 |
67 | drawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() {
68 | @Override
69 | public void onDrawerSlide(View drawerView, float slideOffset) {
70 |
71 | }
72 |
73 | @Override
74 | public void onDrawerOpened(View drawerView) {
75 | EditorFragment editorFragment = mainFragment.getSelectedTab();
76 | if (editorFragment != null) {
77 | updateProjectStructure(editorFragment.getFilePath());
78 | updateNavViews(editorFragment.getFileName(), editorFragment.getFileInfo());
79 | updateExtension(editorFragment.getFileName());
80 | }
81 | }
82 |
83 | @Override
84 | public void onDrawerClosed(View drawerView) {
85 |
86 | }
87 |
88 | @Override
89 | public void onDrawerStateChanged(int newState) {
90 |
91 | }
92 | });
93 | }
94 |
95 | @Override
96 | public void onSaveInstanceState(Bundle outState) {
97 | super.onSaveInstanceState(outState);
98 | getSupportFragmentManager().putFragment(outState, "mainFragment", mainFragment);
99 | }
100 |
101 | @Override
102 | public boolean onCreateOptionsMenu(Menu menu) {
103 | // Inflate the menu; this adds items to the action bar if it is present.
104 | getMenuInflater().inflate(R.menu.menu_main, menu);
105 | return true;
106 | }
107 |
108 | @Override
109 | public boolean onOptionsItemSelected(MenuItem item) {
110 | // Handle action bar item clicks here. The action bar will
111 | // automatically handle clicks on the Home/Up button, so long
112 | // as you specify a parent activity in AndroidManifest.xml.
113 | int id = item.getItemId();
114 | switch (id) {
115 |
116 | case android.R.id.home:
117 | drawerLayout.openDrawer(GravityCompat.START);
118 | return true;
119 | case R.id.action_add:
120 | addFile();
121 | return true;
122 | }
123 |
124 | return super.onOptionsItemSelected(item);
125 | }
126 |
127 | private void addFile() {
128 | final Context context = this;
129 |
130 | AlertDialog.Builder builder = new AlertDialog.Builder(this);
131 | builder.setTitle("Create New File : ");
132 |
133 | View root = getLayoutInflater().inflate(R.layout.dialog_new_file, null);
134 | builder.setView(root);
135 |
136 | final TextView dir = (TextView) root.findViewById(R.id.dir_path);
137 | final EditText file_name = (EditText) root.findViewById(R.id.file_name);
138 | final FloatingActionButton fab = (FloatingActionButton) root.findViewById(R.id.open);
139 | final AppCompatCheckBox isFolder = (AppCompatCheckBox) root.findViewById(R.id.isFolder);
140 | dir.setText(DialogConfigs.DEFAULT_DIR);
141 |
142 | View.OnClickListener openDir = new View.OnClickListener() {
143 | @Override
144 | public void onClick(View view) {
145 | DialogProperties properties = new DialogProperties();
146 | properties.selection_mode = DialogConfigs.SINGLE_MODE;
147 | properties.selection_type = DialogConfigs.DIR_SELECT;
148 | properties.root = new File(DialogConfigs.DEFAULT_DIR);
149 | properties.extensions = null;
150 |
151 | final FilePickerDialog dialog = new FilePickerDialog(context, properties);
152 | dialog.setDialogSelectionListener(new DialogSelectionListener() {
153 | @Override
154 | public void onSelectedFilePaths(String[] files) {
155 | if (files.length == 0) {
156 | Toast.makeText(context, "No directory selected", Toast.LENGTH_SHORT).show();
157 | return;
158 | }
159 | dir.setText(files[0]);
160 | }
161 | });
162 | dialog.show();
163 | }
164 | };
165 |
166 | fab.setOnClickListener(openDir);
167 | dir.setOnClickListener(openDir);
168 | builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
169 | @Override
170 | public void onClick(DialogInterface dialogInterface, int i) {
171 | if(isFolder.isChecked()){
172 | if (!Pattern.compile("[_a-zA-Z0-9\\-]+").matcher(file_name.getText().toString()).matches()) {
173 | Toast.makeText(context, "Invalid Folder name", Toast.LENGTH_SHORT).show();
174 | return;
175 | }
176 |
177 | File file = new File(dir.getText().toString() + File.separator + file_name.getText().toString());
178 | if (!file.exists()) {
179 | boolean created = file.mkdir();
180 | if(created)
181 | Toast.makeText(context, "New folder created", Toast.LENGTH_SHORT).show();
182 | else
183 | Toast.makeText(context, "Couldn't create new folder : " + file.getAbsolutePath(), Toast.LENGTH_SHORT).show();
184 |
185 | } else {
186 | Toast.makeText(context, "Folder already present", Toast.LENGTH_SHORT).show();
187 | }
188 |
189 | return;
190 | }
191 |
192 |
193 | if (!Pattern.compile("[_a-zA-Z0-9\\-\\.]+").matcher(file_name.getText().toString()).matches()) {
194 | Toast.makeText(context, "Invalid File name", Toast.LENGTH_SHORT).show();
195 | return;
196 | }
197 |
198 | File file = new File(dir.getText().toString() + File.separator + file_name.getText().toString());
199 | if (!file.exists()) {
200 | try {
201 | file.createNewFile();
202 | Toast.makeText(context, "New file created", Toast.LENGTH_SHORT).show();
203 | mainFragment.addTab(file.getPath());
204 | } catch (IOException e) {
205 | Toast.makeText(context, "Couldn't create new file : " + file.getAbsolutePath(), Toast.LENGTH_SHORT).show();
206 | e.printStackTrace();
207 | }
208 | } else {
209 | mainFragment.addTab(file.getPath());
210 | }
211 |
212 | }
213 | });
214 | builder.show();
215 |
216 |
217 | }
218 |
219 | public String[] getSavedFiles() {
220 | SharedPreferences sharedPref = getPreferences(Context.MODE_PRIVATE);
221 | String json = sharedPref.getString("files", null);
222 | String[] files = null;
223 | if (json != null) {
224 | try {
225 | JSONArray a = new JSONArray(json);
226 | files = new String[a.length()];
227 | for (int i = 0; i < a.length(); i++) {
228 | files[i] = a.optString(i);
229 | }
230 | } catch (JSONException e) {
231 | e.printStackTrace();
232 | }
233 | }
234 |
235 | return files;
236 | }
237 |
238 | @Override
239 | protected void onPause() {
240 |
241 | if (mainFragment == null) {
242 | super.onPause();
243 | return;
244 | }
245 |
246 | SharedPreferences sharedPref = getPreferences(Context.MODE_PRIVATE);
247 | String[] files = mainFragment.getOpenFiles();
248 | SharedPreferences.Editor editor = sharedPref.edit();
249 | JSONArray array = new JSONArray();
250 |
251 | for (int i = 0; i < files.length; i++) {
252 | array.put(files[i]);
253 | }
254 |
255 | if (files.length > 0) {
256 | editor.putString("files", array.toString());
257 | } else {
258 | editor.putString("files", null);
259 | }
260 | editor.commit();
261 |
262 | super.onPause();
263 | }
264 |
265 | private void updateProjectStructure(String path){
266 | final LinearLayout linearLayout = (LinearLayout) findViewById(R.id.rootLayout);
267 | linearLayout.removeAllViews();
268 |
269 | DirectoryBuilder directoryBuilder = new DirectoryBuilder(this, path);
270 | directoryBuilder.setOnDirectoryBuiltListener(new OnDirectoryBuiltListener() {
271 | @Override
272 | public void onDirectoryBuilt(AndroidTreeView treeView) {
273 | treeView.setDefaultAnimation(true);
274 | treeView.setDefaultViewHolder(IconTreeItemHolder.class);
275 | treeView.setDefaultContainerStyle(R.style.TreeNodeStyle);
276 | treeView.setDefaultNodeClickListener(new TreeNode.TreeNodeClickListener() {
277 | @Override
278 | public void onClick(TreeNode node, Object value) {
279 | IconTreeItemHolder.FileTreeItem fileTreeItem = (IconTreeItemHolder.FileTreeItem) value;
280 | if(node.isLeaf()) {
281 | File file = new File(fileTreeItem.path);
282 |
283 | if(!ExtensionManager.isBinaryFile(file)){
284 | mainFragment.addTab(file);
285 | } else {
286 | Toast.makeText(getApplicationContext(), "Not a code file", Toast.LENGTH_SHORT).show();
287 | }
288 |
289 | drawerLayout.closeDrawer(GravityCompat.START);
290 | }
291 | }
292 | });
293 |
294 | linearLayout.addView(treeView.getView());
295 | }
296 | });
297 | directoryBuilder.start();
298 |
299 |
300 | }
301 |
302 | private void updateNavViews(String header, String projectInfo) {
303 | headerProject.setText(header);
304 | projectStructure.setText(projectInfo);
305 | }
306 |
307 | private void updateExtension(String extension) {
308 | ImageView extImage = (ImageView) findViewById(R.id.extImage);
309 |
310 | if (extImage == null)
311 | return;
312 |
313 | extImage.setVisibility(View.VISIBLE);
314 | extImage.setImageResource(ExtensionManager.getIcon(extension));
315 | }
316 |
317 | }
318 |
--------------------------------------------------------------------------------
/app/src/main/java/com/kodis/ui/component/ShaderEditor.java:
--------------------------------------------------------------------------------
1 | package com.kodis.ui.component;
2 |
3 |
4 | import android.content.Context;
5 | import android.graphics.Canvas;
6 | import android.graphics.Color;
7 | import android.graphics.Paint;
8 | import android.os.Handler;
9 | import android.support.v4.content.ContextCompat;
10 | import android.text.*;
11 | import android.text.style.BackgroundColorSpan;
12 | import android.text.style.ForegroundColorSpan;
13 | import android.text.style.ReplacementSpan;
14 | import android.util.AttributeSet;
15 | import android.widget.EditText;
16 | import com.kodis.R;
17 |
18 | import java.util.regex.Matcher;
19 | import java.util.regex.Pattern;
20 |
21 | public class ShaderEditor extends EditText {
22 | private static final Pattern PATTERN_LINE = Pattern.compile(
23 | ".*\\n");
24 | private static final Pattern PATTERN_NUMBERS = Pattern.compile(
25 | "\\b(\\d*[.]?\\d+)\\b");
26 | private static final Pattern PATTERN_PREPROCESSOR = Pattern.compile(
27 | "^[\t ]*(#define|#undef|#if|#ifdef|#ifndef|#else|#elif|#endif|" +
28 | "#error|#pragma|#extension|#version|#line|#include)\\b",
29 | Pattern.MULTILINE);
30 | private static final Pattern PATTERN_KEYWORDS = Pattern.compile(
31 | "\\b(" +
32 | "and|or|xor|for|do|while|foreach|as|return|die|exit|if|then|else|" +
33 | "elseif|new|delete|try|throw|catch|finally|class|function|string|" +
34 | "array|object|resource|var|bool|boolean|int|integer|float|double|" +
35 | "real|string|array|global|const|static|public|private|protected|" +
36 | "published|extends|switch|true|false|null|void|this|self|struct|" +
37 | "char|signed|unsigned|short|long|True|False|a|address|app|applet|" +
38 | "area|b|base|basefont|bgsound|big|blink|blockquote|body|br|button|" +
39 | "caption|center|cite|code|col|colgroup|comment|dd|del|dfn|dir|div|" +
40 | "dl|dt|em|embed|fieldset|font|form|frame|frameset|h1|h2|h3|h4|h5|h6|" +
41 | "head|hr|html|htmlplus|hype|i|iframe|img|input|ins|del|isindex|kbd|" +
42 | "label|legend|li|link|listing|map|marquee|menu|meta|multicol|nobr|" +
43 | "noembed|noframes|noscript|ol|option|p|param|plaintext|pre|s|samp|" +
44 | "script|select|small|sound|spacer|span|strike|strong|style|sub|sup|" +
45 | "table|tbody|td|textarea|tfoot|th|thead|title|tr|tt|u|var|wbr|xmp" +
46 | ")\\b");
47 | private static final Pattern PATTERN_BUILTINS = Pattern.compile(
48 | "\\b(radians|degrees|sin|cos|tan|asin|acos|atan|pow|" +
49 | "exp|log|sqrt|inversesqrt|abs|sign|floor|ceil|fract|mod|" +
50 | "min|max|length|Math|System|out|printf|print|println|" +
51 | "console|Arrays|Array|vector|List|list|ArrayList|Map|HashMap|" +
52 | "dict|java|util|lang|import|from|in|charset|lang|href|name|" +
53 | "target|onclick|onmouseover|onmouseout|accesskey|code|codebase|" +
54 | "width|height|align|vspace|hspace|border|name|archive|mayscript|" +
55 | "alt|shape|coords|target|nohref|size|color|face|src|loop|bgcolor|" +
56 | "background|text|vlink|alink|bgproperties|topmargin|leftmargin|" +
57 | "marginheight|marginwidth|onload|onunload|onfocus|onblur|stylesrc|" +
58 | "scroll|clear|type|value|valign|span|compact|pluginspage|pluginurl|" +
59 | "hidden|autostart|playcount|volume|controls|controller|mastersound|" +
60 | "starttime|endtime|point-size|weight|action|method|enctype|onsubmit|" +
61 | "onreset|scrolling|noresize|frameborder|bordercolor|cols|rows|" +
62 | "framespacing|border|noshade|longdesc|ismap|usemap|lowsrc|naturalsizeflag|" +
63 | "nosave|dynsrc|controls|start|suppress|maxlength|checked|language|onchange|" +
64 | "onkeypress|onkeyup|onkeydown|autocomplete|prompt|for|rel|rev|media|direction|" +
65 | "behaviour|scrolldelay|scrollamount|http-equiv|content|gutter|defer|event|" +
66 | "multiple|readonly|cellpadding|cellspacing|rules|bordercolorlight|" +
67 | "bordercolordark|summary|colspan|rowspan|nowrap|halign|disabled|accesskey|" +
68 | "tabindex|id)\\b");
69 | private static final Pattern PATTERN_COMMENTS = Pattern.compile(
70 | "/\\*(?:.|[\\n\\r])*?\\*/|//.*");
71 | private static final Pattern PATTERN_TRAILING_WHITE_SPACE = Pattern.compile(
72 | "[\\t ]+$",
73 | Pattern.MULTILINE);
74 | private final Handler updateHandler = new Handler();
75 | private OnTextChangedListener onTextChangedListener;
76 | private int updateDelay = 1000;
77 | private int errorLine = 0;
78 | private boolean dirty = false;
79 | private boolean modified = true;
80 | private int colorVariable;
81 | private int colorNumber;
82 | private int colorKeyword;
83 | private int colorBuiltin;
84 | private int colorComment;
85 | private final Runnable updateRunnable =
86 | new Runnable() {
87 | @Override
88 | public void run() {
89 | Editable e = getText();
90 |
91 | if (onTextChangedListener != null)
92 | onTextChangedListener.onTextChanged(
93 | e.toString());
94 |
95 | highlightWithoutChange(e);
96 | }
97 | };
98 | private int tabWidthInCharacters = 0;
99 | private int tabWidth = 0;
100 |
101 | public ShaderEditor(Context context) {
102 | super(context);
103 |
104 | init(context);
105 | }
106 |
107 | public ShaderEditor(Context context, AttributeSet attrs) {
108 | super(context, attrs);
109 |
110 | init(context);
111 | }
112 |
113 | private static void clearSpans(Editable e) {
114 | // remove foreground color spans
115 | {
116 | ForegroundColorSpan spans[] = e.getSpans(
117 | 0,
118 | e.length(),
119 | ForegroundColorSpan.class);
120 |
121 | for (int n = spans.length; n-- > 0; )
122 | e.removeSpan(spans[n]);
123 | }
124 |
125 | // remove background color spans
126 | {
127 | BackgroundColorSpan spans[] = e.getSpans(
128 | 0,
129 | e.length(),
130 | BackgroundColorSpan.class);
131 |
132 | for (int n = spans.length; n-- > 0; )
133 | e.removeSpan(spans[n]);
134 | }
135 | }
136 |
137 | public void setOnTextChangedListener(OnTextChangedListener listener) {
138 | onTextChangedListener = listener;
139 | }
140 |
141 | public void setUpdateDelay(int ms) {
142 | updateDelay = ms;
143 | }
144 |
145 | public void setTabWidth(int characters) {
146 | if (tabWidthInCharacters == characters)
147 | return;
148 |
149 | tabWidthInCharacters = characters;
150 | tabWidth = Math.round(
151 | getPaint().measureText("m") *
152 | characters);
153 | }
154 |
155 | public boolean hasErrorLine() {
156 | return errorLine > 0;
157 | }
158 |
159 | public void setErrorLine(int line) {
160 | errorLine = line;
161 | }
162 |
163 | public void updateHighlighting() {
164 | highlightWithoutChange(getText());
165 | }
166 |
167 | public boolean isModified() {
168 | return dirty;
169 | }
170 |
171 | public void setTextHighlighted(CharSequence text) {
172 | if (text == null)
173 | text = "";
174 |
175 | cancelUpdate();
176 |
177 | errorLine = 0;
178 | dirty = false;
179 |
180 | modified = false;
181 | setText(highlight(new SpannableStringBuilder(text)));
182 | modified = true;
183 |
184 | if (onTextChangedListener != null)
185 | onTextChangedListener.onTextChanged(text.toString());
186 | }
187 |
188 | public String getCleanText() {
189 | return PATTERN_TRAILING_WHITE_SPACE
190 | .matcher(getText())
191 | .replaceAll("");
192 | }
193 |
194 | public void insertTab() {
195 | int start = getSelectionStart();
196 | int end = getSelectionEnd();
197 |
198 | getText().replace(
199 | Math.min(start, end),
200 | Math.max(start, end),
201 | "\t",
202 | 0,
203 | 1);
204 | }
205 |
206 | private void init(Context context) {
207 | setHorizontallyScrolling(true);
208 |
209 | setFilters(new InputFilter[]{
210 | new InputFilter() {
211 | @Override
212 | public CharSequence filter(
213 | CharSequence source,
214 | int start,
215 | int end,
216 | Spanned dest,
217 | int dstart,
218 | int dend) {
219 | if (modified &&
220 | end - start == 1 &&
221 | start < source.length() &&
222 | dstart < dest.length()) {
223 | char c = source.charAt(start);
224 |
225 | if (c == '\n')
226 | return autoIndent(
227 | source,
228 | dest,
229 | dstart,
230 | dend);
231 | }
232 |
233 | return source;
234 | }
235 | }});
236 |
237 | addTextChangedListener(
238 | new TextWatcher() {
239 | private int start = 0;
240 | private int count = 0;
241 |
242 | @Override
243 | public void onTextChanged(
244 | CharSequence s,
245 | int start,
246 | int before,
247 | int count) {
248 | this.start = start;
249 | this.count = count;
250 | }
251 |
252 | @Override
253 | public void beforeTextChanged(
254 | CharSequence s,
255 | int start,
256 | int count,
257 | int after) {
258 | }
259 |
260 | @Override
261 | public void afterTextChanged(Editable e) {
262 | cancelUpdate();
263 | convertTabs(e, start, count);
264 |
265 | if (!modified)
266 | return;
267 |
268 | dirty = true;
269 | updateHandler.postDelayed(
270 | updateRunnable,
271 | updateDelay);
272 | }
273 | });
274 |
275 | setSyntaxColors(context);
276 | /*setUpdateDelay(
277 | ShaderEditorApplication
278 | .preferences
279 | .getUpdateDelay() );
280 | setTabWidth(
281 | ShaderEditorApplication
282 | .preferences
283 | .getTabWidth() );*/
284 | }
285 |
286 | private void setSyntaxColors(Context context) {
287 | colorVariable = ContextCompat.getColor(context, R.color.syntax_variable);
288 | colorNumber = ContextCompat.getColor(
289 | context,
290 | R.color.syntax_number);
291 | colorKeyword = ContextCompat.getColor(
292 | context,
293 | R.color.syntax_keyword);
294 | colorBuiltin = ContextCompat.getColor(
295 | context,
296 | R.color.syntax_builtin);
297 | colorComment = ContextCompat.getColor(
298 | context,
299 | R.color.syntax_comment);
300 |
301 | }
302 |
303 | private void cancelUpdate() {
304 | updateHandler.removeCallbacks(updateRunnable);
305 | }
306 |
307 | private void highlightWithoutChange(Editable e) {
308 | modified = false;
309 | highlight(e);
310 | modified = true;
311 | }
312 |
313 | private Editable highlight(Editable e) {
314 | try {
315 | // don't use e.clearSpans() because it will
316 | // remove too much
317 | clearSpans(e);
318 |
319 | if (e.length() == 0)
320 | return e;
321 |
322 | for (Matcher m = PATTERN_NUMBERS.matcher(e); m.find(); )
323 | e.setSpan(new ForegroundColorSpan(colorNumber), m.start(), m.end(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
324 |
325 | for (Matcher m = PATTERN_PREPROCESSOR.matcher(e); m.find(); )
326 | e.setSpan(new ForegroundColorSpan(colorBuiltin), m.start(), m.end(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
327 |
328 | for (Matcher m = PATTERN_KEYWORDS.matcher(e); m.find(); )
329 | e.setSpan(new ForegroundColorSpan(colorKeyword), m.start(), m.end(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
330 |
331 | for (Matcher m = PATTERN_BUILTINS.matcher(e); m.find(); )
332 | e.setSpan(new ForegroundColorSpan(colorBuiltin), m.start(), m.end(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
333 |
334 | for(Matcher m = Pattern.compile("\\$\\w+").matcher(e); m.find(); ) {
335 | e.setSpan(new ForegroundColorSpan(colorVariable), m.start(), m.end(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
336 | }
337 |
338 | for(Matcher m = Pattern.compile("\\\"(.*?)\\\"|\\\'(.*?)\\\'").matcher(e); m.find(); ) {
339 | ForegroundColorSpan spans[] = e.getSpans(m.start(), m.end(), ForegroundColorSpan.class);
340 | for(ForegroundColorSpan span : spans)
341 | e.removeSpan(span);
342 | e.setSpan(new ForegroundColorSpan(Color.parseColor("#81C784")), m.start(), m.end(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
343 | }
344 |
345 | for (Matcher m = PATTERN_COMMENTS.matcher(e); m.find(); ) {
346 | ForegroundColorSpan spans[] = e.getSpans(m.start(), m.end(), ForegroundColorSpan.class);
347 | for(ForegroundColorSpan span : spans)
348 | e.removeSpan(span);
349 | e.setSpan(new ForegroundColorSpan(colorComment), m.start(), m.end(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
350 | }
351 | } catch (IllegalStateException ex) {
352 | // raised by Matcher.start()/.end() when
353 | // no successful match has been made what
354 | // shouldn't ever happen because of find()
355 | }
356 |
357 | return e;
358 | }
359 |
360 | private CharSequence autoIndent(
361 | CharSequence source,
362 | Spanned dest,
363 | int dstart,
364 | int dend) {
365 | String indent = "";
366 | int istart = dstart - 1;
367 |
368 | // find start of this line
369 | boolean dataBefore = false;
370 | int pt = 0;
371 |
372 | for (; istart > -1; --istart) {
373 | char c = dest.charAt(istart);
374 |
375 | if (c == '\n')
376 | break;
377 |
378 | if (c != ' ' &&
379 | c != '\t') {
380 | if (!dataBefore) {
381 | // indent always after those characters
382 | if (c == '{' ||
383 | c == '+' ||
384 | c == '-' ||
385 | c == '*' ||
386 | c == '/' ||
387 | c == '%' ||
388 | c == '^' ||
389 | c == '=')
390 | pt--;
391 |
392 | dataBefore = true;
393 | }
394 |
395 | // parenthesis counter
396 | if (c == '(')
397 | --pt;
398 | else if (c == ')')
399 | ++pt;
400 | }
401 | }
402 |
403 | // copy indent of this line into the next
404 | if (istart > -1) {
405 | char charAtCursor = dest.charAt(dstart);
406 | int iend;
407 |
408 | for (iend = ++istart;
409 | iend < dend;
410 | ++iend) {
411 | char c = dest.charAt(iend);
412 |
413 | // auto expand comments
414 | if (charAtCursor != '\n' &&
415 | c == '/' &&
416 | iend + 1 < dend &&
417 | dest.charAt(iend) == c) {
418 | iend += 2;
419 | break;
420 | }
421 |
422 | if (c != ' ' &&
423 | c != '\t')
424 | break;
425 | }
426 |
427 | indent += dest.subSequence(istart, iend);
428 | }
429 |
430 | // add new indent
431 | if (pt < 0)
432 | indent += "\t";
433 |
434 | // append white space of previous line and new indent
435 | return source + indent;
436 | }
437 |
438 | private void convertTabs(Editable e, int start, int count) {
439 | if (tabWidth < 1)
440 | return;
441 |
442 | String s = e.toString();
443 |
444 | for (int stop = start + count;
445 | (start = s.indexOf("\t", start)) > -1 && start < stop;
446 | ++start)
447 | e.setSpan(
448 | new TabWidthSpan(),
449 | start,
450 | start + 1,
451 | Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
452 | }
453 |
454 | public interface OnTextChangedListener {
455 | void onTextChanged(String text);
456 | }
457 |
458 | private class TabWidthSpan extends ReplacementSpan {
459 | @Override
460 | public int getSize(
461 | Paint paint,
462 | CharSequence text,
463 | int start,
464 | int end,
465 | Paint.FontMetricsInt fm) {
466 | return tabWidth;
467 | }
468 |
469 | @Override
470 | public void draw(
471 | Canvas canvas,
472 | CharSequence text,
473 | int start,
474 | int end,
475 | float x,
476 | int top,
477 | int y,
478 | int bottom,
479 | Paint paint) {
480 | }
481 | }
482 | }
483 |
--------------------------------------------------------------------------------