24 Commits
v1.10 ... v1.13

Author SHA1 Message Date
4d26fa7f3d Updated translations from crowdin 2014-10-18 15:47:54 +02:00
4b16119b53 Version 1.13 2014-10-18 15:20:23 +02:00
bf4dcd51b8 Merge pull request #41 from RyDroid/gitignore
Adding some rules to .gitignore
2014-10-18 15:02:54 +02:00
e52a3cb9f5 Merge pull request #42 from RyDroid/mime-types
Adding and reorganizing a little mime types
2014-10-18 15:02:27 +02:00
5702a6c24f Adding and reorganizing a little mime types 2014-10-18 01:29:07 +02:00
ffcd50616e Adding some rules to .gitignore 2014-10-18 01:25:09 +02:00
46842a5343 Version 1.12 2014-10-12 18:11:19 +02:00
df5a302129 Some fixes 2014-10-07 18:55:18 +02:00
04faa104ed Merge pull request #30 from DF1E/master
update LinuxShell from SimpleExplorer
2014-10-02 19:03:37 +02:00
a20f93f0f6 remove old info from LinuxShell 2014-10-02 18:33:44 +02:00
564e55385f update LinuxShell from SimpleExplorer
https://github.com/DF1E/SimpleExplorer/blob/master/src/com/dnielfe/manager/commands/RootCommands.java
2014-10-02 18:25:12 +02:00
2c62965a02 Version 1.11 2014-09-30 20:01:43 +02:00
eaab21069b Some minor but major changes
New advance settings screen, new setting to ignore the back button, new
behaviour of the left and right panels, now the app highlightes all the
visible text
2014-09-28 10:55:13 +02:00
2dbab5220a fixed a visual issue 2014-09-27 17:48:57 +02:00
520c4c53c0 Readded the jar, and fixed a keyboard issue 2014-09-27 15:20:05 +02:00
2fefb3963c Update README.md 2014-09-27 14:49:52 +02:00
e0eb01168e Update README.md 2014-09-26 12:53:13 +02:00
30294f72b5 Update README.md 2014-09-26 12:07:42 +02:00
ed621d369e Update README.md 2014-09-26 12:06:32 +02:00
6507331360 Update README.md 2014-09-26 11:40:00 +02:00
ec4742149b Update README.md 2014-09-26 11:36:05 +02:00
97dd211dc2 Removed the jar "juniversalchardet" 2014-09-26 11:24:19 +02:00
fc67e5930e Added the "pro" module
The pro version doesn't have ads so it doesn't require crashlytics and
play-services
2014-09-25 20:00:25 +02:00
44d4a2828b Small change 2014-09-25 14:31:27 +02:00
336 changed files with 12771 additions and 12173 deletions

5
.gitignore vendored
View File

@ -11,6 +11,7 @@
# Generated files # Generated files
bin/ bin/
gen/ gen/
doc/
# Proguard folder generated by Eclipse # Proguard folder generated by Eclipse
proguard/ proguard/
@ -18,8 +19,6 @@ proguard/
# Log Files # Log Files
*.log *.log
app-pro/
# Built application files # Built application files
/*/build/ /*/build/
@ -59,3 +58,5 @@ local.properties
.Trashes .Trashes
ehthumbs.db ehthumbs.db
Thumbs.db Thumbs.db
*~
#*#

174
.idea/codeStyleSettings.xml generated Normal file
View File

@ -0,0 +1,174 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectCodeStyleSettingsManager">
<option name="PER_PROJECT_SETTINGS">
<value>
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
<option name="PACKAGES_TO_USE_IMPORT_ON_DEMAND">
<value />
</option>
<option name="IMPORT_LAYOUT_TABLE">
<value>
<package name="android" withSubpackages="true" static="false" />
<emptyLine />
<package name="com" withSubpackages="true" static="false" />
<emptyLine />
<package name="junit" withSubpackages="true" static="false" />
<emptyLine />
<package name="net" withSubpackages="true" static="false" />
<emptyLine />
<package name="org" withSubpackages="true" static="false" />
<emptyLine />
<package name="java" withSubpackages="true" static="false" />
<emptyLine />
<package name="javax" withSubpackages="true" static="false" />
<emptyLine />
<package name="" withSubpackages="true" static="false" />
<emptyLine />
<package name="" withSubpackages="true" static="true" />
<emptyLine />
</value>
</option>
<option name="RIGHT_MARGIN" value="100" />
<AndroidXmlCodeStyleSettings>
<option name="USE_CUSTOM_SETTINGS" value="true" />
</AndroidXmlCodeStyleSettings>
<XML>
<option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" />
</XML>
<codeStyleSettings language="XML">
<option name="FORCE_REARRANGE_MODE" value="1" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
<arrangement>
<rules>
<rule>
<match>
<AND>
<NAME>xmlns:android</NAME>
<XML_NAMESPACE>Namespace:</XML_NAMESPACE>
</AND>
</match>
</rule>
<rule>
<match>
<AND>
<NAME>xmlns:.*</NAME>
<XML_NAMESPACE>Namespace:</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
<rule>
<match>
<AND>
<NAME>.*:id</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
<rule>
<match>
<AND>
<NAME>.*:name</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
<rule>
<match>
<AND>
<NAME>name</NAME>
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
<rule>
<match>
<AND>
<NAME>style</NAME>
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
<rule>
<match>
<AND>
<NAME>.*:layout_width</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
<rule>
<match>
<AND>
<NAME>.*:layout_height</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
<rule>
<match>
<AND>
<NAME>.*:layout_.*</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
<rule>
<match>
<AND>
<NAME>.*:width</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
<rule>
<match>
<AND>
<NAME>.*:height</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_NAMESPACE>.*</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</rules>
</arrangement>
</codeStyleSettings>
</value>
</option>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default (1)" />
</component>
</project>

View File

@ -4,6 +4,11 @@
<option name="myLocal" value="false" /> <option name="myLocal" value="false" />
<inspection_tool class="AndroidLintMissingQuantity" enabled="false" level="ERROR" enabled_by_default="false" /> <inspection_tool class="AndroidLintMissingQuantity" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="AndroidLintMissingTranslation" enabled="false" level="ERROR" enabled_by_default="false" /> <inspection_tool class="AndroidLintMissingTranslation" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="AndroidLintNewApi" enabled="false" level="ERROR" enabled_by_default="false" />
<inspection_tool class="AndroidLintUnusedQuantity" enabled="false" level="WARNING" enabled_by_default="false" /> <inspection_tool class="AndroidLintUnusedQuantity" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="LoggerInitializedWithForeignClass" enabled="false" level="WARNING" enabled_by_default="false">
<option name="loggerClassName" value="org.apache.log4j.Logger,org.slf4j.LoggerFactory,org.apache.commons.logging.LogFactory,java.util.logging.Logger" />
<option name="loggerFactoryMethodName" value="getLogger,getLogger,getLog,getLogger" />
</inspection_tool>
</profile> </profile>
</component> </component>

View File

@ -1,38 +1,32 @@
# Turbo Editor # Turbo Editor
[![Crowdin](https://d322cqt584bo4o.cloudfront.net/turbo-client/localized.png)](https://crowdin.com/project/turbo-client) [![Crowdin](https://d322cqt584bo4o.cloudfront.net/turbo-client/localized.png)](https://crowdin.com/project/turbo-client)
Simple, powerful and Open Source file editor for Android licensed under the GPLv3 license. Simple, powerful and Open Source text editor for Android licensed under the GPLv3 license.
### Download
[![Play Store](http://developer.android.com/images/brand/en_generic_rgb_wo_60.png)](http://play.google.com/store/apps/details?id=com.maskyn.fileeditorpro)
[![F-Droid](https://lh5.googleusercontent.com/-zezQqsBye0c/VCUpPVjcKEI/AAAAAAAAAIQ/HbcG5f1qMIw/w129-h45-no/getitonfdroid.png)](https://f-droid.org/repository/browse/?fdid=com.maskyn.fileeditorpro)
### Contribute ### Contribute
You can contribute to this project in many ways: You can contribute to this project in many ways:
* Browse our issues, comment on proposals, report bugs. * Browse our issues, comment on proposals, report bugs.
* Clone the balanced-dashboard repo, make some changes according to our
development guidelines and issue a pull-request with your changes.
* Help to translate the application on [Crowdin][crowdin] * Help to translate the application on [Crowdin][crowdin]
* [Donate][donate] * Be a part of the Google Plus [Community][community googleplus]
* Discuss with us on [XDA thread][xda thread]
------ * Help to maintain this project active and [Donate][donate]
### Development guidelines
1. Fork it (`git clone git://github.com/vmihalachi/turbo-editor.git`)
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Write your code
4. Commit your changes (`git commit -am 'Add some feature'`)
5. Push to the branch (`git push origin my-new-feature`)
6. Create new [pull request](https://help.github.com/articles/using-pull-requests)
------ ------
###Donate ###Donate
* Make a [Paypal][donate paypal] donation [![Paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=26VWS2TSAMUJA)
* Flattr this project [![Flattr this git repo](http://api.flattr.com/button/flattr-badge-large.png)](https://flattr.com/submit/auto?user_id=vmihalachi&url=https://raw.github.com/vmihalachi/turbo-editor&title=Turbo Editor&language=&tags=github&category=software)
[![Flattr this git repo](http://api.flattr.com/button/flattr-badge-large.png)](https://flattr.com/submit/auto?user_id=vmihalachi&url=https://github.com/vmihalachi/turbo-editor&title=Turbo Editor&language=java&tags=github&category=software)
------ ------
###Download ###Images
* From the [Play Store][download playstore] ![](https://lh3.googleusercontent.com/-0GHukwGQPW4/VCUpEhKnZCI/AAAAAAAAAH4/cclI70K79_Q/w347-h520-no/PhoneCustom_7.png)![](https://lh3.googleusercontent.com/-OvazluFl_QQ/VCUo9DAje9I/AAAAAAAAAHQ/i7n1uCpU1hE/w347-h520-no/PhoneCustom_1.png)![](https://lh4.googleusercontent.com/-zh4CYdQPecg/VCUpD3QXpAI/AAAAAAAAAHw/ulL5-V0iJUw/w347-h520-no/PhoneCustom_6.png)![](https://lh4.googleusercontent.com/-LT3k4z9JHo8/VCUo_0jnZRI/AAAAAAAAAHg/Npk9UlkXIV8/w347-h520-no/PhoneCustom_4.png)![](https://lh5.googleusercontent.com/-hXvsf-WYvBs/VCUo9sYfR-I/AAAAAAAAAHY/TTfAgiV_7ko/w347-h520-no/PhoneCustom_3.png)![](https://lh6.googleusercontent.com/-Qib82pK6mZU/VCUpAgYmUdI/AAAAAAAAAHo/zoPVmwcatbQ/w347-h520-no/PhoneCustom_5.png)![](https://lh5.googleusercontent.com/-SERL7X-JHuc/VCax0QSlCGI/AAAAAAAAAJA/hu8dvbvJGBM/w375-h563-no/PhoneCustom_2.png)
* Want to try beta releases? Be a part of the Google Plus [Community][community googleplus]!
------ ------
### Developer ### Developer
@ -56,8 +50,8 @@ See the [LICENSE][license] file that accompanies this distribution for the full
[license]: https://github.com/vmihalachi/turbo-editor/LICENSE [license]: https://github.com/vmihalachi/turbo-editor/LICENSE
[donate]: https://github.com/vmihalachi/turbo-editor#donate [donate]: https://github.com/vmihalachi/turbo-editor#donate
[donate paypal]: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=PUQXSX6MTXHZ2 [donate paypal]: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=PUQXSX6MTXHZ2
[xda thread]: http://forum.xda-developers.com/android/apps-games/app-turbo-editor-text-editor-t2832016
[community googleplus]: https://plus.google.com/u/0/communities/111974095419108178946 [community googleplus]: https://plus.google.com/u/0/communities/111974095419108178946
[download playstore]: https://play.google.com/store/apps/details?id=com.maskyn.fileeditor
[crowdin]: https://crowdin.net/project/turbo-client [crowdin]: https://crowdin.net/project/turbo-client
[developer site]: http://vmihalachi.com/ [developer site]: http://vmihalachi.com/
[crowdin]: https://crowdin.net/project/turbo-client [crowdin]: https://crowdin.net/project/turbo-client

1
app-pro/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

56
app-pro/build.gradle Normal file
View File

@ -0,0 +1,56 @@
/*
* Copyright (C) 2014 Vlad Mihalachi
*
* This file is part of Turbo Editor.
*
* Turbo Editor is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Turbo Editor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
apply plugin: 'com.android.application'
android {
compileSdkVersion 20
buildToolsVersion '20.0.0'
defaultConfig {
applicationId "com.maskyn.fileeditorpro"
minSdkVersion 11
targetSdkVersion 19
versionCode 29
versionName "1.12"
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
buildTypes {
release {
runProguard false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
lintOptions {
abortOnError false
}
packagingOptions {
exclude 'META-INF/LICENSE.txt'
exclude 'META-INF/NOTICE.txt'
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile project(':libraries:sharedCode')
}

17
app-pro/proguard-rules.pro vendored Normal file
View File

@ -0,0 +1,17 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in C:/Users/Vlad/AppData/Local/Android/android-sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

View File

@ -17,15 +17,16 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package sharedcode.turboeditor.util; package com.maskyn.fileeditorpro;
import android.widget.ScrollView; import android.app.Application;
import android.test.ApplicationTestCase;
import sharedcode.turboeditor.views.GoodScrollView; /**
* <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
public interface EditorInterface { */
public GoodScrollView getVerticalScrollView(); public class ApplicationTest extends ApplicationTestCase<Application> {
public String getFilePath(); public ApplicationTest() {
public PageSystem getPageSystem(); super(Application.class);
public void updateTextSyntax();
} }
}

View File

@ -0,0 +1,120 @@
<!--
~ Copyright (C) 2014 Vlad Mihalachi
~
~ This file is part of Turbo Editor.
~
~ Turbo Editor is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ Turbo Editor is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.maskyn.fileeditorpro"
android:installLocation="auto">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_SUPERUSER" />
<supports-screens
android:anyDensity="true"
android:largeScreens="true"
android:normalScreens="true"
android:resizeable="true"
android:smallScreens="true"
android:xlargeScreens="true" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/nome_app_turbo_editor"
android:hardwareAccelerated="true"
android:largeHeap="true"
android:supportsRtl="true"
android:name="sharedcode.turboeditor.activity.MyApp"
>
<!-- android:alwaysRetainTaskState="true" -->
<activity
android:name=".HomeActivity"
android:configChanges="fontScale|keyboard|keyboardHidden|locale|mnc|mcc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|uiMode|touchscreen"
android:launchMode="singleTop"
android:windowSoftInputMode="stateUnspecified|adjustResize"
android:theme="@style/AppThemeEditorDark">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.MULTIWINDOW_LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.intent.action.EDIT" />
<action android:name="android.intent.action.PICK" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="file" />
<data android:mimeType="text/*" />
<data android:pathPattern="*.txt" />
<data android:pathPattern="*.html" />
<data android:pathPattern="*.css" />
<data android:pathPattern="*.js" />
<data android:pathPattern="*.md"/>
<data android:pathPattern="*.php" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
</activity>
<activity
android:name="sharedcode.turboeditor.activity.SelectFileActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:label="@string/open_a_file"
android:parentActivityName=".HomeActivity"
android:theme="@style/AppThemeBaseLight">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".HomeActivity" />
</activity>
<activity
android:name="sharedcode.turboeditor.preferences.ExtraSettingsActivity"
android:label="@string/extra_options"
android:parentActivityName=".HomeActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".HomeActivity" />
</activity>
<meta-data
android:name="com.sec.android.support.multiwindow"
android:value="true" />
<meta-data
android:name="com.sec.android.multiwindow.DEFAULT_SIZE_W"
android:value="632.0dip" />
<meta-data
android:name="com.sec.android.multiwindow.DEFAULT_SIZE_H"
android:value="598.0dip" />
<meta-data
android:name="com.sec.android.multiwindow.MINIMUM_SIZE_W"
android:value="632.0dip" />
<meta-data
android:name="com.sec.android.multiwindow.MINIMUM_SIZE_H"
android:value="598.0dip" />
</application>
</manifest>

View File

@ -17,9 +17,16 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package sharedcode.turboeditor.util; package com.maskyn.fileeditorpro;
public class Constants { import android.os.Bundle;
public static final int MAX_FILE_SIZE = 20_000;
public static final boolean FOR_AMAZON = false; import sharedcode.turboeditor.activity.BaseHomeActivity;
public class HomeActivity extends BaseHomeActivity {
@Override
public void displayInterstitial() {
// nothing to do here
}
} }

View File

@ -39,13 +39,13 @@ repositories {
android { android {
compileSdkVersion 20 compileSdkVersion 20
buildToolsVersion "20.0.0" buildToolsVersion '20.0.0'
defaultConfig { defaultConfig {
applicationId "com.maskyn.fileeditor" applicationId "com.maskyn.fileeditor"
minSdkVersion 14 minSdkVersion 11
targetSdkVersion 20 targetSdkVersion 19
versionCode 26 versionCode 30
versionName "1.10" versionName "1.13"
} }
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7 sourceCompatibility JavaVersion.VERSION_1_7
@ -57,6 +57,13 @@ android {
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
} }
} }
lintOptions {
abortOnError false
}
packagingOptions {
exclude 'META-INF/LICENSE.txt'
exclude 'META-INF/NOTICE.txt'
}
} }
dependencies { dependencies {
@ -64,5 +71,4 @@ dependencies {
compile project(':libraries:sharedCode') compile project(':libraries:sharedCode')
compile 'com.google.android.gms:play-services:5.0.89' compile 'com.google.android.gms:play-services:5.0.89'
compile 'com.crashlytics.android:crashlytics:1.+' compile 'com.crashlytics.android:crashlytics:1.+'
} }

View File

@ -26,6 +26,7 @@
<uses-permission android:name="android.permission.ACCESS_SUPERUSER" /> <uses-permission android:name="android.permission.ACCESS_SUPERUSER" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="com.android.vending.BILLING" />
<supports-screens <supports-screens
android:anyDensity="true" android:anyDensity="true"
@ -39,9 +40,10 @@
android:allowBackup="true" android:allowBackup="true"
android:icon="@drawable/ic_launcher" android:icon="@drawable/ic_launcher"
android:label="@string/nome_app_turbo_editor" android:label="@string/nome_app_turbo_editor"
android:hardwareAccelerated="false" android:hardwareAccelerated="true"
android:largeHeap="true" android:largeHeap="true"
android:supportsRtl="true" android:supportsRtl="true"
android:name="sharedcode.turboeditor.activity.MyApp"
> >
<!-- android:alwaysRetainTaskState="true" --> <!-- android:alwaysRetainTaskState="true" -->
@ -52,8 +54,8 @@
android:name=".HomeActivity" android:name=".HomeActivity"
android:configChanges="fontScale|keyboard|keyboardHidden|locale|mnc|mcc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|uiMode|touchscreen" android:configChanges="fontScale|keyboard|keyboardHidden|locale|mnc|mcc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|uiMode|touchscreen"
android:launchMode="singleTop" android:launchMode="singleTop"
android:windowSoftInputMode="stateUnspecified|adjustPan" android:windowSoftInputMode="stateUnspecified|adjustResize"
android:theme="@style/AppTheme.Light.Editor"> android:theme="@style/AppThemeEditorDark">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
@ -83,35 +85,24 @@
<data android:mimeType="text/plain" /> <data android:mimeType="text/plain" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity
android:name="sharedcode.turboeditor.activity.PreferenceAbout"
android:configChanges="keyboardHidden|orientation|screenSize"
android:label="@string/info"
android:parentActivityName=".HomeActivity"
android:theme="@style/AppTheme.Dark">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.maskyn.fileeditor.activity.HomeActivity" />
</activity>
<activity
android:name="sharedcode.turboeditor.activity.LicensesActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:label="@string/open_source_license"
android:parentActivityName="sharedcode.turboeditor.activity.PreferenceAbout"
android:theme="@style/AppTheme.Light">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.maskyn.fileeditor.activity.PreferenceAbout" />
</activity>
<activity <activity
android:name="sharedcode.turboeditor.activity.SelectFileActivity" android:name="sharedcode.turboeditor.activity.SelectFileActivity"
android:configChanges="keyboardHidden|orientation|screenSize" android:configChanges="keyboardHidden|orientation|screenSize"
android:label="@string/open_a_file" android:label="@string/open_a_file"
android:parentActivityName=".HomeActivity" android:parentActivityName=".HomeActivity"
android:theme="@style/AppTheme.Light"> android:theme="@style/AppThemeBaseLight">
<meta-data <meta-data
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
android:value="com.maskyn.fileeditor.activity.HomeActivity" /> android:value=".HomeActivity" />
</activity>
<activity
android:name="sharedcode.turboeditor.preferences.ExtraSettingsActivity"
android:label="@string/extra_options"
android:parentActivityName=".HomeActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".HomeActivity" />
</activity> </activity>
<meta-data <meta-data

View File

@ -20,6 +20,8 @@
package com.maskyn.fileeditor; package com.maskyn.fileeditor;
import android.app.Activity; import android.app.Activity;
import android.preference.PreferenceManager;
import com.google.android.gms.ads.AdRequest; import com.google.android.gms.ads.AdRequest;
import com.google.android.gms.ads.InterstitialAd; import com.google.android.gms.ads.InterstitialAd;
@ -28,32 +30,21 @@ import java.util.Calendar;
import sharedcode.turboeditor.preferences.PreferenceHelper; import sharedcode.turboeditor.preferences.PreferenceHelper;
public class AdsHelper { public class AdsHelper {
private Activity activity;
private InterstitialAd interstitial; private InterstitialAd interstitial;
public AdsHelper(Activity activity) { public AdsHelper(Activity activity) {
this.activity = activity;
int today = Calendar.getInstance().get(Calendar.DAY_OF_MONTH);
int lastDayAdShowed = PreferenceHelper.getLastDayAdShowed(activity);
boolean showAd = today != lastDayAdShowed;
if (showAd) {
interstitial = new InterstitialAd(activity); interstitial = new InterstitialAd(activity);
interstitial.setAdUnitId("ca-app-pub-5679083452234719/7178038180"); interstitial.setAdUnitId("ca-app-pub-5679083452234719/7178038180");
// Create ad request. // Create ad request.
AdRequest adRequest = new AdRequest.Builder().build(); AdRequest adRequest = new AdRequest.Builder().build();
// Begin loading your interstitial. // Begin loading your interstitial.
interstitial.loadAd(adRequest); interstitial.loadAd(adRequest);
}
} }
public void displayInterstitial() { public void displayInterstitial() {
if (interstitial != null && interstitial.isLoaded()) { interstitial.show();
interstitial.show();
int today = Calendar.getInstance().get(Calendar.DAY_OF_MONTH);
PreferenceHelper.setLastDayAdShowed(activity, today);
}
} }
} }

View File

@ -22,8 +22,10 @@ package com.maskyn.fileeditor;
import android.os.Bundle; import android.os.Bundle;
import com.crashlytics.android.Crashlytics; import com.crashlytics.android.Crashlytics;
import sharedcode.turboeditor.activity.BaseHomeActivity; import sharedcode.turboeditor.activity.BaseHomeActivity;
import sharedcode.turboeditor.preferences.PreferenceHelper; import sharedcode.turboeditor.preferences.PreferenceHelper;
import sharedcode.turboeditor.util.ProCheckUtils;
public class HomeActivity extends BaseHomeActivity { public class HomeActivity extends BaseHomeActivity {
@ -36,11 +38,13 @@ public class HomeActivity extends BaseHomeActivity {
if(PreferenceHelper.getSendErrorReports(this)) if(PreferenceHelper.getSendErrorReports(this))
Crashlytics.start(this); Crashlytics.start(this);
// setup the ads // setup the ads
adsHelper = new AdsHelper(this); if(!ProCheckUtils.isPro(this))
adsHelper = new AdsHelper(this);
} }
@Override @Override
public void displayInterstitial() { public void displayInterstitial() {
adsHelper.displayInterstitial(); if(adsHelper != null && !ProCheckUtils.isPro(this))
adsHelper.displayInterstitial();
} }
} }

View File

@ -1,23 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2014 Vlad Mihalachi
~
~ This file is part of Turbo Editor.
~
~ Turbo Editor is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ Turbo Editor is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<pre-dex-items> <pre-dex-items>
<item <item
@ -27,17 +8,17 @@
revision="20.0.0" revision="20.0.0"
sha1="a18ff12a9ab5ae52fd30d42f134517997568231e"/> sha1="a18ff12a9ab5ae52fd30d42f134517997568231e"/>
<item <item
dex="C:\Users\Vlad\Documents\AndroidStudioProjects\TurboMaterialEditor\app-pro\build\intermediates\pre-dexed\debug\juniversalchardet-1.0.3-24b647622164ce26bc5d0be361e05056efc68e13.jar" dex="C:\Users\Vlad\Documents\AndroidStudioProjects\turbo-editor\app-pro\build\intermediates\pre-dexed\debug\classes-9b612f0cb16e63277808158fe971bb4f40c98d29.jar"
jar="C:\Users\Vlad\Documents\AndroidStudioProjects\TurboMaterialEditor\app-pro\build\intermediates\exploded-aar\TurboMaterialEditor.libraries\sharedCode\unspecified\libs\juniversalchardet-1.0.3.jar" jar="C:\Users\Vlad\Documents\AndroidStudioProjects\turbo-editor\app-pro\build\intermediates\exploded-aar\com.github.gabrielemariotti.changeloglib\library\1.5.1\classes.jar"
jumboMode="false" jumboMode="false"
revision="20.0.0" revision="19.1.0"
sha1="591d72211acc0b909b79c840e0b3ed9a0982d807"/> sha1="74a89f0f8b56d9f11d70b8d8134cf4109f4797dc"/>
<item <item
dex="C:\Users\Vlad\Documents\AndroidStudioProjects\TurboMaterialEditor\app-pro\build\intermediates\pre-dexed\debug\support-v4-19.0.1-f87b13e7ed00736c2050eda75093bf42b0503c64.jar" dex="C:\Users\Vlad\Documents\AndroidStudioProjects\turbo-editor\app-pro\build\intermediates\pre-dexed\debug\library-2.4.0-6b043a8574fb97c742ca3065362b2a29e8f870bc.jar"
jar="C:\Users\Vlad\AppData\Local\Android\android-sdk\extras\android\m2repository\com\android\support\support-v4\19.0.1\support-v4-19.0.1.jar" jar="C:\Users\Vlad\.gradle\caches\modules-2\files-2.1\com.nineoldandroids\library\2.4.0\e9b63380f3a242dbdbf103a2355ad7e43bad17cb\library-2.4.0.jar"
jumboMode="false" jumboMode="false"
revision="20.0.0" revision="19.1.0"
sha1="587da9a481ffd341d2fa091704489b89845ddacd"/> sha1="e9b63380f3a242dbdbf103a2355ad7e43bad17cb"/>
<item <item
dex="C:\Users\Vlad\Documents\AndroidStudioProjects\TurboMaterialEditor\app\build\intermediates\pre-dexed\debug\commons-io-2.4-63b64e68cd19031cd252ac65a3ef94421c1bf0f4.jar" dex="C:\Users\Vlad\Documents\AndroidStudioProjects\TurboMaterialEditor\app\build\intermediates\pre-dexed\debug\commons-io-2.4-63b64e68cd19031cd252ac65a3ef94421c1bf0f4.jar"
jar="C:\Users\Vlad\.gradle\caches\modules-2\files-2.1\commons-io\commons-io\2.4\b1b6ea3b7e4aa4f492509a4952029cd8e48019ad\commons-io-2.4.jar" jar="C:\Users\Vlad\.gradle\caches\modules-2\files-2.1\commons-io\commons-io\2.4\b1b6ea3b7e4aa4f492509a4952029cd8e48019ad\commons-io-2.4.jar"
@ -51,53 +32,41 @@
revision="20.0.0" revision="20.0.0"
sha1="2f3117da0016b1126fafe7fb332a45d2f910d76c"/> sha1="2f3117da0016b1126fafe7fb332a45d2f910d76c"/>
<item <item
dex="C:\Users\Vlad\Documents\AndroidStudioProjects\TurboMaterialEditor\app-pro\build\intermediates\pre-dexed\debug\classes-a52f37b7ca7cde7ab02ff10bba80d1c496284125.jar" dex="C:\Users\Vlad\Documents\AndroidStudioProjects\turbo-editor\app-pro\build\intermediates\pre-dexed\debug\juniversalchardet-1.0.3-sources-58cfedaebe3b94ec0eaa2ede4e66aae8dbe309b0.jar"
jar="C:\Users\Vlad\Documents\AndroidStudioProjects\TurboMaterialEditor\app-pro\build\intermediates\exploded-aar\TurboMaterialEditor.libraries\FloatingActionButton\unspecified\classes.jar" jar="C:\Users\Vlad\Documents\AndroidStudioProjects\turbo-editor\app-pro\build\intermediates\exploded-aar\turbo-editor.libraries\sharedCode\unspecified\libs\juniversalchardet-1.0.3-sources.jar"
jumboMode="false" jumboMode="false"
revision="20.0.0" revision="19.1.0"
sha1="2f3117da0016b1126fafe7fb332a45d2f910d76c"/> sha1="77979eaa98f90806f984155f44f63cc1fb60ac25"/>
<item <item
dex="C:\Users\Vlad\Documents\AndroidStudioProjects\TurboMaterialEditor\app\build\intermediates\pre-dexed\debug\support-v4-19.1.0-cf7033c44b86e758f85990185111614bac3ecfa8.jar" dex="C:\Users\Vlad\Documents\AndroidStudioProjects\turbo-editor\app\build\intermediates\pre-dexed\debug\juniversalchardet-1.0.3-9db20cdcb8ae72104757d81297c98978c65bd91b.jar"
jar="C:\Users\Vlad\AppData\Local\Android\android-sdk\extras\android\m2repository\com\android\support\support-v4\19.1.0\support-v4-19.1.0.jar" jar="C:\Users\Vlad\Documents\AndroidStudioProjects\turbo-editor\app\build\intermediates\exploded-aar\turbo-editor.libraries\sharedCode\unspecified\libs\juniversalchardet-1.0.3.jar"
jumboMode="false"
revision="20.0.0"
sha1="85f201b380937e61a9dce6ca90ccf6872abbfb67"/>
<item
dex="C:\Users\Vlad\Documents\AndroidStudioProjects\TurboMaterialEditor\app\build\intermediates\pre-dexed\debug\juniversalchardet-1.0.3-2e38d54a78dd518320bb6abe3d8931a19ff26792.jar"
jar="C:\Users\Vlad\Documents\AndroidStudioProjects\TurboMaterialEditor\app\build\intermediates\exploded-aar\TurboMaterialEditor.libraries\sharedCode\unspecified\libs\juniversalchardet-1.0.3.jar"
jumboMode="false" jumboMode="false"
revision="20.0.0" revision="20.0.0"
sha1="591d72211acc0b909b79c840e0b3ed9a0982d807"/> sha1="591d72211acc0b909b79c840e0b3ed9a0982d807"/>
<item <item
dex="C:\Users\Vlad\Documents\AndroidStudioProjects\TurboMaterialEditor\app\build\intermediates\pre-dexed\debug\classes-2ad27a3265673aeeb8f40e4322d8e19509329c96.jar" dex="C:\Users\Vlad\Documents\AndroidStudioProjects\turbo-editor\app\build\intermediates\pre-dexed\debug\juniversalchardet-1.0.3-sources-436707958bb47977373cb4a2d842cdbf635fd840.jar"
jar="C:\Users\Vlad\Documents\AndroidStudioProjects\TurboMaterialEditor\app\build\intermediates\exploded-aar\com.github.gabrielemariotti.changeloglib\library\1.5.1\classes.jar" jar="C:\Users\Vlad\Documents\AndroidStudioProjects\turbo-editor\app\build\intermediates\exploded-aar\turbo-editor.libraries\sharedCode\unspecified\libs\juniversalchardet-1.0.3-sources.jar"
jumboMode="false"
revision="20.0.0"
sha1="77979eaa98f90806f984155f44f63cc1fb60ac25"/>
<item
dex="C:\Users\Vlad\Documents\AndroidStudioProjects\turbo-editor\app-pro\build\intermediates\pre-dexed\debug\classes-9b612f0cb16e63277808158fe971bb4f40c98d29.jar"
jar="C:\Users\Vlad\Documents\AndroidStudioProjects\turbo-editor\app-pro\build\intermediates\exploded-aar\com.github.gabrielemariotti.changeloglib\library\1.5.1\classes.jar"
jumboMode="false" jumboMode="false"
revision="20.0.0" revision="20.0.0"
sha1="74a89f0f8b56d9f11d70b8d8134cf4109f4797dc"/> sha1="74a89f0f8b56d9f11d70b8d8134cf4109f4797dc"/>
<item <item
dex="C:\Users\Vlad\Documents\AndroidStudioProjects\TurboMaterialEditor\app-pro\build\intermediates\pre-dexed\debug\classes-bfc447e4dbe83598b94dbdc8e38492cbbda6ebb2.jar" dex="C:\Users\Vlad\Documents\AndroidStudioProjects\turbo-editor\app-pro\build\intermediates\pre-dexed\debug\support-v4-19.0.1-f87b13e7ed00736c2050eda75093bf42b0503c64.jar"
jar="C:\Users\Vlad\Documents\AndroidStudioProjects\TurboMaterialEditor\app-pro\build\intermediates\exploded-aar\TurboMaterialEditor.libraries\RootCommands\unspecified\classes.jar" jar="C:\Users\Vlad\AppData\Local\Android\android-sdk\extras\android\m2repository\com\android\support\support-v4\19.0.1\support-v4-19.0.1.jar"
jumboMode="false" jumboMode="false"
revision="20.0.0" revision="19.1.0"
sha1="cdecd8167dfb75d5785decb911fc4516445dd6a6"/> sha1="587da9a481ffd341d2fa091704489b89845ddacd"/>
<item <item
dex="C:\Users\Vlad\Documents\AndroidStudioProjects\TurboMaterialEditor\app\build\intermediates\pre-dexed\release\classes-579a5ce52888f504a812e0f68758a11f20a21c15.jar" dex="C:\Users\Vlad\Documents\AndroidStudioProjects\turbo-editor\app-pro\build\intermediates\pre-dexed\debug\support-annotations-20.0.0-be727b9c9ce08c6ee055559b9506b675c13db989.jar"
jar="C:\Users\Vlad\Documents\AndroidStudioProjects\TurboMaterialEditor\app\build\intermediates\exploded-aar\TurboMaterialEditor.libraries\sharedCode\unspecified\classes.jar" jar="C:\Users\Vlad\AppData\Local\Android\android-sdk\extras\android\m2repository\com\android\support\support-annotations\20.0.0\support-annotations-20.0.0.jar"
jumboMode="false" jumboMode="false"
revision="20.0.0" revision="20.0.0"
sha1="a593d4ce7ccdfa1eac8d97a82db64f23614b59a1"/> sha1="9d9013e9ff35fc3756411e62873c363c70c638fa"/>
<item
dex="C:\Users\Vlad\Documents\AndroidStudioProjects\TurboMaterialEditor\app-pro\build\intermediates\pre-dexed\debug\classes-0a2ec632e1127b260f2b888ca5539fd41a1a638b.jar"
jar="C:\Users\Vlad\Documents\AndroidStudioProjects\TurboMaterialEditor\app-pro\build\intermediates\exploded-aar\com.github.gabrielemariotti.changeloglib\library\1.5.1\classes.jar"
jumboMode="false"
revision="20.0.0"
sha1="74a89f0f8b56d9f11d70b8d8134cf4109f4797dc"/>
<item
dex="C:\Users\Vlad\Documents\AndroidStudioProjects\TurboMaterialEditor\app\build\intermediates\pre-dexed\debug\classes-a8678f35ea62f18f2f7b60a15203aa6c74b5e559.jar"
jar="C:\Users\Vlad\Documents\AndroidStudioProjects\TurboMaterialEditor\app\build\intermediates\exploded-aar\com.google.android.gms\play-services\5.0.89\classes.jar"
jumboMode="false"
revision="20.0.0"
sha1="d71573c9c5ea98a8db47ad6ff993a63d492b3bfa"/>
<item <item
dex="C:\Users\Vlad\Documents\AndroidStudioProjects\TurboMaterialEditor\app-pro\build\intermediates\pre-dexed\release\classes-3e47e18f46719c7bf296f4b49ff03aaa3d406ba6.jar" dex="C:\Users\Vlad\Documents\AndroidStudioProjects\TurboMaterialEditor\app-pro\build\intermediates\pre-dexed\release\classes-3e47e18f46719c7bf296f4b49ff03aaa3d406ba6.jar"
jar="C:\Users\Vlad\Documents\AndroidStudioProjects\TurboMaterialEditor\app-pro\build\intermediates\exploded-aar\TurboMaterialEditor.libraries\sharedCode\unspecified\classes.jar" jar="C:\Users\Vlad\Documents\AndroidStudioProjects\TurboMaterialEditor\app-pro\build\intermediates\exploded-aar\TurboMaterialEditor.libraries\sharedCode\unspecified\classes.jar"
@ -110,6 +79,174 @@
jumboMode="false" jumboMode="false"
revision="20.0.0" revision="20.0.0"
sha1="0e821eafa1bf489a26bdb71f95078c26785b37a1"/> sha1="0e821eafa1bf489a26bdb71f95078c26785b37a1"/>
<item
dex="C:\Users\Vlad\Documents\AndroidStudioProjects\turbo-editor\app-pro\build\intermediates\pre-dexed\debug\juniversalchardet-1.0.3-sources-58cfedaebe3b94ec0eaa2ede4e66aae8dbe309b0.jar"
jar="C:\Users\Vlad\Documents\AndroidStudioProjects\turbo-editor\app-pro\build\intermediates\exploded-aar\turbo-editor.libraries\sharedCode\unspecified\libs\juniversalchardet-1.0.3-sources.jar"
jumboMode="false"
revision="20.0.0"
sha1="77979eaa98f90806f984155f44f63cc1fb60ac25"/>
<item
dex="C:\Users\Vlad\Documents\AndroidStudioProjects\turbo-editor\app-pro\build\intermediates\pre-dexed\debug\juniversalchardet-1.0.3-65b2b356e3f2da4b67e00aba70923d6321852204.jar"
jar="C:\Users\Vlad\Documents\AndroidStudioProjects\turbo-editor\app-pro\build\intermediates\exploded-aar\turbo-editor.libraries\sharedCode\unspecified\libs\juniversalchardet-1.0.3.jar"
jumboMode="false"
revision="20.0.0"
sha1="591d72211acc0b909b79c840e0b3ed9a0982d807"/>
<item
dex="C:\Users\Vlad\Documents\AndroidStudioProjects\TurboMaterialEditor\app-pro\build\intermediates\pre-dexed\debug\support-v4-19.0.1-f87b13e7ed00736c2050eda75093bf42b0503c64.jar"
jar="C:\Users\Vlad\AppData\Local\Android\android-sdk\extras\android\m2repository\com\android\support\support-v4\19.0.1\support-v4-19.0.1.jar"
jumboMode="false"
revision="20.0.0"
sha1="587da9a481ffd341d2fa091704489b89845ddacd"/>
<item
dex="C:\Users\Vlad\Documents\AndroidStudioProjects\TurboMaterialEditor\app\build\intermediates\pre-dexed\debug\support-v4-19.1.0-cf7033c44b86e758f85990185111614bac3ecfa8.jar"
jar="C:\Users\Vlad\AppData\Local\Android\android-sdk\extras\android\m2repository\com\android\support\support-v4\19.1.0\support-v4-19.1.0.jar"
jumboMode="false"
revision="20.0.0"
sha1="85f201b380937e61a9dce6ca90ccf6872abbfb67"/>
<item
dex="C:\Users\Vlad\Documents\AndroidStudioProjects\TurboMaterialEditor\app-pro\build\intermediates\pre-dexed\debug\classes-a52f37b7ca7cde7ab02ff10bba80d1c496284125.jar"
jar="C:\Users\Vlad\Documents\AndroidStudioProjects\TurboMaterialEditor\app-pro\build\intermediates\exploded-aar\TurboMaterialEditor.libraries\FloatingActionButton\unspecified\classes.jar"
jumboMode="false"
revision="20.0.0"
sha1="2f3117da0016b1126fafe7fb332a45d2f910d76c"/>
<item
dex="C:\Users\Vlad\Documents\AndroidStudioProjects\TurboMaterialEditor\app-pro\build\intermediates\pre-dexed\debug\classes-bfc447e4dbe83598b94dbdc8e38492cbbda6ebb2.jar"
jar="C:\Users\Vlad\Documents\AndroidStudioProjects\TurboMaterialEditor\app-pro\build\intermediates\exploded-aar\TurboMaterialEditor.libraries\RootCommands\unspecified\classes.jar"
jumboMode="false"
revision="20.0.0"
sha1="cdecd8167dfb75d5785decb911fc4516445dd6a6"/>
<item
dex="C:\Users\Vlad\Documents\AndroidStudioProjects\turbo-editor\app-pro\build\intermediates\pre-dexed\debug\library-2.4.0-6b043a8574fb97c742ca3065362b2a29e8f870bc.jar"
jar="C:\Users\Vlad\.gradle\caches\modules-2\files-2.1\com.nineoldandroids\library\2.4.0\e9b63380f3a242dbdbf103a2355ad7e43bad17cb\library-2.4.0.jar"
jumboMode="false"
revision="20.0.0"
sha1="e9b63380f3a242dbdbf103a2355ad7e43bad17cb"/>
<item
dex="C:\Users\Vlad\Documents\AndroidStudioProjects\turbo-editor\app-pro\build\intermediates\pre-dexed\debug\eventbus-2.2.1-32e81c5612ed132ff771b5425053d87f4f6c68c5.jar"
jar="C:\Users\Vlad\.gradle\caches\modules-2\files-2.1\de.greenrobot\eventbus\2.2.1\a18ff12a9ab5ae52fd30d42f134517997568231e\eventbus-2.2.1.jar"
jumboMode="false"
revision="19.1.0"
sha1="a18ff12a9ab5ae52fd30d42f134517997568231e"/>
<item
dex="C:\Users\Vlad\Documents\AndroidStudioProjects\TurboMaterialEditor\app\build\intermediates\pre-dexed\release\classes-579a5ce52888f504a812e0f68758a11f20a21c15.jar"
jar="C:\Users\Vlad\Documents\AndroidStudioProjects\TurboMaterialEditor\app\build\intermediates\exploded-aar\TurboMaterialEditor.libraries\sharedCode\unspecified\classes.jar"
jumboMode="false"
revision="20.0.0"
sha1="a593d4ce7ccdfa1eac8d97a82db64f23614b59a1"/>
<item
dex="C:\Users\Vlad\Documents\AndroidStudioProjects\turbo-editor\app-pro\build\intermediates\pre-dexed\debug\classes-30cc9565ecef1e8ae8577530d7ddd41993d192d7.jar"
jar="C:\Users\Vlad\Documents\AndroidStudioProjects\turbo-editor\app-pro\build\intermediates\exploded-aar\turbo-editor.libraries\RootCommands\unspecified\classes.jar"
jumboMode="false"
revision="19.1.0"
sha1="cb3d22565863773944a8c15de408e864e34d6da1"/>
<item
dex="C:\Users\Vlad\Documents\AndroidStudioProjects\TurboMaterialEditor\app-pro\build\intermediates\pre-dexed\debug\juniversalchardet-1.0.3-24b647622164ce26bc5d0be361e05056efc68e13.jar"
jar="C:\Users\Vlad\Documents\AndroidStudioProjects\TurboMaterialEditor\app-pro\build\intermediates\exploded-aar\TurboMaterialEditor.libraries\sharedCode\unspecified\libs\juniversalchardet-1.0.3.jar"
jumboMode="false"
revision="20.0.0"
sha1="591d72211acc0b909b79c840e0b3ed9a0982d807"/>
<item
dex="C:\Users\Vlad\Documents\AndroidStudioProjects\turbo-editor\app\build\intermediates\pre-dexed\debug\classes-91de6979bb6be1b46cde32b462c23831eadd01d4.jar"
jar="C:\Users\Vlad\Documents\AndroidStudioProjects\turbo-editor\app\build\intermediates\exploded-aar\com.github.gabrielemariotti.changeloglib\library\1.5.1\classes.jar"
jumboMode="false"
revision="20.0.0"
sha1="74a89f0f8b56d9f11d70b8d8134cf4109f4797dc"/>
<item
dex="C:\Users\Vlad\Documents\AndroidStudioProjects\turbo-editor\app-pro\build\intermediates\pre-dexed\debug\classes-30cc9565ecef1e8ae8577530d7ddd41993d192d7.jar"
jar="C:\Users\Vlad\Documents\AndroidStudioProjects\turbo-editor\app-pro\build\intermediates\exploded-aar\turbo-editor.libraries\RootCommands\unspecified\classes.jar"
jumboMode="false"
revision="20.0.0"
sha1="85e7b55f468c5591965a5171179d3858b46822a9"/>
<item
dex="C:\Users\Vlad\Documents\AndroidStudioProjects\turbo-editor\app\build\intermediates\pre-dexed\debug\classes-515446996fed08836a9331ef47b508a0383ffa22.jar"
jar="C:\Users\Vlad\Documents\AndroidStudioProjects\turbo-editor\app\build\intermediates\exploded-aar\turbo-editor.libraries\RootCommands\unspecified\classes.jar"
jumboMode="false"
revision="20.0.0"
sha1="85e7b55f468c5591965a5171179d3858b46822a9"/>
<item
dex="C:\Users\Vlad\Documents\AndroidStudioProjects\turbo-editor\app-pro\build\intermediates\pre-dexed\debug\classes-bcfe21eb1248db73c27c811996e28274cf39b024.jar"
jar="C:\Users\Vlad\Documents\AndroidStudioProjects\turbo-editor\app-pro\build\intermediates\exploded-aar\turbo-editor.libraries\sharedCode\unspecified\classes.jar"
jumboMode="false"
revision="19.1.0"
sha1="8203f36efafa2a6ef438b83fc86b9bdc469e7368"/>
<item
dex="C:\Users\Vlad\Documents\AndroidStudioProjects\turbo-editor\app-pro\build\intermediates\pre-dexed\debug\commons-lang3-3.1-84728078c80f2c8637e0c3fe426ad61433c75bb6.jar"
jar="C:\Users\Vlad\.gradle\caches\modules-2\files-2.1\org.apache.commons\commons-lang3\3.1\905075e6c80f206bbe6cf1e809d2caa69f420c76\commons-lang3-3.1.jar"
jumboMode="false"
revision="20.0.0"
sha1="905075e6c80f206bbe6cf1e809d2caa69f420c76"/>
<item
dex="C:\Users\Vlad\Documents\AndroidStudioProjects\turbo-editor\app-pro\build\intermediates\pre-dexed\debug\classes-82d5b6cab7f16bad663de7c7008673037efb0e1b.jar"
jar="C:\Users\Vlad\Documents\AndroidStudioProjects\turbo-editor\app-pro\build\intermediates\exploded-aar\turbo-editor.libraries\FloatingActionButton\unspecified\classes.jar"
jumboMode="false"
revision="19.1.0"
sha1="2e2f6526f3c5fb34230d14e52bdc24addb67ea9f"/>
<item
dex="C:\Users\Vlad\Documents\AndroidStudioProjects\turbo-editor\app\build\intermediates\pre-dexed\release\classes-4dd6beddde59ff8bd6c22bd0d65ac974f7e489d2.jar"
jar="C:\Users\Vlad\Documents\AndroidStudioProjects\turbo-editor\app\build\intermediates\exploded-aar\turbo-editor.libraries\sharedCode\unspecified\classes.jar"
jumboMode="false"
revision="20.0.0"
sha1="84a25d78b651ad8b1fb73e463fde4d34a4e79ca6"/>
<item
dex="C:\Users\Vlad\Documents\AndroidStudioProjects\TurboMaterialEditor\app\build\intermediates\pre-dexed\debug\classes-2ad27a3265673aeeb8f40e4322d8e19509329c96.jar"
jar="C:\Users\Vlad\Documents\AndroidStudioProjects\TurboMaterialEditor\app\build\intermediates\exploded-aar\com.github.gabrielemariotti.changeloglib\library\1.5.1\classes.jar"
jumboMode="false"
revision="20.0.0"
sha1="74a89f0f8b56d9f11d70b8d8134cf4109f4797dc"/>
<item
dex="C:\Users\Vlad\Documents\AndroidStudioProjects\turbo-editor\app-pro\build\intermediates\pre-dexed\debug\classes-bcfe21eb1248db73c27c811996e28274cf39b024.jar"
jar="C:\Users\Vlad\Documents\AndroidStudioProjects\turbo-editor\app-pro\build\intermediates\exploded-aar\turbo-editor.libraries\sharedCode\unspecified\classes.jar"
jumboMode="false"
revision="20.0.0"
sha1="84a25d78b651ad8b1fb73e463fde4d34a4e79ca6"/>
<item
dex="C:\Users\Vlad\Documents\AndroidStudioProjects\turbo-editor\app-pro\build\intermediates\pre-dexed\debug\classes-82d5b6cab7f16bad663de7c7008673037efb0e1b.jar"
jar="C:\Users\Vlad\Documents\AndroidStudioProjects\turbo-editor\app-pro\build\intermediates\exploded-aar\turbo-editor.libraries\FloatingActionButton\unspecified\classes.jar"
jumboMode="false"
revision="20.0.0"
sha1="df1a8540495d6724b8582db31fcdd49b04d8b707"/>
<item
dex="C:\Users\Vlad\Documents\AndroidStudioProjects\turbo-editor\app\build\intermediates\pre-dexed\debug\classes-7a329e7f23515542bf02d96773d08da1d2333a7b.jar"
jar="C:\Users\Vlad\Documents\AndroidStudioProjects\turbo-editor\app\build\intermediates\exploded-aar\turbo-editor.libraries\FloatingActionButton\unspecified\classes.jar"
jumboMode="false"
revision="20.0.0"
sha1="df1a8540495d6724b8582db31fcdd49b04d8b707"/>
<item
dex="C:\Users\Vlad\Documents\AndroidStudioProjects\turbo-editor\app-pro\build\intermediates\pre-dexed\debug\juniversalchardet-1.0.3-65b2b356e3f2da4b67e00aba70923d6321852204.jar"
jar="C:\Users\Vlad\Documents\AndroidStudioProjects\turbo-editor\app-pro\build\intermediates\exploded-aar\turbo-editor.libraries\sharedCode\unspecified\libs\juniversalchardet-1.0.3.jar"
jumboMode="false"
revision="19.1.0"
sha1="591d72211acc0b909b79c840e0b3ed9a0982d807"/>
<item
dex="C:\Users\Vlad\Documents\AndroidStudioProjects\TurboMaterialEditor\app\build\intermediates\pre-dexed\debug\juniversalchardet-1.0.3-2e38d54a78dd518320bb6abe3d8931a19ff26792.jar"
jar="C:\Users\Vlad\Documents\AndroidStudioProjects\TurboMaterialEditor\app\build\intermediates\exploded-aar\TurboMaterialEditor.libraries\sharedCode\unspecified\libs\juniversalchardet-1.0.3.jar"
jumboMode="false"
revision="20.0.0"
sha1="591d72211acc0b909b79c840e0b3ed9a0982d807"/>
<item
dex="C:\Users\Vlad\Documents\AndroidStudioProjects\TurboMaterialEditor\app-pro\build\intermediates\pre-dexed\debug\classes-0a2ec632e1127b260f2b888ca5539fd41a1a638b.jar"
jar="C:\Users\Vlad\Documents\AndroidStudioProjects\TurboMaterialEditor\app-pro\build\intermediates\exploded-aar\com.github.gabrielemariotti.changeloglib\library\1.5.1\classes.jar"
jumboMode="false"
revision="20.0.0"
sha1="74a89f0f8b56d9f11d70b8d8134cf4109f4797dc"/>
<item
dex="C:\Users\Vlad\Documents\AndroidStudioProjects\TurboMaterialEditor\app\build\intermediates\pre-dexed\debug\classes-a8678f35ea62f18f2f7b60a15203aa6c74b5e559.jar"
jar="C:\Users\Vlad\Documents\AndroidStudioProjects\TurboMaterialEditor\app\build\intermediates\exploded-aar\com.google.android.gms\play-services\5.0.89\classes.jar"
jumboMode="false"
revision="20.0.0"
sha1="d71573c9c5ea98a8db47ad6ff993a63d492b3bfa"/>
<item
dex="C:\Users\Vlad\Documents\AndroidStudioProjects\turbo-editor\app\build\intermediates\pre-dexed\debug\classes-579992e349b2c32e50f8907b9538ad0aa6df57b4.jar"
jar="C:\Users\Vlad\Documents\AndroidStudioProjects\turbo-editor\app\build\intermediates\exploded-aar\com.google.android.gms\play-services\5.0.89\classes.jar"
jumboMode="false"
revision="20.0.0"
sha1="d71573c9c5ea98a8db47ad6ff993a63d492b3bfa"/>
<item
dex="C:\Users\Vlad\Documents\AndroidStudioProjects\turbo-editor\app-pro\build\intermediates\pre-dexed\debug\commons-io-2.4-63b64e68cd19031cd252ac65a3ef94421c1bf0f4.jar"
jar="C:\Users\Vlad\.gradle\caches\modules-2\files-2.1\commons-io\commons-io\2.4\b1b6ea3b7e4aa4f492509a4952029cd8e48019ad\commons-io-2.4.jar"
jumboMode="false"
revision="19.1.0"
sha1="b1b6ea3b7e4aa4f492509a4952029cd8e48019ad"/>
<item <item
dex="C:\Users\Vlad\Documents\AndroidStudioProjects\TurboMaterialEditor\app\build\intermediates\pre-dexed\debug\classes-c9a98cf79b48106440348a9fa210060d1b8b2647.jar" dex="C:\Users\Vlad\Documents\AndroidStudioProjects\TurboMaterialEditor\app\build\intermediates\pre-dexed\debug\classes-c9a98cf79b48106440348a9fa210060d1b8b2647.jar"
jar="C:\Users\Vlad\Documents\AndroidStudioProjects\TurboMaterialEditor\app\build\intermediates\exploded-aar\TurboMaterialEditor.libraries\RootCommands\unspecified\classes.jar" jar="C:\Users\Vlad\Documents\AndroidStudioProjects\TurboMaterialEditor\app\build\intermediates\exploded-aar\TurboMaterialEditor.libraries\RootCommands\unspecified\classes.jar"

Binary file not shown.

View File

@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest <manifest
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
package="com.faizmalkani.floatingactionbutton"> package="com.faizmalkani.floatingactionbutton">

View File

@ -1,11 +1,11 @@
apply plugin: 'com.android.library' apply plugin: 'com.android.library'
android { android {
compileSdkVersion 19 compileSdkVersion 20
buildToolsVersion "20.0.0" buildToolsVersion '20.0.0'
defaultConfig { defaultConfig {
applicationId 'com.faizmalkani.floatingactionbutton' applicationId 'com.faizmalkani.floatingactionbutton'
minSdkVersion 14 minSdkVersion 7
targetSdkVersion 19 targetSdkVersion 19
versionName "1.0" versionName "1.0"
versionCode 1 versionCode 1
@ -25,4 +25,5 @@ android {
} }
dependencies { dependencies {
compile 'com.nineoldandroids:library:2.4.0'
} }

View File

@ -5,7 +5,7 @@
android:versionName="1.0" > android:versionName="1.0" >
<uses-sdk <uses-sdk
android:minSdkVersion="14" android:minSdkVersion="7"
android:targetSdkVersion="19" /> android:targetSdkVersion="19" />
<application android:allowBackup="true" > <application android:allowBackup="true" >

View File

@ -5,7 +5,7 @@
android:versionName="1.0" > android:versionName="1.0" >
<uses-sdk <uses-sdk
android:minSdkVersion="14" android:minSdkVersion="7"
android:targetSdkVersion="19" /> android:targetSdkVersion="19" />
<application android:allowBackup="true" > <application android:allowBackup="true" >

View File

@ -3,7 +3,7 @@
package="com.faizmalkani.floatingactionbutton.test" > package="com.faizmalkani.floatingactionbutton.test" >
<uses-sdk <uses-sdk
android:minSdkVersion="14" android:minSdkVersion="7"
android:targetSdkVersion="19" /> android:targetSdkVersion="19" />
<instrumentation <instrumentation

View File

@ -1,4 +1,3 @@
# This file is automatically generated by Android Tools. # This file is automatically generated by Android Tools.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED! # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
# #

View File

@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<declare-styleable name="FloatingActionButton"> <declare-styleable name="FloatingActionButton">

View File

@ -1,10 +1,27 @@
/*
* Copyright (c) 2014 SBG Apps
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.faizmalkani.floatingactionbutton; package com.faizmalkani.floatingactionbutton;
import android.view.View; import android.view.View;
import android.widget.AbsListView; import android.widget.AbsListView;
/**
* Created by Stéphane on 09/07/2014.
*/
class DirectionScrollListener implements AbsListView.OnScrollListener { class DirectionScrollListener implements AbsListView.OnScrollListener {
private static final int DIRECTION_CHANGE_THRESHOLD = 1; private static final int DIRECTION_CHANGE_THRESHOLD = 1;
@ -34,10 +51,7 @@ class DirectionScrollListener implements AbsListView.OnScrollListener {
goingDown = firstVisibleItem > mPrevPosition; goingDown = firstVisibleItem > mPrevPosition;
} }
if (changed && mUpdated) { if (changed && mUpdated) {
if(goingDown) mFloatingActionButton.hide(goingDown);
mFloatingActionButton.hideFab();
else
mFloatingActionButton.showFab();
} }
mPrevPosition = firstVisibleItem; mPrevPosition = firstVisibleItem;
mPrevTop = firstViewTop; mPrevTop = firstViewTop;

View File

@ -1,9 +1,7 @@
package com.faizmalkani.floatingactionbutton; package com.faizmalkani.floatingactionbutton;
import android.animation.ObjectAnimator;
import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Color; import android.graphics.Color;
@ -11,135 +9,146 @@ import android.graphics.Paint;
import android.graphics.Point; import android.graphics.Point;
import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.Display; import android.view.Display;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.WindowManager; import android.view.WindowManager;
import android.view.animation.AccelerateInterpolator; import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator;
import android.widget.AbsListView; import android.widget.AbsListView;
public class FloatingActionButton extends View import com.nineoldandroids.animation.ObjectAnimator;
{ import com.nineoldandroids.view.ViewHelper;
Context _context;
Paint mButtonPaint, mDrawablePaint;
Bitmap mBitmap;
int mScreenHeight;
float currentY;
boolean mHidden = false;
ObjectAnimator mShowAnimation; public class FloatingActionButton extends View {
ObjectAnimator mHideAnimation;
public FloatingActionButton(Context context, AttributeSet attributeSet) private final Interpolator mInterpolator = new AccelerateDecelerateInterpolator();
{ private final Paint mButtonPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
super(context, attributeSet); private final Paint mDrawablePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
_context = context; private Bitmap mBitmap;
init(Color.WHITE); private int mColor;
private boolean mHidden = false;
/**
* The FAB button's Y position when it is displayed.
*/
private float mYDisplayed = -1;
/**
* The FAB button's Y position when it is hidden.
*/
private float mYHidden = -1;
public FloatingActionButton(Context context) {
this(context, null);
} }
@SuppressLint("NewApi") public FloatingActionButton(Context context, AttributeSet attributeSet) {
public FloatingActionButton(Context context) this(context, attributeSet, 0);
{
super(context);
_context = context;
init(Color.WHITE);
}
public void setColor(int fabColor)
{
init(fabColor);
}
public void setDrawable(Drawable fabDrawable)
{
mBitmap = ((BitmapDrawable) fabDrawable).getBitmap();
invalidate();
} }
public void init(int fabColor) public FloatingActionButton(Context context, AttributeSet attrs, int defStyleAttr) {
{ super(context, attrs, defStyleAttr);
setWillNotDraw(false);
this.setLayerType(View.LAYER_TYPE_SOFTWARE, null); TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.FloatingActionButton);
mButtonPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mColor = a.getColor(R.styleable.FloatingActionButton_color, Color.WHITE);
mButtonPaint.setColor(fabColor);
mButtonPaint.setStyle(Paint.Style.FILL); mButtonPaint.setStyle(Paint.Style.FILL);
mButtonPaint.setShadowLayer(10.0f, 0.0f, 3.5f, Color.argb(100, 0, 0, 0)); mButtonPaint.setColor(mColor);
mDrawablePaint = new Paint(Paint.ANTI_ALIAS_FLAG); float radius, dx, dy;
invalidate(); radius = a.getFloat(R.styleable.FloatingActionButton_shadowRadius, 10.0f);
dx = a.getFloat(R.styleable.FloatingActionButton_shadowDx, 0.0f);
dy = a.getFloat(R.styleable.FloatingActionButton_shadowDy, 3.5f);
int color = a.getInteger(R.styleable.FloatingActionButton_shadowColor, Color.argb(100, 0, 0, 0));
mButtonPaint.setShadowLayer(radius, dx, dy, color);
WindowManager mWindowManager = (WindowManager) _context.getSystemService(Context.WINDOW_SERVICE); Drawable drawable = a.getDrawable(R.styleable.FloatingActionButton_drawable);
if (null != drawable) {
mBitmap = ((BitmapDrawable) drawable).getBitmap();
}
setWillNotDraw(false);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
WindowManager mWindowManager = (WindowManager)
context.getSystemService(Context.WINDOW_SERVICE);
Display display = mWindowManager.getDefaultDisplay(); Display display = mWindowManager.getDefaultDisplay();
Point size = new Point(); Point size = new Point();
display.getSize(size); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
mScreenHeight = size.y; display.getSize(size);
mYHidden = size.y;
} else mYHidden = display.getHeight();
}
mShowAnimation = ObjectAnimator.ofFloat(this, "Y", currentY); public static int darkenColor(int color) {
mHideAnimation = ObjectAnimator.ofFloat(this, "Y", mScreenHeight); float[] hsv = new float[3];
Color.colorToHSV(color, hsv);
hsv[2] *= 0.8f;
return Color.HSVToColor(hsv);
}
public void setColor(int color) {
mColor = color;
mButtonPaint.setColor(mColor);
invalidate();
}
public void setDrawable(Drawable drawable) {
mBitmap = ((BitmapDrawable) drawable).getBitmap();
invalidate();
} }
@Override @Override
protected void onDraw(Canvas canvas) protected void onDraw(Canvas canvas) {
{ canvas.drawCircle(getWidth() / 2, getHeight() / 2, (float) (getWidth() / 2.6), mButtonPaint);
setClickable(true); if (null != mBitmap) {
canvas.drawCircle(getWidth()/2, getHeight()/2,(float) (getWidth()/2.6), mButtonPaint); canvas.drawBitmap(mBitmap, (getWidth() - mBitmap.getWidth()) / 2,
canvas.drawBitmap(mBitmap, (getWidth() - mBitmap.getWidth()) / 2, (getHeight() - mBitmap.getHeight()) / 2, mDrawablePaint); (getHeight() - mBitmap.getHeight()) / 2, mDrawablePaint);
}
} }
@Override @Override
public boolean onTouchEvent(MotionEvent event) protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
{ // Perform the default behavior
if(event.getAction() == MotionEvent.ACTION_UP) super.onLayout(changed, left, top, right, bottom);
{
setAlpha(1.0f); // Store the FAB button's displayed Y position if we are not already aware of it
if (mYDisplayed == -1) {
mYDisplayed = ViewHelper.getY(this);
} }
else if(event.getAction() == MotionEvent.ACTION_DOWN) }
{
setAlpha(0.6f); @Override
public boolean onTouchEvent(MotionEvent event) {
int color;
if (event.getAction() == MotionEvent.ACTION_UP) {
color = mColor;
} else {
color = darkenColor(mColor);
} }
mButtonPaint.setColor(color);
invalidate();
return super.onTouchEvent(event); return super.onTouchEvent(event);
} }
public int dpToPx(int dp) public void hide(boolean hide) {
{ // If the hidden state is being updated
DisplayMetrics displayMetrics = getContext().getResources().getDisplayMetrics(); if (mHidden != hide) {
return Math.round(dp * (displayMetrics.xdpi / DisplayMetrics.DENSITY_DEFAULT));
}
public void hideFab() // Store the new hidden state
{ mHidden = hide;
if(!mHidden && mShowAnimation != null && !mShowAnimation.isRunning())
{ // Animate the FAB to it's new Y position
currentY = getY(); ObjectAnimator animator = ObjectAnimator.ofFloat(this, "y", mHidden ? mYHidden : mYDisplayed).setDuration(500);
mHideAnimation = ObjectAnimator.ofFloat(this, "Y", mScreenHeight); animator.setInterpolator(mInterpolator);
mHideAnimation.setInterpolator(new AccelerateInterpolator()); animator.start();
mHideAnimation.start();
mHidden = true;
} }
} }
public void showFab()
{
if(mHidden && mHideAnimation != null && !mHideAnimation.isRunning())
{
mShowAnimation = ObjectAnimator.ofFloat(this, "Y", currentY);
mShowAnimation.setInterpolator(new DecelerateInterpolator());
mShowAnimation.start();
mHidden = false;
}
}
public boolean isHidden(){
return mHidden;
}
public void listenTo(AbsListView listView) { public void listenTo(AbsListView listView) {
if (null != listView) { if (null != listView) {
listView.setOnScrollListener(new DirectionScrollListener(this)); listView.setOnScrollListener(new DirectionScrollListener(this));
} }
} }
} }

View File

@ -16,11 +16,11 @@ dependencies {
android { android {
compileSdkVersion 20 compileSdkVersion 20
buildToolsVersion "20.0.0" buildToolsVersion '20.0.0'
defaultConfig { defaultConfig {
minSdkVersion 7 minSdkVersion 7
targetSdkVersion 20 targetSdkVersion 19
} }
} }

View File

@ -25,12 +25,12 @@ android {
} }
compileSdkVersion 20 compileSdkVersion 20
buildToolsVersion "20.0.0" buildToolsVersion '20.0.0'
defaultConfig { defaultConfig {
applicationId "sharedcode.turboeditor" applicationId "sharedcode.turboeditor"
minSdkVersion 14 minSdkVersion 11
targetSdkVersion 20 targetSdkVersion 19
versionCode 1 versionCode 1
versionName "1.0" versionName "1.0"
} }
@ -45,17 +45,29 @@ android {
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
} }
} }
lintOptions {
abortOnError false
}
packagingOptions {
exclude 'META-INF/LICENSE.txt'
exclude 'META-INF/NOTICE.txt'
}
} }
dependencies { dependencies {
compile fileTree(dir: 'libs', include: ['*.jar']) compile fileTree(dir: 'libs', include: ['*.jar'])
compile project(':libraries:RootCommands') compile project(':libraries:RootCommands')
compile project(':libraries:FloatingActionButton') compile project(':libraries:FloatingActionButton')
compile "com.android.support:support-v4:19.0.+"
compile('de.greenrobot:eventbus:2.2.1') { compile('de.greenrobot:eventbus:2.2.1') {
exclude module: 'support-v4' exclude module: 'support-v4'
} }
// compile 'com.googlecode.juniversalchardet:juniversalchardet:1.0.3'
compile 'org.apache.commons:commons-lang3:3.1'
compile files('libs/juniversalchardet-1.0.3.jar')
compile 'com.android.support:support-v4:19.0.+'
compile 'com.github.gabrielemariotti.changeloglib:library:1.5.1' compile 'com.github.gabrielemariotti.changeloglib:library:1.5.1'
compile 'commons-io:commons-io:2.4' compile 'commons-io:commons-io:2.4'
compile 'com.android.support:support-annotations:20.0.0'
} }

View File

@ -1,3 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- <!--
~ Copyright (C) 2014 Vlad Mihalachi ~ Copyright (C) 2014 Vlad Mihalachi
~ ~
@ -15,10 +16,8 @@
~ ~
~ You should have received a copy of the GNU General Public License ~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>. ~ along with this program. If not, see <http://www.gnu.org/licenses/>.
--> -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="sharedcode.turboeditor"> package="sharedcode.turboeditor" >
</manifest> </manifest>

View File

@ -0,0 +1,144 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.vending.billing;
import android.os.Bundle;
/**
* InAppBillingService is the service that provides in-app billing version 3 and beyond.
* This service provides the following features:
* 1. Provides a new API to get details of in-app items published for the app including
* price, type, title and description.
* 2. The purchase flow is synchronous and purchase information is available immediately
* after it completes.
* 3. Purchase information of in-app purchases is maintained within the Google Play system
* till the purchase is consumed.
* 4. An API to consume a purchase of an inapp item. All purchases of one-time
* in-app items are consumable and thereafter can be purchased again.
* 5. An API to get current purchases of the user immediately. This will not contain any
* consumed purchases.
*
* All calls will give a response code with the following possible values
* RESULT_OK = 0 - success
* RESULT_USER_CANCELED = 1 - user pressed back or canceled a dialog
* RESULT_BILLING_UNAVAILABLE = 3 - this billing API version is not supported for the type requested
* RESULT_ITEM_UNAVAILABLE = 4 - requested SKU is not available for purchase
* RESULT_DEVELOPER_ERROR = 5 - invalid arguments provided to the API
* RESULT_ERROR = 6 - Fatal error during the API action
* RESULT_ITEM_ALREADY_OWNED = 7 - Failure to purchase since item is already owned
* RESULT_ITEM_NOT_OWNED = 8 - Failure to consume since item is not owned
*/
interface IInAppBillingService {
/**
* Checks support for the requested billing API version, package and in-app type.
* Minimum API version supported by this interface is 3.
* @param apiVersion the billing version which the app is using
* @param packageName the package name of the calling app
* @param type type of the in-app item being purchased "inapp" for one-time purchases
* and "subs" for subscription.
* @return RESULT_OK(0) on success, corresponding result code on failures
*/
int isBillingSupported(int apiVersion, String packageName, String type);
/**
* Provides details of a list of SKUs
* Given a list of SKUs of a valid type in the skusBundle, this returns a bundle
* with a list JSON strings containing the productId, price, title and description.
* This API can be called with a maximum of 20 SKUs.
* @param apiVersion billing API version that the Third-party is using
* @param packageName the package name of the calling app
* @param skusBundle bundle containing a StringArrayList of SKUs with key "ITEM_ID_LIST"
* @return Bundle containing the following key-value pairs
* "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on
* failure as listed above.
* "DETAILS_LIST" with a StringArrayList containing purchase information
* in JSON format similar to:
* '{ "productId" : "exampleSku", "type" : "inapp", "price" : "$5.00",
* "title : "Example Title", "description" : "This is an example description" }'
*/
Bundle getSkuDetails(int apiVersion, String packageName, String type, in Bundle skusBundle);
/**
* Returns a pending intent to launch the purchase flow for an in-app item by providing a SKU,
* the type, a unique purchase token and an optional developer payload.
* @param apiVersion billing API version that the app is using
* @param packageName package name of the calling app
* @param sku the SKU of the in-app item as published in the developer console
* @param type the type of the in-app item ("inapp" for one-time purchases
* and "subs" for subscription).
* @param developerPayload optional argument to be sent back with the purchase information
* @return Bundle containing the following key-value pairs
* "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on
* failure as listed above.
* "BUY_INTENT" - PendingIntent to start the purchase flow
*
* The Pending intent should be launched with startIntentSenderForResult. When purchase flow
* has completed, the onActivityResult() will give a resultCode of OK or CANCELED.
* If the purchase is successful, the result data will contain the following key-value pairs
* "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on
* failure as listed above.
* "INAPP_PURCHASE_DATA" - String in JSON format similar to
* '{"orderId":"12999763169054705758.1371079406387615",
* "packageName":"com.example.app",
* "productId":"exampleSku",
* "purchaseTime":1345678900000,
* "purchaseToken" : "122333444455555",
* "developerPayload":"example developer payload" }'
* "INAPP_DATA_SIGNATURE" - String containing the signature of the purchase data that
* was signed with the private key of the developer
* TODO: change this to app-specific keys.
*/
Bundle getBuyIntent(int apiVersion, String packageName, String sku, String type,
String developerPayload);
/**
* Returns the current SKUs owned by the user of the type and package name specified along with
* purchase information and a signature of the data to be validated.
* This will return all SKUs that have been purchased in V3 and managed items purchased using
* V1 and V2 that have not been consumed.
* @param apiVersion billing API version that the app is using
* @param packageName package name of the calling app
* @param type the type of the in-app items being requested
* ("inapp" for one-time purchases and "subs" for subscription).
* @param continuationToken to be set as null for the first call, if the number of owned
* skus are too many, a continuationToken is returned in the response bundle.
* This method can be called again with the continuation token to get the next set of
* owned skus.
* @return Bundle containing the following key-value pairs
* "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on
* failure as listed above.
* "INAPP_PURCHASE_ITEM_LIST" - StringArrayList containing the list of SKUs
* "INAPP_PURCHASE_DATA_LIST" - StringArrayList containing the purchase information
* "INAPP_DATA_SIGNATURE_LIST"- StringArrayList containing the signatures
* of the purchase information
* "INAPP_CONTINUATION_TOKEN" - String containing a continuation token for the
* next set of in-app purchases. Only set if the
* user has more owned skus than the current list.
*/
Bundle getPurchases(int apiVersion, String packageName, String type, String continuationToken);
/**
* Consume the last purchase of the given SKU. This will result in this item being removed
* from all subsequent responses to getPurchases() and allow re-purchase of this item.
* @param apiVersion billing API version that the app is using
* @param packageName package name of the calling app
* @param purchaseToken token in the purchase information JSON that identifies the purchase
* to be consumed
* @return 0 if consumption succeeded. Appropriate error values for failures.
*/
int consumePurchase(int apiVersion, String packageName, String purchaseToken);
}

View File

@ -30,20 +30,16 @@ import android.widget.ListView;
import android.widget.TextView; import android.widget.TextView;
import sharedcode.turboeditor.R; import sharedcode.turboeditor.R;
import sharedcode.turboeditor.preferences.PreferenceHelper; import sharedcode.turboeditor.util.ThemeUtils;
public class LicensesActivity extends Activity implements AdapterView.OnItemClickListener { public class LicensesActivity extends Activity implements AdapterView.OnItemClickListener {
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
ThemeUtils.setTheme(this);
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
boolean light = PreferenceHelper.getLightTheme(this);
if (light) {
setTheme(R.style.AppTheme_Light);
} else {
setTheme(R.style.AppTheme_Dark);
}
setContentView(R.layout.activity_licenses); setContentView(R.layout.activity_licenses);
ListView listView = (ListView) findViewById(android.R.id.list); ListView listView = (ListView) findViewById(android.R.id.list);
listView.setOnItemClickListener(this); listView.setOnItemClickListener(this);

View File

@ -0,0 +1,44 @@
/*
* Copyright (C) 2014 Vlad Mihalachi
*
* This file is part of Turbo Editor.
*
* Turbo Editor is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Turbo Editor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package sharedcode.turboeditor.activity;
import android.app.Application;
import android.view.ViewConfiguration;
import java.lang.reflect.Field;
public class MyApp extends Application {
@Override
public void onCreate() {
super.onCreate();
// force to sow the overflow menu icon
try {
ViewConfiguration config = ViewConfiguration.get(this);
Field menuKeyField = ViewConfiguration.class.getDeclaredField("sHasPermanentMenuKey");
if (menuKeyField != null) {
menuKeyField.setAccessible(true);
menuKeyField.setBoolean(config, false);
}
} catch (Exception ex) {
// Ignore
}
}
}

View File

@ -1,166 +0,0 @@
/*
* Copyright (C) 2014 Vlad Mihalachi
*
* This file is part of Turbo Editor.
*
* Turbo Editor is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Turbo Editor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package sharedcode.turboeditor.activity;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;
import sharedcode.turboeditor.R;
import sharedcode.turboeditor.util.AppInfoHelper;
import sharedcode.turboeditor.util.Constants;
import sharedcode.turboeditor.util.ProCheckUtils;
public class PreferenceAbout extends Activity {
@Override
public void onCreate(final Bundle savedInstanceState) {
setTheme(R.style.AppTheme_Dark);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_about);
TextView changeLogText = (TextView) findViewById(R.id.changelog_text);
TextView proVersionText = (TextView) findViewById(R.id.pro_version_text);
try {
changeLogText.setText(String.format(getString(R.string.app_version), getPackageManager().getPackageInfo(getPackageName(), 0).versionName));
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
proVersionText.setVisibility(ProCheckUtils.isPro(getBaseContext()) ? View.GONE : View.VISIBLE);
}
@Override
protected void onDestroy() {
//checkout.stop();
super.onDestroy();
}
public void OpenPlayStore(View view) {
try {
if (Constants.FOR_AMAZON) {
String url = "amzn://apps/android?p=com.maskyn.fileeditor";
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url))
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
} else {
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://search?q=pub:Maskyn"))
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
}
} catch (Exception e) {
}
}
public void GoToProVersion(View view) {
try {
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=com.maskyn.fileeditorpro"))
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
} catch (Exception e) {
}
}
public void OpenGithub(View view) {
String url = "https://github.com/vmihalachi/TurboEditor";
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url))
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
}
public void SendFeedback(View view) {
String url = "http://forum.xda-developers.com/android/apps-games/app-turbo-editor-text-editor-t2832016";
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url))
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
}
public void SendMail(View view) {
Intent i = new Intent(Intent.ACTION_SEND);
i.setType("message/rfc822");
i.putExtra(Intent.EXTRA_EMAIL, new String[]{"maskyngames@gmail.com"});
i.putExtra(Intent.EXTRA_SUBJECT, AppInfoHelper.getApplicationName(getBaseContext()) + " " + AppInfoHelper.getCurrentVersion(getBaseContext()));
i.putExtra(Intent.EXTRA_TEXT, "");
try {
startActivity(Intent.createChooser(i, getString(R.string.nome_app_turbo_editor)));
} catch (android.content.ActivityNotFoundException ex) {
}
}
public void OpenTranslatePage(View view) {
String url = "http://crowdin.net/project/turbo-client";
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url))
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
}
public void OpenGooglePlusCommunity(View view) {
String url = "https://plus.google.com/u/0/communities/111974095419108178946";
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url))
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
}
/*void setupClickablePreferences() {
final Preference email = findPreference("aboutactivity_authoremail"),
changelog = findPreference("aboutactivity_changelog"),
open_source_licenses = findPreference("aboutactivity_open_source_licenses"),
market = findPreference("aboutactivity_authormarket");
if (email != null) {
email.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(final Preference preference) {
return false;
}
});
}
if (changelog != null) {
changelog.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(final Preference preference) {
ChangelogDialogFragment.showChangeLogDialog(getFragmentManager());
return false;
}
});
}
if (open_source_licenses != null) {
open_source_licenses.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(final Preference preference) {
startActivity(new Intent(PreferenceAbout.this, LicensesActivity.class));
return false;
}
});
}
if (market != null) {
market.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(final Preference preference) {
return false;
}
});
}
}*/
}

View File

@ -23,11 +23,13 @@ import android.app.Activity;
import android.content.Intent; import android.content.Intent;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.view.MenuItemCompat;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.widget.AdapterView; import android.widget.AdapterView;
import android.widget.Filter;
import android.widget.ListView; import android.widget.ListView;
import android.widget.PopupMenu; import android.widget.PopupMenu;
import android.widget.SearchView; import android.widget.SearchView;
@ -35,13 +37,6 @@ import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import com.faizmalkani.floatingactionbutton.FloatingActionButton; import com.faizmalkani.floatingactionbutton.FloatingActionButton;
import sharedcode.turboeditor.R;
import sharedcode.turboeditor.adapter.AdapterDetailedList;
import sharedcode.turboeditor.fragment.EditDialogFragment;
import sharedcode.turboeditor.util.AlphanumComparator;
import sharedcode.turboeditor.util.Constants;
import sharedcode.turboeditor.preferences.PreferenceHelper;
import sharedcode.turboeditor.util.RootUtils;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.FilenameUtils;
@ -57,24 +52,29 @@ import java.util.Comparator;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
public class SelectFileActivity extends Activity implements SearchView.OnQueryTextListener, AdapterView.OnItemClickListener, EditDialogFragment.EditDialogListener { import sharedcode.turboeditor.R;
private String currentFolder; import sharedcode.turboeditor.adapter.AdapterDetailedList;
import sharedcode.turboeditor.fragment.EditTextDialog;
import sharedcode.turboeditor.preferences.PreferenceHelper;
import sharedcode.turboeditor.root.RootUtils;
import sharedcode.turboeditor.util.AlphanumComparator;
import sharedcode.turboeditor.util.Build;
import sharedcode.turboeditor.util.ThemeUtils;
public class SelectFileActivity extends Activity implements SearchView.OnQueryTextListener, AdapterView.OnItemClickListener, EditTextDialog.EditDialogListener {
private String currentFolder = PreferenceHelper.SD_CARD_ROOT;
private ListView listView; private ListView listView;
private boolean wantAFile; private boolean wantAFile = true;
private MenuItem mSearchViewMenuItem; private MenuItem mSearchViewMenuItem;
private SearchView mSearchView; private SearchView mSearchView;
private Filter filter;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
boolean light = PreferenceHelper.getLightTheme(this); ThemeUtils.setTheme(this);
if (light) {
setTheme(R.style.AppTheme_Light);
} else {
setTheme(R.style.AppTheme_Dark);
}
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_select_file); setContentView(R.layout.activity_select_file);
@ -87,7 +87,7 @@ public class SelectFileActivity extends Activity implements SearchView.OnQueryTe
listView.setOnItemClickListener(this); listView.setOnItemClickListener(this);
listView.setTextFilterEnabled(true); listView.setTextFilterEnabled(true);
FloatingActionButton mFab = (FloatingActionButton)findViewById(R.id.fabbutton); FloatingActionButton mFab = (FloatingActionButton) findViewById(R.id.fabbutton);
mFab.setColor(getResources().getColor(R.color.fab_light)); mFab.setColor(getResources().getColor(R.color.fab_light));
mFab.setDrawable(getResources().getDrawable(R.drawable.ic_fab_add)); mFab.setDrawable(getResources().getDrawable(R.drawable.ic_fab_add));
@ -101,14 +101,14 @@ public class SelectFileActivity extends Activity implements SearchView.OnQueryTe
popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override @Override
public boolean onMenuItemClick(MenuItem item) { public boolean onMenuItemClick(MenuItem item) {
final EditDialogFragment dialogFrag; final EditTextDialog dialogFrag;
int i = item.getItemId(); int i = item.getItemId();
if (i == R.id.im_new_file) { if (i == R.id.im_new_file) {
dialogFrag = EditDialogFragment.newInstance(EditDialogFragment.Actions.NewFile); dialogFrag = EditTextDialog.newInstance(EditTextDialog.Actions.NewFile);
dialogFrag.show(getFragmentManager().beginTransaction(), "dialog"); dialogFrag.show(getFragmentManager().beginTransaction(), "dialog");
return true; return true;
} else if (i == R.id.im_new_folder) { } else if (i == R.id.im_new_folder) {
dialogFrag = EditDialogFragment.newInstance(EditDialogFragment.Actions.NewFolder); dialogFrag = EditTextDialog.newInstance(EditTextDialog.Actions.NewFolder);
dialogFrag.show(getFragmentManager().beginTransaction(), "dialog"); dialogFrag.show(getFragmentManager().beginTransaction(), "dialog");
return true; return true;
} else { } else {
@ -147,10 +147,13 @@ public class SelectFileActivity extends Activity implements SearchView.OnQueryTe
} }
public boolean onQueryTextChange(String newText) { public boolean onQueryTextChange(String newText) {
if (filter == null)
return true;
if (TextUtils.isEmpty(newText)) { if (TextUtils.isEmpty(newText)) {
listView.clearTextFilter(); filter.filter(null);
} else { } else {
listView.setFilterText(newText); filter.filter(newText);
} }
return true; return true;
} }
@ -205,7 +208,7 @@ public class SelectFileActivity extends Activity implements SearchView.OnQueryTe
public boolean onCreateOptionsMenu(Menu menu) { public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_select_file, menu); getMenuInflater().inflate(R.menu.activity_select_file, menu);
mSearchViewMenuItem = menu.findItem(R.id.im_search); mSearchViewMenuItem = menu.findItem(R.id.im_search);
mSearchView = (SearchView) mSearchViewMenuItem.getActionView(); mSearchView = (SearchView) MenuItemCompat.getActionView(mSearchViewMenuItem);
mSearchView.setIconifiedByDefault(true); mSearchView.setIconifiedByDefault(true);
mSearchView.setOnQueryTextListener(this); mSearchView.setOnQueryTextListener(this);
mSearchView.setSubmitButtonEnabled(false); mSearchView.setSubmitButtonEnabled(false);
@ -218,15 +221,15 @@ public class SelectFileActivity extends Activity implements SearchView.OnQueryTe
MenuItem imSetAsWorkingFolder = menu.findItem(R.id.im_set_as_working_folder); MenuItem imSetAsWorkingFolder = menu.findItem(R.id.im_set_as_working_folder);
MenuItem imIsWorkingFolder = menu.findItem(R.id.im_is_working_folder); MenuItem imIsWorkingFolder = menu.findItem(R.id.im_is_working_folder);
MenuItem imSelectFolder = menu.findItem(R.id.im_select_folder); MenuItem imSelectFolder = menu.findItem(R.id.im_select_folder);
if(imSetAsWorkingFolder != null){ if (imSetAsWorkingFolder != null) {
// set the imSetAsWorkingFolder visible only if the two folder dont concide // set the imSetAsWorkingFolder visible only if the two folder dont concide
imSetAsWorkingFolder.setVisible(!currentFolder.equals(PreferenceHelper.getWorkingFolder(SelectFileActivity.this))); imSetAsWorkingFolder.setVisible(!currentFolder.equals(PreferenceHelper.getWorkingFolder(SelectFileActivity.this)));
} }
if(imIsWorkingFolder != null) { if (imIsWorkingFolder != null) {
// set visible is the other is invisible // set visible is the other is invisible
imIsWorkingFolder.setVisible(!imSetAsWorkingFolder.isVisible()); imIsWorkingFolder.setVisible(!imSetAsWorkingFolder.isVisible());
} }
if(imSelectFolder != null) { if (imSelectFolder != null) {
imSelectFolder.setVisible(!wantAFile); imSelectFolder.setVisible(!wantAFile);
} }
return super.onPrepareOptionsMenu(menu); return super.onPrepareOptionsMenu(menu);
@ -242,6 +245,9 @@ public class SelectFileActivity extends Activity implements SearchView.OnQueryTe
PreferenceHelper.setWorkingFolder(SelectFileActivity.this, currentFolder); PreferenceHelper.setWorkingFolder(SelectFileActivity.this, currentFolder);
invalidateOptionsMenu(); invalidateOptionsMenu();
return true; return true;
} else if (i == R.id.im_is_working_folder) {
Toast.makeText(getBaseContext(), R.string.is_the_working_folder, Toast.LENGTH_SHORT).show();
return true;
} else if (i == R.id.im_select_folder) { } else if (i == R.id.im_select_folder) {
returnData(currentFolder); returnData(currentFolder);
return true; return true;
@ -251,11 +257,11 @@ public class SelectFileActivity extends Activity implements SearchView.OnQueryTe
@Override @Override
public void onFinishEditDialog(final String inputText, final String hint, final EditDialogFragment.Actions actions) { public void onFinishEditDialog(final String inputText, final String hint, final EditTextDialog.Actions actions) {
if (actions == EditDialogFragment.Actions.NewFile && !TextUtils.isEmpty(inputText)) { if (actions == EditTextDialog.Actions.NewFile && !TextUtils.isEmpty(inputText)) {
File file = new File(currentFolder, inputText); File file = new File(currentFolder, inputText);
returnData(file.getAbsolutePath()); returnData(file.getAbsolutePath());
} else if (actions == EditDialogFragment.Actions.NewFolder && !TextUtils.isEmpty(inputText)) { } else if (actions == EditTextDialog.Actions.NewFolder && !TextUtils.isEmpty(inputText)) {
File file = new File(currentFolder, inputText); File file = new File(currentFolder, inputText);
file.mkdirs(); file.mkdirs();
new UpdateList().execute(currentFolder); new UpdateList().execute(currentFolder);
@ -275,7 +281,7 @@ public class SelectFileActivity extends Activity implements SearchView.OnQueryTe
super.onPreExecute(); super.onPreExecute();
if (mSearchView != null) { if (mSearchView != null) {
mSearchView.setIconified(true); mSearchView.setIconified(true);
mSearchViewMenuItem.collapseActionView(); MenuItemCompat.collapseActionView(mSearchViewMenuItem);
mSearchView.setQuery("", false); mSearchView.setQuery("", false);
} }
@ -329,8 +335,8 @@ public class SelectFileActivity extends Activity implements SearchView.OnQueryTe
getString(R.string.folder), getString(R.string.folder),
"")); ""));
} else if (f.isFile() } else if (f.isFile()
&& !FilenameUtils.isExtension(f.getName().toLowerCase(), unopenableExtensions) && !FilenameUtils.isExtension(f.getName().toLowerCase(), unopenableExtensions)
&& FileUtils.sizeOf(f) <= Constants.MAX_FILE_SIZE * FileUtils.ONE_KB) { && FileUtils.sizeOf(f) <= Build.MAX_FILE_SIZE * FileUtils.ONE_KB) {
final long fileSize = f.length(); final long fileSize = f.length();
SimpleDateFormat format = new SimpleDateFormat("MMM dd, yyyy hh:mm a"); SimpleDateFormat format = new SimpleDateFormat("MMM dd, yyyy hh:mm a");
String date = format.format(f.lastModified()); String date = format.format(f.lastModified());
@ -357,6 +363,7 @@ public class SelectFileActivity extends Activity implements SearchView.OnQueryTe
boolean isRoot = currentFolder.equals("/"); boolean isRoot = currentFolder.equals("/");
AdapterDetailedList mAdapter = new AdapterDetailedList(getBaseContext(), names, isRoot); AdapterDetailedList mAdapter = new AdapterDetailedList(getBaseContext(), names, isRoot);
listView.setAdapter(mAdapter); listView.setAdapter(mAdapter);
filter = mAdapter.getFilter();
} else if (exceptionMessage != null) { } else if (exceptionMessage != null) {
Toast.makeText(SelectFileActivity.this, exceptionMessage, Toast.LENGTH_SHORT).show(); Toast.makeText(SelectFileActivity.this, exceptionMessage, Toast.LENGTH_SHORT).show();
} }

View File

@ -29,14 +29,14 @@ import android.widget.Filter;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import sharedcode.turboeditor.R;
import sharedcode.turboeditor.util.MimeTypes;
import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.FilenameUtils;
import java.util.Arrays; import java.util.Arrays;
import java.util.LinkedList; import java.util.LinkedList;
import sharedcode.turboeditor.R;
import sharedcode.turboeditor.util.MimeTypes;
public class AdapterDetailedList extends public class AdapterDetailedList extends
ArrayAdapter<AdapterDetailedList.FileDetail> { ArrayAdapter<AdapterDetailedList.FileDetail> {

View File

@ -29,11 +29,11 @@ import android.widget.ArrayAdapter;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import sharedcode.turboeditor.R;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import sharedcode.turboeditor.R;
public class AdapterDrawer extends public class AdapterDrawer extends
ArrayAdapter<File> { ArrayAdapter<File> {
@ -78,10 +78,17 @@ public class AdapterDrawer extends
} }
}); });
if (TextUtils.equals(selectedPath, files.get(position).getAbsolutePath()))
if (TextUtils.equals(selectedPath, files.get(position).getAbsolutePath())) {
hold.nameLabel.setTypeface(hold.nameLabel.getTypeface(), Typeface.BOLD); hold.nameLabel.setTypeface(hold.nameLabel.getTypeface(), Typeface.BOLD);
else convertView.setBackgroundColor((convertView.getResources()
.getColor(R.color.item_selected)));
} else {
hold.nameLabel.setTypeface(hold.nameLabel.getTypeface(), Typeface.NORMAL); hold.nameLabel.setTypeface(hold.nameLabel.getTypeface(), Typeface.NORMAL);
convertView.setBackgroundColor((convertView.getResources()
.getColor(android.R.color.transparent)));
}
} else { } else {
final ViewHolder hold = ((ViewHolder) convertView.getTag()); final ViewHolder hold = ((ViewHolder) convertView.getTag());
final String fileName = files.get(position).getName(); final String fileName = files.get(position).getName();
@ -95,23 +102,30 @@ public class AdapterDrawer extends
selectedPath = ""; selectedPath = "";
} }
}); });
if (TextUtils.equals(selectedPath, files.get(position).getAbsolutePath()))
hold.nameLabel.setTypeface(hold.nameLabel.getTypeface(), Typeface.BOLD); if (TextUtils.equals(selectedPath, files.get(position).getAbsolutePath())) {
else hold.nameLabel.setTypeface(null, Typeface.BOLD);
hold.nameLabel.setTypeface(hold.nameLabel.getTypeface(), Typeface.NORMAL); convertView.setBackgroundColor((convertView.getResources()
.getColor(R.color.item_selected)));
} else {
hold.nameLabel.setTypeface(null, Typeface.NORMAL);
convertView.setBackgroundColor((convertView.getResources()
.getColor(android.R.color.transparent)));
}
} }
return convertView; return convertView;
} }
public void selectView(String selectedPath) { public void selectView(String selectedPath) {
callbacks.ItemSelected(selectedPath); //callbacks.ItemSelected(selectedPath);
this.selectedPath = selectedPath; this.selectedPath = selectedPath;
notifyDataSetChanged(); notifyDataSetChanged();
} }
public interface Callbacks { public interface Callbacks {
void CancelItem(int position, boolean andCloseOpenedFile); void CancelItem(int position, boolean andCloseOpenedFile);
void ItemSelected(String path);
//void ItemSelected(String path);
} }
public static class ViewHolder { public static class ViewHolder {

View File

@ -0,0 +1,75 @@
/*
* Copyright (C) 2014 Vlad Mihalachi
*
* This file is part of Turbo Editor.
*
* Turbo Editor is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Turbo Editor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package sharedcode.turboeditor.adapter;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
import sharedcode.turboeditor.R;
public class AdapterTwoItem extends
ArrayAdapter<String> {
private final LayoutInflater inflater;
private final String[] lines1;
private final String[] lines2;
public AdapterTwoItem(Context context,
String[] lines1,
String[] lines2) {
super(context, R.layout.item_two_lines, lines1);
this.lines1 = lines1;
this.lines2 = lines2;
this.inflater = LayoutInflater.from(context);
}
@Override
public View getView(final int position,
View convertView, final ViewGroup parent) {
if (convertView == null) {
convertView = this.inflater
.inflate(R.layout.item_two_lines,
parent, false);
final ViewHolder hold = new ViewHolder();
hold.line1 = (TextView) convertView.findViewById(android.R.id.text1);
hold.line2 = (TextView) convertView.findViewById(android.R.id.text2);
convertView.setTag(hold);
hold.line1.setText(lines1[position]);
hold.line2.setText(lines2[position]);
} else {
final ViewHolder hold = ((ViewHolder) convertView.getTag());
hold.line1.setText(lines1[position]);
hold.line2.setText(lines2[position]);
}
return convertView;
}
public static class ViewHolder {
public TextView line1;
public TextView line2;
}
}

View File

@ -0,0 +1,90 @@
/*
* Copyright (C) 2014 Vlad Mihalachi
*
* This file is part of Turbo Editor.
*
* Turbo Editor is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Turbo Editor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package sharedcode.turboeditor.fragment;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.os.Bundle;
import android.text.Html;
import android.view.View;
import android.widget.Toast;
import sharedcode.turboeditor.R;
import sharedcode.turboeditor.views.DialogHelper;
/**
* Dialog fragment that shows some info about this application.
*
* @author Artem Chepurnoy
*/
public class AboutDialog extends DialogFragment {
private static final String VERSION_UNAVAILABLE = "N/A";
/**
* Merges app name and version name into one.
*/
public static CharSequence getVersionName(Context context) {
PackageManager pm = context.getPackageManager();
String packageName = context.getPackageName();
String versionName;
try {
PackageInfo info = pm.getPackageInfo(packageName, 0);
versionName = info.versionName;
// Make the info part of version name a bit smaller.
if (versionName.indexOf('-') >= 0) {
versionName = versionName.replaceFirst("\\-", "<small>-") + "</small>";
}
} catch (PackageManager.NameNotFoundException e) {
versionName = VERSION_UNAVAILABLE;
}
Resources res = context.getResources();
return Html.fromHtml(
res.getString(R.string.about_title,
res.getString(R.string.nome_app_turbo_editor), versionName)
);
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Context context = getActivity();
assert context != null;
CharSequence message = Html.fromHtml(getString(
R.string.about_message));
View view = new DialogHelper.Builder(context)
.setIcon(getResources().getDrawable(R.drawable.ic_launcher))
.setTitle(getVersionName(context))
.setMessage(message)
.createCommonView();
return new AlertDialog.Builder(context)
.setView(view)
.setNeutralButton(R.string.close, null)
.create();
}
}

View File

@ -31,22 +31,21 @@ import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import sharedcode.turboeditor.R;
import it.gmariotti.changelibs.library.view.ChangeLogListView; import it.gmariotti.changelibs.library.view.ChangeLogListView;
import sharedcode.turboeditor.util.Constants; import sharedcode.turboeditor.R;
import sharedcode.turboeditor.util.Build;
public class ChangelogDialogFragment extends DialogFragment { public class ChangelogDialog extends DialogFragment {
public static void showChangeLogDialog(FragmentManager fragmentManager) { public static void showChangeLogDialog(FragmentManager fragmentManager) {
ChangelogDialogFragment changelogDialogFragment = new ChangelogDialogFragment(); ChangelogDialog changelogDialog = new ChangelogDialog();
FragmentTransaction ft = fragmentManager.beginTransaction(); FragmentTransaction ft = fragmentManager.beginTransaction();
Fragment prev = fragmentManager.findFragmentByTag("changelogdemo_dialog"); Fragment prev = fragmentManager.findFragmentByTag("changelogdemo_dialog");
if (prev != null) { if (prev != null) {
ft.remove(prev); ft.remove(prev);
} }
ft.addToBackStack(null); ft.addToBackStack(null);
changelogDialogFragment.show(ft, "changelogdemo_dialog"); changelogDialog.show(ft, "changelogdemo_dialog");
} }
@ -73,7 +72,7 @@ public class ChangelogDialogFragment extends DialogFragment {
@Override @Override
public void onClick(final DialogInterface dialog, final int which) { public void onClick(final DialogInterface dialog, final int which) {
try { try {
if (Constants.FOR_AMAZON) { if (Build.FOR_AMAZON) {
String url = "amzn://apps/android?p=com.maskyn.fileeditor"; String url = "amzn://apps/android?p=com.maskyn.fileeditor";
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)) startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url))
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));

View File

@ -34,16 +34,16 @@ import android.widget.TextView;
import sharedcode.turboeditor.R; import sharedcode.turboeditor.R;
// ... // ...
public class EditDialogFragment extends DialogFragment implements TextView.OnEditorActionListener { public class EditTextDialog extends DialogFragment implements TextView.OnEditorActionListener {
private EditText mEditText; private EditText mEditText;
public static EditDialogFragment newInstance(final Actions action) { public static EditTextDialog newInstance(final Actions action) {
return EditDialogFragment.newInstance(action, ""); return EditTextDialog.newInstance(action, "");
} }
public static EditDialogFragment newInstance(final Actions action, final String hint) { public static EditTextDialog newInstance(final Actions action, final String hint) {
final EditDialogFragment f = new EditDialogFragment(); final EditTextDialog f = new EditTextDialog();
final Bundle args = new Bundle(); final Bundle args = new Bundle();
args.putSerializable("action", action); args.putSerializable("action", action);
args.putString("hint", hint); args.putString("hint", hint);

View File

@ -22,25 +22,20 @@ package sharedcode.turboeditor.fragment;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.Dialog; import android.app.Dialog;
import android.app.DialogFragment; import android.app.DialogFragment;
import android.content.DialogInterface;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView; import android.widget.AdapterView;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import android.widget.CompoundButton; import android.widget.CompoundButton;
import android.widget.ListView; import android.widget.ListView;
import android.widget.Switch; import android.widget.Switch;
import sharedcode.turboeditor.R;
import sharedcode.turboeditor.preferences.PreferenceHelper;
import sharedcode.turboeditor.preferences.SettingsFragment;
import sharedcode.turboeditor.util.SaveFileTask;
import org.mozilla.universalchardet.Constants; import org.mozilla.universalchardet.Constants;
public class EncodingDialogFragment extends DialogFragment implements AdapterView.OnItemClickListener { import sharedcode.turboeditor.R;
import sharedcode.turboeditor.preferences.PreferenceHelper;
public class EncodingDialog extends DialogFragment implements AdapterView.OnItemClickListener {
private final String[] encodings = new String[]{ private final String[] encodings = new String[]{
Constants.CHARSET_BIG5, Constants.CHARSET_BIG5,
@ -72,8 +67,8 @@ public class EncodingDialogFragment extends DialogFragment implements AdapterVie
}; };
private ListView list; private ListView list;
public static EncodingDialogFragment newInstance() { public static EncodingDialog newInstance() {
final EncodingDialogFragment f = new EncodingDialogFragment(); final EncodingDialog f = new EncodingDialog();
return f; return f;
} }

View File

@ -0,0 +1,91 @@
/*
* Copyright (C) 2014 Vlad Mihalachi
*
* This file is part of Turbo Editor.
*
* Turbo Editor is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Turbo Editor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package sharedcode.turboeditor.fragment;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.View;
import android.widget.ListView;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.util.Date;
import sharedcode.turboeditor.R;
import sharedcode.turboeditor.adapter.AdapterTwoItem;
// ...
public class FileInfoDialog extends DialogFragment {
public static FileInfoDialog newInstance(String filePath) {
final FileInfoDialog f = new FileInfoDialog();
final Bundle args = new Bundle();
args.putString("filePath", filePath);
f.setArguments(args);
return f;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final View view = getActivity().getLayoutInflater().inflate(R.layout.dialog_fragment_file_info, null);
ListView list = (ListView) view.findViewById(android.R.id.list);
File file = new File(getArguments().getString("filePath"));
// Get the last modification information.
Long lastModified = file.lastModified();
// Create a new date object and pass last modified information
// to the date object.
Date date = new Date(lastModified);
String[] lines1 = {
getString(R.string.name),
getString(R.string.folder),
getString(R.string.size),
getString(R.string.modification_date)
};
String[] lines2 = {
file.getName(),
file.getParent(),
FileUtils.byteCountToDisplaySize(file.length()),
date.toString()
};
list.setAdapter(new AdapterTwoItem(getActivity(), lines1, lines2));
return new AlertDialog.Builder(getActivity())
//.setTitle(title)
.setView(view)
.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
}
)
.create();
}
}

View File

@ -32,22 +32,22 @@ import android.widget.CompoundButton;
import android.widget.EditText; import android.widget.EditText;
import android.widget.Toast; import android.widget.Toast;
import sharedcode.turboeditor.R;
import sharedcode.turboeditor.util.SearchResult;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import sharedcode.turboeditor.R;
import sharedcode.turboeditor.texteditor.SearchResult;
import sharedcode.turboeditor.views.DialogHelper;
// ... // ...
public class FindTextDialogFragment extends DialogFragment { public class FindTextDialog extends DialogFragment {
private EditText textToFind, textToReplace; private EditText textToFind, textToReplace;
private CheckBox regexCheck, replaceCheck, matchCaseCheck; private CheckBox regexCheck, replaceCheck, matchCaseCheck;
public static FindTextDialogFragment newInstance(String allText) { public static FindTextDialog newInstance(String allText) {
final FindTextDialogFragment f = new FindTextDialogFragment(); final FindTextDialog f = new FindTextDialog();
final Bundle args = new Bundle(); final Bundle args = new Bundle();
args.putString("allText", allText); args.putString("allText", allText);
f.setArguments(args); f.setArguments(args);
@ -57,7 +57,13 @@ public class FindTextDialogFragment extends DialogFragment {
@Override @Override
public Dialog onCreateDialog(Bundle savedInstanceState) { public Dialog onCreateDialog(Bundle savedInstanceState) {
final View view = getActivity().getLayoutInflater().inflate(R.layout.dialog_fragment_find_text, null); //final View view = getActivity().getLayoutInflater().inflate(R.layout.dialog_fragment_find_text, null);
View view = new DialogHelper.Builder(getActivity())
.setTitle(R.string.find)
.setView(R.layout.dialog_fragment_find_text)
.createSkeletonView();
this.textToFind = (EditText) view.findViewById(R.id.text_to_find); this.textToFind = (EditText) view.findViewById(R.id.text_to_find);
this.textToReplace = (EditText) view.findViewById(R.id.text_to_replace); this.textToReplace = (EditText) view.findViewById(R.id.text_to_replace);
this.regexCheck = (CheckBox) view.findViewById(R.id.regex_check); this.regexCheck = (CheckBox) view.findViewById(R.id.regex_check);
@ -80,41 +86,29 @@ public class FindTextDialogFragment extends DialogFragment {
} }
} }
) )
.setNegativeButton(android.R.string.cancel, .setNegativeButton(android.R.string.cancel, null)
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
}
)
.create(); .create();
} }
@Override @Override
public void onStart() public void onStart() {
{
super.onStart(); //super.onStart() is where dialog.show() is actually called on the underlying dialog, so we have to do it after this point super.onStart(); //super.onStart() is where dialog.show() is actually called on the underlying dialog, so we have to do it after this point
AlertDialog d = (AlertDialog)getDialog(); AlertDialog d = (AlertDialog) getDialog();
if(d != null) if (d != null) {
{
Button positiveButton = (Button) d.getButton(Dialog.BUTTON_POSITIVE); Button positiveButton = (Button) d.getButton(Dialog.BUTTON_POSITIVE);
positiveButton.setText(getString(R.string.find)); positiveButton.setText(getString(R.string.find));
positiveButton.setOnClickListener(new View.OnClickListener() positiveButton.setOnClickListener(new View.OnClickListener() {
{
@Override @Override
public void onClick(View v) public void onClick(View v) {
{
returnData(); returnData();
} }
}); });
Button negativeButton = (Button) d.getButton(Dialog.BUTTON_NEGATIVE); Button negativeButton = (Button) d.getButton(Dialog.BUTTON_NEGATIVE);
negativeButton.setText(getString(android.R.string.cancel)); negativeButton.setText(getString(android.R.string.cancel));
negativeButton.setOnClickListener(new View.OnClickListener() negativeButton.setOnClickListener(new View.OnClickListener() {
{
@Override @Override
public void onClick(View v) public void onClick(View v) {
{
dismiss(); dismiss();
} }
}); });
@ -122,7 +116,7 @@ public class FindTextDialogFragment extends DialogFragment {
} }
void returnData() { void returnData() {
if(textToFind.getText().toString().isEmpty()) { if (textToFind.getText().toString().isEmpty()) {
this.dismiss(); this.dismiss();
} else { } else {
// we disable the okButton while we search // we disable the okButton while we search
@ -130,7 +124,11 @@ public class FindTextDialogFragment extends DialogFragment {
} }
} }
private class SearchTask extends AsyncTask<Void, Void, Void>{ public interface SearchDialogInterface {
void onSearchDone(SearchResult searchResult);
}
private class SearchTask extends AsyncTask<Void, Void, Void> {
LinkedList<Integer> foundIndex; LinkedList<Integer> foundIndex;
boolean foundSomething; boolean foundSomething;
@ -145,9 +143,9 @@ public class FindTextDialogFragment extends DialogFragment {
Matcher matcher = null; Matcher matcher = null;
foundSomething = false; foundSomething = false;
if(isRegex) { if (isRegex) {
try { try {
if(caseSensitive) if (caseSensitive)
matcher = Pattern.compile(whatToSearch, Pattern.MULTILINE).matcher(allText); matcher = Pattern.compile(whatToSearch, Pattern.MULTILINE).matcher(allText);
else else
matcher = Pattern.compile(whatToSearch, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE).matcher(allText); matcher = Pattern.compile(whatToSearch, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE).matcher(allText);
@ -156,14 +154,14 @@ public class FindTextDialogFragment extends DialogFragment {
} }
} }
if(isRegex) { if (isRegex) {
while (matcher.find()) { while (matcher.find()) {
foundSomething = true; foundSomething = true;
foundIndex.add(matcher.start()); foundIndex.add(matcher.start());
} }
} else { } else {
if(caseSensitive == false) { // by default is case sensitive if (caseSensitive == false) { // by default is case sensitive
whatToSearch = whatToSearch.toLowerCase(); whatToSearch = whatToSearch.toLowerCase();
allText = allText.toLowerCase(); allText = allText.toLowerCase();
} }
@ -183,15 +181,15 @@ public class FindTextDialogFragment extends DialogFragment {
@Override @Override
protected void onPostExecute(Void aVoid) { protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid); super.onPostExecute(aVoid);
if(foundSomething) { if (foundSomething) {
// the class that called this Dialog should implement the SearchDialogIterface // the class that called this Dialog should implement the SearchDialogIterface
SearchDialogInterface searchDialogInterface; SearchDialogInterface searchDialogInterface;
searchDialogInterface = ((SearchDialogInterface) getTargetFragment()); searchDialogInterface = ((SearchDialogInterface) getTargetFragment());
if(searchDialogInterface == null) if (searchDialogInterface == null)
searchDialogInterface = ((SearchDialogInterface) getActivity()); searchDialogInterface = ((SearchDialogInterface) getActivity());
// if who called this has not implemented the interface we return nothing // if who called this has not implemented the interface we return nothing
if(searchDialogInterface == null) if (searchDialogInterface == null)
return; return;
// else we return positions and other things // else we return positions and other things
else { else {
@ -203,11 +201,7 @@ public class FindTextDialogFragment extends DialogFragment {
} }
Toast.makeText(getActivity(), String.format(getString(R.string.occurrences_found), foundIndex.size()), Toast.LENGTH_SHORT).show(); Toast.makeText(getActivity(), String.format(getString(R.string.occurrences_found), foundIndex.size()), Toast.LENGTH_SHORT).show();
// dismiss the dialog // dismiss the dialog
FindTextDialogFragment.this.dismiss(); FindTextDialog.this.dismiss();
} }
} }
public interface SearchDialogInterface {
void onSearchDone(SearchResult searchResult);
}
} }

View File

@ -27,18 +27,17 @@ import android.view.ViewGroup;
import android.widget.AdapterView; import android.widget.AdapterView;
import android.widget.ListView; import android.widget.ListView;
import sharedcode.turboeditor.R;
import sharedcode.turboeditor.adapter.AdapterDrawer;
import sharedcode.turboeditor.util.EventBusEvents;
import sharedcode.turboeditor.preferences.PreferenceHelper;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import de.greenrobot.event.EventBus; import de.greenrobot.event.EventBus;
import sharedcode.turboeditor.R;
import sharedcode.turboeditor.adapter.AdapterDrawer;
import sharedcode.turboeditor.preferences.PreferenceHelper;
import sharedcode.turboeditor.util.EventBusEvents;
public class NavigationDrawerListFragment extends Fragment implements AdapterView.OnItemClickListener, AdapterDrawer.Callbacks { public class NavigationDrawer extends Fragment implements AdapterView.OnItemClickListener, AdapterDrawer.Callbacks {
private AdapterDrawer arrayAdapter; private AdapterDrawer arrayAdapter;
@ -91,31 +90,29 @@ public class NavigationDrawerListFragment extends Fragment implements AdapterVie
String filePath = savedPaths[position]; String filePath = savedPaths[position];
// Send the event that a file was selected // Send the event that a file was selected
EventBus.getDefault().post(new EventBusEvents.NewFileToOpen(new File(filePath))); EventBus.getDefault().post(new EventBusEvents.NewFileToOpen(new File(filePath)));
arrayAdapter.selectView(filePath);
} }
/** public void onEvent(EventBusEvents.AFileIsSelected event) {
* When a new file is opened arrayAdapter.selectView(event.getPath());
* Invoked by the main activity which receive the intent
* EventBus.getDefault().removeStickyEvent(event);
* @param event The event called }
*/
public void onEvent(EventBusEvents.NewFileToOpen event) { public void onEvent(EventBusEvents.NewFileToOpen event) {
// File paths saved in preferences // File paths saved in preferences
String[] savedPaths = PreferenceHelper.getSavedPaths(getActivity()); String[] savedPaths = PreferenceHelper.getSavedPaths(getActivity());
String selectedPath = event.getFile().getAbsolutePath(); String selectedPath = event.getFile().getAbsolutePath();
boolean pathAlreadyExist = false;
for (String savedPath : savedPaths) { for (String savedPath : savedPaths) {
// We don't need to save the file path twice // We don't need to save the file path twice
if (savedPath.equals(selectedPath)) { if (savedPath.equals(selectedPath)) {
arrayAdapter.selectView(selectedPath); pathAlreadyExist = true;
return;
} }
} }
// Add the path if it wasn't added before // Add the path if it wasn't added before
addPath(selectedPath); if (!pathAlreadyExist)
addPath(selectedPath);
arrayAdapter.selectView(selectedPath);
EventBus.getDefault().removeStickyEvent(event); EventBus.getDefault().removeStickyEvent(event);
} }
@ -198,9 +195,4 @@ public class NavigationDrawerListFragment extends Fragment implements AdapterVie
if (andCloseOpenedFile) if (andCloseOpenedFile)
EventBus.getDefault().post(new EventBusEvents.CannotOpenAFile()); EventBus.getDefault().post(new EventBusEvents.CannotOpenAFile());
} }
@Override
public void ItemSelected(String path) {
EventBus.getDefault().post(new EventBusEvents.AFileIsSelected(path));
}
} }

View File

@ -24,28 +24,25 @@ import android.app.Dialog;
import android.app.DialogFragment; import android.app.DialogFragment;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.os.Bundle; import android.os.Bundle;
import android.view.KeyEvent;
import android.view.View; import android.view.View;
import android.view.WindowManager; import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
import android.widget.EditText; import android.widget.EditText;
import android.widget.TextView;
import java.io.File; import java.io.File;
import sharedcode.turboeditor.R; import sharedcode.turboeditor.R;
import sharedcode.turboeditor.activity.PreferenceAbout;
import sharedcode.turboeditor.preferences.PreferenceHelper; import sharedcode.turboeditor.preferences.PreferenceHelper;
import sharedcode.turboeditor.util.SaveFileTask; import sharedcode.turboeditor.task.SaveFileTask;
import sharedcode.turboeditor.views.DialogHelper;
// ... // ...
public class NewFileDetailsDialogFragment extends DialogFragment { public class NewFileDetailsDialog extends DialogFragment {
private EditText mName; private EditText mName;
private EditText mFolder; private EditText mFolder;
public static NewFileDetailsDialogFragment newInstance(String fileText, String fileEncoding) { public static NewFileDetailsDialog newInstance(String fileText, String fileEncoding) {
final NewFileDetailsDialogFragment f = new NewFileDetailsDialogFragment(); final NewFileDetailsDialog f = new NewFileDetailsDialog();
final Bundle args = new Bundle(); final Bundle args = new Bundle();
args.putString("fileText", fileText); args.putString("fileText", fileText);
args.putString("fileEncoding", fileEncoding); args.putString("fileEncoding", fileEncoding);
@ -56,7 +53,11 @@ public class NewFileDetailsDialogFragment extends DialogFragment {
@Override @Override
public Dialog onCreateDialog(Bundle savedInstanceState) { public Dialog onCreateDialog(Bundle savedInstanceState) {
final View view = getActivity().getLayoutInflater().inflate(R.layout.dialog_fragment_new_file_details, null); View view = new DialogHelper.Builder(getActivity())
.setTitle(R.string.file)
.setView(R.layout.dialog_fragment_new_file_details)
.createSkeletonView();
this.mName = (EditText) view.findViewById(android.R.id.text1); this.mName = (EditText) view.findViewById(android.R.id.text1);
this.mFolder = (EditText) view.findViewById(android.R.id.text2); this.mFolder = (EditText) view.findViewById(android.R.id.text2);
@ -72,9 +73,10 @@ public class NewFileDetailsDialogFragment extends DialogFragment {
new DialogInterface.OnClickListener() { new DialogInterface.OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
if(!mName.getText().toString().isEmpty() && !mFolder.getText().toString().isEmpty()) { if (!mName.getText().toString().isEmpty() && !mFolder.getText().toString().isEmpty()) {
File file = new File(mFolder.getText().toString(), mName.getText().toString()); File file = new File(mFolder.getText().toString(), mName.getText().toString());
new SaveFileTask(getActivity(), file.getPath(), getArguments().getString("fileText"), getArguments().getString("fileEncoding")).execute(); new SaveFileTask(getActivity(), file.getPath(), getArguments().getString("fileText"), getArguments().getString("fileEncoding")).execute();
PreferenceHelper.setWorkingFolder(getActivity(), file.getParent());
} }
} }
} }

View File

@ -24,60 +24,89 @@ import android.app.Dialog;
import android.app.DialogFragment; import android.app.DialogFragment;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.os.Bundle; import android.os.Bundle;
import android.view.View;
import sharedcode.turboeditor.R;
import sharedcode.turboeditor.preferences.SettingsFragment;
import sharedcode.turboeditor.util.SaveFileTask;
import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.FilenameUtils;
import java.io.File; import java.io.File;
public class SaveFileDialogFragment extends DialogFragment { import sharedcode.turboeditor.R;
import sharedcode.turboeditor.task.SaveFileTask;
import sharedcode.turboeditor.views.DialogHelper;
public static SaveFileDialogFragment newInstance(String filePath, String text) { public class SaveFileDialog extends DialogFragment {
SaveFileDialogFragment frag = new SaveFileDialogFragment();
public static SaveFileDialog newInstance(String filePath, String text, String encoding) {
return newInstance(filePath, text, encoding, false, "");
}
public static SaveFileDialog newInstance(String filePath, String text, String encoding, boolean openNewFileAfter, String pathOfNewFile) {
SaveFileDialog frag = new SaveFileDialog();
Bundle args = new Bundle(); Bundle args = new Bundle();
args.putString("filePath", filePath); args.putString("filePath", filePath);
args.putString("text", text); args.putString("text", text);
args.putString("encoding", encoding);
args.putBoolean("openNewFileAfter", openNewFileAfter);
args.putString("pathOfNewFile", pathOfNewFile);
frag.setArguments(args); frag.setArguments(args);
return frag; return frag;
} }
@Override @Override
public Dialog onCreateDialog(Bundle savedInstanceState) { public Dialog onCreateDialog(Bundle savedInstanceState) {
final String filePath = getArguments().getString("filePath"); final String filePath = getArguments().getString("filePath");
final String text = getArguments().getString("text"); final String text = getArguments().getString("text");
final String encoding = getArguments().getString("encoding");
final String fileName = FilenameUtils.getName(filePath); final String fileName = FilenameUtils.getName(filePath);
final File file = new File(filePath); final File file = new File(filePath);
return new AlertDialog.Builder(getActivity()) View view = new DialogHelper.Builder(getActivity())
.setIcon(getResources().getDrawable(R.drawable.ic_action_save))
.setTitle(R.string.salva)
.setMessage(String.format(getString(R.string.save_changes), fileName)) .setMessage(String.format(getString(R.string.save_changes), fileName))
.setPositiveButton(android.R.string.yes, .createCommonView();
return new AlertDialog.Builder(getActivity())
.setView(view)
.setPositiveButton(R.string.salva,
new DialogInterface.OnClickListener() { new DialogInterface.OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
if(!fileName.isEmpty()) if (!fileName.isEmpty())
new SaveFileTask(getActivity(), filePath, text, SettingsFragment.sCurrentEncoding).execute(); new SaveFileTask(getActivity(), filePath, text,
encoding).execute();
else { else {
NewFileDetailsDialogFragment dialogFrag = NewFileDetailsDialogFragment.newInstance(text, SettingsFragment.sCurrentEncoding); NewFileDetailsDialog dialogFrag =
dialogFrag.show(getFragmentManager().beginTransaction(), "dialog"); NewFileDetailsDialog.newInstance(text,
encoding);
dialogFrag.show(getFragmentManager().beginTransaction(),
"dialog");
} }
} }
} }
) )
.setNegativeButton(android.R.string.no, .setNeutralButton(android.R.string.cancel, null)
.setNegativeButton(R.string.no,
new DialogInterface.OnClickListener() { new DialogInterface.OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
ISaveDialog target = (ISaveDialog) getTargetFragment();
if (target == null) {
target = (ISaveDialog) getActivity();
}
target.userDoesntWantToSave(
getArguments().getBoolean("openNewFileAfter"),
getArguments().getString("pathOfNewFile")
);
} }
} }
) )
.create(); .create();
} }
public static enum Action { public interface ISaveDialog {
SaveAFile void userDoesntWantToSave(boolean openNewFile, String pathOfNewFile);
} }
} }

View File

@ -28,18 +28,19 @@ import android.view.View;
import android.widget.NumberPicker; import android.widget.NumberPicker;
import sharedcode.turboeditor.R; import sharedcode.turboeditor.R;
import sharedcode.turboeditor.views.DialogHelper;
// ... // ...
public class SeekbarDialogFragment extends DialogFragment { public class SeekbarDialog extends DialogFragment {
private NumberPicker mSeekBar; private NumberPicker mSeekBar;
public static SeekbarDialogFragment newInstance(final Actions action) { public static SeekbarDialog newInstance(final Actions action) {
return SeekbarDialogFragment.newInstance(action, 0, 50, 100); return SeekbarDialog.newInstance(action, 0, 50, 100);
} }
public static SeekbarDialogFragment newInstance(final Actions action, final int min, final int current, final int max) { public static SeekbarDialog newInstance(final Actions action, final int min, final int current, final int max) {
final SeekbarDialogFragment f = new SeekbarDialogFragment(); final SeekbarDialog f = new SeekbarDialog();
final Bundle args = new Bundle(); final Bundle args = new Bundle();
args.putSerializable("action", action); args.putSerializable("action", action);
args.putInt("min", min); args.putInt("min", min);
@ -52,9 +53,28 @@ public class SeekbarDialogFragment extends DialogFragment {
@Override @Override
public Dialog onCreateDialog(Bundle savedInstanceState) { public Dialog onCreateDialog(Bundle savedInstanceState) {
final Actions action = (Actions) getArguments().getSerializable("action"); Actions action = (Actions) getArguments().getSerializable("action");
int title;
switch (action){
case FontSize:
title = R.string.font_size;
break;
case SelectPage:
title = R.string.goto_page;
break;
case GoToLine:
title = R.string.goto_line;
break;
default:
title = R.string.nome_app_turbo_editor;
break;
}
View view = new DialogHelper.Builder(getActivity())
.setTitle(title)
.setView(R.layout.dialog_fragment_seekbar)
.createSkeletonView();
final View view = getActivity().getLayoutInflater().inflate(R.layout.dialog_fragment_seekbar, null);
this.mSeekBar = (NumberPicker) view.findViewById(android.R.id.input); this.mSeekBar = (NumberPicker) view.findViewById(android.R.id.input);
this.mSeekBar.setMaxValue(getArguments().getInt("max")); this.mSeekBar.setMaxValue(getArguments().getInt("max"));
this.mSeekBar.setMinValue(getArguments().getInt("min")); this.mSeekBar.setMinValue(getArguments().getInt("min"));
@ -82,9 +102,9 @@ public class SeekbarDialogFragment extends DialogFragment {
} }
void returnData() { void returnData() {
onSeekbarDialogDismissed target = (onSeekbarDialogDismissed) getTargetFragment(); ISeekbarDialog target = (ISeekbarDialog) getTargetFragment();
if (target == null) { if (target == null) {
target = (onSeekbarDialogDismissed) getActivity(); target = (ISeekbarDialog) getActivity();
} }
target.onSeekbarDialogDismissed( target.onSeekbarDialogDismissed(
(Actions) getArguments().getSerializable("action"), (Actions) getArguments().getSerializable("action"),
@ -94,10 +114,10 @@ public class SeekbarDialogFragment extends DialogFragment {
} }
public enum Actions { public enum Actions {
FileSize, SelectPage, GoToLine FontSize, SelectPage, GoToLine
} }
public interface onSeekbarDialogDismissed { public interface ISeekbarDialog {
void onSeekbarDialogDismissed(Actions action, int value); void onSeekbarDialogDismissed(Actions action, int value);
} }
} }

View File

@ -0,0 +1,78 @@
/*
* Copyright (C) 2014 Vlad Mihalachi
*
* This file is part of Turbo Editor.
*
* Turbo Editor is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Turbo Editor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package sharedcode.turboeditor.iab;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
/**
* The helper class of donation item.
*
* @author Artem Chepurnoy
*/
public class Donation {
public final int amount;
public final String sku;
public final String text;
public Donation(int amount, String text) {
this.amount = amount;
this.text = text;
// Notice that all of them are defined in
// my Play Store's account!
this.sku = "donation_" + amount;
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
return new HashCodeBuilder(9, 51)
.append(amount)
.append(text)
.append(sku)
.toHashCode();
}
/**
* {@inheritDoc}
*/
@Override
public boolean equals(Object o) {
if (o == null)
return false;
if (o == this)
return true;
if (!(o instanceof Donation))
return false;
Donation donation = (Donation) o;
return new EqualsBuilder()
.append(amount, donation.amount)
.append(text, donation.text)
.append(sku, donation.sku)
.isEquals();
}
}

View File

@ -0,0 +1,98 @@
/*
* Copyright (C) 2014 Vlad Mihalachi
*
* This file is part of Turbo Editor.
*
* Turbo Editor is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Turbo Editor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package sharedcode.turboeditor.iab;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Paint;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
import java.util.HashSet;
import sharedcode.turboeditor.R;
/**
* Created by achep on 06.05.14 for AcDisplay.
*
* @author Artem Chepurnoy
*/
public class DonationAdapter extends ArrayAdapter<Donation> {
private final HashSet<String> mInventorySet;
private final LayoutInflater mInflater;
private final String mDonationAmountLabel;
private final int mColorNormal;
private final int mColorPurchased;
public DonationAdapter(Context context, Donation[] items, HashSet<String> inventory) {
super(context, 0, items);
mInventorySet = inventory;
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
Resources res = context.getResources();
mDonationAmountLabel = res.getString(R.string.donation_item_label);
mColorNormal = res.getColor(R.color.donation_normal);
mColorPurchased = res.getColor(R.color.donation_purchased);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final Donation donation = getItem(position);
final Holder holder;
final View view;
if (convertView == null) {
holder = new Holder();
view = mInflater.inflate(R.layout.donation_iab_item, parent, false);
assert view != null;
holder.title = (TextView) view.findViewById(android.R.id.title);
holder.summary = (TextView) view.findViewById(android.R.id.summary);
view.setTag(holder);
} else {
view = convertView;
holder = (Holder) view.getTag();
}
boolean bought = mInventorySet.contains(donation.sku);
String amount = Integer.toString(donation.amount);
holder.title.setText(String.format(mDonationAmountLabel, amount));
holder.title.setTextColor(bought ? mColorNormal : mColorPurchased);
holder.summary.setText(donation.text);
holder.summary.setPaintFlags(bought
? holder.summary.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG
: holder.summary.getPaintFlags() & (~Paint.STRIKE_THRU_TEXT_FLAG));
return view;
}
private static class Holder {
TextView title;
TextView summary;
}
}

View File

@ -0,0 +1,370 @@
/*
* Copyright (C) 2014 Vlad Mihalachi
*
* This file is part of Turbo Editor.
*
* Turbo Editor is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Turbo Editor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package sharedcode.turboeditor.iab;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.content.ActivityNotFoundException;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.text.Html;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.method.LinkMovementMethod;
import android.text.style.ImageSpan;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.GridView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import java.util.HashSet;
import sharedcode.turboeditor.R;
import sharedcode.turboeditor.iab.utils.IabHelper;
import sharedcode.turboeditor.iab.utils.IabResult;
import sharedcode.turboeditor.iab.utils.Inventory;
import sharedcode.turboeditor.iab.utils.Purchase;
import sharedcode.turboeditor.preferences.PreferenceHelper;
import sharedcode.turboeditor.util.Build;
import sharedcode.turboeditor.util.ToastUtils;
import sharedcode.turboeditor.util.ViewUtils;
import sharedcode.turboeditor.views.DialogHelper;
/**
* Fragment that represents an ability to donate to me. Be sure to redirect
* {@link android.app.Activity#onActivityResult(int, int, android.content.Intent)}
* to this fragment!
*
* @author Artem Chepurnoy
*/
public class DonationFragment extends DialogFragment {
public static final int RC_REQUEST = 10001;
private static final String TAG = "DonationFragment";
private final HashSet<String> mInventorySet = new HashSet<>();
private GridView mGridView;
private ProgressBar mProgressBar;
private TextView mError;
private IabHelper mHelper;
private final IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener =
new IabHelper.OnIabPurchaseFinishedListener() {
public void onIabPurchaseFinished(IabResult result, Purchase purchase) {
if (mHelper == null) return;
if (result.isFailure()) {
complain("Error purchasing: " + result);
setWaitScreen(false);
return;
}
if (!verifyDeveloperPayload(purchase)) {
complain("Error purchasing. Authenticity verification failed.");
setWaitScreen(false);
return;
}
// else, it is a success, the user has donated!
String sku = purchase.getSku();
mInventorySet.add(sku);
PreferenceHelper.setHasDonated(getActivity(), true);
}
};
private Donation[] mDonationList;
private final IabHelper.QueryInventoryFinishedListener mGotInventoryListener =
new IabHelper.QueryInventoryFinishedListener() {
public void onQueryInventoryFinished(IabResult result, Inventory inventory) {
if (mHelper == null) return;
if (result.isFailure()) {
complain("Failed to query inventory: " + result);
return;
}
mInventorySet.clear();
for (Donation donation : mDonationList) {
Purchase purchase = inventory.getPurchase(donation.sku);
boolean isBought = (purchase != null && verifyDeveloperPayload(purchase));
if (isBought) {
mInventorySet.add(donation.sku);
PreferenceHelper.setHasDonated(getActivity(), true);
}
}
/*
// Fake items to debug user interface.
mInventorySet.add(mDonationList[0].sku);
mInventorySet.add(mDonationList[1].sku);
mInventorySet.add(mDonationList[2].sku);
*/
updateUi();
setWaitScreen(false);
}
};
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
mDonationList = DonationItems.get(getResources());
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Activity activity = getActivity();
assert activity != null;
View view = new DialogHelper.Builder(activity)
.setTitle(R.string.donation_title)
.setView(R.layout.donation_dialog)
.createSkeletonView();
AlertDialog.Builder builder = new AlertDialog.Builder(activity)
.setView(view)
.setNegativeButton(android.R.string.cancel, null);
TextView info = (TextView) view.findViewById(R.id.info);
info.setText(Html.fromHtml(getString(R.string.donation_info)));
info.setMovementMethod(new LinkMovementMethod());
mError = (TextView) view.findViewById(R.id.error);
mProgressBar = (ProgressBar) view.findViewById(android.R.id.progress);
mGridView = (GridView) view.findViewById(R.id.grid);
mGridView.setAdapter(new DonationAdapter(getActivity(), mDonationList, mInventorySet));
mGridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
DonationAdapter adapter = (DonationAdapter) parent.getAdapter();
Donation donation = adapter.getItem(position);
if (!mInventorySet.contains(donation.sku)) {
/**
* See {@link sharedcode.turboeditor.iab.DonationFragment#verifyDeveloperPayload(Purchase)}.
*/
String payload = "";
try {
mHelper.launchPurchaseFlow(
getActivity(), donation.sku, RC_REQUEST,
mPurchaseFinishedListener, payload);
} catch (Exception e) {
ToastUtils.showShort(getActivity(), "Failed to launch a purchase flow.");
}
} else {
ToastUtils.showShort(getActivity(), getString(R.string.donation_item_bought));
}
}
});
final AlertDialog alertDialog;
// Show PayPal button.
final Intent paypalIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(Build.Links.DONATE));
builder.setNeutralButton(R.string.paypal, null);
alertDialog = builder.create();
alertDialog.setOnShowListener(new DialogInterface.OnShowListener() {
@Override
public void onShow(DialogInterface dialog) {
Data[] datas = new Data[]{
new Data(
alertDialog.getButton(DialogInterface.BUTTON_NEUTRAL),
paypalIntent, R.drawable.ic_action_paypal)
};
ImageSpan span;
SpannableString text;
for (final Data data : datas) {
final Button btn = data.button;
if (btn != null) {
span = new ImageSpan(getActivity(), data.iconResource);
// Replace text with an icon.
// This is a workaround to fix compound button's aligment.
text = new SpannableString(" ");
text.setSpan(span, 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
btn.setText(text);
// Eat default weight.
btn.setLayoutParams(new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startPaymentIntentWithWarningAlertDialog(data.intent);
}
});
}
}
}
final class Data {
private final Button button;
private final Intent intent;
private final int iconResource;
private Data(Button button, Intent intent, int iconResource) {
this.button = button;
this.intent = intent;
this.iconResource = iconResource;
}
}
});
initBilling();
return alertDialog;
}
/**
* Shows a warning alert dialog to note, that those methods
* may suck hard and nobody will care about it.<br/>
* Starts an intent if user is agree with it.
*/
private void startPaymentIntentWithWarningAlertDialog(final Intent intent) {
CharSequence messageText = getString(R.string.donation_no_responsibility);
new DialogHelper.Builder(getActivity())
.setMessage(messageText)
.wrap()
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
try {
startActivity(intent);
dismiss(); // Dismiss main fragment
} catch (ActivityNotFoundException e) { /* hell no */ }
}
})
.create()
.show();
}
private void setWaitScreen(boolean loading) {
ViewUtils.setVisible(mProgressBar, loading);
ViewUtils.setVisible(mGridView, !loading);
ViewUtils.setVisible(mError, false);
}
private void setErrorScreen(String errorMessage, final Runnable runnable) {
mProgressBar.setVisibility(View.GONE);
mGridView.setVisibility(View.GONE);
mError.setVisibility(View.VISIBLE);
mError.setText(errorMessage);
mError.setOnClickListener(runnable != null ? new View.OnClickListener() {
@Override
public void onClick(View v) {
runnable.run();
}
} : null);
}
/**
* Updates GUI to display changes.
*/
private void updateUi() {
DonationAdapter adapter = (DonationAdapter) mGridView.getAdapter();
adapter.notifyDataSetChanged();
}
@Override
public void onDestroy() {
super.onDestroy();
disposeBilling();
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (mHelper.handleActivityResult(requestCode, resultCode, data)) {
return;
}
super.onActivityResult(requestCode, resultCode, data);
}
/**
* Releases billing service.
*
* @see #initBilling()
*/
private void disposeBilling() {
if (mHelper != null) {
mHelper.dispose();
mHelper = null;
}
}
/**
* <b>Make sure you call {@link #disposeBilling()}!</b>
*
* @see #disposeBilling()
*/
private void initBilling() {
setWaitScreen(true);
disposeBilling();
String base64EncodedPublicKey = Build.GOOGLE_PLAY_PUBLIC_KEY;
mHelper = new IabHelper(getActivity(), base64EncodedPublicKey);
mHelper.enableDebugLogging(Build.DEBUG);
mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {
public void onIabSetupFinished(IabResult result) {
if (mHelper == null) return;
if (!result.isSuccess()) {
setErrorScreen(getString(R.string.donation_error_iab_setup), new Runnable() {
@Override
public void run() {
// Try to initialize billings again.
initBilling();
}
});
return;
}
setWaitScreen(false);
mHelper.queryInventoryAsync(mGotInventoryListener);
}
});
}
private boolean verifyDeveloperPayload(Purchase purchase) {
// TODO: This method itself is a big question.
// Personally, I think that this whole best practices part
// is confusing and is trying to make you do work that the API
// should really be doing. Since the purchase is tied to a Google account,
// and the Play Store obviously saves this information, they should
// just give you this in the purchase details. Getting a proper user ID
// requires additional permissions that you shouldnt need to add just
// to cover for the deficiencies of the IAB API.
return true;
}
private void complain(String message) {
ToastUtils.showShort(getActivity(), message);
}
}

View File

@ -0,0 +1,53 @@
/*
* Copyright (C) 2014 Vlad Mihalachi
*
* This file is part of Turbo Editor.
*
* Turbo Editor is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Turbo Editor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package sharedcode.turboeditor.iab;
import android.content.res.Resources;
import sharedcode.turboeditor.R;
/**
* Created by achep on 07.05.14 for AcDisplay.
*
* @author Artem Chepurnoy
*/
public class DonationItems {
public static Donation[] get(Resources res) {
int[] data = new int[]{
2, R.string.donation_2,
4, R.string.donation_4,
10, R.string.donation_10,
20, R.string.donation_20,
50, R.string.donation_50,
99, R.string.donation_99,
};
Donation[] donation = new Donation[data.length / 2];
int length = donation.length;
for (int i = 0; i < length; i++) {
donation[i] = new Donation(data[i * 2],
res.getString(data[i * 2 + 1]));
}
return donation;
}
}

View File

@ -0,0 +1,582 @@
// Portions copyright 2002, Google, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package sharedcode.turboeditor.iab.utils;
// This code was converted from code at http://iharder.sourceforge.net/base64/
// Lots of extraneous features were removed.
/* The original code said:
* <p>
* I am placing this code in the Public Domain. Do with it as you will.
* This software comes with no guarantees or warranties but with
* plenty of well-wishing instead!
* Please visit
* <a href="http://iharder.net/xmlizable">http://iharder.net/xmlizable</a>
* periodically to check for updates or to contribute improvements.
* </p>
*
* @author Robert Harder
* @author rharder@usa.net
* @version 1.3
*/
/**
* Base64 converter class. This code is not a complete MIME encoder;
* it simply converts binary data to base64 data and back.
* <p/>
* <p>Note {@link CharBase64} is a GWT-compatible implementation of this
* class.
*/
public class Base64 {
/**
* Specify encoding (value is {@code true}).
*/
public final static boolean ENCODE = true;
/**
* Specify decoding (value is {@code false}).
*/
public final static boolean DECODE = false;
/**
* The equals sign (=) as a byte.
*/
private final static byte EQUALS_SIGN = (byte) '=';
/**
* The new line character (\n) as a byte.
*/
private final static byte NEW_LINE = (byte) '\n';
/**
* The 64 valid Base64 values.
*/
private final static byte[] ALPHABET =
{(byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F',
(byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K',
(byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P',
(byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U',
(byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z',
(byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e',
(byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j',
(byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o',
(byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't',
(byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y',
(byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3',
(byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8',
(byte) '9', (byte) '+', (byte) '/'};
/**
* The 64 valid web safe Base64 values.
*/
private final static byte[] WEBSAFE_ALPHABET =
{(byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F',
(byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K',
(byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P',
(byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U',
(byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z',
(byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e',
(byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j',
(byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o',
(byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't',
(byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y',
(byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3',
(byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8',
(byte) '9', (byte) '-', (byte) '_'};
/**
* Translates a Base64 value to either its 6-bit reconstruction value
* or a negative number indicating some other meaning.
*/
private final static byte[] DECODABET = {-9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8
-5, -5, // Whitespace: Tab and Linefeed
-9, -9, // Decimal 11 - 12
-5, // Whitespace: Carriage Return
-9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26
-9, -9, -9, -9, -9, // Decimal 27 - 31
-5, // Whitespace: Space
-9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42
62, // Plus sign at decimal 43
-9, -9, -9, // Decimal 44 - 46
63, // Slash at decimal 47
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine
-9, -9, -9, // Decimal 58 - 60
-1, // Equals sign at decimal 61
-9, -9, -9, // Decimal 62 - 64
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N'
14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z'
-9, -9, -9, -9, -9, -9, // Decimal 91 - 96
26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm'
39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z'
-9, -9, -9, -9, -9 // Decimal 123 - 127
/* ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */
};
/**
* The web safe decodabet
*/
private final static byte[] WEBSAFE_DECODABET =
{-9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8
-5, -5, // Whitespace: Tab and Linefeed
-9, -9, // Decimal 11 - 12
-5, // Whitespace: Carriage Return
-9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26
-9, -9, -9, -9, -9, // Decimal 27 - 31
-5, // Whitespace: Space
-9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 44
62, // Dash '-' sign at decimal 45
-9, -9, // Decimal 46-47
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine
-9, -9, -9, // Decimal 58 - 60
-1, // Equals sign at decimal 61
-9, -9, -9, // Decimal 62 - 64
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N'
14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z'
-9, -9, -9, -9, // Decimal 91-94
63, // Underscore '_' at decimal 95
-9, // Decimal 96
26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm'
39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z'
-9, -9, -9, -9, -9 // Decimal 123 - 127
/* ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */
};
// Indicates white space in encoding
private final static byte WHITE_SPACE_ENC = -5;
// Indicates equals sign in encoding
private final static byte EQUALS_SIGN_ENC = -1;
/**
* Defeats instantiation.
*/
private Base64() {
}
/* ******** E N C O D I N G M E T H O D S ******** */
/**
* Encodes up to three bytes of the array <var>source</var>
* and writes the resulting four Base64 bytes to <var>destination</var>.
* The source and destination arrays can be manipulated
* anywhere along their length by specifying
* <var>srcOffset</var> and <var>destOffset</var>.
* This method does not check to make sure your arrays
* are large enough to accommodate <var>srcOffset</var> + 3 for
* the <var>source</var> array or <var>destOffset</var> + 4 for
* the <var>destination</var> array.
* The actual number of significant bytes in your array is
* given by <var>numSigBytes</var>.
*
* @param source the array to convert
* @param srcOffset the index where conversion begins
* @param numSigBytes the number of significant bytes in your array
* @param destination the array to hold the conversion
* @param destOffset the index where output will be put
* @param alphabet is the encoding alphabet
* @return the <var>destination</var> array
* @since 1.3
*/
private static byte[] encode3to4(byte[] source, int srcOffset,
int numSigBytes, byte[] destination, int destOffset, byte[] alphabet) {
// 1 2 3
// 01234567890123456789012345678901 Bit position
// --------000000001111111122222222 Array position from threeBytes
// --------| || || || | Six bit groups to index alphabet
// >>18 >>12 >> 6 >> 0 Right shift necessary
// 0x3f 0x3f 0x3f Additional AND
// Create buffer with zero-padding if there are only one or two
// significant bytes passed in the array.
// We have to shift left 24 in order to flush out the 1's that appear
// when Java treats a value as negative that is cast from a byte to an int.
int inBuff =
(numSigBytes > 0 ? ((source[srcOffset] << 24) >>> 8) : 0)
| (numSigBytes > 1 ? ((source[srcOffset + 1] << 24) >>> 16) : 0)
| (numSigBytes > 2 ? ((source[srcOffset + 2] << 24) >>> 24) : 0);
switch (numSigBytes) {
case 3:
destination[destOffset] = alphabet[(inBuff >>> 18)];
destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f];
destination[destOffset + 2] = alphabet[(inBuff >>> 6) & 0x3f];
destination[destOffset + 3] = alphabet[(inBuff) & 0x3f];
return destination;
case 2:
destination[destOffset] = alphabet[(inBuff >>> 18)];
destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f];
destination[destOffset + 2] = alphabet[(inBuff >>> 6) & 0x3f];
destination[destOffset + 3] = EQUALS_SIGN;
return destination;
case 1:
destination[destOffset] = alphabet[(inBuff >>> 18)];
destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f];
destination[destOffset + 2] = EQUALS_SIGN;
destination[destOffset + 3] = EQUALS_SIGN;
return destination;
default:
return destination;
} // end switch
} // end encode3to4
/**
* Encodes a byte array into Base64 notation.
* Equivalent to calling
* {@code encodeBytes(source, 0, source.length)}
*
* @param source The data to convert
* @since 1.4
*/
public static String encode(byte[] source) {
return encode(source, 0, source.length, ALPHABET, true);
}
/**
* Encodes a byte array into web safe Base64 notation.
*
* @param source The data to convert
* @param doPadding is {@code true} to pad result with '=' chars
* if it does not fall on 3 byte boundaries
*/
public static String encodeWebSafe(byte[] source, boolean doPadding) {
return encode(source, 0, source.length, WEBSAFE_ALPHABET, doPadding);
}
/**
* Encodes a byte array into Base64 notation.
*
* @param source the data to convert
* @param off offset in array where conversion should begin
* @param len length of data to convert
* @param alphabet the encoding alphabet
* @param doPadding is {@code true} to pad result with '=' chars
* if it does not fall on 3 byte boundaries
* @since 1.4
*/
public static String encode(byte[] source, int off, int len, byte[] alphabet,
boolean doPadding) {
byte[] outBuff = encode(source, off, len, alphabet, Integer.MAX_VALUE);
int outLen = outBuff.length;
// If doPadding is false, set length to truncate '='
// padding characters
while (doPadding == false && outLen > 0) {
if (outBuff[outLen - 1] != '=') {
break;
}
outLen -= 1;
}
return new String(outBuff, 0, outLen);
}
/**
* Encodes a byte array into Base64 notation.
*
* @param source the data to convert
* @param off offset in array where conversion should begin
* @param len length of data to convert
* @param alphabet is the encoding alphabet
* @param maxLineLength maximum length of one line.
* @return the BASE64-encoded byte array
*/
public static byte[] encode(byte[] source, int off, int len, byte[] alphabet,
int maxLineLength) {
int lenDiv3 = (len + 2) / 3; // ceil(len / 3)
int len43 = lenDiv3 * 4;
byte[] outBuff = new byte[len43 // Main 4:3
+ (len43 / maxLineLength)]; // New lines
int d = 0;
int e = 0;
int len2 = len - 2;
int lineLength = 0;
for (; d < len2; d += 3, e += 4) {
// The following block of code is the same as
// encode3to4( source, d + off, 3, outBuff, e, alphabet );
// but inlined for faster encoding (~20% improvement)
int inBuff =
((source[d + off] << 24) >>> 8)
| ((source[d + 1 + off] << 24) >>> 16)
| ((source[d + 2 + off] << 24) >>> 24);
outBuff[e] = alphabet[(inBuff >>> 18)];
outBuff[e + 1] = alphabet[(inBuff >>> 12) & 0x3f];
outBuff[e + 2] = alphabet[(inBuff >>> 6) & 0x3f];
outBuff[e + 3] = alphabet[(inBuff) & 0x3f];
lineLength += 4;
if (lineLength == maxLineLength) {
outBuff[e + 4] = NEW_LINE;
e++;
lineLength = 0;
} // end if: end of line
} // end for: each piece of array
if (d < len) {
encode3to4(source, d + off, len - d, outBuff, e, alphabet);
lineLength += 4;
if (lineLength == maxLineLength) {
// Add a last newline
outBuff[e + 4] = NEW_LINE;
e++;
}
e += 4;
}
assert (e == outBuff.length);
return outBuff;
}
/* ******** D E C O D I N G M E T H O D S ******** */
/**
* Decodes four bytes from array <var>source</var>
* and writes the resulting bytes (up to three of them)
* to <var>destination</var>.
* The source and destination arrays can be manipulated
* anywhere along their length by specifying
* <var>srcOffset</var> and <var>destOffset</var>.
* This method does not check to make sure your arrays
* are large enough to accommodate <var>srcOffset</var> + 4 for
* the <var>source</var> array or <var>destOffset</var> + 3 for
* the <var>destination</var> array.
* This method returns the actual number of bytes that
* were converted from the Base64 encoding.
*
* @param source the array to convert
* @param srcOffset the index where conversion begins
* @param destination the array to hold the conversion
* @param destOffset the index where output will be put
* @param decodabet the decodabet for decoding Base64 content
* @return the number of decoded bytes converted
* @since 1.3
*/
private static int decode4to3(byte[] source, int srcOffset,
byte[] destination, int destOffset, byte[] decodabet) {
// Example: Dk==
if (source[srcOffset + 2] == EQUALS_SIGN) {
int outBuff =
((decodabet[source[srcOffset]] << 24) >>> 6)
| ((decodabet[source[srcOffset + 1]] << 24) >>> 12);
destination[destOffset] = (byte) (outBuff >>> 16);
return 1;
} else if (source[srcOffset + 3] == EQUALS_SIGN) {
// Example: DkL=
int outBuff =
((decodabet[source[srcOffset]] << 24) >>> 6)
| ((decodabet[source[srcOffset + 1]] << 24) >>> 12)
| ((decodabet[source[srcOffset + 2]] << 24) >>> 18);
destination[destOffset] = (byte) (outBuff >>> 16);
destination[destOffset + 1] = (byte) (outBuff >>> 8);
return 2;
} else {
// Example: DkLE
int outBuff =
((decodabet[source[srcOffset]] << 24) >>> 6)
| ((decodabet[source[srcOffset + 1]] << 24) >>> 12)
| ((decodabet[source[srcOffset + 2]] << 24) >>> 18)
| ((decodabet[source[srcOffset + 3]] << 24) >>> 24);
destination[destOffset] = (byte) (outBuff >> 16);
destination[destOffset + 1] = (byte) (outBuff >> 8);
destination[destOffset + 2] = (byte) (outBuff);
return 3;
}
} // end decodeToBytes
/**
* Decodes data from Base64 notation.
*
* @param s the string to decode (decoded in default encoding)
* @return the decoded data
* @since 1.4
*/
public static byte[] decode(String s) throws Base64DecoderException {
byte[] bytes = s.getBytes();
return decode(bytes, 0, bytes.length);
}
/**
* Decodes data from web safe Base64 notation.
* Web safe encoding uses '-' instead of '+', '_' instead of '/'
*
* @param s the string to decode (decoded in default encoding)
* @return the decoded data
*/
public static byte[] decodeWebSafe(String s) throws Base64DecoderException {
byte[] bytes = s.getBytes();
return decodeWebSafe(bytes, 0, bytes.length);
}
/**
* Decodes Base64 content in byte array format and returns
* the decoded byte array.
*
* @param source The Base64 encoded data
* @return decoded data
* @throws Base64DecoderException
* @since 1.3
*/
public static byte[] decode(byte[] source) throws Base64DecoderException {
return decode(source, 0, source.length);
}
/**
* Decodes web safe Base64 content in byte array format and returns
* the decoded data.
* Web safe encoding uses '-' instead of '+', '_' instead of '/'
*
* @param source the string to decode (decoded in default encoding)
* @return the decoded data
*/
public static byte[] decodeWebSafe(byte[] source)
throws Base64DecoderException {
return decodeWebSafe(source, 0, source.length);
}
/**
* Decodes Base64 content in byte array format and returns
* the decoded byte array.
*
* @param source the Base64 encoded data
* @param off the offset of where to begin decoding
* @param len the length of characters to decode
* @return decoded data
* @throws Base64DecoderException
* @since 1.3
*/
public static byte[] decode(byte[] source, int off, int len)
throws Base64DecoderException {
return decode(source, off, len, DECODABET);
}
/**
* Decodes web safe Base64 content in byte array format and returns
* the decoded byte array.
* Web safe encoding uses '-' instead of '+', '_' instead of '/'
*
* @param source the Base64 encoded data
* @param off the offset of where to begin decoding
* @param len the length of characters to decode
* @return decoded data
*/
public static byte[] decodeWebSafe(byte[] source, int off, int len)
throws Base64DecoderException {
return decode(source, off, len, WEBSAFE_DECODABET);
}
/**
* Decodes Base64 content using the supplied decodabet and returns
* the decoded byte array.
*
* @param source the Base64 encoded data
* @param off the offset of where to begin decoding
* @param len the length of characters to decode
* @param decodabet the decodabet for decoding Base64 content
* @return decoded data
*/
public static byte[] decode(byte[] source, int off, int len, byte[] decodabet)
throws Base64DecoderException {
int len34 = len * 3 / 4;
byte[] outBuff = new byte[2 + len34]; // Upper limit on size of output
int outBuffPosn = 0;
byte[] b4 = new byte[4];
int b4Posn = 0;
int i = 0;
byte sbiCrop = 0;
byte sbiDecode = 0;
for (i = 0; i < len; i++) {
sbiCrop = (byte) (source[i + off] & 0x7f); // Only the low seven bits
sbiDecode = decodabet[sbiCrop];
if (sbiDecode >= WHITE_SPACE_ENC) { // White space Equals sign or better
if (sbiDecode >= EQUALS_SIGN_ENC) {
// An equals sign (for padding) must not occur at position 0 or 1
// and must be the last byte[s] in the encoded value
if (sbiCrop == EQUALS_SIGN) {
int bytesLeft = len - i;
byte lastByte = (byte) (source[len - 1 + off] & 0x7f);
if (b4Posn == 0 || b4Posn == 1) {
throw new Base64DecoderException(
"invalid padding byte '=' at byte offset " + i);
} else if ((b4Posn == 3 && bytesLeft > 2)
|| (b4Posn == 4 && bytesLeft > 1)) {
throw new Base64DecoderException(
"padding byte '=' falsely signals end of encoded value "
+ "at offset " + i
);
} else if (lastByte != EQUALS_SIGN && lastByte != NEW_LINE) {
throw new Base64DecoderException(
"encoded value has invalid trailing byte");
}
break;
}
b4[b4Posn++] = sbiCrop;
if (b4Posn == 4) {
outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, decodabet);
b4Posn = 0;
}
}
} else {
throw new Base64DecoderException("Bad Base64 input character at " + i
+ ": " + source[i + off] + "(decimal)");
}
}
// Because web safe encoding allows non padding base64 encodes, we
// need to pad the rest of the b4 buffer with equal signs when
// b4Posn != 0. There can be at most 2 equal signs at the end of
// four characters, so the b4 buffer must have two or three
// characters. This also catches the case where the input is
// padded with EQUALS_SIGN
if (b4Posn != 0) {
if (b4Posn == 1) {
throw new Base64DecoderException("single trailing character at offset "
+ (len - 1));
}
b4[b4Posn++] = EQUALS_SIGN;
outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, decodabet);
}
byte[] out = new byte[outBuffPosn];
System.arraycopy(outBuff, 0, out, 0, outBuffPosn);
return out;
}
}

View File

@ -17,21 +17,21 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package sharedcode.turboeditor.fragment; package sharedcode.turboeditor.iab.utils;
import android.app.Fragment; /**
import android.os.Bundle; * Exception thrown when encountering an invalid Base64 input character.
import android.view.LayoutInflater; *
import android.view.View; * @author nelson
import android.view.ViewGroup; */
public class Base64DecoderException extends Exception {
private static final long serialVersionUID = 1L;
import sharedcode.turboeditor.R; public Base64DecoderException() {
super();
public class NoFileOpenedFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_no_file_open, container, false);
} }
public Base64DecoderException(String s) {
super(s);
}
} }

View File

@ -0,0 +1,54 @@
/*
* Copyright (C) 2014 Vlad Mihalachi
*
* This file is part of Turbo Editor.
*
* Turbo Editor is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Turbo Editor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package sharedcode.turboeditor.iab.utils;
/**
* Exception thrown when something went wrong with in-app billing.
* An IabException has an associated IabResult (an error).
* To get the IAB result that caused this exception to be thrown,
* call {@link #getResult()}.
*/
public class IabException extends Exception {
IabResult mResult;
public IabException(IabResult r) {
this(r, null);
}
public IabException(int response, String message) {
this(new IabResult(response, message));
}
public IabException(IabResult r, Exception cause) {
super(r.getMessage(), cause);
mResult = r;
}
public IabException(int response, String message, Exception cause) {
this(new IabResult(response, message), cause);
}
/**
* Returns the IAB result (error) that this exception signals.
*/
public IabResult getResult() {
return mResult;
}
}

View File

@ -0,0 +1,966 @@
/*
* Copyright (C) 2014 Vlad Mihalachi
*
* This file is part of Turbo Editor.
*
* Turbo Editor is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Turbo Editor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package sharedcode.turboeditor.iab.utils;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender.SendIntentException;
import android.content.ServiceConnection;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.text.TextUtils;
import android.util.Log;
import org.json.JSONException;
import com.android.vending.billing.*;
import java.util.ArrayList;
import java.util.List;
/**
* Provides convenience methods for in-app billing. You can create one instance of this
* class for your application and use it to process in-app billing operations.
* It provides synchronous (blocking) and asynchronous (non-blocking) methods for
* many common in-app billing operations, as well as automatic signature
* verification.
* <p/>
* After instantiating, you must perform setup in order to start using the object.
* To perform setup, call the {@link #startSetup} method and provide a listener;
* that listener will be notified when setup is complete, after which (and not before)
* you may call other methods.
* <p/>
* After setup is complete, you will typically want to request an inventory of owned
* items and subscriptions. See {@link #queryInventory}, {@link #queryInventoryAsync}
* and related methods.
* <p/>
* When you are done with this object, don't forget to call {@link #dispose}
* to ensure proper cleanup. This object holds a binding to the in-app billing
* service, which will leak unless you dispose of it correctly. If you created
* the object on an Activity's onCreate method, then the recommended
* place to dispose of it is the Activity's onDestroy method.
* <p/>
* A note about threading: When using this object from a background thread, you may
* call the blocking versions of methods; when using from a UI thread, call
* only the asynchronous versions and handle the results via callbacks.
* Also, notice that you can only call one asynchronous operation at a time;
* attempting to start a second asynchronous operation while the first one
* has not yet completed will result in an exception being thrown.
*
* @author Bruno Oliveira (Google)
*/
public class IabHelper {
// Billing response codes
public static final int BILLING_RESPONSE_RESULT_OK = 0;
public static final int BILLING_RESPONSE_RESULT_USER_CANCELED = 1;
public static final int BILLING_RESPONSE_RESULT_BILLING_UNAVAILABLE = 3;
public static final int BILLING_RESPONSE_RESULT_ITEM_UNAVAILABLE = 4;
public static final int BILLING_RESPONSE_RESULT_DEVELOPER_ERROR = 5;
public static final int BILLING_RESPONSE_RESULT_ERROR = 6;
public static final int BILLING_RESPONSE_RESULT_ITEM_ALREADY_OWNED = 7;
public static final int BILLING_RESPONSE_RESULT_ITEM_NOT_OWNED = 8;
// IAB Helper error codes
public static final int IABHELPER_ERROR_BASE = -1000;
public static final int IABHELPER_REMOTE_EXCEPTION = -1001;
public static final int IABHELPER_BAD_RESPONSE = -1002;
public static final int IABHELPER_VERIFICATION_FAILED = -1003;
public static final int IABHELPER_SEND_INTENT_FAILED = -1004;
public static final int IABHELPER_USER_CANCELLED = -1005;
public static final int IABHELPER_UNKNOWN_PURCHASE_RESPONSE = -1006;
public static final int IABHELPER_MISSING_TOKEN = -1007;
public static final int IABHELPER_UNKNOWN_ERROR = -1008;
public static final int IABHELPER_SUBSCRIPTIONS_NOT_AVAILABLE = -1009;
public static final int IABHELPER_INVALID_CONSUMPTION = -1010;
// Keys for the responses from InAppBillingService
public static final String RESPONSE_CODE = "RESPONSE_CODE";
public static final String RESPONSE_GET_SKU_DETAILS_LIST = "DETAILS_LIST";
public static final String RESPONSE_BUY_INTENT = "BUY_INTENT";
public static final String RESPONSE_INAPP_PURCHASE_DATA = "INAPP_PURCHASE_DATA";
public static final String RESPONSE_INAPP_SIGNATURE = "INAPP_DATA_SIGNATURE";
public static final String RESPONSE_INAPP_ITEM_LIST = "INAPP_PURCHASE_ITEM_LIST";
public static final String RESPONSE_INAPP_PURCHASE_DATA_LIST = "INAPP_PURCHASE_DATA_LIST";
public static final String RESPONSE_INAPP_SIGNATURE_LIST = "INAPP_DATA_SIGNATURE_LIST";
public static final String INAPP_CONTINUATION_TOKEN = "INAPP_CONTINUATION_TOKEN";
// Item types
public static final String ITEM_TYPE_INAPP = "inapp";
public static final String ITEM_TYPE_SUBS = "subs";
// some fields on the getSkuDetails response bundle
public static final String GET_SKU_DETAILS_ITEM_LIST = "ITEM_ID_LIST";
public static final String GET_SKU_DETAILS_ITEM_TYPE_LIST = "ITEM_TYPE_LIST";
// Is debug logging enabled?
boolean mDebugLog = false;
String mDebugTag = "IabHelper";
// Is setup done?
boolean mSetupDone = false;
// Has this object been disposed of? (If so, we should ignore callbacks, etc)
boolean mDisposed = false;
boolean mIsBound = false;
// Are subscriptions supported?
boolean mSubscriptionsSupported = false;
// Is an asynchronous operation in progress?
// (only one at a time can be in progress)
boolean mAsyncInProgress = false;
// (for logging/debugging)
// if mAsyncInProgress == true, what asynchronous operation is in progress?
String mAsyncOperation = "";
// Context we were passed during initialization
Context mContext;
// Connection to the service
IInAppBillingService mService;
ServiceConnection mServiceConn;
// The request code used to launch purchase flow
int mRequestCode;
// The item type of the current purchase flow
String mPurchasingItemType;
// Public key for verifying signature, in base64 encoding
String mSignatureBase64 = null;
// The listener registered on launchPurchaseFlow, which we have to call back when
// the purchase finishes
OnIabPurchaseFinishedListener mPurchaseListener;
/**
* Creates an instance. After creation, it will not yet be ready to use. You must perform
* setup by calling {@link #startSetup} and wait for setup to complete. This constructor does not
* block and is safe to call from a UI thread.
*
* @param ctx Your application or Activity context. Needed to bind to the in-app billing service.
* @param base64PublicKey Your application's public key, encoded in base64.
* This is used for verification of purchase signatures. You can find your app's base64-encoded
* public key in your application's page on Google Play Developer Console. Note that this
* is NOT your "developer public key".
*/
public IabHelper(Context ctx, String base64PublicKey) {
mContext = ctx.getApplicationContext();
mSignatureBase64 = base64PublicKey;
logDebug("IAB helper created.");
}
/**
* Returns a human-readable description for the given response code.
*
* @param code The response code
* @return A human-readable string explaining the result code.
* It also includes the result code numerically.
*/
public static String getResponseDesc(int code) {
String[] iab_msgs = ("0:OK/1:User Canceled/2:Unknown/" +
"3:Billing Unavailable/4:Item unavailable/" +
"5:Developer Error/6:Error/7:Item Already Owned/" +
"8:Item not owned").split("/");
String[] iabhelper_msgs = ("0:OK/-1001:Remote exception during initialization/" +
"-1002:Bad response received/" +
"-1003:Purchase signature verification failed/" +
"-1004:Send intent failed/" +
"-1005:User cancelled/" +
"-1006:Unknown purchase response/" +
"-1007:Missing token/" +
"-1008:Unknown error/" +
"-1009:Subscriptions not available/" +
"-1010:Invalid consumption attempt").split("/");
if (code <= IABHELPER_ERROR_BASE) {
int index = IABHELPER_ERROR_BASE - code;
if (index >= 0 && index < iabhelper_msgs.length) return iabhelper_msgs[index];
else return String.valueOf(code) + ":Unknown IAB Helper Error";
} else if (code < 0 || code >= iab_msgs.length)
return String.valueOf(code) + ":Unknown";
else
return iab_msgs[code];
}
/**
* Enables or disable debug logging through LogCat.
*/
public void enableDebugLogging(boolean enable, String tag) {
checkNotDisposed();
mDebugLog = enable;
mDebugTag = tag;
}
public void enableDebugLogging(boolean enable) {
checkNotDisposed();
mDebugLog = enable;
}
/**
* Starts the setup process. This will start up the setup process asynchronously.
* You will be notified through the listener when the setup process is complete.
* This method is safe to call from a UI thread.
*
* @param listener The listener to notify when the setup process is complete.
*/
public void startSetup(final OnIabSetupFinishedListener listener) {
// If already set up, can't do it again.
checkNotDisposed();
if (mSetupDone) throw new IllegalStateException("IAB helper is already set up.");
// Connection to IAB service
logDebug("Starting in-app billing setup.");
mServiceConn = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
logDebug("Billing service disconnected.");
mService = null;
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
if (mDisposed) return;
logDebug("Billing service connected.");
mService = IInAppBillingService.Stub.asInterface(service);
String packageName = mContext.getPackageName();
try {
logDebug("Checking for in-app billing 3 support.");
// check for in-app billing v3 support
int response = mService.isBillingSupported(3, packageName, ITEM_TYPE_INAPP);
if (response != BILLING_RESPONSE_RESULT_OK) {
if (listener != null) listener.onIabSetupFinished(new IabResult(response,
"Error checking for billing v3 support."));
// if in-app purchases aren't supported, neither are subscriptions.
mSubscriptionsSupported = false;
return;
}
logDebug("In-app billing version 3 supported for " + packageName);
// check for v3 subscriptions support
response = mService.isBillingSupported(3, packageName, ITEM_TYPE_SUBS);
if (response == BILLING_RESPONSE_RESULT_OK) {
logDebug("Subscriptions AVAILABLE.");
mSubscriptionsSupported = true;
} else {
logDebug("Subscriptions NOT AVAILABLE. Response: " + response);
}
mSetupDone = true;
} catch (RemoteException e) {
if (listener != null) {
listener.onIabSetupFinished(new IabResult(IABHELPER_REMOTE_EXCEPTION,
"RemoteException while setting up in-app billing."));
}
e.printStackTrace();
return;
}
if (listener != null) {
listener.onIabSetupFinished(new IabResult(BILLING_RESPONSE_RESULT_OK, "Setup successful."));
}
}
};
Intent serviceIntent = new Intent("com.android.vending.billing.InAppBillingService.BIND");
serviceIntent.setPackage("com.android.vending");
List<ResolveInfo> ri = mContext.getPackageManager().queryIntentServices(serviceIntent, 0);
if (ri != null && !ri.isEmpty()) {
// service available to handle that Intent
mIsBound = mContext.bindService(serviceIntent, mServiceConn, Context.BIND_AUTO_CREATE);
} else {
// no service available to handle that Intent
if (listener != null) {
listener.onIabSetupFinished(
new IabResult(BILLING_RESPONSE_RESULT_BILLING_UNAVAILABLE,
"Billing service unavailable on device.")
);
}
}
}
/**
* Dispose of object, releasing resources. It's very important to call this
* method when you are done with this object. It will release any resources
* used by it such as service connections. Naturally, once the object is
* disposed of, it can't be used again.
*/
public void dispose() {
logDebug("Disposing.");
mSetupDone = false;
if (mServiceConn != null) {
logDebug("Unbinding from service.");
if (mContext != null && mIsBound) {
mContext.unbindService(mServiceConn);
}
}
mDisposed = true;
mContext = null;
mServiceConn = null;
mService = null;
mPurchaseListener = null;
}
private void checkNotDisposed() {
if (mDisposed)
throw new IllegalStateException("IabHelper was disposed of, so it cannot be used.");
}
/**
* Returns whether subscriptions are supported.
*/
public boolean subscriptionsSupported() {
checkNotDisposed();
return mSubscriptionsSupported;
}
public void launchPurchaseFlow(Activity act, String sku, int requestCode, OnIabPurchaseFinishedListener listener) {
launchPurchaseFlow(act, sku, requestCode, listener, "");
}
public void launchPurchaseFlow(Activity act, String sku, int requestCode,
OnIabPurchaseFinishedListener listener, String extraData) {
launchPurchaseFlow(act, sku, ITEM_TYPE_INAPP, requestCode, listener, extraData);
}
public void launchSubscriptionPurchaseFlow(Activity act, String sku, int requestCode,
OnIabPurchaseFinishedListener listener) {
launchSubscriptionPurchaseFlow(act, sku, requestCode, listener, "");
}
public void launchSubscriptionPurchaseFlow(Activity act, String sku, int requestCode,
OnIabPurchaseFinishedListener listener, String extraData) {
launchPurchaseFlow(act, sku, ITEM_TYPE_SUBS, requestCode, listener, extraData);
}
/**
* Initiate the UI flow for an in-app purchase. Call this method to initiate an in-app purchase,
* which will involve bringing up the Google Play screen. The calling activity will be paused while
* the user interacts with Google Play, and the result will be delivered via the activity's
* {@link android.app.Activity#onActivityResult} method, at which point you must call
* this object's {@link #handleActivityResult} method to continue the purchase flow. This method
* MUST be called from the UI thread of the Activity.
*
* @param act The calling activity.
* @param sku The sku of the item to purchase.
* @param itemType indicates if it's a product or a subscription (ITEM_TYPE_INAPP or ITEM_TYPE_SUBS)
* @param requestCode A request code (to differentiate from other responses --
* as in {@link android.app.Activity#startActivityForResult}).
* @param listener The listener to notify when the purchase process finishes
* @param extraData Extra data (developer payload), which will be returned with the purchase data
* when the purchase completes. This extra data will be permanently bound to that purchase
* and will always be returned when the purchase is queried.
*/
public void launchPurchaseFlow(Activity act, String sku, String itemType, int requestCode,
OnIabPurchaseFinishedListener listener, String extraData) {
checkNotDisposed();
checkSetupDone("launchPurchaseFlow");
flagStartAsync("launchPurchaseFlow");
IabResult result;
if (itemType.equals(ITEM_TYPE_SUBS) && !mSubscriptionsSupported) {
IabResult r = new IabResult(IABHELPER_SUBSCRIPTIONS_NOT_AVAILABLE,
"Subscriptions are not available.");
flagEndAsync();
if (listener != null) listener.onIabPurchaseFinished(r, null);
return;
}
try {
logDebug("Constructing buy intent for " + sku + ", item type: " + itemType);
Bundle buyIntentBundle = mService.getBuyIntent(3, mContext.getPackageName(), sku, itemType, extraData);
int response = getResponseCodeFromBundle(buyIntentBundle);
if (response != BILLING_RESPONSE_RESULT_OK) {
logError("Unable to buy item, Error response: " + getResponseDesc(response));
flagEndAsync();
result = new IabResult(response, "Unable to buy item");
if (listener != null) listener.onIabPurchaseFinished(result, null);
return;
}
PendingIntent pendingIntent = buyIntentBundle.getParcelable(RESPONSE_BUY_INTENT);
logDebug("Launching buy intent for " + sku + ". Request code: " + requestCode);
mRequestCode = requestCode;
mPurchaseListener = listener;
mPurchasingItemType = itemType;
act.startIntentSenderForResult(pendingIntent.getIntentSender(),
requestCode, new Intent(),
0, 0,
0);
} catch (SendIntentException e) {
logError("SendIntentException while launching purchase flow for sku " + sku);
e.printStackTrace();
flagEndAsync();
result = new IabResult(IABHELPER_SEND_INTENT_FAILED, "Failed to send intent.");
if (listener != null) listener.onIabPurchaseFinished(result, null);
} catch (RemoteException e) {
logError("RemoteException while launching purchase flow for sku " + sku);
e.printStackTrace();
flagEndAsync();
result = new IabResult(IABHELPER_REMOTE_EXCEPTION, "Remote exception while starting purchase flow");
if (listener != null) listener.onIabPurchaseFinished(result, null);
}
}
/**
* Handles an activity result that's part of the purchase flow in in-app billing. If you
* are calling {@link #launchPurchaseFlow}, then you must call this method from your
* Activity's {@link android.app.Activity@onActivityResult} method. This method
* MUST be called from the UI thread of the Activity.
*
* @param requestCode The requestCode as you received it.
* @param resultCode The resultCode as you received it.
* @param data The data (Intent) as you received it.
* @return Returns true if the result was related to a purchase flow and was handled;
* false if the result was not related to a purchase, in which case you should
* handle it normally.
*/
public boolean handleActivityResult(int requestCode, int resultCode, Intent data) {
IabResult result;
if (requestCode != mRequestCode) return false;
checkNotDisposed();
checkSetupDone("handleActivityResult");
// end of async purchase operation that started on launchPurchaseFlow
flagEndAsync();
if (data == null) {
logError("Null data in IAB activity result.");
result = new IabResult(IABHELPER_BAD_RESPONSE, "Null data in IAB result");
if (mPurchaseListener != null) mPurchaseListener.onIabPurchaseFinished(result, null);
return true;
}
int responseCode = getResponseCodeFromIntent(data);
String purchaseData = data.getStringExtra(RESPONSE_INAPP_PURCHASE_DATA);
String dataSignature = data.getStringExtra(RESPONSE_INAPP_SIGNATURE);
if (resultCode == Activity.RESULT_OK && responseCode == BILLING_RESPONSE_RESULT_OK) {
logDebug("Successful resultcode from purchase activity.");
logDebug("Purchase data: " + purchaseData);
logDebug("Data signature: " + dataSignature);
logDebug("Extras: " + data.getExtras());
logDebug("Expected item type: " + mPurchasingItemType);
if (purchaseData == null || dataSignature == null) {
logError("BUG: either purchaseData or dataSignature is null.");
logDebug("Extras: " + data.getExtras().toString());
result = new IabResult(IABHELPER_UNKNOWN_ERROR, "IAB returned null purchaseData or dataSignature");
if (mPurchaseListener != null)
mPurchaseListener.onIabPurchaseFinished(result, null);
return true;
}
@SuppressWarnings("UnusedAssignment") Purchase purchase = null;
try {
purchase = new Purchase(mPurchasingItemType, purchaseData, dataSignature);
String sku = purchase.getSku();
// Verify signature
if (!Security.verifyPurchase(mSignatureBase64, purchaseData, dataSignature)) {
logError("Purchase signature verification FAILED for sku " + sku);
result = new IabResult(IABHELPER_VERIFICATION_FAILED, "Signature verification failed for sku " + sku);
if (mPurchaseListener != null)
mPurchaseListener.onIabPurchaseFinished(result, purchase);
return true;
}
logDebug("Purchase signature successfully verified.");
} catch (JSONException e) {
logError("Failed to parse purchase data.");
e.printStackTrace();
result = new IabResult(IABHELPER_BAD_RESPONSE, "Failed to parse purchase data.");
if (mPurchaseListener != null)
mPurchaseListener.onIabPurchaseFinished(result, null);
return true;
}
if (mPurchaseListener != null) {
mPurchaseListener.onIabPurchaseFinished(new IabResult(BILLING_RESPONSE_RESULT_OK, "Success"), purchase);
}
} else if (resultCode == Activity.RESULT_OK) {
// result code was OK, but in-app billing response was not OK.
logDebug("Result code was OK but in-app billing response was not OK: " + getResponseDesc(responseCode));
if (mPurchaseListener != null) {
result = new IabResult(responseCode, "Problem purchashing item.");
mPurchaseListener.onIabPurchaseFinished(result, null);
}
} else if (resultCode == Activity.RESULT_CANCELED) {
logDebug("Purchase canceled - Response: " + getResponseDesc(responseCode));
result = new IabResult(IABHELPER_USER_CANCELLED, "User canceled.");
if (mPurchaseListener != null) mPurchaseListener.onIabPurchaseFinished(result, null);
} else {
logError("Purchase failed. Result code: " + Integer.toString(resultCode)
+ ". Response: " + getResponseDesc(responseCode));
result = new IabResult(IABHELPER_UNKNOWN_PURCHASE_RESPONSE, "Unknown purchase response.");
if (mPurchaseListener != null) mPurchaseListener.onIabPurchaseFinished(result, null);
}
return true;
}
public Inventory queryInventory(boolean querySkuDetails, List<String> moreSkus) throws IabException {
return queryInventory(querySkuDetails, moreSkus, null);
}
/**
* Queries the inventory. This will query all owned items from the server, as well as
* information on additional skus, if specified. This method may block or take long to execute.
* Do not call from a UI thread. For that, use the non-blocking version {@link #refreshInventoryAsync}.
*
* @param querySkuDetails if true, SKU details (price, description, etc) will be queried as well
* as purchase information.
* @param moreItemSkus additional PRODUCT skus to query information on, regardless of ownership.
* Ignored if null or if querySkuDetails is false.
* @param moreSubsSkus additional SUBSCRIPTIONS skus to query information on, regardless of ownership.
* Ignored if null or if querySkuDetails is false.
* @throws IabException if a problem occurs while refreshing the inventory.
*/
public Inventory queryInventory(boolean querySkuDetails, List<String> moreItemSkus,
List<String> moreSubsSkus) throws IabException {
checkNotDisposed();
checkSetupDone("queryInventory");
try {
Inventory inv = new Inventory();
int r = queryPurchases(inv, ITEM_TYPE_INAPP);
if (r != BILLING_RESPONSE_RESULT_OK) {
throw new IabException(r, "Error refreshing inventory (querying owned items).");
}
if (querySkuDetails) {
r = querySkuDetails(ITEM_TYPE_INAPP, inv, moreItemSkus);
if (r != BILLING_RESPONSE_RESULT_OK) {
throw new IabException(r, "Error refreshing inventory (querying prices of items).");
}
}
// if subscriptions are supported, then also query for subscriptions
if (mSubscriptionsSupported) {
r = queryPurchases(inv, ITEM_TYPE_SUBS);
if (r != BILLING_RESPONSE_RESULT_OK) {
throw new IabException(r, "Error refreshing inventory (querying owned subscriptions).");
}
if (querySkuDetails) {
r = querySkuDetails(ITEM_TYPE_SUBS, inv, moreItemSkus);
if (r != BILLING_RESPONSE_RESULT_OK) {
throw new IabException(r, "Error refreshing inventory (querying prices of subscriptions).");
}
}
}
return inv;
} catch (RemoteException e) {
throw new IabException(IABHELPER_REMOTE_EXCEPTION, "Remote exception while refreshing inventory.", e);
} catch (JSONException e) {
throw new IabException(IABHELPER_BAD_RESPONSE, "Error parsing JSON response while refreshing inventory.", e);
}
}
/**
* Asynchronous wrapper for inventory query. This will perform an inventory
* query as described in {@link #queryInventory}, but will do so asynchronously
* and call back the specified listener upon completion. This method is safe to
* call from a UI thread.
*
* @param querySkuDetails as in {@link #queryInventory}
* @param moreSkus as in {@link #queryInventory}
* @param listener The listener to notify when the refresh operation completes.
*/
public void queryInventoryAsync(final boolean querySkuDetails,
final List<String> moreSkus,
final QueryInventoryFinishedListener listener) {
final Handler handler = new Handler();
checkNotDisposed();
checkSetupDone("queryInventory");
flagStartAsync("refresh inventory");
(new Thread(new Runnable() {
public void run() {
IabResult result = new IabResult(BILLING_RESPONSE_RESULT_OK, "Inventory refresh successful.");
Inventory inv = null;
try {
inv = queryInventory(querySkuDetails, moreSkus);
} catch (IabException ex) {
result = ex.getResult();
}
flagEndAsync();
final IabResult result_f = result;
final Inventory inv_f = inv;
if (!mDisposed && listener != null) {
handler.post(new Runnable() {
public void run() {
listener.onQueryInventoryFinished(result_f, inv_f);
}
});
}
}
})).start();
}
public void queryInventoryAsync(QueryInventoryFinishedListener listener) {
queryInventoryAsync(true, null, listener);
}
public void queryInventoryAsync(boolean querySkuDetails, QueryInventoryFinishedListener listener) {
queryInventoryAsync(querySkuDetails, null, listener);
}
/**
* Consumes a given in-app product. Consuming can only be done on an item
* that's owned, and as a result of consumption, the user will no longer own it.
* This method may block or take long to return. Do not call from the UI thread.
* For that, see {@link #consumeAsync}.
*
* @param itemInfo The PurchaseInfo that represents the item to consume.
* @throws IabException if there is a problem during consumption.
*/
void consume(Purchase itemInfo) throws IabException {
checkNotDisposed();
checkSetupDone("consume");
if (!itemInfo.mItemType.equals(ITEM_TYPE_INAPP)) {
throw new IabException(IABHELPER_INVALID_CONSUMPTION,
"Items of type '" + itemInfo.mItemType + "' can't be consumed.");
}
try {
String token = itemInfo.getToken();
String sku = itemInfo.getSku();
if (token == null || token.equals("")) {
logError("Can't consume " + sku + ". No token.");
throw new IabException(IABHELPER_MISSING_TOKEN, "PurchaseInfo is missing token for sku: "
+ sku + " " + itemInfo);
}
logDebug("Consuming sku: " + sku + ", token: " + token);
int response = mService.consumePurchase(3, mContext.getPackageName(), token);
if (response == BILLING_RESPONSE_RESULT_OK) {
logDebug("Successfully consumed sku: " + sku);
} else {
logDebug("Error consuming consuming sku " + sku + ". " + getResponseDesc(response));
throw new IabException(response, "Error consuming sku " + sku);
}
} catch (RemoteException e) {
throw new IabException(IABHELPER_REMOTE_EXCEPTION, "Remote exception while consuming. PurchaseInfo: " + itemInfo, e);
}
}
/**
* Asynchronous wrapper to item consumption. Works like {@link #consume}, but
* performs the consumption in the background and notifies completion through
* the provided listener. This method is safe to call from a UI thread.
*
* @param purchase The purchase to be consumed.
* @param listener The listener to notify when the consumption operation finishes.
*/
public void consumeAsync(Purchase purchase, OnConsumeFinishedListener listener) {
checkNotDisposed();
checkSetupDone("consume");
List<Purchase> purchases = new ArrayList<>();
purchases.add(purchase);
consumeAsyncInternal(purchases, listener, null);
}
/**
* Same as {@link consumeAsync}, but for multiple items at once.
*
* @param purchases The list of PurchaseInfo objects representing the purchases to consume.
* @param listener The listener to notify when the consumption operation finishes.
*/
public void consumeAsync(List<Purchase> purchases, OnConsumeMultiFinishedListener listener) {
checkNotDisposed();
checkSetupDone("consume");
consumeAsyncInternal(purchases, null, listener);
}
// Checks that setup was done; if not, throws an exception.
void checkSetupDone(String operation) {
if (!mSetupDone) {
logError("Illegal state for operation (" + operation + "): IAB helper is not set up.");
throw new IllegalStateException("IAB helper is not set up. Can't perform operation: " + operation);
}
}
// Workaround to bug where sometimes response codes come as Long instead of Integer
int getResponseCodeFromBundle(Bundle b) {
Object o = b.get(RESPONSE_CODE);
if (o == null) {
logDebug("Bundle with null response code, assuming OK (known issue)");
return BILLING_RESPONSE_RESULT_OK;
} else if (o instanceof Integer) return (Integer) o;
else if (o instanceof Long) return (int) ((Long) o).longValue();
else {
logError("Unexpected type for bundle response code.");
logError(o.getClass().getName());
throw new RuntimeException("Unexpected type for bundle response code: " + o.getClass().getName());
}
}
// Workaround to bug where sometimes response codes come as Long instead of Integer
int getResponseCodeFromIntent(Intent i) {
Object o = i.getExtras().get(RESPONSE_CODE);
if (o == null) {
logError("Intent with no response code, assuming OK (known issue)");
return BILLING_RESPONSE_RESULT_OK;
} else if (o instanceof Integer) return (Integer) o;
else if (o instanceof Long) return (int) ((Long) o).longValue();
else {
logError("Unexpected type for intent response code.");
logError(o.getClass().getName());
throw new RuntimeException("Unexpected type for intent response code: " + o.getClass().getName());
}
}
void flagStartAsync(String operation) {
if (mAsyncInProgress) throw new IllegalStateException("Can't start async operation (" +
operation + ") because another async operation(" + mAsyncOperation + ") is in progress.");
mAsyncOperation = operation;
mAsyncInProgress = true;
logDebug("Starting async operation: " + operation);
}
void flagEndAsync() {
logDebug("Ending async operation: " + mAsyncOperation);
mAsyncOperation = "";
mAsyncInProgress = false;
}
int queryPurchases(Inventory inv, String itemType) throws JSONException, RemoteException {
// Query purchases
logDebug("Querying owned items, item type: " + itemType);
logDebug("Package name: " + mContext.getPackageName());
boolean verificationFailed = false;
String continueToken = null;
do {
logDebug("Calling getPurchases with continuation token: " + continueToken);
Bundle ownedItems = mService.getPurchases(3, mContext.getPackageName(),
itemType, continueToken);
int response = getResponseCodeFromBundle(ownedItems);
logDebug("Owned items response: " + String.valueOf(response));
if (response != BILLING_RESPONSE_RESULT_OK) {
logDebug("getPurchases() failed: " + getResponseDesc(response));
return response;
}
if (!ownedItems.containsKey(RESPONSE_INAPP_ITEM_LIST)
|| !ownedItems.containsKey(RESPONSE_INAPP_PURCHASE_DATA_LIST)
|| !ownedItems.containsKey(RESPONSE_INAPP_SIGNATURE_LIST)) {
logError("Bundle returned from getPurchases() doesn't contain required fields.");
return IABHELPER_BAD_RESPONSE;
}
ArrayList<String> ownedSkus = ownedItems.getStringArrayList(
RESPONSE_INAPP_ITEM_LIST);
ArrayList<String> purchaseDataList = ownedItems.getStringArrayList(
RESPONSE_INAPP_PURCHASE_DATA_LIST);
ArrayList<String> signatureList = ownedItems.getStringArrayList(
RESPONSE_INAPP_SIGNATURE_LIST);
for (int i = 0; i < purchaseDataList.size(); ++i) {
String purchaseData = purchaseDataList.get(i);
String signature = signatureList.get(i);
String sku = ownedSkus.get(i);
if (Security.verifyPurchase(mSignatureBase64, purchaseData, signature)) {
logDebug("Sku is owned: " + sku);
Purchase purchase = new Purchase(itemType, purchaseData, signature);
if (TextUtils.isEmpty(purchase.getToken())) {
logWarn("BUG: empty/null token!");
logDebug("Purchase data: " + purchaseData);
}
// Record ownership and token
inv.addPurchase(purchase);
} else {
logWarn("Purchase signature verification **FAILED**. Not adding item.");
logDebug(" Purchase data: " + purchaseData);
logDebug(" Signature: " + signature);
verificationFailed = true;
}
}
continueToken = ownedItems.getString(INAPP_CONTINUATION_TOKEN);
logDebug("Continuation token: " + continueToken);
} while (!TextUtils.isEmpty(continueToken));
return verificationFailed ? IABHELPER_VERIFICATION_FAILED : BILLING_RESPONSE_RESULT_OK;
}
int querySkuDetails(String itemType, Inventory inv, List<String> moreSkus)
throws RemoteException, JSONException {
logDebug("Querying SKU details.");
ArrayList<String> skuList = new ArrayList<>();
skuList.addAll(inv.getAllOwnedSkus(itemType));
if (moreSkus != null) {
for (String sku : moreSkus) {
if (!skuList.contains(sku)) {
skuList.add(sku);
}
}
}
if (skuList.size() == 0) {
logDebug("queryPrices: nothing to do because there are no SKUs.");
return BILLING_RESPONSE_RESULT_OK;
}
Bundle querySkus = new Bundle();
querySkus.putStringArrayList(GET_SKU_DETAILS_ITEM_LIST, skuList);
Bundle skuDetails = mService.getSkuDetails(3, mContext.getPackageName(),
itemType, querySkus);
if (!skuDetails.containsKey(RESPONSE_GET_SKU_DETAILS_LIST)) {
int response = getResponseCodeFromBundle(skuDetails);
if (response != BILLING_RESPONSE_RESULT_OK) {
logDebug("getSkuDetails() failed: " + getResponseDesc(response));
return response;
} else {
logError("getSkuDetails() returned a bundle with neither an error nor a detail list.");
return IABHELPER_BAD_RESPONSE;
}
}
ArrayList<String> responseList = skuDetails.getStringArrayList(
RESPONSE_GET_SKU_DETAILS_LIST);
for (String thisResponse : responseList) {
SkuDetails d = new SkuDetails(itemType, thisResponse);
logDebug("Got sku details: " + d);
inv.addSkuDetails(d);
}
return BILLING_RESPONSE_RESULT_OK;
}
void consumeAsyncInternal(final List<Purchase> purchases,
final OnConsumeFinishedListener singleListener,
final OnConsumeMultiFinishedListener multiListener) {
final Handler handler = new Handler();
flagStartAsync("consume");
(new Thread(new Runnable() {
public void run() {
final List<IabResult> results = new ArrayList<>();
for (Purchase purchase : purchases) {
try {
consume(purchase);
results.add(new IabResult(BILLING_RESPONSE_RESULT_OK, "Successful consume of sku " + purchase.getSku()));
} catch (IabException ex) {
results.add(ex.getResult());
}
}
flagEndAsync();
if (!mDisposed && singleListener != null) {
handler.post(new Runnable() {
public void run() {
singleListener.onConsumeFinished(purchases.get(0), results.get(0));
}
});
}
if (!mDisposed && multiListener != null) {
handler.post(new Runnable() {
public void run() {
multiListener.onConsumeMultiFinished(purchases, results);
}
});
}
}
})).start();
}
void logDebug(String msg) {
if (mDebugLog) Log.d(mDebugTag, msg);
}
void logError(String msg) {
Log.e(mDebugTag, "In-app billing error: " + msg);
}
void logWarn(String msg) {
Log.w(mDebugTag, "In-app billing warning: " + msg);
}
/**
* Callback for setup process. This listener's {@link #onIabSetupFinished} method is called
* when the setup process is complete.
*/
public interface OnIabSetupFinishedListener {
/**
* Called to notify that setup is complete.
*
* @param result The result of the setup process.
*/
public void onIabSetupFinished(IabResult result);
}
/**
* Callback that notifies when a purchase is finished.
*/
public interface OnIabPurchaseFinishedListener {
/**
* Called to notify that an in-app purchase finished. If the purchase was successful,
* then the sku parameter specifies which item was purchased. If the purchase failed,
* the sku and extraData parameters may or may not be null, depending on how far the purchase
* process went.
*
* @param result The result of the purchase.
* @param info The purchase information (null if purchase failed)
*/
public void onIabPurchaseFinished(IabResult result, Purchase info);
}
/**
* Listener that notifies when an inventory query operation completes.
*/
public interface QueryInventoryFinishedListener {
/**
* Called to notify that an inventory query operation completed.
*
* @param result The result of the operation.
* @param inv The inventory.
*/
public void onQueryInventoryFinished(IabResult result, Inventory inv);
}
/**
* Callback that notifies when a consumption operation finishes.
*/
public interface OnConsumeFinishedListener {
/**
* Called to notify that a consumption has finished.
*
* @param purchase The purchase that was (or was to be) consumed.
* @param result The result of the consumption operation.
*/
public void onConsumeFinished(Purchase purchase, IabResult result);
}
/**
* Callback that notifies when a multi-item consumption operation finishes.
*/
public interface OnConsumeMultiFinishedListener {
/**
* Called to notify that a consumption of multiple items has finished.
*
* @param purchases The purchases that were (or were to be) consumed.
* @param results The results of each consumption operation, corresponding to each
* sku.
*/
public void onConsumeMultiFinished(List<Purchase> purchases, List<IabResult> results);
}
}

View File

@ -0,0 +1,63 @@
/*
* Copyright (C) 2014 Vlad Mihalachi
*
* This file is part of Turbo Editor.
*
* Turbo Editor is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Turbo Editor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package sharedcode.turboeditor.iab.utils;
/**
* Represents the result of an in-app billing operation.
* A result is composed of a response code (an integer) and possibly a
* message (String). You can get those by calling
* {@link #getResponse} and {@link #getMessage()}, respectively. You
* can also inquire whether a result is a success or a failure by
* calling {@link #isSuccess()} and {@link #isFailure()}.
*/
public class IabResult {
int mResponse;
String mMessage;
public IabResult(int response, String message) {
mResponse = response;
if (message == null || message.trim().length() == 0) {
mMessage = IabHelper.getResponseDesc(response);
} else {
mMessage = message + " (response: " + IabHelper.getResponseDesc(response) + ")";
}
}
public int getResponse() {
return mResponse;
}
public String getMessage() {
return mMessage;
}
public boolean isSuccess() {
return mResponse == IabHelper.BILLING_RESPONSE_RESULT_OK;
}
public boolean isFailure() {
return !isSuccess();
}
public String toString() {
return "IabResult: " + getMessage();
}
}

View File

@ -0,0 +1,110 @@
/*
* Copyright (C) 2014 Vlad Mihalachi
*
* This file is part of Turbo Editor.
*
* Turbo Editor is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Turbo Editor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package sharedcode.turboeditor.iab.utils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Represents a block of information about in-app items.
* An Inventory is returned by such methods as {@link IabHelper#queryInventory}.
*/
public class Inventory {
Map<String, SkuDetails> mSkuMap = new HashMap<>();
Map<String, Purchase> mPurchaseMap = new HashMap<>();
Inventory() {
}
/**
* Returns the listing details for an in-app product.
*/
public SkuDetails getSkuDetails(String sku) {
return mSkuMap.get(sku);
}
/**
* Returns purchase information for a given product, or null if there is no purchase.
*/
public Purchase getPurchase(String sku) {
return mPurchaseMap.get(sku);
}
/**
* Returns whether or not there exists a purchase of the given product.
*/
public boolean hasPurchase(String sku) {
return mPurchaseMap.containsKey(sku);
}
/**
* Return whether or not details about the given product are available.
*/
public boolean hasDetails(String sku) {
return mSkuMap.containsKey(sku);
}
/**
* Erase a purchase (locally) from the inventory, given its product ID. This just
* modifies the Inventory object locally and has no effect on the server! This is
* useful when you have an existing Inventory object which you know to be up to date,
* and you have just consumed an item successfully, which means that erasing its
* purchase data from the Inventory you already have is quicker than querying for
* a new Inventory.
*/
public void erasePurchase(String sku) {
if (mPurchaseMap.containsKey(sku)) mPurchaseMap.remove(sku);
}
/**
* Returns a list of all owned product IDs.
*/
List<String> getAllOwnedSkus() {
return new ArrayList<>(mPurchaseMap.keySet());
}
/**
* Returns a list of all owned product IDs of a given type
*/
List<String> getAllOwnedSkus(String itemType) {
List<String> result = new ArrayList<>();
for (Purchase p : mPurchaseMap.values()) {
if (p.getItemType().equals(itemType)) result.add(p.getSku());
}
return result;
}
/**
* Returns a list of all purchases.
*/
List<Purchase> getAllPurchases() {
return new ArrayList<>(mPurchaseMap.values());
}
void addSkuDetails(SkuDetails d) {
mSkuMap.put(d.getSku(), d);
}
void addPurchase(Purchase p) {
mPurchaseMap.put(p.getSku(), p);
}
}

View File

@ -0,0 +1,98 @@
/*
* Copyright (C) 2014 Vlad Mihalachi
*
* This file is part of Turbo Editor.
*
* Turbo Editor is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Turbo Editor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package sharedcode.turboeditor.iab.utils;
import org.json.JSONException;
import org.json.JSONObject;
/**
* Represents an in-app billing purchase.
*/
public class Purchase {
String mItemType; // ITEM_TYPE_INAPP or ITEM_TYPE_SUBS
String mOrderId;
String mPackageName;
String mSku;
long mPurchaseTime;
int mPurchaseState;
String mDeveloperPayload;
String mToken;
String mOriginalJson;
String mSignature;
public Purchase(String itemType, String jsonPurchaseInfo, String signature) throws JSONException {
mItemType = itemType;
mOriginalJson = jsonPurchaseInfo;
JSONObject o = new JSONObject(mOriginalJson);
mOrderId = o.optString("orderId");
mPackageName = o.optString("packageName");
mSku = o.optString("productId");
mPurchaseTime = o.optLong("purchaseTime");
mPurchaseState = o.optInt("purchaseState");
mDeveloperPayload = o.optString("developerPayload");
mToken = o.optString("token", o.optString("purchaseToken"));
mSignature = signature;
}
public String getItemType() {
return mItemType;
}
public String getOrderId() {
return mOrderId;
}
public String getPackageName() {
return mPackageName;
}
public String getSku() {
return mSku;
}
public long getPurchaseTime() {
return mPurchaseTime;
}
public int getPurchaseState() {
return mPurchaseState;
}
public String getDeveloperPayload() {
return mDeveloperPayload;
}
public String getToken() {
return mToken;
}
public String getOriginalJson() {
return mOriginalJson;
}
public String getSignature() {
return mSignature;
}
@Override
public String toString() {
return "PurchaseInfo(type:" + mItemType + "):" + mOriginalJson;
}
}

View File

@ -0,0 +1,124 @@
/*
* Copyright (C) 2014 Vlad Mihalachi
*
* This file is part of Turbo Editor.
*
* Turbo Editor is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Turbo Editor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package sharedcode.turboeditor.iab.utils;
import android.text.TextUtils;
import android.util.Log;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
/**
* Security-related methods. For a secure implementation, all of this code
* should be implemented on a server that communicates with the
* application on the device. For the sake of simplicity and clarity of this
* example, this code is included here and is executed on the device. If you
* must verify the purchases on the phone, you should obfuscate this code to
* make it harder for an attacker to replace the code with stubs that treat all
* purchases as verified.
*/
public class Security {
private static final String TAG = "IABUtil/Security";
private static final String KEY_FACTORY_ALGORITHM = "RSA";
private static final String SIGNATURE_ALGORITHM = "SHA1withRSA";
/**
* Verifies that the data was signed with the given signature, and returns
* the verified purchase. The data is in JSON format and signed
* with a private key. The data also contains the {@link PurchaseState}
* and product ID of the purchase.
*
* @param base64PublicKey the base64-encoded public key to use for verifying.
* @param signedData the signed JSON string (signed, not encrypted)
* @param signature the signature for the data, signed with the private key
*/
public static boolean verifyPurchase(String base64PublicKey, String signedData, String signature) {
if (TextUtils.isEmpty(signedData) || TextUtils.isEmpty(base64PublicKey) ||
TextUtils.isEmpty(signature)) {
Log.e(TAG, "Purchase verification failed: missing data.");
return false;
}
PublicKey key = Security.generatePublicKey(base64PublicKey);
return Security.verify(key, signedData, signature);
}
/**
* Generates a PublicKey instance from a string containing the
* Base64-encoded public key.
*
* @param encodedPublicKey Base64-encoded public key
* @throws IllegalArgumentException if encodedPublicKey is invalid
*/
public static PublicKey generatePublicKey(String encodedPublicKey) {
try {
byte[] decodedKey = Base64.decode(encodedPublicKey);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM);
return keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
} catch (InvalidKeySpecException e) {
Log.e(TAG, "Invalid key specification.");
throw new IllegalArgumentException(e);
} catch (Base64DecoderException e) {
Log.e(TAG, "Base64 decoding failed.");
throw new IllegalArgumentException(e);
}
}
/**
* Verifies that the signature from the server matches the computed
* signature on the data. Returns true if the data is correctly signed.
*
* @param publicKey public key associated with the developer account
* @param signedData signed data from server
* @param signature server signature
* @return true if the data and signature match
*/
public static boolean verify(PublicKey publicKey, String signedData, String signature) {
Signature sig;
try {
sig = Signature.getInstance(SIGNATURE_ALGORITHM);
sig.initVerify(publicKey);
sig.update(signedData.getBytes());
if (!sig.verify(Base64.decode(signature))) {
Log.e(TAG, "Signature verification failed.");
return false;
}
return true;
} catch (NoSuchAlgorithmException e) {
Log.e(TAG, "NoSuchAlgorithmException.");
} catch (InvalidKeyException e) {
Log.e(TAG, "Invalid key specification.");
} catch (SignatureException e) {
Log.e(TAG, "Signature exception.");
} catch (Base64DecoderException e) {
Log.e(TAG, "Base64 decoding failed.");
}
return false;
}
}

View File

@ -0,0 +1,76 @@
/*
* Copyright (C) 2014 Vlad Mihalachi
*
* This file is part of Turbo Editor.
*
* Turbo Editor is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Turbo Editor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package sharedcode.turboeditor.iab.utils;
import org.json.JSONException;
import org.json.JSONObject;
/**
* Represents an in-app product's listing details.
*/
public class SkuDetails {
String mItemType;
String mSku;
String mType;
String mPrice;
String mTitle;
String mDescription;
String mJson;
public SkuDetails(String jsonSkuDetails) throws JSONException {
this(IabHelper.ITEM_TYPE_INAPP, jsonSkuDetails);
}
public SkuDetails(String itemType, String jsonSkuDetails) throws JSONException {
mItemType = itemType;
mJson = jsonSkuDetails;
JSONObject o = new JSONObject(mJson);
mSku = o.optString("productId");
mType = o.optString("type");
mPrice = o.optString("price");
mTitle = o.optString("title");
mDescription = o.optString("description");
}
public String getSku() {
return mSku;
}
public String getType() {
return mType;
}
public String getPrice() {
return mPrice;
}
public String getTitle() {
return mTitle;
}
public String getDescription() {
return mDescription;
}
@Override
public String toString() {
return "SkuDetails:" + mJson;
}
}

View File

@ -0,0 +1,211 @@
/*
* Copyright (C) 2014 Vlad Mihalachi
*
* This file is part of Turbo Editor.
*
* Turbo Editor is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Turbo Editor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package sharedcode.turboeditor.preferences;
import android.content.Context;
import android.content.res.Configuration;
import android.os.Build;
import android.os.Bundle;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceFragment;
import android.preference.PreferenceManager;
import java.util.ArrayList;
import java.util.List;
import de.greenrobot.event.EventBus;
import sharedcode.turboeditor.R;
import sharedcode.turboeditor.util.EventBusEvents;
import sharedcode.turboeditor.util.ThemeUtils;
/**
* A {@link PreferenceActivity} that presents a set of application settings. On
* handset devices, settings are presented as a single list. On tablets,
* settings are split by category, with category headers shown to the left of
* the list of settings.
* <p/>
* See <a href="http://developer.android.com/design/patterns/settings.html">
* Android Design: Settings</a> for design guidelines and the <a
* href="http://developer.android.com/guide/topics/ui/settings.html">Settings
* API Guide</a> for more information on developing a Settings UI.
*/
public class ExtraSettingsActivity extends PreferenceActivity {
/**
* Determines whether to always show the simplified settings UI, where
* settings are presented in a single list. When false, settings are shown
* as a master/detail two-pane view on tablets. When true, a single pane is
* shown on tablets.
*/
private static final boolean ALWAYS_SIMPLE_PREFS = true;
boolean encodingChanged = false,
themeChanged = false,
keyboardSuggestionsChanged = false;
/**
* A preference value change listener that updates the preference's summary
* to reflect its new value.
*/
private Preference.OnPreferenceChangeListener sBindPreferenceSummaryToValueListener = new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object value) {
String stringValue = value.toString();
if (preference instanceof ListPreference) {
// For list preferences, look up the correct display value in
// the preference's 'entries' list.
ListPreference listPreference = (ListPreference) preference;
int index = listPreference.findIndexOfValue(stringValue);
// Set the summary to reflect the new value.
preference.setSummary(
index >= 0
? listPreference.getEntries()[index]
: null);
}
switch (preference.getKey()) {
case "light_theme":
themeChanged = !themeChanged;
break;
case "suggestion_active":
keyboardSuggestionsChanged = !keyboardSuggestionsChanged;
break;
case "editor_encoding":
encodingChanged = true;
break;
}
return true;
}
};
/**
* Helper method to determine if the device has an extra-large screen. For
* example, 10" tablets are extra-large.
*/
private static boolean isXLargeTablet(Context context) {
return (context.getResources().getConfiguration().screenLayout
& Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_XLARGE;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
ThemeUtils.setTheme(this);
super.onCreate(savedInstanceState);
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
setupSimplePreferencesScreen();
}
/**
* Shows the simplified settings UI if the device configuration if the
* device configuration dictates that a simplified, single-pane UI should be
* shown.
*/
private void setupSimplePreferencesScreen() {
if (!isSimplePreferences(this)) {
return;
}
// In the simplified UI, fragments are not used at all and we instead
// use the older PreferenceActivity APIs.
// Add 'general' preferences.
addPreferencesFromResource(R.xml.extra_options);
// Bind the summaries of EditText/List/Dialog/Ringtone preferences to
// their values. When their values change, their summaries are updated
// to reflect the new value, per the Android Design guidelines.
bindPreferenceSummaryToValue(findPreference("editor_encoding"));
String[] checkBoxPreferences = {"light_theme", "suggestion_active"};
for (String key : checkBoxPreferences) {
// Set the listener to watch for value changes.
findPreference(key).setOnPreferenceChangeListener(sBindPreferenceSummaryToValueListener);
}
}
/**
* {@inheritDoc}
*/
@Override
public boolean onIsMultiPane() {
return isXLargeTablet(this) && !isSimplePreferences(this);
}
/**
* Determines whether the simplified settings UI should be shown. This is
* true if this is forced via {@link #ALWAYS_SIMPLE_PREFS}, or the device
* doesn't have newer APIs like {@link PreferenceFragment}, or the device
* doesn't have an extra-large screen. In these cases, a single-pane
* "simplified" settings UI should be shown.
*/
private boolean isSimplePreferences(Context context) {
return ALWAYS_SIMPLE_PREFS
|| Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB
|| !isXLargeTablet(context);
}
@Override
protected void onDestroy() {
List<EventBusEvents.APreferenceValueWasChanged.Type> listOfChanges = new ArrayList<>();
if (themeChanged) {
listOfChanges.add(EventBusEvents.APreferenceValueWasChanged.Type.THEME_CHANGE);
}
if (keyboardSuggestionsChanged) {
listOfChanges.add(EventBusEvents.APreferenceValueWasChanged.Type.TEXT_SUGGESTIONS);
}
if (encodingChanged) {
listOfChanges.add(EventBusEvents.APreferenceValueWasChanged.Type.ENCODING);
}
if (listOfChanges.size() > 0) {
EventBus.getDefault().postSticky(new EventBusEvents.APreferenceValueWasChanged(listOfChanges));
}
super.onDestroy();
}
/**
* Binds a preference's summary to its value. More specifically, when the
* preference's value is changed, its summary (line of text below the
* preference title) is updated to reflect the value. The summary is also
* immediately updated upon calling this method. The exact display format is
* dependent on the type of preference.
*
* @see #sBindPreferenceSummaryToValueListener
*/
private void bindPreferenceSummaryToValue(Preference preference) {
// Set the listener to watch for value changes.
preference.setOnPreferenceChangeListener(sBindPreferenceSummaryToValueListener);
// Trigger the listener immediately with the preference's
// current value.
sBindPreferenceSummaryToValueListener.onPreferenceChange(preference,
PreferenceManager
.getDefaultSharedPreferences(preference.getContext())
.getString(preference.getKey(), ""));
}
}

View File

@ -73,10 +73,6 @@ public final class PreferenceHelper {
return getPrefs(context).getBoolean("send_error_reports", true); return getPrefs(context).getBoolean("send_error_reports", true);
} }
public static int getLastDayAdShowed(Context context) {
return getPrefs(context).getInt("last_day_ad_showed", 0);
}
public static String getEncoding(Context context) { public static String getEncoding(Context context) {
return getPrefs(context).getString("editor_encoding", "UTF-8"); return getPrefs(context).getString("editor_encoding", "UTF-8");
} }
@ -105,6 +101,17 @@ public final class PreferenceHelper {
return getPrefs(context).getBoolean("read_only", false); return getPrefs(context).getBoolean("read_only", false);
} }
public static boolean getIgnoreBackButton(Context context) {
return getPrefs(context).getBoolean("ignore_back_button", false);
}
public static boolean getPageSystemEnabled(Context context) {
return getPrefs(context).getBoolean("page_system_active", true);
}
public static boolean hasDonated(Context context) {
return getPrefs(context).getBoolean("has_donated", false);
}
// Setter methods // Setter methods
public static void setUseMonospace(Context context, boolean value) { public static void setUseMonospace(Context context, boolean value) {
@ -123,30 +130,10 @@ public final class PreferenceHelper {
getEditor(context).putBoolean("editor_wrap_content", value).commit(); getEditor(context).putBoolean("editor_wrap_content", value).commit();
} }
public static void setLightTheme(Context context, boolean value) {
getEditor(context).putBoolean("light_theme", value).commit();
}
public static void setSuggestionActive(Context context, boolean value) {
getEditor(context).putBoolean("suggestion_active", value).commit();
}
public static void setAutoencoding(Context context, boolean value) { public static void setAutoencoding(Context context, boolean value) {
getEditor(context).putBoolean("autoencoding", value).commit(); getEditor(context).putBoolean("autoencoding", value).commit();
} }
public static void setSendErrorReports(Context context, boolean value) {
getEditor(context).putBoolean("send_error_reports", value).commit();
}
public static void setLastDayAdShowed(Context context, int value) {
getEditor(context).putInt("last_day_ad_showed", value).commit();
}
public static void setEncoding(Context context, String value) {
getEditor(context).putString("editor_encoding", value).commit();
}
public static void setFontSize(Context context, int value) { public static void setFontSize(Context context, int value) {
getEditor(context).putInt("font_size", value).commit(); getEditor(context).putInt("font_size", value).commit();
} }
@ -163,12 +150,12 @@ public final class PreferenceHelper {
getEditor(context).putBoolean("page_system_button_popup_shown", value).commit(); getEditor(context).putBoolean("page_system_button_popup_shown", value).commit();
} }
public static void setAutoSave(Context context, boolean value) {
getEditor(context).putBoolean("auto_save", value).commit();
}
public static void setReadOnly(Context context, boolean value) { public static void setReadOnly(Context context, boolean value) {
getEditor(context).putBoolean("read_only", value).commit(); getEditor(context).putBoolean("read_only", value).commit();
} }
public static void setHasDonated(Context context, boolean value) {
getEditor(context).putBoolean("has_donated", value);
}
} }

View File

@ -21,65 +21,47 @@ package sharedcode.turboeditor.preferences;
import android.app.Fragment; import android.app.Fragment;
import android.content.Intent; import android.content.Intent;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.CheckBox; import android.widget.CheckBox;
import android.widget.CheckedTextView;
import android.widget.CompoundButton; import android.widget.CompoundButton;
import android.widget.Switch;
import android.widget.TextView; import android.widget.TextView;
import sharedcode.turboeditor.R;
import sharedcode.turboeditor.fragment.EncodingDialogFragment;
import sharedcode.turboeditor.fragment.SeekbarDialogFragment;
import de.greenrobot.event.EventBus; import de.greenrobot.event.EventBus;
import sharedcode.turboeditor.R;
import sharedcode.turboeditor.fragment.SeekbarDialog;
import sharedcode.turboeditor.util.AnimationUtils;
import sharedcode.turboeditor.util.ProCheckUtils; import sharedcode.turboeditor.util.ProCheckUtils;
import sharedcode.turboeditor.util.ViewUtils;
import sharedcode.turboeditor.views.DialogHelper;
import static sharedcode.turboeditor.util.EventBusEvents.APreferenceValueWasChanged; import static sharedcode.turboeditor.util.EventBusEvents.APreferenceValueWasChanged;
import static sharedcode.turboeditor.util.EventBusEvents.APreferenceValueWasChanged.Type.AUTO_SAVE;
import static sharedcode.turboeditor.util.EventBusEvents.APreferenceValueWasChanged.Type.ENCODING;
import static sharedcode.turboeditor.util.EventBusEvents.APreferenceValueWasChanged.Type.FONT_SIZE; import static sharedcode.turboeditor.util.EventBusEvents.APreferenceValueWasChanged.Type.FONT_SIZE;
import static sharedcode.turboeditor.util.EventBusEvents.APreferenceValueWasChanged.Type.LINE_NUMERS; import static sharedcode.turboeditor.util.EventBusEvents.APreferenceValueWasChanged.Type.LINE_NUMERS;
import static sharedcode.turboeditor.util.EventBusEvents.APreferenceValueWasChanged.Type.MONOSPACE; import static sharedcode.turboeditor.util.EventBusEvents.APreferenceValueWasChanged.Type.MONOSPACE;
import static sharedcode.turboeditor.util.EventBusEvents.APreferenceValueWasChanged.Type.READ_ONLY; import static sharedcode.turboeditor.util.EventBusEvents.APreferenceValueWasChanged.Type.READ_ONLY;
import static sharedcode.turboeditor.util.EventBusEvents.APreferenceValueWasChanged.Type.SYNTAX; import static sharedcode.turboeditor.util.EventBusEvents.APreferenceValueWasChanged.Type.SYNTAX;
import static sharedcode.turboeditor.util.EventBusEvents.APreferenceValueWasChanged.Type.TEXT_SUGGESTIONS;
import static sharedcode.turboeditor.util.EventBusEvents.APreferenceValueWasChanged.Type.THEME_CHANGE;
import static sharedcode.turboeditor.util.EventBusEvents.APreferenceValueWasChanged.Type.WRAP_CONTENT; import static sharedcode.turboeditor.util.EventBusEvents.APreferenceValueWasChanged.Type.WRAP_CONTENT;
public class SettingsFragment extends Fragment implements EncodingDialogFragment.DialogListener, SeekbarDialogFragment.onSeekbarDialogDismissed { public class SettingsFragment extends Fragment implements SeekbarDialog.ISeekbarDialog {
public static String sCurrentEncoding;
// Editor Variables // Editor Variables
public static boolean sLineNumbers; private boolean sLineNumbers;
public static boolean sColorSyntax; private boolean sColorSyntax;
public static boolean sWrapContent; private boolean sWrapContent;
public static int sFontSize; private boolean sUseMonospace;
public static boolean sUseMonospace; private boolean sReadOnly;
public static boolean sLightTheme;
public static boolean sSuggestionsActive;
public static boolean sAutoSave;
public static boolean sReadOnly;
public static boolean sSendErrorReports;
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
SettingsFragment.sCurrentEncoding = PreferenceHelper.getEncoding(getActivity()); sUseMonospace = PreferenceHelper.getUseMonospace(getActivity());
SettingsFragment.sUseMonospace = PreferenceHelper.getUseMonospace(getActivity()); sColorSyntax = PreferenceHelper.getSyntaxHiglight(getActivity());
SettingsFragment.sColorSyntax = PreferenceHelper.getSyntaxHiglight(getActivity()); sWrapContent = PreferenceHelper.getWrapContent(getActivity());
SettingsFragment.sWrapContent = PreferenceHelper.getWrapContent(getActivity()); sLineNumbers = PreferenceHelper.getLineNumbers(getActivity());
SettingsFragment.sLineNumbers = PreferenceHelper.getLineNumbers(getActivity()); sReadOnly = PreferenceHelper.getReadOnly(getActivity());
SettingsFragment.sFontSize = PreferenceHelper.getFontSize(getActivity());
SettingsFragment.sSuggestionsActive = PreferenceHelper.getSuggestionActive(getActivity());
SettingsFragment.sLightTheme = PreferenceHelper.getLightTheme(getActivity());
SettingsFragment.sAutoSave = PreferenceHelper.getAutoSave(getActivity());
SettingsFragment.sReadOnly = PreferenceHelper.getReadOnly(getActivity());
SettingsFragment.sSendErrorReports = PreferenceHelper.getReadOnly(getActivity());
} }
@Override @Override
@ -87,31 +69,26 @@ public class SettingsFragment extends Fragment implements EncodingDialogFragment
Bundle savedInstanceState) { Bundle savedInstanceState) {
// Our custom layout // Our custom layout
View rootView = inflater.inflate(R.layout.fragment_settings, container, false); View rootView = inflater.inflate(R.layout.fragment_settings, container, false);
final CheckBox switchLineNumbers, switchSyntax, switchWrapContent, switchMonospace, switchLightTheme, switchSuggestionsActive, switchAutoSave, switchReadOnly, switchSendErrorReports; final CheckBox switchLineNumbers, switchSyntax, switchWrapContent, switchMonospace, switchReadOnly;
switchLineNumbers = (CheckBox) rootView.findViewById(R.id.switch_line_numbers); switchLineNumbers = (CheckBox) rootView.findViewById(R.id.switch_line_numbers);
switchSyntax = (CheckBox) rootView.findViewById(R.id.switch_syntax); switchSyntax = (CheckBox) rootView.findViewById(R.id.switch_syntax);
switchWrapContent = (CheckBox) rootView.findViewById(R.id.switch_wrap_content); switchWrapContent = (CheckBox) rootView.findViewById(R.id.switch_wrap_content);
switchMonospace = (CheckBox) rootView.findViewById(R.id.switch_monospace); switchMonospace = (CheckBox) rootView.findViewById(R.id.switch_monospace);
switchLightTheme = (CheckBox) rootView.findViewById(R.id.switch_light_theme);
switchSuggestionsActive = (CheckBox) rootView.findViewById(R.id.switch_suggestions_active);
switchAutoSave = (CheckBox) rootView.findViewById(R.id.switch_auto_save);
switchReadOnly = (CheckBox) rootView.findViewById(R.id.switch_read_only); switchReadOnly = (CheckBox) rootView.findViewById(R.id.switch_read_only);
switchSendErrorReports = (CheckBox) rootView.findViewById(R.id.switch_send_error_reports);
switchLineNumbers.setChecked(sLineNumbers); switchLineNumbers.setChecked(sLineNumbers);
switchSyntax.setChecked(sColorSyntax); switchSyntax.setChecked(sColorSyntax);
switchWrapContent.setChecked(sWrapContent); switchWrapContent.setChecked(sWrapContent);
switchMonospace.setChecked(sUseMonospace); switchMonospace.setChecked(sUseMonospace);
switchLightTheme.setChecked(sLightTheme);
switchSuggestionsActive.setChecked(sSuggestionsActive);
switchAutoSave.setChecked(sAutoSave);
switchReadOnly.setChecked(sReadOnly); switchReadOnly.setChecked(sReadOnly);
TextView fontSizeView, donateView, extraOptionsView;
TextView encodingView, fontSizeView, goProView;
encodingView = (TextView) rootView.findViewById(R.id.drawer_button_encoding);
fontSizeView = (TextView) rootView.findViewById(R.id.drawer_button_font_size); fontSizeView = (TextView) rootView.findViewById(R.id.drawer_button_font_size);
goProView = (TextView) rootView.findViewById(R.id.drawer_button_go_pro); extraOptionsView = (TextView) rootView.findViewById(R.id.drawer_button_extra_options);
donateView = (TextView) rootView.findViewById(R.id.drawer_button_go_pro);
if(ProCheckUtils.isPro(getActivity(), false))
ViewUtils.setVisible(donateView, false);
switchLineNumbers.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { switchLineNumbers.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override @Override
@ -151,33 +128,6 @@ public class SettingsFragment extends Fragment implements EncodingDialogFragment
} }
}); });
switchLightTheme.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
sLightTheme = isChecked;
PreferenceHelper.setLightTheme(getActivity(), isChecked);
EventBus.getDefault().post(new APreferenceValueWasChanged(THEME_CHANGE));
}
});
switchSuggestionsActive.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
sSuggestionsActive = isChecked;
PreferenceHelper.setSuggestionActive(getActivity(), isChecked);
EventBus.getDefault().post(new APreferenceValueWasChanged(TEXT_SUGGESTIONS));
}
});
switchAutoSave.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
sAutoSave = isChecked;
PreferenceHelper.setAutoSave(getActivity(), isChecked);
EventBus.getDefault().post(new APreferenceValueWasChanged(AUTO_SAVE));
}
});
switchReadOnly.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { switchReadOnly.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override @Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
@ -187,23 +137,6 @@ public class SettingsFragment extends Fragment implements EncodingDialogFragment
} }
}); });
switchSendErrorReports.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
sSendErrorReports = isChecked;
PreferenceHelper.setSendErrorReports(getActivity(), isChecked);
}
});
encodingView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
EncodingDialogFragment dialogFrag = EncodingDialogFragment.newInstance();
dialogFrag.setTargetFragment(SettingsFragment.this, 0);
dialogFrag.show(getFragmentManager().beginTransaction(), "dialog");
}
});
fontSizeView.setOnClickListener(new View.OnClickListener() { fontSizeView.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
@ -211,54 +144,35 @@ public class SettingsFragment extends Fragment implements EncodingDialogFragment
int fontCurrent = //(int) (mEditor.getTextSize() / scaledDensity); int fontCurrent = //(int) (mEditor.getTextSize() / scaledDensity);
//fontMax / 2; //fontMax / 2;
PreferenceHelper.getFontSize(getActivity()); PreferenceHelper.getFontSize(getActivity());
SeekbarDialogFragment dialogFrag = SeekbarDialogFragment.newInstance(SeekbarDialogFragment.Actions.FileSize, 1, fontCurrent, fontMax); SeekbarDialog dialogFrag = SeekbarDialog.newInstance(SeekbarDialog.Actions
.FontSize, 1, fontCurrent, fontMax);
dialogFrag.setTargetFragment(SettingsFragment.this, 0); dialogFrag.setTargetFragment(SettingsFragment.this, 0);
dialogFrag.show(getFragmentManager().beginTransaction(), "dialog"); dialogFrag.show(getFragmentManager().beginTransaction(), "dialog");
} }
}); });
goProView.setOnClickListener(new View.OnClickListener() { extraOptionsView.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
try { AnimationUtils.startActivityWithScale(getActivity(), new Intent(getActivity(),
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=com.maskyn.fileeditorpro")) ExtraSettingsActivity.class), false, 0, v);
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); }
} catch (Exception e) { });
}
donateView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
DialogHelper.showDonateDialog(getActivity());
} }
}); });
goProView.setVisibility(ProCheckUtils.isPro(getActivity()) ? View.GONE : View.VISIBLE);
return rootView; return rootView;
} }
@Override @Override
public void onEncodingSelected(String value) { public void onSeekbarDialogDismissed(SeekbarDialog.Actions action, int value) {
PreferenceHelper.setEncoding(getActivity(), value);
EventBus.getDefault().post(new APreferenceValueWasChanged(ENCODING));
}
@Override
public void onSeekbarDialogDismissed(SeekbarDialogFragment.Actions action, int value) {
sFontSize = value;
PreferenceHelper.setFontSize(getActivity(), value); PreferenceHelper.setFontSize(getActivity(), value);
EventBus.getDefault().post(new APreferenceValueWasChanged(FONT_SIZE)); EventBus.getDefault().post(new APreferenceValueWasChanged(FONT_SIZE));
} }
/**
* This interface must be implemented by activities that contain this
* fragment to allow an interaction in this fragment to be communicated
* to the activity and potentially other fragments contained in that
* activity.
* <p/>
* See the Android Training lesson <a href=
* "http://developer.android.com/training/basics/fragments/communicating.html"
* >Communicating with Other Fragments</a> for more information.
*/
public interface OnFragmentInteractionListener {
// TODO: Update argument type and name
public void onFragmentInteraction(Uri uri);
}
} }

View File

@ -0,0 +1,77 @@
/*
* Copyright (C) 2014 Vlad Mihalachi
*
* This file is part of Turbo Editor.
*
* Turbo Editor is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Turbo Editor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package sharedcode.turboeditor.root;
import android.util.Log;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class LinuxShell {
private static final String UNIX_ESCAPE_EXPRESSION = "(\\(|\\)|\\[|\\]|\\s|\'|\"|`|\\{|\\}|&|\\\\|\\?)";
public static String getCommandLineString(String input) {
return input.replaceAll(UNIX_ESCAPE_EXPRESSION, "\\\\$1");
}
public static BufferedReader execute(String cmd) {
BufferedReader reader = null;
try {
Process process = Runtime.getRuntime().exec("su");
DataOutputStream os = new DataOutputStream(
process.getOutputStream());
os.writeBytes(cmd + "\n");
os.writeBytes("exit\n");
reader = new BufferedReader(new InputStreamReader(
process.getInputStream()));
String err = (new BufferedReader(new InputStreamReader(
process.getErrorStream()))).readLine();
os.flush();
if (process.waitFor() != 0 || (!"".equals(err) && null != err)
&& containsIllegals(err) != true) {
Log.e("Root Error, cmd: " + cmd, err);
return null;
}
return reader;
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static boolean containsIllegals(String toExamine) {
// checks for "+" sign so the program doesn't throw an error when its
// not erroring.
Pattern pattern = Pattern.compile("[+]");
Matcher matcher = pattern.matcher(toExamine);
return matcher.find();
}
}

View File

@ -32,7 +32,7 @@
* along with 920 Text Editor. If not, see <http://www.gnu.org/licenses/>. * along with 920 Text Editor. If not, see <http://www.gnu.org/licenses/>.
*/ */
package sharedcode.turboeditor.util; package sharedcode.turboeditor.root;
import android.content.Context; import android.content.Context;
@ -80,8 +80,7 @@ public class RootUtils {
} else { } else {
BufferedReader reader = null; //errReader = null; BufferedReader reader = null; //errReader = null;
try { try {
LinuxShell.execute("ls -a " + LinuxShell.getCommandLineString(path));
reader = LinuxShell.execute("IFS='\n';CURDIR='" + LinuxShell.getCmdPath(path) + "';for i in `ls $CURDIR`; do if [ -d $CURDIR/$i ]; then echo \"d $CURDIR/$i\";else echo \"f $CURDIR/$i\"; fi; done");
if (reader == null) if (reader == null)
return null; return null;

View File

@ -17,14 +17,12 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package sharedcode.turboeditor.util; package sharedcode.turboeditor.task;
import android.content.Context; import android.content.Context;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.widget.Toast; import android.widget.Toast;
import sharedcode.turboeditor.R;
import org.sufficientlysecure.rootcommands.Shell; import org.sufficientlysecure.rootcommands.Shell;
import org.sufficientlysecure.rootcommands.Toolbox; import org.sufficientlysecure.rootcommands.Toolbox;
@ -33,6 +31,9 @@ import java.io.IOException;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import de.greenrobot.event.EventBus; import de.greenrobot.event.EventBus;
import sharedcode.turboeditor.R;
import sharedcode.turboeditor.root.RootUtils;
import sharedcode.turboeditor.util.EventBusEvents;
public class SaveFileTask extends AsyncTask<Void, Void, Void> { public class SaveFileTask extends AsyncTask<Void, Void, Void> {
@ -100,7 +101,7 @@ public class SaveFileTask extends AsyncTask<Void, Void, Void> {
protected void onPostExecute(final Void aVoid) { protected void onPostExecute(final Void aVoid) {
super.onPostExecute(aVoid); super.onPostExecute(aVoid);
Toast.makeText(context, message, Toast.LENGTH_LONG).show(); Toast.makeText(context, message, Toast.LENGTH_LONG).show();
if(message.equals(positiveMessage)) if (message.equals(positiveMessage))
EventBus.getDefault().post(new EventBusEvents.SavedAFile(filePath)); EventBus.getDefault().post(new EventBusEvents.SavedAFile(filePath));
} }
} }

View File

@ -17,18 +17,20 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package sharedcode.turboeditor.util; package sharedcode.turboeditor.texteditor;
import android.content.Context; import android.content.Context;
public class EdittextPadding { import sharedcode.turboeditor.util.PixelDipConverter;
public class EditTextPadding {
public static int getPaddingWithoutLineNumbers(Context context) { public static int getPaddingWithoutLineNumbers(Context context) {
return (int) PixelDipConverter.convertDpToPixel(5, context); return (int) PixelDipConverter.convertDpToPixel(5, context);
} }
public static int getPaddingWithLineNumbers(Context context, float fontSize) { public static int getPaddingWithLineNumbers(Context context, float fontSize) {
return (int) PixelDipConverter.convertDpToPixel(fontSize * 1.85f, context); return (int) PixelDipConverter.convertDpToPixel(fontSize * 2f, context);
} }
public static int getPaddingTop(Context context) { public static int getPaddingTop(Context context) {

View File

@ -17,7 +17,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package sharedcode.turboeditor.util; package sharedcode.turboeditor.texteditor;
import org.mozilla.universalchardet.UniversalDetector; import org.mozilla.universalchardet.UniversalDetector;

View File

@ -17,7 +17,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package sharedcode.turboeditor.util; package sharedcode.turboeditor.texteditor;
import android.text.Layout; import android.text.Layout;
import android.widget.ScrollView; import android.widget.ScrollView;
@ -34,23 +34,18 @@ public class LineUtils {
return realLines; return realLines;
} }
public int getYAtLine(ScrollView scrollView, int lineCount, int line){ public int getYAtLine(ScrollView scrollView, int lineCount, int line) {
return scrollView.getChildAt(0).getHeight() / lineCount * line; return scrollView.getChildAt(0).getHeight() / lineCount * line;
} }
public int getFirstVisibleLine(ScrollView scrollView, int lineCount){ public int getFirstVisibleLine(ScrollView scrollView, int childHeight, int lineCount) throws ArithmeticException {
return getFirstVisibleLine(scrollView, scrollView.getChildAt(0).getHeight(), lineCount);
}
public int getFirstVisibleLine(ScrollView scrollView, int childHeight, int lineCount) throws ArithmeticException{
int line = (scrollView.getScrollY() * lineCount) / childHeight; int line = (scrollView.getScrollY() * lineCount) / childHeight;
if (line < 0) line = 0; if (line < 0) line = 0;
return line; return line;
} }
public int getLastVisibleLine(int firstVisibleLine, int lineCount) { public int getLastVisibleLine(ScrollView scrollView, int childHeight, int lineCount, int deviceHeight) {
int line; int line = ((scrollView.getScrollY() + deviceHeight) * lineCount) / childHeight;
line = firstVisibleLine + 150;
if (line > lineCount) line = lineCount; if (line > lineCount) line = lineCount;
return line; return line;
} }
@ -90,6 +85,7 @@ public class LineUtils {
/** /**
* Gets the line from the index of the letter in the text * Gets the line from the index of the letter in the text
*
* @param index * @param index
* @param lineCount * @param lineCount
* @param layout * @param layout
@ -109,15 +105,19 @@ public class LineUtils {
return line; return line;
} }
public int firstReadLine() {
return realLines[0];
}
public int lastReadLine() { public int lastReadLine() {
return realLines[realLines.length-1]; return realLines[realLines.length - 1];
} }
public int fakeLineFromRealLine(int realLine) { public int fakeLineFromRealLine(int realLine) {
int i; int i;
int fakeLine = 0; int fakeLine = 0;
for(i = 0; i < realLines.length; i++) { for (i = 0; i < realLines.length; i++) {
if (realLine == realLines[i]){ if (realLine == realLines[i]) {
fakeLine = i; fakeLine = i;
break; break;
} }

View File

@ -17,26 +17,23 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package sharedcode.turboeditor.util; package sharedcode.turboeditor.texteditor;
import android.content.Context;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import sharedcode.turboeditor.preferences.PreferenceHelper;
public class PageSystem { public class PageSystem {
public interface PageSystemInterface {
void onPageChanged(int page);
}
private List<String> pages; private List<String> pages;
private int[] startingLines; private int[] startingLines;
private int currentPage = 0; private int currentPage = 0;
private PageSystemInterface pageSystemInterface; private PageSystemInterface pageSystemInterface;
public PageSystem(PageSystemInterface pageSystemInterface, String text) { public PageSystem(Context context, PageSystemInterface pageSystemInterface, String text) {
this.pageSystemInterface = pageSystemInterface; this.pageSystemInterface = pageSystemInterface;
pages = new LinkedList<>(); pages = new LinkedList<>();
@ -46,8 +43,9 @@ public class PageSystem {
int to; int to;
int indexOfReturn; int indexOfReturn;
int textLenght = text.length(); int textLenght = text.length();
if(textLenght > maxLenghtInOnePage) { boolean pageSystemEnabled = PreferenceHelper.getPageSystemEnabled(context);
while (i < textLenght) { if (pageSystemEnabled && textLenght > maxLenghtInOnePage) {
while (i < textLenght && pageSystemEnabled) {
to = i + charForPage; to = i + charForPage;
indexOfReturn = text.indexOf("\n", to); indexOfReturn = text.indexOf("\n", to);
if (indexOfReturn > to) to = indexOfReturn; if (indexOfReturn > to) to = indexOfReturn;
@ -78,8 +76,8 @@ public class PageSystem {
public String getTextOfNextPages(boolean includeCurrent, int nOfPages) { public String getTextOfNextPages(boolean includeCurrent, int nOfPages) {
StringBuilder stringBuilder = new StringBuilder(); StringBuilder stringBuilder = new StringBuilder();
int i; int i;
for(i = includeCurrent ? 0 : 1; i < nOfPages; i++){ for (i = includeCurrent ? 0 : 1; i < nOfPages; i++) {
if(pages.size() > (currentPage + i)) { if (pages.size() > (currentPage + i)) {
stringBuilder.append(pages.get(currentPage + 1)); stringBuilder.append(pages.get(currentPage + 1));
} }
} }
@ -92,25 +90,25 @@ public class PageSystem {
} }
public void nextPage() { public void nextPage() {
if(!canReadNextPage()) return; if (!canReadNextPage()) return;
goToPage(currentPage + 1); goToPage(currentPage + 1);
} }
public void prevPage() { public void prevPage() {
if(!canReadPrevPage()) return; if (!canReadPrevPage()) return;
goToPage(currentPage - 1); goToPage(currentPage - 1);
} }
public void goToPage(int page) { public void goToPage(int page) {
if(page >= pages.size()) page = pages.size() - 1; if (page >= pages.size()) page = pages.size() - 1;
if(page < 0) page = 0; if (page < 0) page = 0;
boolean shouldUpdateLines = page > currentPage && canReadNextPage(); boolean shouldUpdateLines = page > currentPage && canReadNextPage();
if(shouldUpdateLines) { if (shouldUpdateLines) {
String text = getCurrentPageText(); String text = getCurrentPageText();
int nOfNewLineNow = (text.length() - text.replace("\n", "").length()) + 1; // normally the last line is not counted so we have to add 1 int nOfNewLineNow = (text.length() - text.replace("\n", "").length()) + 1; // normally the last line is not counted so we have to add 1
int nOfNewLineBefore = startingLines[currentPage+1] - startingLines[currentPage]; int nOfNewLineBefore = startingLines[currentPage + 1] - startingLines[currentPage];
int difference = nOfNewLineNow - nOfNewLineBefore; int difference = nOfNewLineNow - nOfNewLineBefore;
updateStartingLines(currentPage+1, difference); updateStartingLines(currentPage + 1, difference);
} }
currentPage = page; currentPage = page;
pageSystemInterface.onPageChanged(page); pageSystemInterface.onPageChanged(page);
@ -122,8 +120,8 @@ public class PageSystem {
int nOfNewLines; int nOfNewLines;
String text; String text;
startingLines[0] = 0; startingLines[0] = 0;
for(i = 1; i < pages.size(); i++) { for (i = 1; i < pages.size(); i++) {
text = pages.get(i-1); text = pages.get(i - 1);
nOfNewLines = text.length() - text.replace("\n", "").length() + 1; nOfNewLines = text.length() - text.replace("\n", "").length() + 1;
startingLine = startingLines[i - 1] + nOfNewLines; startingLine = startingLines[i - 1] + nOfNewLines;
startingLines[i] = startingLine; startingLines[i] = startingLine;
@ -131,11 +129,11 @@ public class PageSystem {
} }
public void updateStartingLines(int fromPage, int difference) { public void updateStartingLines(int fromPage, int difference) {
if(difference == 0) if (difference == 0)
return; return;
int i; int i;
if(fromPage < 1) fromPage = 1; if (fromPage < 1) fromPage = 1;
for(i = fromPage; i < pages.size(); i++) { for (i = fromPage; i < pages.size(); i++) {
startingLines[i] += difference; startingLines[i] += difference;
} }
} }
@ -144,13 +142,15 @@ public class PageSystem {
return pages.size() - 1; return pages.size() - 1;
} }
public int getCurrentPage() { return currentPage; } public int getCurrentPage() {
return currentPage;
}
public String getAllText(String currentPageText) { public String getAllText(String currentPageText) {
pages.set(currentPage, currentPageText); pages.set(currentPage, currentPageText);
int i; int i;
StringBuilder allText = new StringBuilder(); StringBuilder allText = new StringBuilder();
for(i = 0; i < pages.size(); i++) { for (i = 0; i < pages.size(); i++) {
allText.append(pages.get(i)).append("\n"); allText.append(pages.get(i)).append("\n");
} }
return allText.toString(); return allText.toString();
@ -163,4 +163,8 @@ public class PageSystem {
public boolean canReadPrevPage() { public boolean canReadPrevPage() {
return currentPage >= 1; return currentPage >= 1;
} }
public interface PageSystemInterface {
void onPageChanged(int page);
}
} }

View File

@ -17,28 +17,27 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package sharedcode.turboeditor.util; package sharedcode.turboeditor.texteditor;
import android.content.Context; import android.content.Context;
import android.os.Handler; import android.os.Handler;
import android.view.View; import android.view.View;
import com.faizmalkani.floatingactionbutton.FloatingActionButton; import com.faizmalkani.floatingactionbutton.FloatingActionButton;
import sharedcode.turboeditor.R; import sharedcode.turboeditor.R;
public class PageSystemButtons { public class PageSystemButtons {
private static final int TIME_TO_SHOW_FABS = 2000; private static final int TIME_TO_SHOW_FABS = 2000;
final Handler handler = new Handler();
public interface PageButtonsInterface { final Runnable runnable = new Runnable() {
public void nextPageClicked(); @Override
public void prevPageClicked(); public void run() {
public void pageSystemButtonLongClicked(); PageSystemButtons.this.next.setVisibility(View.GONE);
PageSystemButtons.this.prev.setVisibility(View.GONE);
public boolean canReadNextPage(); }
public boolean canReadPrevPage(); };
}
FloatingActionButton prev, next; FloatingActionButton prev, next;
PageButtonsInterface pageButtonsInterface; PageButtonsInterface pageButtonsInterface;
@ -53,10 +52,10 @@ public class PageSystemButtons {
this.prev.setColor(context.getResources().getColor(R.color.fab_light)); this.prev.setColor(context.getResources().getColor(R.color.fab_light));
this.prev.setDrawable(context.getResources().getDrawable(R.drawable.ic_keyboard_arrow_left)); this.prev.setDrawable(context.getResources().getDrawable(R.drawable.ic_keyboard_arrow_left));
if(pageButtonsInterface.canReadNextPage()) if (pageButtonsInterface.canReadNextPage())
next.setVisibility(View.VISIBLE); next.setVisibility(View.VISIBLE);
if(pageButtonsInterface.canReadPrevPage()) if (pageButtonsInterface.canReadPrevPage())
prev.setVisibility(View.VISIBLE); prev.setVisibility(View.VISIBLE);
this.next.setOnClickListener(new View.OnClickListener() { this.next.setOnClickListener(new View.OnClickListener() {
@ -90,23 +89,14 @@ public class PageSystemButtons {
}); });
} }
final Handler handler = new Handler();
final Runnable runnable = new Runnable() {
@Override
public void run() {
PageSystemButtons.this.next.setVisibility(View.GONE);
PageSystemButtons.this.prev.setVisibility(View.GONE);
}
};
public void updateVisibility(boolean autoHide) { public void updateVisibility(boolean autoHide) {
if(pageButtonsInterface.canReadNextPage()) if (pageButtonsInterface.canReadNextPage())
PageSystemButtons.this.next.setVisibility(View.VISIBLE); PageSystemButtons.this.next.setVisibility(View.VISIBLE);
else else
PageSystemButtons.this.next.setVisibility(View.GONE); PageSystemButtons.this.next.setVisibility(View.GONE);
if(pageButtonsInterface.canReadPrevPage()) if (pageButtonsInterface.canReadPrevPage())
PageSystemButtons.this.prev.setVisibility(View.VISIBLE); PageSystemButtons.this.prev.setVisibility(View.VISIBLE);
else else
PageSystemButtons.this.prev.setVisibility(View.GONE); PageSystemButtons.this.prev.setVisibility(View.GONE);
@ -121,7 +111,7 @@ public class PageSystemButtons {
else else
prev.hideFab();*/ prev.hideFab();*/
if(autoHide) { if (autoHide) {
handler.removeCallbacks(runnable); handler.removeCallbacks(runnable);
handler.postDelayed(runnable, TIME_TO_SHOW_FABS); handler.postDelayed(runnable, TIME_TO_SHOW_FABS);
} else { } else {
@ -129,4 +119,16 @@ public class PageSystemButtons {
} }
} }
public interface PageButtonsInterface {
public void nextPageClicked();
public void prevPageClicked();
public void pageSystemButtonLongClicked();
public boolean canReadNextPage();
public boolean canReadPrevPage();
}
} }

View File

@ -17,7 +17,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package sharedcode.turboeditor.util; package sharedcode.turboeditor.texteditor;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -69,9 +69,9 @@ public class Patterns {
"\\b(int|float|long|complex|str|unicode|list|tuple|bytearray|buffer|xrange|set|frozenset|dict|bool)|(True|False|None|self|NotImplemented|Ellipsis|__debug__|__file__)|(and|del|from|not|while|as|elif|global|or|with|assert|else|if|pass|yield|break|except|import|print|class|exec|in|raise|continue|finally|is|return|def|for|lambda|try)|(ArithmeticError|AssertionError|AttributeError|BaseException|DeprecationWarning|EnvironmentError|EOFError|Exception|FloatingPointError|FutureWarning|GeneratorExit|IOError|ImportError|ImportWarning|IndexError|KeyError|KeyboardInterrupt|LookupError|MemoryError|NameError|NotImplementedError|OSError|OverflowError|PendingDeprecationWarning|ReferenceError|RuntimeError|RuntimeWarning|StandardError|StopIteration|SyntaxError|SyntaxWarning|SystemError|SystemExit|TypeError|UnboundLocalError|UserWarning|UnicodeError|UnicodeWarning|UnicodeEncodeError|UnicodeDecodeError|UnicodeTranslateError|ValueError|Warning|WindowsError|ZeroDivisionError)\\b", Pattern.CASE_INSENSITIVE); "\\b(int|float|long|complex|str|unicode|list|tuple|bytearray|buffer|xrange|set|frozenset|dict|bool)|(True|False|None|self|NotImplemented|Ellipsis|__debug__|__file__)|(and|del|from|not|while|as|elif|global|or|with|assert|else|if|pass|yield|break|except|import|print|class|exec|in|raise|continue|finally|is|return|def|for|lambda|try)|(ArithmeticError|AssertionError|AttributeError|BaseException|DeprecationWarning|EnvironmentError|EOFError|Exception|FloatingPointError|FutureWarning|GeneratorExit|IOError|ImportError|ImportWarning|IndexError|KeyError|KeyboardInterrupt|LookupError|MemoryError|NameError|NotImplementedError|OSError|OverflowError|PendingDeprecationWarning|ReferenceError|RuntimeError|RuntimeWarning|StandardError|StopIteration|SyntaxError|SyntaxWarning|SystemError|SystemExit|TypeError|UnboundLocalError|UserWarning|UnicodeError|UnicodeWarning|UnicodeEncodeError|UnicodeDecodeError|UnicodeTranslateError|ValueError|Warning|WindowsError|ZeroDivisionError)\\b", Pattern.CASE_INSENSITIVE);
public static final Pattern LUA_KEYWORDS = Pattern.compile( public static final Pattern LUA_KEYWORDS = Pattern.compile(
"@[A-Za-z0-9_\\.]*|\\b(local|global|boolean|number|userdata)\\b|\\b(true|false|nil)\\b|\\b(return|then|while|and|break|do|else|elseif|end|for|function|if|in|not|or|repeat|until|thread|table)\\b" + "@[A-Za-z0-9_\\.]*|\\b(local|global|boolean|number|userdata)\\b|\\b(true|false|nil)\\b|\\b(return|then|while|and|break|do|else|elseif|end|for|function|if|in|not|or|repeat|until|thread|table)\\b" +
"|(?i)\\b(editsetText|editText|inkey|touch|system.exit|system.expCall|system.getAppPath|system.getCardMnt|system.getSec|system.impCallActionSend|system.impCallActionView|system.setrun|system.setScreen|system.version|El_Psy_Congroo|canvas.drawCircle|canvas.drawCls|canvas.drawLine|canvas.drawRect|canvas.getBmpSize|canvas.getColor|canvas.getg|canvas.getviewSize|canvas.loadBmp|canvas.putCircle|canvas.putCls|canvas.putflush|canvas.putg|canvas.putLine|canvas.putRect|canvas.putrotg|canvas.putWork|canvas.saveBmp|canvas.setMainBmp|canvas.setWorkBmp|canvas.workCls|canvas.workflush|color|canvas.drawText|canvas.drawTextBox|canvas.drawTextCenter|canvas.drawTextRotate|canvas.putText|canvas.putTextBox|canvas.putTextRotate|http.addHeader|http.addParam|http.clrHeader|http.clrParam|http.get|http.post|http.setContentType|http.setPostFile|http.status|dialog|item.add|item.check|item.clear|item.list|item.radio|toast|sensor.getAccel|sensor.setdevAccel|sensor.setdevMagnet|sensor.setdevOrient|sensor.getGdirection|sensor.getMagnet|sensor.getOrient|sound.beep|sound.isPlay|sound.pause|sound.restart|sound.setSoundFile|sound.start|sound.stop|zip.addFile|zip.exec|zip.status|sock.close|sock.connectOpen|sock.getAddress|sock.listenOpen|sock.recv|sock.send|sprite.clear|sprite.define|sprite.init|sprite.move|sprite.put)\\b" + "|(?i)\\b(editsetText|editText|inkey|touch|system.exit|system.expCall|system.getAppPath|system.getCardMnt|system.getSec|system.impCallActionSend|system.impCallActionView|system.setrun|system.setScreen|system.version|El_Psy_Congroo|canvas.drawCircle|canvas.drawCls|canvas.drawLine|canvas.drawRect|canvas.getBmpSize|canvas.getColor|canvas.getg|canvas.getviewSize|canvas.loadBmp|canvas.putCircle|canvas.putCls|canvas.putflush|canvas.putg|canvas.putLine|canvas.putRect|canvas.putrotg|canvas.putWork|canvas.saveBmp|canvas.setMainBmp|canvas.setWorkBmp|canvas.workCls|canvas.workflush|color|canvas.drawText|canvas.drawTextBox|canvas.drawTextCenter|canvas.drawTextRotate|canvas.putText|canvas.putTextBox|canvas.putTextRotate|http.addHeader|http.addParam|http.clrHeader|http.clrParam|http.get|http.post|http.setContentType|http.setPostFile|http.status|dialog|item.add|item.check|item.clear|item.list|item.radio|toast|sensor.getAccel|sensor.setdevAccel|sensor.setdevMagnet|sensor.setdevOrient|sensor.getGdirection|sensor.getMagnet|sensor.getOrient|sound.beep|sound.isPlay|sound.pause|sound.restart|sound.setSoundFile|sound.start|sound.stop|zip.addFile|zip.exec|zip.status|sock.close|sock.connectOpen|sock.getAddress|sock.listenOpen|sock.recv|sock.send|sprite.clear|sprite.define|sprite.init|sprite.move|sprite.put)\\b" +
"|(?i)\\b(assert|collectgarbage|coroutine.create|coroutine.resume|coroutine.running|coroutine.status|coroutine.wrap|coroutine.yield|debug.debug|debug.getfenv|debug.gethook|debug.getinfo|debug.getlocal|debug.getmetatable|debug.getregistry|debug.getupvalue|debug.setfenv|debug.sethook|debug.setlocal|debug.setmetatable|debug.setupvalue|debug.traceback|dofile|error|file:close|file:flush|file:lines|file:read|file:seek|file:setvbuf|file:write|getfenv|getmetatable|io.close|io.flush|io.input|io.lines|io.open|io.output|io.popen|io.read|io.tmpfile|io.type|io.write|ipairs|load|loadfile|loadstring|math.abs|math.acos|math.asin|math.atan2|math.atan|math.ceil|math.cosh|math.cos|math.deg|math.exp|math.floor|math.fmod|math.frexp|math.ldexp|math.log10|math.log|math.max|math.min|math.modf|math.pow|math.rad|math.random|math.randomseed|math.sinh|math.sin|math.sqrt|math.tanh|math.tan|module|next|os.clock|os.date|os.difftime|os.execute|os.exit|os.getenv|os.remove|os.rename|os.setlocale|os.time|os.tmpname|package.cpath|package.loaded|package.loadlib|package.path|package.preload|package.seeal|pairs|pcall|print|rawequal|rawget|rawset|require|select|setfenv|setmetatable|string.byte|string.char|string.dump|string.find|string.format|string.gmatch|string.gsub|string.len|string.lower|string.match|string.rep|string.reverse|string.sub|string.upper|table.concat|table.insert|table.maxn|table.remove|table.sort|tonumber|tostring|type|unpack|xpcall)\\b" "|(?i)\\b(assert|collectgarbage|coroutine.create|coroutine.resume|coroutine.running|coroutine.status|coroutine.wrap|coroutine.yield|debug.debug|debug.getfenv|debug.gethook|debug.getinfo|debug.getlocal|debug.getmetatable|debug.getregistry|debug.getupvalue|debug.setfenv|debug.sethook|debug.setlocal|debug.setmetatable|debug.setupvalue|debug.traceback|dofile|error|file:close|file:flush|file:lines|file:read|file:seek|file:setvbuf|file:write|getfenv|getmetatable|io.close|io.flush|io.input|io.lines|io.open|io.output|io.popen|io.read|io.tmpfile|io.type|io.write|ipairs|load|loadfile|loadstring|math.abs|math.acos|math.asin|math.atan2|math.atan|math.ceil|math.cosh|math.cos|math.deg|math.exp|math.floor|math.fmod|math.frexp|math.ldexp|math.log10|math.log|math.max|math.min|math.modf|math.pow|math.rad|math.random|math.randomseed|math.sinh|math.sin|math.sqrt|math.tanh|math.tan|module|next|os.clock|os.date|os.difftime|os.execute|os.exit|os.getenv|os.remove|os.rename|os.setlocale|os.time|os.tmpname|package.cpath|package.loaded|package.loadlib|package.path|package.preload|package.seeal|pairs|pcall|print|rawequal|rawget|rawset|require|select|setfenv|setmetatable|string.byte|string.char|string.dump|string.find|string.format|string.gmatch|string.gsub|string.len|string.lower|string.match|string.rep|string.reverse|string.sub|string.upper|table.concat|table.insert|table.maxn|table.remove|table.sort|tonumber|tostring|type|unpack|xpcall)\\b"
); );
public static final Pattern PHP_VARIABLES = Pattern.compile("\\$\\s*(\\w+)"); public static final Pattern PHP_VARIABLES = Pattern.compile("\\$\\s*(\\w+)");

View File

@ -17,11 +17,12 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package sharedcode.turboeditor.util; package sharedcode.turboeditor.texteditor;
import java.util.LinkedList; import java.util.LinkedList;
public class SearchResult { public class SearchResult {
// list of index
public LinkedList<Integer> foundIndex; public LinkedList<Integer> foundIndex;
public int textLength; public int textLength;
public boolean isReplace; public boolean isReplace;
@ -38,7 +39,7 @@ public class SearchResult {
public void doneReplace() { public void doneReplace() {
foundIndex.remove(index); foundIndex.remove(index);
int i; int i;
for(i = index; i < foundIndex.size(); i++) { for (i = index; i < foundIndex.size(); i++) {
foundIndex.set(i, foundIndex.get(i) + textToReplace.length() - textLength); foundIndex.set(i, foundIndex.get(i) + textToReplace.length() - textLength);
} }
index--; // an element was removed so we decrease the index index--; // an element was removed so we decrease the index
@ -47,4 +48,16 @@ public class SearchResult {
public int numberOfResults() { public int numberOfResults() {
return foundIndex.size(); return foundIndex.size();
} }
public boolean hasNext() {
return index < foundIndex.size() - 1;
}
public boolean hasPrevious() {
return index > 0;
}
public boolean canReplaceSomething() {
return isReplace && foundIndex.size() > 0;
}
} }

View File

@ -0,0 +1,208 @@
/*
* Copyright (C) 2014 Vlad Mihalachi
*
* This file is part of Turbo Editor.
*
* Turbo Editor is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Turbo Editor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package sharedcode.turboeditor.util;
import android.annotation.SuppressLint;
import android.content.ContentUris;
import android.content.Context;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class AccessStorageApi {
public static Bitmap loadPrescaledBitmap(String filename) throws IOException {
// Facebook image size
final int IMAGE_MAX_SIZE = 630;
File file = null;
FileInputStream fis;
BitmapFactory.Options opts;
int resizeScale;
Bitmap bmp;
file = new File(filename);
// This bit determines only the width/height of the bitmap without loading the contents
opts = new BitmapFactory.Options();
opts.inJustDecodeBounds = true;
fis = new FileInputStream(file);
BitmapFactory.decodeStream(fis, null, opts);
fis.close();
// Find the correct scale value. It should be a power of 2
resizeScale = 1;
if (opts.outHeight > IMAGE_MAX_SIZE || opts.outWidth > IMAGE_MAX_SIZE) {
resizeScale = (int) Math.pow(2, (int) Math.round(Math.log(IMAGE_MAX_SIZE / (double) Math.max(opts.outHeight, opts.outWidth)) / Math.log(0.5)));
}
// Load pre-scaled bitmap
opts = new BitmapFactory.Options();
opts.inSampleSize = resizeScale;
fis = new FileInputStream(file);
bmp = BitmapFactory.decodeStream(fis, null, opts);
fis.close();
return bmp;
}
/**
* Get a file path from a Uri. This will get the the path for Storage Access
* Framework Documents, as well as the _data field for the MediaStore and
* other file-based ContentProviders.
*
* @param context The context.
* @param uri The Uri to query.
* @author paulburke
*/
@SuppressLint("NewApi")
public static String getPath(final Context context, final Uri uri) {
final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
// DocumentProvider
if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
// ExternalStorageProvider
if (isExternalStorageDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
if ("primary".equalsIgnoreCase(type)) {
return Environment.getExternalStorageDirectory() + "/" + split[1];
}
// TODO handle non-primary volumes
}
// DownloadsProvider
else if (isDownloadsDocument(uri)) {
final String id = DocumentsContract.getDocumentId(uri);
final Uri contentUri = ContentUris.withAppendedId(
Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
return getDataColumn(context, contentUri, null, null);
}
// MediaProvider
else if (isMediaDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
Uri contentUri = null;
if ("image".equals(type)) {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
} else if ("video".equals(type)) {
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
} else if ("audio".equals(type)) {
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
}
final String selection = "_id=?";
final String[] selectionArgs = new String[]{
split[1]
};
return getDataColumn(context, contentUri, selection, selectionArgs);
}
}
// MediaStore (and general)
else if ("content".equalsIgnoreCase(uri.getScheme())) {
return getDataColumn(context, uri, null, null);
}
// File
else if ("file".equalsIgnoreCase(uri.getScheme())) {
return uri.getPath();
}
return null;
}
/**
* Get the value of the data column for this Uri. This is useful for
* MediaStore Uris, and other file-based ContentProviders.
*
* @param context The context.
* @param uri The Uri to query.
* @param selection (Optional) Filter used in the query.
* @param selectionArgs (Optional) Selection arguments used in the query.
* @return The value of the _data column, which is typically a file path.
*/
public static String getDataColumn(Context context, Uri uri, String selection,
String[] selectionArgs) {
Cursor cursor = null;
final String column = "_data";
final String[] projection = {
column
};
try {
cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
null);
if (cursor != null && cursor.moveToFirst()) {
final int column_index = cursor.getColumnIndexOrThrow(column);
return cursor.getString(column_index);
}
} finally {
if (cursor != null)
cursor.close();
}
return null;
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is ExternalStorageProvider.
*/
public static boolean isExternalStorageDocument(Uri uri) {
return "com.android.externalstorage.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is DownloadsProvider.
*/
public static boolean isDownloadsDocument(Uri uri) {
return "com.android.providers.downloads.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is MediaProvider.
*/
public static boolean isMediaDocument(Uri uri) {
return "com.android.providers.media.documents".equals(uri.getAuthority());
}
}

View File

@ -0,0 +1,52 @@
/*
* Copyright (C) 2014 Vlad Mihalachi
*
* This file is part of Turbo Editor.
*
* Turbo Editor is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Turbo Editor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package sharedcode.turboeditor.util;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.ActivityOptionsCompat;
import android.view.View;
public class AnimationUtils {
public static Bundle getScaleBundle(View view) {
return ActivityOptionsCompat.makeScaleUpAnimation(
view, 0, 0, view.getWidth(), view.getHeight()).toBundle();
}
public static void startActivityWithScale(@NonNull Activity startActivity, @NonNull Intent subActivity, @NonNull boolean forResult, @Nullable int code, @NonNull View view) {
if(forResult){
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN)
startActivity.startActivityForResult(subActivity, code, AnimationUtils.getScaleBundle
(view));
else
startActivity.startActivityForResult(subActivity, code);
}
else {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN)
startActivity.startActivity(subActivity, AnimationUtils.getScaleBundle
(view));
else
startActivity.startActivity(subActivity);
}
}
}

View File

@ -0,0 +1,56 @@
/*
* Copyright (C) 2014 Vlad Mihalachi
*
* This file is part of Turbo Editor.
*
* Turbo Editor is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Turbo Editor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package sharedcode.turboeditor.util;
import sharedcode.turboeditor.BuildConfig;
/**
* Created by Artem on 30.12.13.
*/
public final class Build {
public static final boolean DEBUG = BuildConfig.DEBUG;
public static final String SUPPORT_EMAIL = "maskyngames@gmail.com";
public static final String GOOGLE_PLAY_PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvpZca3gZSeRTHPMgxM+A1nTXuRL+9NTOWA1VJFs6ytppcO96i9EWQhmtXVUOKRQIgbXvkepxF7ut+JEjrbniQubZvmyBs9DxK7xUN4Zc3ZboQDQdfg2HJmZXzn8+joOfjXdS9WzsW7aaWKIQ8QXgOB1RUm9hdMdAlgKw+cEyp27WOoMK5m2H/i7C0MIO9tEQs3Hn9UTjayzzfy3MY+KDaX3T6oKievegbpyqyt8y4cpVusJC+uQFLa4bHKPtA3MaPUG6kU9tRV/DHrvFV6dOaPuTYCnYJELlGNfeqRUF0Nvb3Sv0U+BUoXgevjrlLdLz1bqgPDibLzaQmmofNXOnVQIDAQAB";
public static final int MAX_FILE_SIZE = 20_000;
public static final boolean FOR_AMAZON = false;
public static class Links {
public static final String GITHUB = "http://github.com/vmihalachi/TurboEditor";
public static final String XDA = "http://forum.xda-developers.com/android/apps-games/app-turbo-editor-text-editor-t2832016";
public static final String TRANSLATE = "http://crowdin.net/project/turbo-client";
public static final String DONATE = "https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=26VWS2TSAMUJA";
public static final String GOOGLE_PLUS_COMMUNITY = "http://plus.google.com/u/0/communities/111974095419108178946";
public static final String AMAZON_STORE = "amzn://apps/android?p=com.maskyn.fileeditor";
public static final String PLAY_STORE = "market://search?q=pub:Maskyn";
}
}

View File

@ -0,0 +1,71 @@
/*
* Copyright (C) 2014 Vlad Mihalachi
*
* This file is part of Turbo Editor.
*
* Turbo Editor is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Turbo Editor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package sharedcode.turboeditor.util;
import android.os.Build;
/**
* Contains params of current device. This is nice because we can override
* some here to test compatibility with old API.
*
* @author Artem Chepurnoy
*/
public class Device {
/**
* @return {@code true} if device is device supports given API version,
* {@code false} otherwise.
*/
public static boolean hasTargetApi(int api) {
return Build.VERSION.SDK_INT >= api;
}
/**
* @return {@code true} if device is running
* {@link android.os.Build.VERSION_CODES#L Lemon Cake} or higher, {@code false} otherwise.
*/
public static boolean hasLemonCakeApi() {
return Build.VERSION.SDK_INT >= 20; // Build.VERSION_CODES.L;
}
/**
* @return {@code true} if device is running
* {@link android.os.Build.VERSION_CODES#KITKAT KitKat} or higher, {@code false} otherwise.
*/
public static boolean hasKitKatApi() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
}
/**
* @return {@code true} if device is running
* {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2 Jelly Bean 4.3} or higher, {@code false} otherwise.
*/
public static boolean hasJellyBeanMR2Api() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2;
}
/**
* @return {@code true} if device is running
* {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1 Jelly Bean 4.2} or higher, {@code false} otherwise.
*/
public static boolean hasJellyBeanMR1Api() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1;
}
}

View File

@ -20,6 +20,7 @@
package sharedcode.turboeditor.util; package sharedcode.turboeditor.util;
import java.io.File; import java.io.File;
import java.util.List;
public class EventBusEvents { public class EventBusEvents {
public static class CannotOpenAFile { public static class CannotOpenAFile {
@ -65,21 +66,28 @@ public class EventBusEvents {
public static class APreferenceValueWasChanged { public static class APreferenceValueWasChanged {
private Type type; private Type type;
private List<Type> types;
public APreferenceValueWasChanged(Type type) { public APreferenceValueWasChanged(Type type) {
this.type = type; this.type = type;
} }
public Type getType() { public APreferenceValueWasChanged(List<Type> types) {
return type; this.types = types;
} }
public void setType(Type type) { public boolean hasType(Type value) {
this.type = type;
if (type != null) {
return value == type;
} else {
return types.contains(value);
}
} }
public enum Type { public enum Type {
FONT_SIZE, ENCODING, SYNTAX, WRAP_CONTENT, MONOSPACE, LINE_NUMERS, THEME_CHANGE, TEXT_SUGGESTIONS, AUTO_SAVE, READ_ONLY FONT_SIZE, ENCODING, SYNTAX, WRAP_CONTENT, MONOSPACE, LINE_NUMERS, THEME_CHANGE, TEXT_SUGGESTIONS, READ_ONLY
} }
} }

View File

@ -1,142 +0,0 @@
/*
* Copyright (C) 2014 Vlad Mihalachi
*
* This file is part of Turbo Editor.
*
* Turbo Editor is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Turbo Editor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* 920 Text Editor is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 920 Text Editor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 920 Text Editor. If not, see <http://www.gnu.org/licenses/>.
*/
package sharedcode.turboeditor.util;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
public class LinuxShell {
public static String getCmdPath(String path) {
return path.replace(" ", "\\ ").replace("'", "\\'");
}
/**
* 返回执行完<E8A18C><E5AE8C>?的结果
*
* @param cmd 命令内容
* @return
*/
public static BufferedReader execute(String cmd) {
BufferedReader reader = null; //errReader = null;
try {
Process process = Runtime.getRuntime().exec("su");
DataOutputStream os = new DataOutputStream(process.getOutputStream());
//os.writeBytes("mount -oremount,rw /dev/block/mtdblock3 /system\n");
//os.writeBytes("busybox cp /data/data/com.koushikdutta.superuser/su /system/bin/su\n");
os.writeBytes(cmd + "\n");
os.writeBytes("exit\n");
reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String err = (new BufferedReader(new InputStreamReader(process.getErrorStream()))).readLine();
os.flush();
if (process.waitFor() != 0 || (!"".equals(err) && null != err)) {
return null;
}
return reader;
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/*public static boolean isRoot()
{
boolean retval = false;
Process suProcess;
try
{
suProcess = Runtime.getRuntime().exec("su");
DataOutputStream os =
new DataOutputStream(suProcess.getOutputStream());
DataInputStream osRes =
new DataInputStream(suProcess.getInputStream());
if (null != os && null != osRes)
{
// Getting the id of the current user to check if this is root
os.writeBytes("id\n");
os.flush();
String currUid = osRes.readLine();
boolean exitSu = false;
if (null == currUid)
{
retval = false;
exitSu = false;
Log.e("ROOT", "Can't get root access or denied by user");
}
else if (true == currUid.contains("uid=0"))
{
retval = true;
exitSu = true;
}
else
{
retval = false;
exitSu = true;
Log.e("ROOT", "Root access rejected: " + currUid);
}
if (exitSu)
{
os.writeBytes("exit\n");
os.flush();
}
}
}
catch (Exception e)
{
// Can't get root !
// Probably broken pipe exception on trying to write to output
// stream after su failed, meaning that the device is not rooted
retval = false;
Log.d("ROOT", "Root access rejected [" +
e.getClass().getName() + "] : " + e.getMessage());
}
return retval;
}*/
}

View File

@ -21,26 +21,32 @@ package sharedcode.turboeditor.util;
public class MimeTypes { public class MimeTypes {
public static final String[] MIME_TEXT = { public static final String[] MIME_TEXT = {
"ajx", "am", "asa", "asc", "asp", "aspx", "awk", "bat", "c", "cdf", "cf", "cfg", "cfm", "cgi", "cnf", "conf", "cpp", "css", "csv", "ctl", "dat", "dhtml", "diz", "file", "forward", "grp", "h", "hpp", "hqx", "hta", "htaccess", "htc", "htm", "html", "htpasswd", "htt", "htx", "in", "inc", "info", "ini", "ink", "java", "js", "jsp", "key", "log", "logfile", "m3u", "m4", "m4a", "mak", "map", "model", "msg", "nfo", "nsi", "info", "old", "pas", "patch", "perl", "php", "php2", "php3", "php4", "php5", "php6", "phtml", "pix", "pl", "pm", "po", "pwd", "py", "qmail", "rb", "rbl", "rbw", "readme", "reg", "rss", "rtf", "ruby", "session", "setup", "sh", "shtm", "shtml", "sql", "ssh", "stm", "style", "svg", "tcl", "text", "threads", "tmpl", "tpl", "txt", "ubb", "vbs", "xhtml", "xml", "xrc", "xsl" "ajx", "am", "asa", "asc", "asp", "aspx", "awk", "bat", "c", "cdf", "cf", "cfg", "cfm", "cgi", "cnf", "conf",
"cc", "cpp", "css", "csv", "ctl", "dat", "dhtml", "diz", "file", "forward", "grp", "h", "hh", "hpp", "hqx", "hta", "htaccess",
"htc", "htm", "html", "htpasswd", "htt", "htx", "in", "inc", "info", "ini", "ink", "java", "js", "jsp", "key", "latex", "log",
"logfile", "m3u", "m4", "m4a", "mak", "map", "md", "markdown", "model", "msg", "nfo", "nsi", "info", "old", "pas", "patch", "perl",
"php", "php2", "php3", "php4", "php5", "php6", "phtml", "pix", "pl", "pm", "po", "pwd", "py", "qmail", "rb", "rbl", "rbw",
"readme", "reg", "rss", "rtf", "ruby", "session", "setup", "sh", "shtm", "shtml", "sql", "ssh", "stm", "style", "svg", "tcl",
"tex", "text", "threads", "tmpl", "tpl", "txt", "ubb", "vbs", "xhtml", "xml", "xrc", "xsl"
}; };
public static final String[] MIME_CODE = { public static final String[] MIME_CODE = {
"php", "js", "cs", "java", "py", "aspx", "cshtml", "vbhtml", "go", "c", "h", "cpp", "pl", "pm", "t", "pod", "cs", "php", "js", "java", "py", "rb", "aspx", "cshtml", "vbhtml", "go", "c", "h", "cc", "cpp", "hh", "hpp", "pl", "pm", "t", "pod",
"m", "f", "for", "f90", "f95", "asp", "json", "wiki", "lua" "m", "f", "for", "f90", "f95", "asp", "json", "wiki", "lua", "r"
}; };
public static final String[] MIME_HTML = { public static final String[] MIME_HTML = {
"htm" "htm", "html", "xhtml"
}; };
public static final String[] MIME_PICTURE = { public static final String[] MIME_PICTURE = {
"png", "jpeg", "jpg", "ico", "gif", "bmp", "tiff" "bmp", "eps", "png", "jpeg", "jpg", "ico", "gif", "tiff", "webp"
}; };
public static final String[] MIME_MUSIC = { public static final String[] MIME_MUSIC = {
"mp3", "avi", "flac", "mpga" "aac", "flac", "mp3", "mpga", "oga", "ogg", "opus", "webma", "wav"
}; };
public static final String[] MIME_VIDEO = { public static final String[] MIME_VIDEO = {
"mp4", "mkv", "wmw" "avi", "mp4", "mkv", "wmw", "ogv", "webm"
}; };
public static final String[] MIME_ARCHIVE = { public static final String[] MIME_ARCHIVE = {
"zip", "tar", "gz", "bz2", "rar", "7z" "7z", "arj", "bz2", "gz", "rar", "tar", "tgz", "zip", "xz"
}; };
public static final String[] MIME_SQL = { public static final String[] MIME_SQL = {
"sql", "mdf", "ndf", "ldf" "sql", "mdf", "ndf", "ldf"

View File

@ -21,16 +21,24 @@ package sharedcode.turboeditor.util;
import android.content.Context; import android.content.Context;
import sharedcode.turboeditor.preferences.PreferenceHelper;
public class ProCheckUtils { public class ProCheckUtils {
public static boolean isPro(Context context) { public static boolean isPro(Context context, boolean includeDonations) {
String packageName = context.getPackageName(); String packageName = context.getPackageName();
if(Constants.FOR_AMAZON) if (Build.FOR_AMAZON)
return true; return true;
else if(packageName.equals("com.maskyn.fileeditorpro")) else if (packageName.equals("com.maskyn.fileeditorpro"))
return true;
else if (includeDonations && PreferenceHelper.hasDonated(context))
return true; return true;
else else
return false; return false;
} }
public static boolean isPro(Context context) {
return isPro(context, true);
}
} }

View File

@ -24,7 +24,16 @@ import android.app.Activity;
import sharedcode.turboeditor.R; import sharedcode.turboeditor.R;
import sharedcode.turboeditor.preferences.PreferenceHelper; import sharedcode.turboeditor.preferences.PreferenceHelper;
public class ThemeHelper { public class ThemeUtils {
public static void setTheme(Activity activity){
boolean light = PreferenceHelper.getLightTheme(activity);
if (light) {
activity.setTheme(R.style.AppThemeBaseLight);
} else {
activity.setTheme(R.style.AppThemeBaseDark);
}
}
public static void setWindowsBackground(Activity activity) { public static void setWindowsBackground(Activity activity) {
boolean whiteTheme = PreferenceHelper.getLightTheme(activity); boolean whiteTheme = PreferenceHelper.getLightTheme(activity);

View File

@ -0,0 +1,71 @@
/*
* Copyright (C) 2014 Vlad Mihalachi
*
* This file is part of Turbo Editor.
*
* Turbo Editor is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Turbo Editor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package sharedcode.turboeditor.util;
import android.content.Context;
import android.support.annotation.NonNull;
import android.widget.Toast;
/**
* Helper class with utils related to toasts (no bacon.)
*
* @author Artem Chepurnoy
*/
public class ToastUtils {
/**
* Shows toast message with given message shortly.
*
* @param text message to show
* @see #showLong(android.content.Context, CharSequence)
*/
@NonNull
public static Toast showShort(@NonNull Context context, @NonNull CharSequence text) {
return show(context, text, Toast.LENGTH_SHORT);
}
@NonNull
public static Toast showShort(@NonNull Context context, int stringRes) {
return showShort(context, context.getString(stringRes));
}
/**
* Shows toast message with given message for a long time.
*
* @param text message to show
* @see #showShort(android.content.Context, CharSequence)
*/
@NonNull
public static Toast showLong(@NonNull Context context, @NonNull CharSequence text) {
return show(context, text, Toast.LENGTH_LONG);
}
@NonNull
public static Toast showLong(@NonNull Context context, int stringRes) {
return showLong(context, context.getString(stringRes));
}
@NonNull
private static Toast show(@NonNull Context context, CharSequence text, int duration) {
Toast toast = Toast.makeText(context, text, duration);
toast.show();
return toast;
}
}

View File

@ -0,0 +1,305 @@
/*
* Copyright (C) 2014 Vlad Mihalachi
*
* This file is part of Turbo Editor.
*
* Turbo Editor is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Turbo Editor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package sharedcode.turboeditor.util;
import android.graphics.Matrix;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.TextView;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* Created by Artem on 21.01.14.
*/
public class ViewUtils {
private static final String TAG = "ViewUtils";
@NonNull
private static final MotionEventHandler MOTION_EVENT_HANDLER = Device.hasKitKatApi()
? new MotionEventHandlerReflection()
: new MotionEventHandlerCompat();
public static boolean isTouchPointInView(@NonNull View view, float x, float y) {
final int[] coordinates = new int[3];
view.getLocationInWindow(coordinates);
int left = coordinates[0];
int top = coordinates[1];
return x >= left && x <= left + view.getWidth() &&
y >= top && y <= top + view.getHeight();
}
public static int getLeft(@NonNull View view) {
final int[] coordinates = new int[3];
view.getLocationInWindow(coordinates);
return coordinates[0];
}
public static int getTop(@NonNull View view) {
final int[] coordinates = new int[3];
view.getLocationInWindow(coordinates);
return coordinates[1];
}
public static int getBottom(@NonNull View view) {
return getTop(view) + view.getHeight();
}
public static int indexOf(@NonNull ViewGroup container, @NonNull View view) {
int length = container.getChildCount();
for (int i = 0; i < length; i++) {
View child = container.getChildAt(i);
assert child != null;
if (child.equals(view)) {
return i;
}
}
return -1;
}
// //////////////////////////////////////////
// //////////// -- VISIBILITY -- ////////////
// //////////////////////////////////////////
public static void setVisible(@NonNull View view, boolean visible) {
setVisible(view, visible, View.GONE);
}
public static void setVisible(@NonNull View view, boolean visible, int invisibleFlag) {
int visibility = view.getVisibility();
int visibilityNew = visible ? View.VISIBLE : invisibleFlag;
if (visibility != visibilityNew) {
view.setVisibility(visibilityNew);
}
}
public static void safelySetText(@NonNull TextView textView, @Nullable CharSequence text) {
final boolean visible = text != null;
if (visible) textView.setText(text);
ViewUtils.setVisible(textView, visible);
}
// //////////////////////////////////////////
// /////////// -- TOUCH EVENTS -- ///////////
// //////////////////////////////////////////
public static boolean pointInView(@NonNull View view, float localX, float localY, float slop) {
return localX >= view.getLeft() - slop
&& localX < view.getRight() + slop
&& localY >= view.getTop() - slop
&& localY < view.getBottom() + slop;
}
/**
* Transforms a motion event from view-local coordinates to on-screen
* coordinates.
*
* @param ev the view-local motion event
* @return false if the transformation could not be applied
*/
public static boolean toGlobalMotionEvent(@NonNull View view, @NonNull MotionEvent ev) {
return MOTION_EVENT_HANDLER.toGlobalMotionEvent(view, ev);
}
/**
* Transforms a motion event from on-screen coordinates to view-local
* coordinates.
*
* @param ev the on-screen motion event
* @return false if the transformation could not be applied
*/
public static boolean toLocalMotionEvent(@NonNull View view, @NonNull MotionEvent ev) {
return MOTION_EVENT_HANDLER.toLocalMotionEvent(view, ev);
}
private static abstract class MotionEventHandler {
/**
* Transforms a motion event from view-local coordinates to on-screen
* coordinates.
*
* @param ev the view-local motion event
* @return false if the transformation could not be applied
*/
abstract boolean toGlobalMotionEvent(@NonNull View view, @NonNull MotionEvent ev);
/**
* Transforms a motion event from on-screen coordinates to view-local
* coordinates.
*
* @param ev the on-screen motion event
* @return false if the transformation could not be applied
*/
abstract boolean toLocalMotionEvent(@NonNull View view, @NonNull MotionEvent ev);
}
private static final class MotionEventHandlerReflection extends MotionEventHandler {
@Override
boolean toGlobalMotionEvent(@NonNull View view, @NonNull MotionEvent ev) {
return toMotionEvent(view, ev, "toGlobalMotionEvent");
}
@Override
boolean toLocalMotionEvent(@NonNull View view, @NonNull MotionEvent ev) {
return toMotionEvent(view, ev, "toLocalMotionEvent");
}
private boolean toMotionEvent(View view, MotionEvent ev, String methodName) {
try {
Method method = View.class.getDeclaredMethod(methodName, MotionEvent.class);
method.setAccessible(true);
return (boolean) method.invoke(view, ev);
} catch (InvocationTargetException
| IllegalAccessException
| NoSuchMethodException
| NoClassDefFoundError e) {
Log.wtf(TAG, "Failed to access motion event transforming!!!");
e.printStackTrace();
}
return false;
}
}
private static final class MotionEventHandlerCompat extends MotionEventHandler {
@Nullable
private static int[] getWindowPosition(@NonNull View view) {
Object info;
try {
Field field = View.class.getDeclaredField("mAttachInfo");
field.setAccessible(true);
info = field.get(view);
} catch (Exception e) {
info = null;
Log.e(TAG, "Failed to get AttachInfo.");
}
if (info == null) {
return null;
}
int[] position = new int[2];
try {
Class clazz = Class.forName("android.view.View$AttachInfo");
Field field = clazz.getDeclaredField("mWindowLeft");
field.setAccessible(true);
position[0] = field.getInt(info);
field = clazz.getDeclaredField("mWindowTop");
field.setAccessible(true);
position[1] = field.getInt(info);
} catch (Exception e) {
Log.e(TAG, "Failed to get window\'s position from AttachInfo.");
return null;
}
return position;
}
/**
* Recursive helper method that applies transformations in post-order.
*
* @param ev the on-screen motion event
*/
private static void transformMotionEventToLocal(@NonNull View view, @NonNull MotionEvent ev) {
final ViewParent parent = view.getParent();
if (parent instanceof View) {
final View vp = (View) parent;
transformMotionEventToLocal(vp, ev);
ev.offsetLocation(vp.getScrollX(), vp.getScrollY());
} // TODO: Use reflections to access ViewRootImpl
// else if (parent instanceof ViewRootImpl) {
// final ViewRootImpl vr = (ViewRootImpl) parent;
// ev.offsetLocation(0, vr.mCurScrollY);
// }
ev.offsetLocation(-view.getLeft(), -view.getTop());
Matrix matrix = view.getMatrix();
if (matrix != null) {
ev.transform(matrix);
}
}
/**
* Recursive helper method that applies transformations in pre-order.
*
* @param ev the on-screen motion event
*/
private static void transformMotionEventToGlobal(@NonNull View view, @NonNull MotionEvent ev) {
Matrix matrix = view.getMatrix();
if (matrix != null) {
ev.transform(matrix);
}
ev.offsetLocation(view.getLeft(), view.getTop());
final ViewParent parent = view.getParent();
if (parent instanceof View) {
final View vp = (View) parent;
ev.offsetLocation(-vp.getScrollX(), -vp.getScrollY());
transformMotionEventToGlobal(vp, ev);
} // TODO: Use reflections to access ViewRootImpl
// else if (parent instanceof ViewRootImpl) {
// final ViewRootImpl vr = (ViewRootImpl) parent;
// ev.offsetLocation(0, -vr.mCurScrollY);
// }
}
@Override
boolean toGlobalMotionEvent(@NonNull View view, @NonNull MotionEvent ev) {
final int[] windowPosition = getWindowPosition(view);
if (windowPosition == null) {
return false;
}
transformMotionEventToGlobal(view, ev);
ev.offsetLocation(windowPosition[0], windowPosition[1]);
return true;
}
@Override
boolean toLocalMotionEvent(@NonNull View view, @NonNull MotionEvent ev) {
final int[] windowPosition = getWindowPosition(view);
if (windowPosition == null) {
return false;
}
ev.offsetLocation(-windowPosition[0], -windowPosition[1]);
transformMotionEventToLocal(view, ev);
return true;
}
}
}

View File

@ -0,0 +1,308 @@
/*
* Copyright (C) 2014 Vlad Mihalachi
*
* This file is part of Turbo Editor.
*
* Turbo Editor is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Turbo Editor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package sharedcode.turboeditor.views;
/**
* Created by Artem on 28.01.14.
*/
import android.app.Activity;
import android.app.AlertDialog;
import android.app.DialogFragment;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import sharedcode.turboeditor.R;
import sharedcode.turboeditor.fragment.AboutDialog;
import sharedcode.turboeditor.iab.DonationFragment;
/**
* Helper class for showing fragment dialogs.
*/
public class DialogHelper {
public static final String TAG_FRAGMENT_ABOUT = "dialog_about";
public static final String TAG_FRAGMENT_HELP = "dialog_help";
public static final String TAG_FRAGMENT_DONATION = "dialog_donate";
public static final String TAG_FRAGMENT_FEEDBACK = "dialog_feedback";
public static void showDonateDialog(Activity activity) {
showDialog(activity, DonationFragment.class, TAG_FRAGMENT_DONATION);
}
public static void showAboutDialog(Activity activity) {
showDialog(activity, AboutDialog.class, TAG_FRAGMENT_ABOUT);
}
private static void showDialog(Activity activity, Class clazz, String tag) {
FragmentManager fm = activity.getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
Fragment prev = fm.findFragmentByTag(tag);
if (prev != null) {
ft.remove(prev);
}
ft.addToBackStack(null);
try {
((DialogFragment) clazz.newInstance()).show(ft, tag);
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
}
/**
* Helper class to implement custom dialog's design.
*/
public static class Builder {
/**
* Layout where content's layout exists in a {@link android.widget.ScrollView}.
* This is nice to display simple layout without scrollable elements such as
* {@link android.widget.ListView} or any similar. Use {@link #LAYOUT_SKELETON}
* for them.
*
* @see #LAYOUT_SKELETON
* @see #createCommonView()
* @see #wrap(int)
*/
public static final int LAYOUT_COMMON = 0;
/**
* The skeleton of dialog's layout. The only thing that is here is the custom
* view you set and the title / icon. Use it to display scrollable elements such as
* {@link android.widget.ListView}.
*
* @see #LAYOUT_COMMON
* @see #createSkeletonView()
* @see #wrap(int)
*/
public static final int LAYOUT_SKELETON = 1;
protected final Context mContext;
private Drawable mIcon;
private CharSequence mTitleText;
private CharSequence mMessageText;
private View mView;
private int mViewRes;
public Builder(Context context) {
mContext = context;
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
return new HashCodeBuilder(201, 17)
.append(mContext)
.append(mIcon)
.append(mTitleText)
.append(mMessageText)
.append(mViewRes)
.append(mView)
.toHashCode();
}
/**
* {@inheritDoc}
*/
@Override
public boolean equals(Object o) {
if (o == null)
return false;
if (o == this)
return true;
if (!(o instanceof Builder))
return false;
Builder builder = (Builder) o;
return new EqualsBuilder()
.append(mContext, builder.mContext)
.append(mIcon, builder.mIcon)
.append(mTitleText, builder.mTitleText)
.append(mMessageText, builder.mMessageText)
.append(mViewRes, builder.mViewRes)
.append(mView, builder.mView)
.isEquals();
}
public Builder setIcon(Drawable icon) {
mIcon = icon;
return this;
}
public Builder setTitle(CharSequence title) {
mTitleText = title;
return this;
}
public Builder setMessage(CharSequence message) {
mMessageText = message;
return this;
}
public Builder setIcon(int iconRes) {
return setIcon(iconRes == 0 ? null : mContext.getResources().getDrawable(iconRes));
}
public Builder setTitle(int titleRes) {
return setTitle(titleRes == 0 ? null : getString(titleRes));
}
public Builder setMessage(int messageRes) {
return setMessage(messageRes == 0 ? null : getString(messageRes));
}
private String getString(int stringRes) {
return mContext.getResources().getString(stringRes);
}
public Builder setView(View view) {
mView = view;
mViewRes = 0;
return this;
}
public Builder setView(int layoutRes) {
mView = null;
mViewRes = layoutRes;
return this;
}
/**
* Builds dialog's view
*
* @throws IllegalArgumentException when type is not one of defined.
* @see #LAYOUT_COMMON
* @see #LAYOUT_SKELETON
*/
public View createView(int type) {
switch (type) {
case LAYOUT_COMMON:
return createCommonView();
case LAYOUT_SKELETON:
return createSkeletonView();
default:
throw new IllegalArgumentException();
}
}
/**
* Builds view that based on simple {@link android.widget.ScrollView} container.
* This is nice to display simple layout without scrollable elements such as
* {@link android.widget.ListView} or any similar. Use {@link #createSkeletonView()}
* for them.
*
* @see #LAYOUT_COMMON
* @see #createView(int)
*/
public View createCommonView() {
// Creating skeleton layout will also try
// to add custom view. Avoid of doing it.
int customViewRes = mViewRes;
View customView = mView;
mViewRes = 0;
mView = null;
LayoutInflater inflater = (LayoutInflater) mContext
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
ViewGroup rootLayout = (ViewGroup) createSkeletonView();
View bodyRootView = inflater.inflate(R.layout.dialog, rootLayout, false);
ViewGroup bodyLayout = (ViewGroup) bodyRootView.findViewById(R.id.content);
TextView messageView = (TextView) bodyLayout.findViewById(R.id.message);
rootLayout.addView(bodyRootView);
// Setup content
bodyLayout.removeView(messageView);
if (!TextUtils.isEmpty(mMessageText)) {
messageView.setMovementMethod(new LinkMovementMethod());
messageView.setText(mMessageText);
bodyLayout.addView(messageView);
}
// Custom view
if (customViewRes != 0) customView = inflater.inflate(customViewRes, bodyLayout, false);
if (customView != null) bodyLayout.addView(customView);
mView = customView;
return rootLayout;
}
/**
* @see #LAYOUT_SKELETON
* @see #createView(int)
*/
public View createSkeletonView() {
LayoutInflater inflater = (LayoutInflater) mContext
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
ViewGroup rootLayout = (ViewGroup) inflater.inflate(R.layout.dialog_skeleton, null);
TextView titleView = (TextView) rootLayout.findViewById(R.id.title);
// Setup title
if (mTitleText != null) {
titleView.setText(mTitleText);
titleView.setCompoundDrawablesWithIntrinsicBounds(mIcon, null, null, null);
} else {
// This also removes an icon.
rootLayout.removeView(titleView);
}
// Custom view
if (mViewRes != 0) mView = inflater.inflate(mViewRes, rootLayout, false);
if (mView != null) rootLayout.addView(mView);
return rootLayout;
}
public AlertDialog.Builder wrap() {
return wrap(LAYOUT_COMMON);
}
/**
* Creates view and {@link android.app.AlertDialog.Builder#setView(android.view.View) sets}
* to new {@link android.app.AlertDialog.Builder}.
*
* @param type type of container layout
* @return AlertDialog.Builder with set custom view
*/
public AlertDialog.Builder wrap(int type) {
return new AlertDialog.Builder(mContext).setView(createView(type));
}
}
}

View File

@ -20,12 +20,16 @@
package sharedcode.turboeditor.views; package sharedcode.turboeditor.views;
import android.content.Context; import android.content.Context;
import android.os.Handler;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.View;
import android.widget.ScrollView; import android.widget.ScrollView;
public class GoodScrollView extends ScrollView { public class GoodScrollView extends ScrollView {
public ScrollInterface scrollInterface; public ScrollInterface scrollInterface;
int lastY;
boolean listenerEnabled = true;
public GoodScrollView(Context context) { public GoodScrollView(Context context) {
super(context); super(context);
@ -39,24 +43,42 @@ public class GoodScrollView extends ScrollView {
super(context, attrs, defStyle); super(context, attrs, defStyle);
} }
public interface ScrollInterface {
public void onScrollChanged(int l, int t, int oldl, int oldt);
}
public void setScrollInterface(ScrollInterface scrollInterface) { public void setScrollInterface(ScrollInterface scrollInterface) {
this.scrollInterface = scrollInterface; this.scrollInterface = scrollInterface;
} }
int lastY;
@Override @Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) { protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt); super.onScrollChanged(l, t, oldl, oldt);
if(scrollInterface == null) return;
if(Math.abs(lastY - t) > 50){ if (scrollInterface == null || !listenerEnabled) return;
lastY = t;
scrollInterface.onScrollChanged(l, t, oldl, oldt); if (Math.abs(lastY - t) > 100) {
} lastY = t;
scrollInterface.onScrollChanged(l, t, oldl, oldt);
}
} }
public boolean hasReachedBottom() {
View firstChild = getChildAt(getChildCount() - 1);
int diff = (firstChild.getBottom() - (getHeight() + getScrollY() + firstChild.getTop()));// Calculate the scrolldiff
return diff <= 0;
}
public void tempDisableListener(int mills) {
listenerEnabled = false;
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
listenerEnabled = true;
}
}, mills);
}
public interface ScrollInterface {
public void onScrollChanged(int l, int t, int oldl, int oldt);
}
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 875 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 596 B

Some files were not shown because too many files have changed in this diff Show More