From 4b16119b53c309fa9c8f16f63523e978535105dd Mon Sep 17 00:00:00 2001 From: Vlad Mihalachi Date: Sat, 18 Oct 2014 15:20:23 +0200 Subject: [PATCH] Version 1.13 --- .idea/codeStyleSettings.xml | 174 ++ app-pro/build.gradle | 13 +- app-pro/src/main/AndroidManifest.xml | 24 +- app/build.gradle | 18 +- app/src/main/AndroidManifest.xml | 24 +- .../com/maskyn/fileeditor/HomeActivity.java | 8 +- build/intermediates/dex-cache/cache.xml | 268 ++- build/intermediates/model_data.bin | Bin 548690 -> 533262 bytes libraries/FloatingActionButton/build.gradle | 5 +- .../intermediates/bundles/debug/classes.jar | Bin 5783 -> 5783 bytes .../intermediates/bundles/release/classes.jar | Bin 5709 -> 5709 bytes .../outputs/aar/FloatingActionButton.aar | Bin 6497 -> 6497 bytes libraries/RootCommands/build.gradle | 4 +- libraries/sharedCode/build.gradle | 20 +- .../vending/billing/IInAppBillingService.aidl | 0 .../activity/BaseHomeActivity.java | 1981 +++++++++++++++-- .../activity/LicensesActivity.java | 9 +- .../turboeditor/activity/MyApp.java | 2 +- .../turboeditor/activity/PreferenceAbout.java | 238 -- .../activity/SelectFileActivity.java | 36 +- .../turboeditor/adapter/AdapterDrawer.java | 6 +- .../turboeditor/adapter/AdapterTwoItem.java | 12 +- .../turboeditor/fragment/AboutDialog.java | 90 + ...alogFragment.java => ChangelogDialog.java} | 14 +- ...ialogFragment.java => EditTextDialog.java} | 10 +- .../turboeditor/fragment/EditorFragment.java | 1642 -------------- ...ialogFragment.java => EncodingDialog.java} | 6 +- ...ialogFragment.java => FileInfoDialog.java} | 11 +- ...ialogFragment.java => FindTextDialog.java} | 27 +- ...istFragment.java => NavigationDrawer.java} | 10 +- ...ragment.java => NewFileDetailsDialog.java} | 16 +- .../fragment/NoFileOpenedFragment.java | 37 - ...ialogFragment.java => SaveFileDialog.java} | 54 +- ...DialogFragment.java => SeekbarDialog.java} | 42 +- .../sharedcode/turboeditor/iab/Donation.java | 78 + .../turboeditor/iab/DonationAdapter.java | 98 + .../turboeditor/iab/DonationFragment.java | 370 +++ .../turboeditor/iab/DonationItems.java | 53 + .../turboeditor/iab/utils/Base64.java | 582 +++++ .../utils/Base64DecoderException.java} | 19 +- .../turboeditor/iab/utils/IabException.java | 54 + .../turboeditor/iab/utils/IabHelper.java | 966 ++++++++ .../turboeditor/iab/utils/IabResult.java | 63 + .../turboeditor/iab/utils/Inventory.java | 110 + .../turboeditor/iab/utils/Purchase.java | 98 + .../turboeditor/iab/utils/Security.java | 124 ++ .../turboeditor/iab/utils/SkuDetails.java | 76 + .../preferences/ExtraSettingsActivity.java | 8 +- .../preferences/PreferenceHelper.java | 7 + .../preferences/SettingsFragment.java | 34 +- .../{util => root}/LinuxShell.java | 6 +- .../turboeditor/{util => root}/RootUtils.java | 2 +- .../{util => task}/SaveFileTask.java | 4 +- .../EditTextPadding.java} | 6 +- .../{util => texteditor}/FileUtils.java | 2 +- .../{util => texteditor}/LineUtils.java | 13 +- .../{util => texteditor}/PageSystem.java | 2 +- .../PageSystemButtons.java | 2 +- .../{util => texteditor}/Patterns.java | 2 +- .../{util => texteditor}/SearchResult.java | 2 +- .../turboeditor/util/AccessStorageApi.java | 283 ++- .../turboeditor/util/AnimationUtils.java | 52 + .../sharedcode/turboeditor/util/Build.java | 56 + .../turboeditor/util/Constants.java | 25 - .../sharedcode/turboeditor/util/Device.java | 71 + .../turboeditor/util/EditorInterface.java | 32 - .../turboeditor/util/ProCheckUtils.java | 12 +- .../{ThemeHelper.java => ThemeUtils.java} | 11 +- .../turboeditor/util/ToastUtils.java | 71 + .../turboeditor/util/ViewUtils.java | 305 +++ .../turboeditor/views/DialogHelper.java | 308 +++ .../turboeditor/views/GoodScrollView.java | 7 +- .../res/drawable-hdpi/ic_action_paypal.png | Bin 0 -> 875 bytes .../res/drawable-mdpi/ic_action_paypal.png | Bin 0 -> 596 bytes .../res/drawable-xhdpi/ic_action_paypal.png | Bin 0 -> 1132 bytes .../res/drawable-xxhdpi/ic_action_paypal.png | Bin 0 -> 1639 bytes .../drawable/item_background_holo_dark.xml | 2 +- .../src/main/res/layout/activity_about.xml | 149 -- .../src/main/res/layout/activity_home.xml | 75 +- .../main/res/layout/activity_select_file.xml | 6 +- .../sharedCode/src/main/res/layout/dialog.xml | 42 + .../res/layout/dialog_fragment_find_text.xml | 2 +- .../src/main/res/layout/dialog_skeleton.xml | 42 + .../src/main/res/layout/donation_dialog.xml | 89 + .../src/main/res/layout/donation_iab_item.xml | 50 + .../src/main/res/layout/fragment_editor.xml | 78 - .../src/main/res/layout/fragment_settings.xml | 7 +- .../sharedCode/src/main/res/raw/changelog.xml | 7 + .../dimens.xml | 1 - .../attrs.xml} | 18 +- .../sharedCode/src/main/res/values/colors.xml | 3 + .../sharedCode/src/main/res/values/dimens.xml | 4 +- .../sharedCode/src/main/res/values/ids.xml | 4 +- .../src/main/res/values/strings.xml | 106 +- .../src/main/res/values/strings_dialogs.xml | 40 + .../src/main/res/values/strings_donation.xml | 45 + .../main/res/values/strings_turbo_client.xml | 104 + .../sharedCode/src/main/res/values/styles.xml | 46 +- 98 files changed, 6678 insertions(+), 3057 deletions(-) create mode 100644 .idea/codeStyleSettings.xml rename libraries/sharedCode/src/main/{java => aidl}/com/android/vending/billing/IInAppBillingService.aidl (100%) delete mode 100644 libraries/sharedCode/src/main/java/sharedcode/turboeditor/activity/PreferenceAbout.java create mode 100644 libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/AboutDialog.java rename libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/{ChangelogDialogFragment.java => ChangelogDialog.java} (91%) rename libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/{EditDialogFragment.java => EditTextDialog.java} (91%) delete mode 100644 libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/EditorFragment.java rename libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/{EncodingDialogFragment.java => EncodingDialog.java} (94%) rename libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/{FileInfoDialogFragment.java => FileInfoDialog.java} (88%) rename libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/{FindTextDialogFragment.java => FindTextDialog.java} (90%) rename libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/{NavigationDrawerListFragment.java => NavigationDrawer.java} (94%) rename libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/{NewFileDetailsDialogFragment.java => NewFileDetailsDialog.java} (83%) delete mode 100644 libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/NoFileOpenedFragment.java rename libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/{SaveFileDialogFragment.java => SaveFileDialog.java} (55%) rename libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/{SeekbarDialogFragment.java => SeekbarDialog.java} (68%) create mode 100644 libraries/sharedCode/src/main/java/sharedcode/turboeditor/iab/Donation.java create mode 100644 libraries/sharedCode/src/main/java/sharedcode/turboeditor/iab/DonationAdapter.java create mode 100644 libraries/sharedCode/src/main/java/sharedcode/turboeditor/iab/DonationFragment.java create mode 100644 libraries/sharedCode/src/main/java/sharedcode/turboeditor/iab/DonationItems.java create mode 100644 libraries/sharedCode/src/main/java/sharedcode/turboeditor/iab/utils/Base64.java rename libraries/sharedCode/src/main/java/sharedcode/turboeditor/{util/ApiHelper.java => iab/utils/Base64DecoderException.java} (66%) create mode 100644 libraries/sharedCode/src/main/java/sharedcode/turboeditor/iab/utils/IabException.java create mode 100644 libraries/sharedCode/src/main/java/sharedcode/turboeditor/iab/utils/IabHelper.java create mode 100644 libraries/sharedCode/src/main/java/sharedcode/turboeditor/iab/utils/IabResult.java create mode 100644 libraries/sharedCode/src/main/java/sharedcode/turboeditor/iab/utils/Inventory.java create mode 100644 libraries/sharedCode/src/main/java/sharedcode/turboeditor/iab/utils/Purchase.java create mode 100644 libraries/sharedCode/src/main/java/sharedcode/turboeditor/iab/utils/Security.java create mode 100644 libraries/sharedCode/src/main/java/sharedcode/turboeditor/iab/utils/SkuDetails.java rename libraries/sharedCode/src/main/java/sharedcode/turboeditor/{util => root}/LinuxShell.java (98%) rename libraries/sharedCode/src/main/java/sharedcode/turboeditor/{util => root}/RootUtils.java (98%) rename libraries/sharedCode/src/main/java/sharedcode/turboeditor/{util => task}/SaveFileTask.java (95%) rename libraries/sharedCode/src/main/java/sharedcode/turboeditor/{util/EdittextPadding.java => texteditor/EditTextPadding.java} (90%) rename libraries/sharedCode/src/main/java/sharedcode/turboeditor/{util => texteditor}/FileUtils.java (97%) rename libraries/sharedCode/src/main/java/sharedcode/turboeditor/{util => texteditor}/LineUtils.java (93%) rename libraries/sharedCode/src/main/java/sharedcode/turboeditor/{util => texteditor}/PageSystem.java (99%) rename libraries/sharedCode/src/main/java/sharedcode/turboeditor/{util => texteditor}/PageSystemButtons.java (99%) rename libraries/sharedCode/src/main/java/sharedcode/turboeditor/{util => texteditor}/Patterns.java (99%) rename libraries/sharedCode/src/main/java/sharedcode/turboeditor/{util => texteditor}/SearchResult.java (97%) create mode 100644 libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/AnimationUtils.java create mode 100644 libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/Build.java delete mode 100644 libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/Constants.java create mode 100644 libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/Device.java delete mode 100644 libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/EditorInterface.java rename libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/{ThemeHelper.java => ThemeUtils.java} (80%) create mode 100644 libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/ToastUtils.java create mode 100644 libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/ViewUtils.java create mode 100644 libraries/sharedCode/src/main/java/sharedcode/turboeditor/views/DialogHelper.java create mode 100644 libraries/sharedCode/src/main/res/drawable-hdpi/ic_action_paypal.png create mode 100644 libraries/sharedCode/src/main/res/drawable-mdpi/ic_action_paypal.png create mode 100644 libraries/sharedCode/src/main/res/drawable-xhdpi/ic_action_paypal.png create mode 100644 libraries/sharedCode/src/main/res/drawable-xxhdpi/ic_action_paypal.png delete mode 100644 libraries/sharedCode/src/main/res/layout/activity_about.xml create mode 100644 libraries/sharedCode/src/main/res/layout/dialog.xml create mode 100644 libraries/sharedCode/src/main/res/layout/dialog_skeleton.xml create mode 100644 libraries/sharedCode/src/main/res/layout/donation_dialog.xml create mode 100644 libraries/sharedCode/src/main/res/layout/donation_iab_item.xml delete mode 100644 libraries/sharedCode/src/main/res/layout/fragment_editor.xml rename libraries/sharedCode/src/main/res/{values-sw600dp => values-w820dp}/dimens.xml (96%) rename libraries/sharedCode/src/main/res/{layout/fragment_no_file_open.xml => values/attrs.xml} (66%) create mode 100644 libraries/sharedCode/src/main/res/values/strings_dialogs.xml create mode 100644 libraries/sharedCode/src/main/res/values/strings_donation.xml create mode 100644 libraries/sharedCode/src/main/res/values/strings_turbo_client.xml diff --git a/.idea/codeStyleSettings.xml b/.idea/codeStyleSettings.xml new file mode 100644 index 0000000..29ab470 --- /dev/null +++ b/.idea/codeStyleSettings.xml @@ -0,0 +1,174 @@ + + + + + + + diff --git a/app-pro/build.gradle b/app-pro/build.gradle index 60138bb..5db5783 100644 --- a/app-pro/build.gradle +++ b/app-pro/build.gradle @@ -20,12 +20,12 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 19 - buildToolsVersion "19.1.0" + compileSdkVersion 20 + buildToolsVersion '20.0.0' defaultConfig { applicationId "com.maskyn.fileeditorpro" - minSdkVersion 10 + minSdkVersion 11 targetSdkVersion 19 versionCode 29 versionName "1.12" @@ -41,6 +41,13 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } + lintOptions { + abortOnError false + } + packagingOptions { + exclude 'META-INF/LICENSE.txt' + exclude 'META-INF/NOTICE.txt' + } } dependencies { diff --git a/app-pro/src/main/AndroidManifest.xml b/app-pro/src/main/AndroidManifest.xml index d272756..507dbf5 100644 --- a/app-pro/src/main/AndroidManifest.xml +++ b/app-pro/src/main/AndroidManifest.xml @@ -49,7 +49,7 @@ 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/AppTheme.Light.Editor"> + android:theme="@style/AppThemeEditorDark"> @@ -79,32 +79,12 @@ - - - - - - + android:theme="@style/AppThemeBaseLight"> diff --git a/app/build.gradle b/app/build.gradle index 5d23940..7f73020 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -38,14 +38,14 @@ repositories { android { - compileSdkVersion 19 - buildToolsVersion "19.1.0" + compileSdkVersion 20 + buildToolsVersion '20.0.0' defaultConfig { applicationId "com.maskyn.fileeditor" - minSdkVersion 10 + minSdkVersion 11 targetSdkVersion 19 - versionCode 29 - versionName "1.12" + versionCode 30 + versionName "1.13" } compileOptions { sourceCompatibility JavaVersion.VERSION_1_7 @@ -57,6 +57,13 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } + lintOptions { + abortOnError false + } + packagingOptions { + exclude 'META-INF/LICENSE.txt' + exclude 'META-INF/NOTICE.txt' + } } dependencies { @@ -64,5 +71,4 @@ dependencies { compile project(':libraries:sharedCode') compile 'com.google.android.gms:play-services:5.0.89' compile 'com.crashlytics.android:crashlytics:1.+' - } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 286bd5f..085d282 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -55,7 +55,7 @@ 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/AppTheme.Light.Editor"> + android:theme="@style/AppThemeEditorDark"> @@ -85,32 +85,12 @@ - - - - - - + android:theme="@style/AppThemeBaseLight"> diff --git a/app/src/main/java/com/maskyn/fileeditor/HomeActivity.java b/app/src/main/java/com/maskyn/fileeditor/HomeActivity.java index fe65bbc..40e116e 100644 --- a/app/src/main/java/com/maskyn/fileeditor/HomeActivity.java +++ b/app/src/main/java/com/maskyn/fileeditor/HomeActivity.java @@ -22,8 +22,10 @@ package com.maskyn.fileeditor; import android.os.Bundle; import com.crashlytics.android.Crashlytics; + import sharedcode.turboeditor.activity.BaseHomeActivity; import sharedcode.turboeditor.preferences.PreferenceHelper; +import sharedcode.turboeditor.util.ProCheckUtils; public class HomeActivity extends BaseHomeActivity { @@ -36,11 +38,13 @@ public class HomeActivity extends BaseHomeActivity { if(PreferenceHelper.getSendErrorReports(this)) Crashlytics.start(this); // setup the ads - adsHelper = new AdsHelper(this); + if(!ProCheckUtils.isPro(this)) + adsHelper = new AdsHelper(this); } @Override public void displayInterstitial() { - adsHelper.displayInterstitial(); + if(adsHelper != null && !ProCheckUtils.isPro(this)) + adsHelper.displayInterstitial(); } } diff --git a/build/intermediates/dex-cache/cache.xml b/build/intermediates/dex-cache/cache.xml index 3561180..39a2cbe 100644 --- a/build/intermediates/dex-cache/cache.xml +++ b/build/intermediates/dex-cache/cache.xml @@ -7,54 +7,18 @@ jumboMode="false" revision="20.0.0" sha1="a18ff12a9ab5ae52fd30d42f134517997568231e"/> - - - - - - - + revision="20.0.0" + sha1="591d72211acc0b909b79c840e0b3ed9a0982d807"/> + - - + - - - - - + sha1="77979eaa98f90806f984155f44f63cc1fb60ac25"/> - + + sha1="cdecd8167dfb75d5785decb911fc4516445dd6a6"/> + + + + + + + + + + + + + + + + - - - - - - + revision="20.0.0" + sha1="d71573c9c5ea98a8db47ad6ff993a63d492b3bfa"/> uqEgS&4;rUnT2=K=8T@Z+s=~5`K`GS@J|K>G1 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 diff --git a/libraries/FloatingActionButton/build/intermediates/bundles/release/classes.jar b/libraries/FloatingActionButton/build/intermediates/bundles/release/classes.jar index c6394fe2ef9c01049ded25b095194bb6d56412a4..8ba9b433a2ab5518ec49eda228b8ef2eced2f54d 100644 GIT binary patch delta 139 zcmX@Bb5@5pz?+$ci-CcIgTcJre@Dm(xH delta 139 zcmX@Bb5@5pz?+$ci-CcIgW>Ef?}@x>%pl6l3rsaDf~n=YVCuRPh}tZ}c$X2(_`?*< x0%k<9=kS0SlTQkpfapmg_F%4)h(4Gu5wQl-n?wS@G^=PLn9dZ<1=BZ0O90E1G>iZM diff --git a/libraries/FloatingActionButton/build/outputs/aar/FloatingActionButton.aar b/libraries/FloatingActionButton/build/outputs/aar/FloatingActionButton.aar index 0f7519763dfd4aa9a0376cf418da3db825fd9d02..db4bac9329714e785a57b404cab6b2aa27ee8877 100644 GIT binary patch delta 5213 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 to implement the recommended @@ -80,23 +151,31 @@ public abstract class BaseHomeActivity extends Activity { * The Drawer Layout */ private CustomDrawerLayout mDrawerLayout; + private GoodScrollView verticalScroll; + private String sFilePath = ""; + private Editor mEditor; + private HorizontalScrollView horizontalScroll; + private boolean searchingText; + private SearchResult searchResult; + private PageSystem pageSystem; + private PageSystemButtons pageSystemButtons; + private String currentEncoding = "UTF-8"; + //endregion //region Activity facts @Override protected void onCreate(Bundle savedInstanceState) { // set the windows background - ThemeHelper.setWindowsBackground(this); + ThemeUtils.setWindowsBackground(this); // super!! super.onCreate(savedInstanceState); // setup the layout setContentView(R.layout.activity_home); // setup the navigation drawer setupNavigationDrawer(); - // Replace fragment - getFragmentManager() - .beginTransaction() - .replace(R.id.fragment_editor, new NoFileOpenedFragment()) - .commit(); + // reset text editor + setupTextEditor(); + hideTextEditor(); /* First Time we open this activity */ if (savedInstanceState == null) { // Open @@ -117,7 +196,6 @@ public abstract class BaseHomeActivity extends Activity { mDrawerToggle.syncState(); } - @Override public void onResume() { super.onResume(); @@ -125,14 +203,23 @@ public abstract class BaseHomeActivity extends Activity { EventBus.getDefault().registerSticky(this); } + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + parseIntent(intent); + } @Override public void onPause() { super.onPause(); // Unregister the Event Bus EventBus.getDefault().unregister(this); - } + if (PreferenceHelper.getAutoSave(getBaseContext()) && mEditor.canSaveFile()) { + saveTheFile(); + mEditor.fileSaved(); // so it doesn't ask to save in onDetach + } + } @Override protected void onDestroy() { @@ -145,36 +232,9 @@ public abstract class BaseHomeActivity extends Activity { } @Override - public void onBackPressed() { - - // if we should ignore the back button - if(PreferenceHelper.getIgnoreBackButton(this)) - return; - - - boolean fileOpened = getFragmentManager().findFragmentById(R.id.fragment_editor) instanceof EditorFragment; - if (mDrawerLayout.isDrawerOpen(Gravity.START) && fileOpened) { - mDrawerLayout.closeDrawer(Gravity.START); - } else if (mDrawerLayout.isDrawerOpen(Gravity.END) && fileOpened) { - mDrawerLayout.closeDrawer(Gravity.END); - } else if (fileOpened) { - - // remove editor fragment - getFragmentManager() - .beginTransaction() - .replace(R.id.fragment_editor, new NoFileOpenedFragment()) - .commit(); - // Set the default title - getActionBar().setTitle(getString(R.string.nome_app_turbo_editor)); - - EventBus.getDefault().post(new EventBusEvents.ClosedAFile()); - - mDrawerLayout.openDrawer(Gravity.START); - mDrawerLayout.closeDrawer(Gravity.END); - } else { - displayInterstitial(); - super.onBackPressed(); - } + public final void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + mDrawerToggle.onConfigurationChanged(newConfig); } @Override @@ -183,20 +243,19 @@ public abstract class BaseHomeActivity extends Activity { if (keyCode == KeyEvent.KEYCODE_BACK) { onBackPressed(); return true; - } - else if(keyCode == KeyEvent.KEYCODE_MENU){ + } else if (keyCode == KeyEvent.KEYCODE_MENU) { return false; - } - else { - if (editor == null) - editor = (EditText) findViewById(R.id.editor); + } else { + if (mEditor == null) + mEditor = (Editor) findViewById(R.id.editor); + // this will happen on first key pressed on hard-keyboard only. Once myInputField // gets the focus again, it will automatically receive further key presses. try { - if (editor != null && !editor.hasFocus()) { - editor.requestFocus(); - editor.onKeyDown(keyCode, event); + if (fileOpened && mEditor != null && !mEditor.hasFocus()) { + mEditor.requestFocus(); + mEditor.onKeyDown(keyCode, event); return true; } } catch (NullPointerException ex) { @@ -209,9 +268,40 @@ public abstract class BaseHomeActivity extends Activity { } @Override - public final void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - mDrawerToggle.onConfigurationChanged(newConfig); + public void onBackPressed() { + + try { + // if we should ignore the back button + if (PreferenceHelper.getIgnoreBackButton(this)) + return; + + if (mDrawerLayout.isDrawerOpen(Gravity.START) && fileOpened) { + mDrawerLayout.closeDrawer(Gravity.START); + } else if (mDrawerLayout.isDrawerOpen(Gravity.END) && fileOpened) { + mDrawerLayout.closeDrawer(Gravity.END); + } else if (fileOpened && mEditor.canSaveFile()) { + SaveFileDialog.newInstance(sFilePath, pageSystem.getAllText(mEditor + .getText().toString()), currentEncoding).show(getFragmentManager(), + "dialog"); + } else if (fileOpened) { + + // remove editor fragment + hideTextEditor(); + + // Set the default title + getActionBar().setTitle(getString(R.string.nome_app_turbo_editor)); + + EventBus.getDefault().post(new EventBusEvents.ClosedAFile()); + + mDrawerLayout.openDrawer(Gravity.START); + mDrawerLayout.closeDrawer(Gravity.END); + } else { + displayInterstitial(); + super.onBackPressed(); + } + } catch (Exception e) { + // maybe something is null, who knows + } } @Override @@ -227,71 +317,405 @@ public abstract class BaseHomeActivity extends Activity { path = AccessStorageApi.getPath(getBaseContext(), data.getData()); } - if(!path.isEmpty()){ + if (!path.isEmpty()) { File file = new File(path); if (file.isFile() && file.exists()) { - EventBus.getDefault().postSticky(new EventBusEvents.NewFileToOpen(new File(path))); - EventBus.getDefault().postSticky(new EventBusEvents.AFileIsSelected(path)); + EventBus.getDefault().postSticky(new EventBusEvents.NewFileToOpen(new File + (path))); } } } } + //endregion + + //region MENU + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + if (fileOpened && searchingText) + getMenuInflater().inflate(R.menu.fragment_editor_search, menu); + else if (fileOpened) + getMenuInflater().inflate(R.menu.fragment_editor, menu); + return super.onCreateOptionsMenu(menu); + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + + ActionBar ab = getActionBar(); + if (ab == null) + return false; + + + if (fileOpened && searchingText) { + MenuItem imReplace = menu.findItem(R.id.im_replace); + MenuItem imPrev = menu.findItem(R.id.im_previous_item); + MenuItem imNext = menu.findItem(R.id.im_next_item); + + if (imReplace != null) + imReplace.setVisible(searchResult.canReplaceSomething()); + + if (imPrev != null) + imPrev.setVisible(searchResult.hasPrevious()); + + if (imNext != null) + imNext.setVisible(searchResult.hasNext()); + + + } else if (fileOpened) { + MenuItem imSave = menu.findItem(R.id.im_save); + MenuItem imUndo = menu.findItem(R.id.im_undo); + MenuItem imRedo = menu.findItem(R.id.im_redo); + if (mEditor != null) { + if (imSave != null) + imSave.setVisible(mEditor.canSaveFile()); + if (imUndo != null) + imUndo.setVisible(mEditor.getCanUndo()); + if (imRedo != null) + imRedo.setVisible(mEditor.getCanRedo()); + } else { + imSave.setVisible(false); + imUndo.setVisible(false); + imRedo.setVisible(false); + } + } + + return true; + } @Override public boolean onOptionsItemSelected(MenuItem item) { - /* If we clicked on the Navigation Drawer Menu item */ + int i = item.getItemId(); if (mDrawerToggle.onOptionsItemSelected(item)) { mDrawerLayout.closeDrawer(Gravity.RIGHT); return true; - } else - return super.onOptionsItemSelected(item); - } + } else if (i == R.id.im_save) { + saveTheFile(); - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - parseIntent(intent); + } else if (i == R.id.im_undo) { + this.mEditor.onTextContextMenuItem(ID_UNDO); + + } else if (i == R.id.im_redo) { + this.mEditor.onTextContextMenuItem(ID_REDO); + + } else if (i == R.id.im_search) { + FindTextDialog dialogFrag = FindTextDialog.newInstance(mEditor.getText().toString()); + dialogFrag.show(getFragmentManager().beginTransaction(), "dialog"); + } else if (i == R.id.im_cancel) { + searchingText = false; + invalidateOptionsMenu(); + + } else if (i == R.id.im_replace) { + replaceText(); + + } else if (i == R.id.im_next_item) { + nextResult(); + + } else if (i == R.id.im_previous_item) { + previousResult(); + + } else if (i == R.id.im_goto_line) { + int min = mEditor.getLineUtils().firstReadLine(); + int max = mEditor.getLineUtils().lastReadLine(); + SeekbarDialog dialogFrag = SeekbarDialog.newInstance + (SeekbarDialog.Actions.GoToLine, min, min, max); + + dialogFrag.show(getFragmentManager().beginTransaction(), "dialog"); + } else if (i == R.id.im_view_it_on_browser) { + Intent browserIntent = null; + try { + browserIntent = new Intent(Intent.ACTION_VIEW); + browserIntent.setDataAndType(Uri.fromFile(new File(sFilePath)), "text/*"); + startActivity(browserIntent); + } catch (ActivityNotFoundException ex2) { + // + } + + } else if (i == R.id.im_share) { + File f = new File(sFilePath); + Intent shareIntent = new Intent(); + shareIntent.setAction(Intent.ACTION_SEND); + shareIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(f)); + shareIntent.setType("text/plain"); + + startActivity(Intent.createChooser(shareIntent, getString(R.string.share))); + + } else if (i == R.id.im_info) { + FileInfoDialog dialogFrag = FileInfoDialog.newInstance(sFilePath); + + dialogFrag.show(getFragmentManager().beginTransaction(), "dialog"); + } + return super.onOptionsItemSelected(item); } //endregion - //region Calls from the layout - public void OpenFile(View view) { + // region OTHER THINGS + public void replaceText() { + int start = searchResult.foundIndex.get(searchResult.index); + int end = start + searchResult.textLength; + mEditor.setText(mEditor.getText().replace(start, end, searchResult.textToReplace)); + searchResult.doneReplace(); - Intent subActivity = new Intent(BaseHomeActivity.this, SelectFileActivity.class); - subActivity.putExtra("action", SelectFileActivity.Actions.SelectFile); - Bundle scaleBundle = ActivityOptionsCompat.makeScaleUpAnimation( - view, 0, 0, view.getWidth(), view.getHeight()).toBundle(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) - startActivityForResult(subActivity, SELECT_FILE_CODE, scaleBundle); - else - startActivityForResult(subActivity, SELECT_FILE_CODE); + invalidateOptionsMenu(); + + if (searchResult.hasNext()) + nextResult(); + else if (searchResult.hasPrevious()) + previousResult(); } - public void CreateFile(View view) { - onEvent(new EventBusEvents.NewFileToOpen("")); // do not send the event to others - EventBus.getDefault().post(new EventBusEvents.AFileIsSelected("")); + public void nextResult() { + if (searchResult.index == mEditor.getLineCount() - 1) // last result of page + { + return; + } + + + if (searchResult.index < searchResult.numberOfResults() - 1) { // equal zero is not good + searchResult.index++; + final int line = mEditor.getLineUtils().getLineFromIndex(searchResult.foundIndex.get + (searchResult.index), mEditor.getLineCount(), mEditor.getLayout()); + + + verticalScroll.post(new Runnable() { + @Override + public void run() { + int y = mEditor.getLayout().getLineTop(line); + if (y > 100) + y -= 100; + else + y = 0; + + verticalScroll.scrollTo(0, y); + } + }); + + mEditor.setSelection(searchResult.foundIndex.get(searchResult.index), + searchResult.foundIndex.get(searchResult.index) + searchResult.textLength); + } + + invalidateOptionsMenu(); } - public void OpenInfo(View view) { - Intent subActivity = new Intent(BaseHomeActivity.this, PreferenceAbout.class); - Bundle scaleBundle = ActivityOptionsCompat.makeScaleUpAnimation( - view, 0, 0, view.getWidth(), view.getHeight()).toBundle(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) - startActivity(subActivity, scaleBundle); - else - startActivity(subActivity); + public void previousResult() { + if (searchResult.index == 0) + return; + if (searchResult.index > 0) { + searchResult.index--; + final int line = mEditor.getLineUtils().getLineFromIndex(searchResult.foundIndex.get + (searchResult.index), mEditor.getLineCount(), mEditor.getLayout()); + verticalScroll.post(new Runnable() { + @Override + public void run() { + int y = mEditor.getLayout().getLineTop(line); + if (y > 100) + y -= 100; + else + y = 0; + verticalScroll.scrollTo(0, y); + } + }); + + mEditor.setSelection(searchResult.foundIndex.get(searchResult.index), + searchResult.foundIndex.get(searchResult.index) + searchResult.textLength); + } + + invalidateOptionsMenu(); } - public void OpenSettings(View view) { - mDrawerLayout.closeDrawer(Gravity.START); - mDrawerLayout.openDrawer(Gravity.END); + public abstract void displayInterstitial(); + + private void saveTheFile() { + File file = new File(sFilePath); + if (!file.getName().isEmpty()) + new SaveFileTask(getBaseContext(), sFilePath, pageSystem.getAllText(mEditor.getText() + .toString()), currentEncoding).execute(); + else { + NewFileDetailsDialog dialogFrag = NewFileDetailsDialog.newInstance + (pageSystem.getAllText(mEditor.getText().toString()), currentEncoding); + dialogFrag.show(getFragmentManager().beginTransaction(), "dialog"); + } + } + + /** + * Setup the navigation drawer + */ + private void setupNavigationDrawer() { + mDrawerLayout = (CustomDrawerLayout) findViewById(R.id.drawer_layout); + /* Action Bar */ + final ActionBar ab = getActionBar(); + ab.setDisplayHomeAsUpEnabled(true); + ab.setHomeButtonEnabled(true); + /* Navigation drawer */ + mDrawerToggle = + new ActionBarDrawerToggle( + this, + mDrawerLayout, + R.drawable.ic_drawer, + R.string.nome_app_turbo_editor, + R.string.nome_app_turbo_editor) { + + /** + * {@inheritDoc} + */ + @Override + public void onDrawerOpened(View drawerView) { + invalidateOptionsMenu(); + try { + closeKeyBoard(); + } catch (NullPointerException e) { + e.printStackTrace(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void onDrawerClosed(View view) { + invalidateOptionsMenu(); + } + }; + /* link the mDrawerToggle to the Drawer Layout */ + mDrawerLayout.setDrawerListener(mDrawerToggle); +//mDrawerLayout.setFocusableInTouchMode(false); + } + + private void setupTextEditor() { + + verticalScroll = (GoodScrollView) findViewById(R.id.vertical_scroll); + horizontalScroll = (HorizontalScrollView) findViewById(R.id.horizontal_scroll); + mEditor = (Editor) findViewById(R.id.editor); + + if (PreferenceHelper.getWrapContent(getBaseContext())) { + horizontalScroll.removeView(mEditor); + verticalScroll.removeView(horizontalScroll); + verticalScroll.addView(mEditor); + } else { + // else show what is in the xml file fragment_editor.xml- + } + + if (PreferenceHelper.getReadOnly(getBaseContext())) { + getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); + } else { + getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED); + } + + verticalScroll.setScrollInterface(this); + + pageSystem = new PageSystem(getBaseContext(), this, ""); + + pageSystemButtons = new PageSystemButtons(getBaseContext(), this, + (FloatingActionButton) findViewById(R.id.fabPrev), + (FloatingActionButton) findViewById(R.id.fabNext)); + } + + private void showTextEditor() { + + fileOpened = true; + + findViewById(R.id.text_editor).setVisibility(View.VISIBLE); + findViewById(R.id.no_file_opened_messagge).setVisibility(View.GONE); + + mEditor.resetVariables(); + searchResult = null; + searchingText = false; + + invalidateOptionsMenu(); + + mEditor.disableTextChangedListener(); + mEditor.replaceTextKeepCursor(pageSystem.getCurrentPageText(), false); + mEditor.enableTextChangedListener(); + } + + private void hideTextEditor() { + + fileOpened = false; + + try { + findViewById(R.id.text_editor).setVisibility(View.GONE); + findViewById(R.id.no_file_opened_messagge).setVisibility(View.VISIBLE); + + mEditor.disableTextChangedListener(); + mEditor.replaceTextKeepCursor("", false); + mEditor.enableTextChangedListener(); + } catch (Exception e) { + // lol + } + } + + /** + * Parses the intent + */ + private void parseIntent(Intent intent) { + final String action = intent.getAction(); + final String type = intent.getType(); + + if (Intent.ACTION_VIEW.equals(action) + || Intent.ACTION_EDIT.equals(action) + || Intent.ACTION_PICK.equals(action) + && type != null) { + // Post event + EventBus.getDefault().postSticky(new EventBusEvents.NewFileToOpen(new File(intent + .getData().getPath()))); + } else if (Intent.ACTION_SEND.equals(action) && type != null) { + if ("text/plain".equals(type)) { + onEvent(new EventBusEvents.NewFileToOpen(intent.getStringExtra(Intent.EXTRA_TEXT))); + } + } + } + + /** + * Show a dialog with the changelog + */ + private void showChangeLog() { + final String currentVersion = AppInfoHelper.getCurrentVersion(this); + final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); + final String lastVersion = preferences.getString("last_version", currentVersion); + preferences.edit().putString("last_version", currentVersion).apply(); + if (!lastVersion.equals(currentVersion)) { + ChangelogDialog.showChangeLogDialog(getFragmentManager()); + } + } + + // closes the soft keyboard + private void closeKeyBoard() throws NullPointerException { + // Central system API to the overall input method framework (IMF) architecture + InputMethodManager inputManager = + (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + + // Base interface for a remotable object + IBinder windowToken = getCurrentFocus().getWindowToken(); + + // Hide type + int hideType = InputMethodManager.HIDE_NOT_ALWAYS; + + // Hide the KeyBoard + inputManager.hideSoftInputFromWindow(windowToken, hideType); + } + + public void updateTextSyntax() { + if (!PreferenceHelper.getSyntaxHiglight(getBaseContext()) || mEditor.hasSelection() || + updateHandler == null || colorRunnable_duringEditing == null) + return; + + updateHandler.removeCallbacks(colorRunnable_duringEditing); + updateHandler.removeCallbacks(colorRunnable_duringScroll); + updateHandler.postDelayed(colorRunnable_duringEditing, SYNTAX_DELAY_MILLIS_LONG); } //endregion - //region Eventbus + //region EVENTBUS public void onEvent(final EventBusEvents.NewFileToOpen event) { + if (fileOpened && mEditor.canSaveFile()) { + SaveFileDialog.newInstance(sFilePath, pageSystem.getAllText(mEditor + .getText().toString()), currentEncoding, true, event.getFile().getAbsolutePath()).show(getFragmentManager(), + "dialog"); + return; + } + new AsyncTask() { File file; @@ -336,14 +760,15 @@ public abstract class BaseHomeActivity extends Activity { tempFile.createNewFile(); Shell shell = Shell.startRootShell(); Toolbox tb = new Toolbox(shell); - tb.copyFile(event.getFile().getAbsolutePath(), tempFile.getAbsolutePath(), false, false); + tb.copyFile(event.getFile().getAbsolutePath(), + tempFile.getAbsolutePath(), false, false); file = new File(tempFile.getAbsolutePath()); } boolean autoencoding = PreferenceHelper.getAutoEncoding(BaseHomeActivity.this); if (autoencoding) { - encoding = sharedcode.turboeditor.util.FileUtils.getDetectedEncoding(file); + encoding = FileUtils.getDetectedEncoding(file); if (encoding.isEmpty()) { encoding = PreferenceHelper.getEncoding(BaseHomeActivity.this); } @@ -375,10 +800,20 @@ public abstract class BaseHomeActivity extends Activity { Toast.makeText(BaseHomeActivity.this, message, Toast.LENGTH_LONG).show(); EventBus.getDefault().post(new EventBusEvents.CannotOpenAFile()); } else { - getFragmentManager() - .beginTransaction() - .replace(R.id.fragment_editor, EditorFragment.newInstance(event.getFile().getAbsolutePath(), fileText, encoding)) - .commit(); + + sFilePath = event.getFile().getAbsolutePath(); + pageSystem = new PageSystem(getBaseContext(), BaseHomeActivity.this, fileText); + currentEncoding = encoding; + + EventBus.getDefault().post(new EventBusEvents.AFileIsSelected(sFilePath)); + + showTextEditor(); + + String name = FilenameUtils.getName(sFilePath); + if (name.isEmpty()) + getActionBar().setTitle("*"); + else + getActionBar().setTitle(name); } @@ -388,6 +823,11 @@ public abstract class BaseHomeActivity extends Activity { } public void onEvent(EventBusEvents.SavedAFile event) { + + mEditor.clearHistory(); + mEditor.fileSaved(); + invalidateOptionsMenu(); + try { closeKeyBoard(); } catch (NullPointerException e) { @@ -408,8 +848,8 @@ public abstract class BaseHomeActivity extends Activity { // finish the activity finish(); } - if (!ProCheckUtils.isPro(getApplicationContext())) - displayInterstitial(); + + displayInterstitial(); } /** @@ -424,113 +864,1264 @@ public abstract class BaseHomeActivity extends Activity { // getActionBar().setTitle(getString(R.string.nome_app_turbo_editor)); // Replace fragment - getFragmentManager() - .beginTransaction() - .replace(R.id.fragment_editor, new NoFileOpenedFragment()) - .commit(); + hideTextEditor(); } public void onEvent(EventBusEvents.APreferenceValueWasChanged event) { if (event.hasType(EventBusEvents.APreferenceValueWasChanged.Type.THEME_CHANGE)) { - ThemeHelper.setWindowsBackground(this); + ThemeUtils.setWindowsBackground(this); + } + + if (event.hasType(WRAP_CONTENT)) { + if (PreferenceHelper.getWrapContent(getBaseContext())) { + horizontalScroll.removeView(mEditor); + verticalScroll.removeView(horizontalScroll); + verticalScroll.addView(mEditor); + } else { + verticalScroll.removeView(mEditor); + verticalScroll.addView(horizontalScroll); + horizontalScroll.addView(mEditor); + } + } else if (event.hasType(LINE_NUMERS)) { + mEditor.disableTextChangedListener(); + mEditor.replaceTextKeepCursor(null, true); + mEditor.enableTextChangedListener(); + if (PreferenceHelper.getLineNumbers(getBaseContext())) { + mEditor.setPadding(EditTextPadding.getPaddingWithLineNumbers(getBaseContext(), + PreferenceHelper.getFontSize(getBaseContext())), + EditTextPadding.getPaddingTop(getBaseContext()), 0, 0); + } else { + mEditor.setPadding(EditTextPadding.getPaddingWithoutLineNumbers(getBaseContext()) + , EditTextPadding.getPaddingTop(getBaseContext()), 0, 0); + } + } else if (event.hasType(SYNTAX)) { + mEditor.disableTextChangedListener(); + mEditor.replaceTextKeepCursor(null, true); + mEditor.enableTextChangedListener(); + } else if (event.hasType(MONOSPACE)) { + if (PreferenceHelper.getUseMonospace(getBaseContext())) + this.mEditor.setTypeface(Typeface.MONOSPACE); + else + this.mEditor.setTypeface(Typeface.DEFAULT); + } else if (event.hasType(THEME_CHANGE)) { + if (PreferenceHelper.getLightTheme(getBaseContext())) { + mEditor.setTextColor(getResources().getColor(R.color.textColorInverted)); + } else { + mEditor.setTextColor(getResources().getColor(R.color.textColor)); + } + } else if (event.hasType(TEXT_SUGGESTIONS) || event.hasType(READ_ONLY)) { + if (PreferenceHelper.getReadOnly(getBaseContext())) { + getWindow().setSoftInputMode(WindowManager.LayoutParams + .SOFT_INPUT_STATE_ALWAYS_HIDDEN); + mEditor.setReadOnly(true); + } else { + getWindow().setSoftInputMode(WindowManager.LayoutParams + .SOFT_INPUT_STATE_UNSPECIFIED); + mEditor.setReadOnly(false); + if (PreferenceHelper.getSuggestionActive(getBaseContext())) { + mEditor.setInputType(InputType.TYPE_CLASS_TEXT | InputType + .TYPE_TEXT_FLAG_MULTI_LINE | InputType.TYPE_TEXT_FLAG_IME_MULTI_LINE); + } else { + mEditor.setInputType(InputType.TYPE_CLASS_TEXT | InputType + .TYPE_TEXT_FLAG_MULTI_LINE | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS + | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD | InputType + .TYPE_TEXT_FLAG_IME_MULTI_LINE); + } + } + // sometimes it becomes monospace after setting the input type + if (PreferenceHelper.getUseMonospace(getBaseContext())) + this.mEditor.setTypeface(Typeface.MONOSPACE); + else + this.mEditor.setTypeface(Typeface.DEFAULT); + } else if (event.hasType(FONT_SIZE)) { + if (PreferenceHelper.getLineNumbers(getBaseContext())) { + mEditor.setPadding(EditTextPadding.getPaddingWithLineNumbers(getBaseContext(), + PreferenceHelper.getFontSize(getBaseContext())), + EditTextPadding.getPaddingTop(getBaseContext()), 0, 0); + } else { + mEditor.setPadding(EditTextPadding.getPaddingWithoutLineNumbers(getBaseContext()) + , EditTextPadding.getPaddingTop(getBaseContext()), 0, 0); + } + this.mEditor.setTextSize(PreferenceHelper.getFontSize(getBaseContext())); + } else if (event.hasType(ENCODING)) { + String oldEncoding, newEncoding; + oldEncoding = currentEncoding; + newEncoding = PreferenceHelper.getEncoding(getBaseContext()); + try { + final byte[] oldText = this.mEditor.getText().toString().getBytes(oldEncoding); + mEditor.disableTextChangedListener(); + mEditor.replaceTextKeepCursor(new String(oldText, newEncoding), true); + mEditor.enableTextChangedListener(); + currentEncoding = newEncoding; + } catch (UnsupportedEncodingException ignored) { + try { + final byte[] oldText = this.mEditor.getText().toString().getBytes(oldEncoding); + mEditor.disableTextChangedListener(); + mEditor.replaceTextKeepCursor(new String(oldText, "UTF-8"), true); + mEditor.enableTextChangedListener(); + } catch (UnsupportedEncodingException ignored2) { + } + } } } //endregion - // closes the soft keyboard - private void closeKeyBoard() throws NullPointerException { - // Central system API to the overall input method framework (IMF) architecture - InputMethodManager inputManager = - (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + //region Calls from the layout + public void OpenFile(View view) { - // Base interface for a remotable object - IBinder windowToken = getCurrentFocus().getWindowToken(); + Intent subActivity = new Intent(BaseHomeActivity.this, SelectFileActivity.class); + subActivity.putExtra("action", SelectFileActivity.Actions.SelectFile); - // Hide type - int hideType = InputMethodManager.HIDE_NOT_ALWAYS; - - // Hide the KeyBoard - inputManager.hideSoftInputFromWindow(windowToken, hideType); + AnimationUtils.startActivityWithScale(this, subActivity, true, SELECT_FILE_CODE, view); } - /** - * Setup the navigation drawer - */ - private void setupNavigationDrawer() { - mDrawerLayout = (CustomDrawerLayout) findViewById(R.id.drawer_layout); - /* Action Bar */ - final ActionBar ab = getActionBar(); - ab.setDisplayHomeAsUpEnabled(true); - ab.setHomeButtonEnabled(true); - /* Navigation drawer */ - mDrawerToggle = - new ActionBarDrawerToggle( - this, - mDrawerLayout, - R.drawable.ic_drawer, - R.string.nome_app_turbo_editor, - R.string.nome_app_turbo_editor) { - - /** - * {@inheritDoc} - */ - @Override - public void onDrawerClosed(View view) { - invalidateOptionsMenu(); - } - - /** - * {@inheritDoc} - */ - @Override - public void onDrawerOpened(View drawerView) { - invalidateOptionsMenu(); - try { - closeKeyBoard(); - } catch (NullPointerException e) { - e.printStackTrace(); - } - } - }; - /* link the mDrawerToggle to the Drawer Layout */ - mDrawerLayout.setDrawerListener(mDrawerToggle); -//mDrawerLayout.setFocusableInTouchMode(false); + public void CreateFile(View view) { + onEvent(new EventBusEvents.NewFileToOpen("")); // do not send the event to others } - /** - * Show a dialog with the changelog - */ - private void showChangeLog() { - final String currentVersion = AppInfoHelper.getCurrentVersion(this); - final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); - final String lastVersion = preferences.getString("last_version", currentVersion); - preferences.edit().putString("last_version", currentVersion).apply(); - if (!lastVersion.equals(currentVersion)) { - ChangelogDialogFragment.showChangeLogDialog(getFragmentManager()); + public void OpenInfo(View view) { + DialogHelper.showAboutDialog(this); + } + + public void OpenSettings(View view) { + mDrawerLayout.closeDrawer(Gravity.START); + mDrawerLayout.openDrawer(Gravity.END); + } + //endregion + + //region Ovverideses + @Override + public void nextPageClicked() { + pageSystem.savePage(mEditor.getText().toString()); + pageSystem.nextPage(); + mEditor.disableTextChangedListener(); + mEditor.replaceTextKeepCursor(pageSystem.getCurrentPageText(), false); + mEditor.enableTextChangedListener(); + + verticalScroll.postDelayed(new Runnable() { + @Override + public void run() { + verticalScroll.smoothScrollTo(0, 0); + } + }, 200); + + if (PreferenceHelper.getPageSystemButtonsPopupShown(getBaseContext()) == false) { + PreferenceHelper.setPageSystemButtonsPopupShown(getBaseContext(), true); + Toast.makeText(getBaseContext(), getString(R.string.long_click_for_more_options), + Toast.LENGTH_LONG).show(); } } - /** - * Parses the intent - */ - private void parseIntent(Intent intent) { - final String action = intent.getAction(); - final String type = intent.getType(); + @Override + public void prevPageClicked() { + pageSystem.savePage(mEditor.getText().toString()); + pageSystem.prevPage(); + mEditor.disableTextChangedListener(); + mEditor.replaceTextKeepCursor(pageSystem.getCurrentPageText(), false); + mEditor.enableTextChangedListener(); - if (Intent.ACTION_VIEW.equals(action) - || Intent.ACTION_EDIT.equals(action) - || Intent.ACTION_PICK.equals(action) - && type != null) { - // Post event - EventBus.getDefault().postSticky(new EventBusEvents.NewFileToOpen(new File(intent.getData().getPath()))); - EventBus.getDefault().postSticky(new EventBusEvents.AFileIsSelected(intent.getData().getPath())); - } else if (Intent.ACTION_SEND.equals(action) && type != null) { - if ("text/plain".equals(type)) { - onEvent(new EventBusEvents.NewFileToOpen(intent.getStringExtra(Intent.EXTRA_TEXT))); + verticalScroll.postDelayed(new Runnable() { + @Override + public void run() { + verticalScroll.smoothScrollTo(0, 0); + } + }, 200); + + if (PreferenceHelper.getPageSystemButtonsPopupShown(getBaseContext()) == false) { + PreferenceHelper.setPageSystemButtonsPopupShown(getBaseContext(), true); + Toast.makeText(getBaseContext(), getString(R.string.long_click_for_more_options), + Toast.LENGTH_LONG).show(); + } + } + + @Override + public void pageSystemButtonLongClicked() { + int maxPages = pageSystem.getMaxPage(); + int currentPage = pageSystem.getCurrentPage(); + SeekbarDialog dialogFrag = SeekbarDialog.newInstance + (SeekbarDialog.Actions.SelectPage, 0, currentPage, maxPages); + + dialogFrag.show(getFragmentManager().beginTransaction(), "dialog"); + } + + @Override + public boolean canReadNextPage() { + return pageSystem.canReadNextPage(); + } + + @Override + public boolean canReadPrevPage() { + return pageSystem.canReadPrevPage(); + } + + @Override + public void onSearchDone(SearchResult searchResult) { + this.searchResult = searchResult; + searchingText = true; + invalidateOptionsMenu(); + + final int line = mEditor.getLineUtils().getLineFromIndex(searchResult.foundIndex.getFirst + (), mEditor.getLineCount(), mEditor.getLayout()); + verticalScroll.post(new Runnable() { + @Override + public void run() { + int y = mEditor.getLayout().getLineTop(line); + if (y > 100) + y -= 100; + else + y = 0; + + verticalScroll.scrollTo(0, y); + } + }); + + mEditor.setSelection(searchResult.foundIndex.getFirst(), searchResult.foundIndex.getFirst + () + searchResult.textLength); + + } + + @Override + public void onPageChanged(int page) { + pageSystemButtons.updateVisibility(false); + searchingText = false; + mEditor.clearHistory(); + invalidateOptionsMenu(); + } + + @Override + public void onScrollChanged(int l, int t, int oldl, int oldt) { + pageSystemButtons.updateVisibility(Math.abs(t) > 10); + + if (!PreferenceHelper.getSyntaxHiglight(getBaseContext()) || (mEditor.hasSelection() && + !searchingText) || updateHandler == null || colorRunnable_duringScroll == null) + return; + + updateHandler.removeCallbacks(colorRunnable_duringEditing); + updateHandler.removeCallbacks(colorRunnable_duringScroll); + updateHandler.postDelayed(colorRunnable_duringScroll, SYNTAX_DELAY_MILLIS_SHORT); + } + + @Override + public void onSeekbarDialogDismissed(SeekbarDialog.Actions action, int value) { + if (action == SeekbarDialog.Actions.SelectPage) { + pageSystem.savePage(mEditor.getText().toString()); + pageSystem.goToPage(value); + mEditor.disableTextChangedListener(); + mEditor.replaceTextKeepCursor(pageSystem.getCurrentPageText(), true); + mEditor.enableTextChangedListener(); + + verticalScroll.postDelayed(new Runnable() { + @Override + public void run() { + verticalScroll.smoothScrollTo(0, 0); + } + }, 200); + + } else if (action == SeekbarDialog.Actions.GoToLine) { + + int fakeLine = mEditor.getLineUtils().fakeLineFromRealLine(value); + final int y = mEditor.getLineUtils().getYAtLine(verticalScroll, + mEditor.getLineCount(), fakeLine); + + verticalScroll.postDelayed(new Runnable() { + @Override + public void run() { + verticalScroll.smoothScrollTo(0, y); + } + }, 200); + } + + } + + @Override + public void userDoesntWantToSave(boolean openNewFile, String pathOfNewFile) { + mEditor.canSaveFile = false; + if(openNewFile) + onEvent(new EventBusEvents.NewFileToOpen(new File(pathOfNewFile))); + } + + //endregion + + public static class Editor extends EditText { + + private final TextPaint mPaintNumbers = new TextPaint(); + /** + * The edit history. + */ + private final EditHistory mEditHistory; + /** + * The change listener. + */ + private final EditTextChangeListener + mChangeListener; + /** + * Disconnect this undo/redo from the text + * view. + */ + private boolean enabledChangeListener; + //region VARIABLES + private int lineCount, realLine, startingLine; + private LineUtils lineUtils; + private boolean modified; + /** + * Is undo/redo being performed? This member + * signals if an undo/redo operation is + * currently being performed. Changes in the + * text during undo/redo are not recorded + * because it would mess up the undo history. + */ + private boolean mIsUndoOrRedo; + private Matcher m; + private boolean mShowUndo, mShowRedo; + private boolean canSaveFile; + private KeyListener keyListener; + private int firstVisibleIndex, firstColoredIndex; + private int deviceHeight; + //endregion + + //region CONSTRUCTOR + public Editor(final Context context, AttributeSet attrs) { + super(context, attrs); + mEditHistory = new EditHistory(); + mChangeListener = new EditTextChangeListener(); + lineUtils = new LineUtils(); + + deviceHeight = getResources().getDisplayMetrics().heightPixels; + + this.mPaintNumbers.setAntiAlias(true); + this.mPaintNumbers.setDither(false); + this.mPaintNumbers.setTextAlign(Paint.Align.RIGHT); + + // Syntax editor + /*setFilters(new InputFilter[]{ + new InputFilter() { + @Override + public CharSequence filter( + CharSequence source, + int start, + int end, + Spanned dest, + int dstart, + int dend) { + if (modified) { + return autoIndent( + source, + start, + end, + dest, + dstart, + dend); + } + + return source; + } + }});*/ + + if (PreferenceHelper.getLightTheme(getContext())) { + setTextColor(getResources().getColor(R.color.textColorInverted)); + } else { + setTextColor(getResources().getColor(R.color.textColor)); + } + if (PreferenceHelper.getLineNumbers(getContext())) { + setPadding(EditTextPadding.getPaddingWithLineNumbers(getContext(), + PreferenceHelper.getFontSize(getContext())), + EditTextPadding.getPaddingTop(getContext()), 0, 0); + } else { + setPadding(EditTextPadding.getPaddingWithoutLineNumbers(getContext()), + EditTextPadding.getPaddingTop(getContext()), 0, 0); + } + + if (PreferenceHelper.getReadOnly(getContext())) { + setReadOnly(true); + } else { + setReadOnly(false); + if (PreferenceHelper.getSuggestionActive(getContext())) { + setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE + | InputType.TYPE_TEXT_FLAG_IME_MULTI_LINE); + } else { + setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE + | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | InputType + .TYPE_TEXT_VARIATION_VISIBLE_PASSWORD | InputType + .TYPE_TEXT_FLAG_IME_MULTI_LINE); + } + } + + if (PreferenceHelper.getUseMonospace(getContext())) { + setTypeface(Typeface.MONOSPACE); + } else { + setTypeface(Typeface.DEFAULT); + } + setTextSize(PreferenceHelper.getFontSize(getContext())); + + setFocusable(true); + setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (!PreferenceHelper.getReadOnly(getContext())) { + ((BaseHomeActivity) getContext()).verticalScroll.tempDisableListener(1000); + ((InputMethodManager) getContext().getSystemService(Context + .INPUT_METHOD_SERVICE)) + .showSoftInput(Editor.this, InputMethodManager.SHOW_IMPLICIT); + } + + } + }); + setOnFocusChangeListener(new View.OnFocusChangeListener() { + @Override + public void onFocusChange(View v, boolean hasFocus) { + if (hasFocus && !PreferenceHelper.getReadOnly(getContext())) { + ((BaseHomeActivity) getContext()).verticalScroll.tempDisableListener(1000); + ((InputMethodManager) getContext().getSystemService(Context + .INPUT_METHOD_SERVICE)) + .showSoftInput(Editor.this, InputMethodManager.SHOW_IMPLICIT); + } + } + }); + + setMaxHistorySize(30); + + resetVariables(); + } + + /*private CharSequence autoIndent( + CharSequence source, + int start, + int end, + Spanned dest, + int dstart, + int dend) { + if (end - start != 1 || + start >= source.length() || + source.charAt(start) != '\n' || + dstart >= dest.length()) { + return source; + } + + int istart = dstart; + int iend; + String indent = ""; + + // skip end of line if cursor is at the end of a line + if (dest.charAt(istart) == '\n') { + --istart; + } + + // indent next line if this one isn't terminated + if (istart > -1) { + // skip white space + for (; istart > -1; --istart) { + char c = dest.charAt(istart); + + if (c != ' ' && + c != '\t') { + break; + } + } + + if (istart > -1) { + char c = dest.charAt(istart); + + if (c != ';' && + c != '\n') { + indent = "\t"; + } + } + } + + // find start of previous line + for (; istart > -1; --istart) { + if (dest.charAt(istart) == '\n') { + break; + } + } + + // cursor is in the first line + if (istart < 0) { + return source; + } + + // span over previous indent + for (iend = ++istart; + iend < dend; + ++iend) { + char c = dest.charAt(iend); + + if (c != ' ' && + c != '\t') { + break; + } + } + + // copy white space of previous lines and append new indent + return "\n" + dest.subSequence( + istart, + iend) + indent; + }*/ + + public void setReadOnly(boolean value) { + if (value) { + keyListener = getKeyListener(); + setKeyListener(null); + } else { + if (keyListener != null) + setKeyListener(keyListener); } } - } - public abstract void displayInterstitial(); + //region OVERRIDES + @Override + public void setTextSize(float size) { + super.setTextSize(size); + final float scale = getContext().getResources().getDisplayMetrics().density; + this.mPaintNumbers.setTextSize((int) (size * scale * 0.65f)); + } + + @Override + public void setTextColor(int color) { + super.setTextColor(color); + //this.mPaintNumbers.setColor(getTextColors().getDefaultColor()); + this.mPaintNumbers.setColor(getResources().getColor(R.color.file_text)); + } + + @Override + public void onDraw(final Canvas canvas) { + + if (PreferenceHelper.getLineNumbers(getContext())) { + if (lineCount != getLineCount() || startingLine != ((BaseHomeActivity) getContext + ()).pageSystem.getStartingLine()) { + startingLine = ((BaseHomeActivity) getContext()).pageSystem.getStartingLine(); + lineCount = getLineCount(); + + lineUtils.updateHasNewLineArray(((BaseHomeActivity) getContext()).pageSystem + .getStartingLine(), lineCount, getLayout(), getText().toString()); + } + + int editorHeight = getHeight(); + int i = lineUtils.getFirstVisibleLine(((BaseHomeActivity) getContext()) + .verticalScroll, editorHeight, lineCount); + int lastLine = lineUtils.getLastVisibleLine(((BaseHomeActivity) getContext()) + .verticalScroll, editorHeight, lineCount, deviceHeight); + boolean[] hasNewLineArray = lineUtils.getToCountLinesArray(); + int[] realLines = lineUtils.getRealLines(); + boolean wrapContent = PreferenceHelper.getWrapContent(getContext()); + int numbersWidth = (int) (EditTextPadding.getPaddingWithLineNumbers(getContext(), + PreferenceHelper.getFontSize(getContext())) * 0.8); + int paddingTop = EditTextPadding.getPaddingTop(getContext()); + + while (i < lastLine) { + // if last line we count it anyway + if (!wrapContent + || hasNewLineArray[i] + || i == lastLine - 1) { + if (i == lastLine - 1) + realLine = realLines[i] + 1; + else + realLine = realLines[i]; + + canvas.drawText(String.valueOf(realLine), + numbersWidth, // they all center aligned + paddingTop + getLineHeight() * (i + 1), + mPaintNumbers); + } + i++; + } + } + + + super.onDraw(canvas); + } + + + //endregion + + //region Other + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + + if (event.isCtrlPressed()) { + switch (keyCode) { + case KeyEvent.KEYCODE_A: + return onTextContextMenuItem(ID_SELECT_ALL); + case KeyEvent.KEYCODE_X: + return onTextContextMenuItem(ID_CUT); + case KeyEvent.KEYCODE_C: + return onTextContextMenuItem(ID_COPY); + case KeyEvent.KEYCODE_V: + return onTextContextMenuItem(ID_PASTE); + case KeyEvent.KEYCODE_Z: + if (getCanUndo()) { + return onTextContextMenuItem(ID_UNDO); + } + case KeyEvent.KEYCODE_Y: + if (getCanRedo()) { + return onTextContextMenuItem(ID_REDO); + } + case KeyEvent.KEYCODE_S: + ((BaseHomeActivity) getContext()).saveTheFile(); + return true; + default: + return super.onKeyDown(keyCode, event); + } + } else { + switch (keyCode) { + case KeyEvent.KEYCODE_TAB: + String textToInsert = " "; + int start, end; + start = Math.max(getSelectionStart(), 0); + end = Math.max(getSelectionEnd(), 0); + getText().replace(Math.min(start, end), Math.max(start, end), + textToInsert, 0, textToInsert.length()); + return true; + default: + return super.onKeyDown(keyCode, event); + } + } + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + if (event.isCtrlPressed()) { + switch (keyCode) { + case KeyEvent.KEYCODE_A: + case KeyEvent.KEYCODE_X: + case KeyEvent.KEYCODE_C: + case KeyEvent.KEYCODE_V: + case KeyEvent.KEYCODE_Z: + case KeyEvent.KEYCODE_Y: + case KeyEvent.KEYCODE_S: + return true; + default: + return false; + } + } else { + switch (keyCode) { + case KeyEvent.KEYCODE_TAB: + return true; + default: + return false; + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean onTextContextMenuItem( + final int id) { + if (id == ID_UNDO) { + undo(); + return true; + } else if (id == ID_REDO) { + redo(); + return true; + } else { + return super.onTextContextMenuItem(id); + } + } + + /** + * Can undo be performed? + */ + public boolean getCanUndo() { + return (mEditHistory.mmPosition > 0); + } + + /** + * Can redo be performed? + */ + public boolean getCanRedo() { + return (mEditHistory.mmPosition + < mEditHistory.mmHistory.size()); + } + + /** + * Perform undo. + */ + public void undo() { + EditItem edit = mEditHistory.getPrevious(); + if (edit == null) { + return; + } + + Editable text = getEditableText(); + int start = edit.mmStart; + int end = start + (edit.mmAfter != null + ? edit.mmAfter.length() : 0); + + mIsUndoOrRedo = true; + text.replace(start, end, edit.mmBefore); + mIsUndoOrRedo = false; + + // This will get rid of underlines inserted when editor tries to come + // up with a suggestion. + for (Object o : text.getSpans(0, + text.length(), UnderlineSpan.class)) { + text.removeSpan(o); + } + + Selection.setSelection(text, + edit.mmBefore == null ? start + : (start + edit.mmBefore.length())); + } + + /** + * Perform redo. + */ + public void redo() { + EditItem edit = mEditHistory.getNext(); + if (edit == null) { + return; + } + + Editable text = getEditableText(); + int start = edit.mmStart; + int end = start + (edit.mmBefore != null + ? edit.mmBefore.length() : 0); + + mIsUndoOrRedo = true; + text.replace(start, end, edit.mmAfter); + mIsUndoOrRedo = false; + + // This will get rid of underlines inserted when editor tries to come + // up with a suggestion. + for (Object o : text.getSpans(0, + text.length(), UnderlineSpan.class)) { + text.removeSpan(o); + } + + Selection.setSelection(text, + edit.mmAfter == null ? start + : (start + edit.mmAfter.length())); + } + + /** + * Set the maximum history size. If size is + * negative, then history size is only limited + * by the device memory. + */ + public void setMaxHistorySize( + int maxHistorySize) { + mEditHistory.setMaxHistorySize( + maxHistorySize); + } + + public void resetVariables() { + mEditHistory.clear(); + enabledChangeListener = false; + lineCount = 0; + realLine = 0; + startingLine = 0; + modified = true; + mIsUndoOrRedo = false; + mShowUndo = false; + mShowRedo = false; + canSaveFile = false; + firstVisibleIndex = 0; + firstColoredIndex = 0; + } + + public boolean canSaveFile() { + return canSaveFile; + } + + public void fileSaved() { + canSaveFile = false; + } + + public void replaceTextKeepCursor(String textToUpdate, boolean mantainCursorPos) { + + int cursorPos; + int cursorPosEnd; + if (textToUpdate != null) { + cursorPos = 0; + cursorPosEnd = 0; + } else { + cursorPos = getSelectionStart(); + cursorPosEnd = getSelectionEnd(); + } + disableTextChangedListener(); + modified = false; + + if (PreferenceHelper.getSyntaxHiglight(getContext())) + setText(highlight(textToUpdate == null ? getEditableText() : Editable.Factory + .getInstance().newEditable(textToUpdate), textToUpdate != null)); + else + setText(textToUpdate == null ? getText().toString() : textToUpdate); + + modified = true; + enableTextChangedListener(); + + if (mantainCursorPos) + firstVisibleIndex = cursorPos; + + if (firstVisibleIndex > -1) { + if (cursorPosEnd != cursorPos) + setSelection(cursorPos, cursorPosEnd); + else + setSelection(firstVisibleIndex); + } + } + //endregion + + //region UNDO REDO + + public void disableTextChangedListener() { + enabledChangeListener = false; + removeTextChangedListener(mChangeListener); + } + + public CharSequence highlight(Editable editable, boolean newText) { + final String fileExtension = FilenameUtils.getExtension(((BaseHomeActivity) + getContext()).sFilePath).toLowerCase(); + editable.clearSpans(); + + if (editable.length() == 0) { + return editable; + } + + int end; + int height = getHeight(); + + if (newText == false && height > 0) { + firstVisibleIndex = getLayout().getLineStart(getLineUtils().getFirstVisibleLine(( + (BaseHomeActivity) getContext()).verticalScroll, height, getLineCount())); + end = getLayout().getLineStart(getLineUtils().getLastVisibleLine(( + (BaseHomeActivity) getContext()).verticalScroll, height, getLineCount(), + deviceHeight)); + } else { + firstVisibleIndex = 0; + end = CHARS_TO_COLOR; + } + + firstColoredIndex = firstVisibleIndex - (CHARS_TO_COLOR / 5); + + // normalize + if (firstColoredIndex < 0) + firstColoredIndex = 0; + if (end > editable.length()) + end = editable.length(); + if (firstColoredIndex > end) + firstColoredIndex = end; + + + CharSequence textToHighlight = editable.subSequence(firstColoredIndex, end); + + if (fileExtension.contains("htm") + || fileExtension.contains("xml")) { + color(Patterns.HTML_OPEN_TAGS, editable, textToHighlight, firstColoredIndex); + color(Patterns.HTML_CLOSE_TAGS, editable, textToHighlight, firstColoredIndex); + color(Patterns.HTML_ATTRS, editable, textToHighlight, firstColoredIndex); + color(Patterns.GENERAL_STRINGS, editable, textToHighlight, firstColoredIndex); + color(Patterns.XML_COMMENTS, editable, textToHighlight, firstColoredIndex); + } else if (fileExtension.equals("css")) { + //color(CSS_STYLE_NAME, editable); + color(Patterns.CSS_ATTRS, editable, textToHighlight, firstColoredIndex); + color(Patterns.CSS_ATTR_VALUE, editable, textToHighlight, firstColoredIndex); + color(Patterns.SYMBOLS, editable, textToHighlight, firstColoredIndex); + color(Patterns.GENERAL_COMMENTS, editable, textToHighlight, firstColoredIndex); + } else if (Arrays.asList(MimeTypes.MIME_CODE).contains(fileExtension)) { + if (fileExtension.equals("lua")) + color(Patterns.LUA_KEYWORDS, editable, textToHighlight, firstColoredIndex); + else if (fileExtension.equals("py")) + color(Patterns.PY_KEYWORDS, editable, textToHighlight, firstColoredIndex); + else + color(Patterns.GENERAL_KEYWORDS, editable, textToHighlight, firstColoredIndex); + color(Patterns.NUMBERS, editable, textToHighlight, firstColoredIndex); + color(Patterns.SYMBOLS, editable, textToHighlight, firstColoredIndex); + color(Patterns.GENERAL_STRINGS, editable, textToHighlight, firstColoredIndex); + color(Patterns.GENERAL_COMMENTS, editable, textToHighlight, firstColoredIndex); + if (fileExtension.equals("php")) + color(Patterns.PHP_VARIABLES, editable, textToHighlight, firstColoredIndex); + } else if (Arrays.asList(MimeTypes.MIME_SQL).contains(fileExtension)) { + color(Patterns.SYMBOLS, editable, textToHighlight, firstColoredIndex); + color(Patterns.GENERAL_STRINGS, editable, textToHighlight, firstColoredIndex); + color(Patterns.SQL_KEYWORDS, editable, textToHighlight, firstColoredIndex); + } else { + if (!fileExtension.contains("md")) + color(Patterns.GENERAL_KEYWORDS, editable, textToHighlight, firstColoredIndex); + color(Patterns.NUMBERS, editable, textToHighlight, firstColoredIndex); + color(Patterns.SYMBOLS, editable, textToHighlight, firstColoredIndex); + color(Patterns.GENERAL_STRINGS, editable, textToHighlight, firstColoredIndex); + if (fileExtension.equals("prop") || fileExtension.contains("conf") || + fileExtension.contains("md")) + color(Patterns.GENERAL_COMMENTS_NO_SLASH, editable, textToHighlight, + firstColoredIndex); + else + color(Patterns.GENERAL_COMMENTS, editable, textToHighlight, firstColoredIndex); + + if (fileExtension.contains("md")) + color(Patterns.LINK, editable, textToHighlight, firstColoredIndex); + } + + return editable; + } + + public void enableTextChangedListener() { + if (!enabledChangeListener) { + addTextChangedListener(mChangeListener); + enabledChangeListener = true; + } + } + + public LineUtils getLineUtils() { + return lineUtils; + } + + private void color(Pattern pattern, + Editable allText, + CharSequence textToHighlight, + int start) { + int color = 0; + if (pattern.equals(Patterns.HTML_OPEN_TAGS) + || pattern.equals(Patterns.HTML_CLOSE_TAGS) + || pattern.equals(Patterns.GENERAL_KEYWORDS) + || pattern.equals(Patterns.SQL_KEYWORDS) + || pattern.equals(Patterns.PY_KEYWORDS) + || pattern.equals(Patterns.LUA_KEYWORDS) + + ) { + color = getResources().getColor(R.color.syntax_keyword); + } else if (pattern.equals(Patterns.HTML_ATTRS) + || pattern.equals(Patterns.CSS_ATTRS) + || pattern.equals(Patterns.LINK)) { + color = getResources().getColor(R.color.syntax_attr); + } else if (pattern.equals(Patterns.CSS_ATTR_VALUE)) { + color = getResources().getColor(R.color.syntax_attr_value); + } else if (pattern.equals(Patterns.XML_COMMENTS) + || pattern.equals(Patterns.GENERAL_COMMENTS) + || pattern.equals(Patterns.GENERAL_COMMENTS_NO_SLASH)) { + color = getResources().getColor(R.color.syntax_comment); + } else if (pattern.equals(Patterns.GENERAL_STRINGS)) { + color = getResources().getColor(R.color.syntax_string); + } else if (pattern.equals(Patterns.NUMBERS) || pattern.equals(Patterns.SYMBOLS)) { + color = getResources().getColor(R.color.syntax_number); + } else if (pattern.equals(Patterns.PHP_VARIABLES)) { + color = getResources().getColor(R.color.syntax_variable); + } + + m = pattern.matcher(textToHighlight); + + while (m.find()) { + allText.setSpan( + new ForegroundColorSpan(color), + start + m.start(), + start + m.end(), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } + + /** + * Clear history. + */ + public void clearHistory() { + mEditHistory.clear(); + mShowUndo = getCanUndo(); + mShowRedo = getCanRedo(); + } + + /** + * Store preferences. + */ + public void storePersistentState( + SharedPreferences.Editor editor, + String prefix) { + // Store hash code of text in the editor so that we can check if the + // editor contents has changed. + editor.putString(prefix + ".hash", + String.valueOf( + getText().toString().hashCode())); + editor.putInt(prefix + ".maxSize", + mEditHistory.mmMaxHistorySize); + editor.putInt(prefix + ".position", + mEditHistory.mmPosition); + editor.putInt(prefix + ".size", + mEditHistory.mmHistory.size()); + + int i = 0; + for (EditItem ei : mEditHistory.mmHistory) { + String pre = prefix + "." + i; + + editor.putInt(pre + ".start", ei.mmStart); + editor.putString(pre + ".before", + ei.mmBefore.toString()); + editor.putString(pre + ".after", + ei.mmAfter.toString()); + + i++; + } + } + + /** + * Restore preferences. + * + * @param prefix The preference key prefix + * used when state was stored. + * @return did restore succeed? If this is + * false, the undo history will be empty. + */ + public boolean restorePersistentState( + SharedPreferences sp, String prefix) + throws IllegalStateException { + + boolean ok = + doRestorePersistentState(sp, prefix); + if (!ok) { + mEditHistory.clear(); + } + + return ok; + } + + private boolean doRestorePersistentState( + SharedPreferences sp, String prefix) { + + String hash = + sp.getString(prefix + ".hash", null); + if (hash == null) { + // No state to be restored. + return true; + } + + if (Integer.valueOf(hash) + != getText().toString().hashCode()) { + return false; + } + + mEditHistory.clear(); + mEditHistory.mmMaxHistorySize = + sp.getInt(prefix + ".maxSize", -1); + + int count = sp.getInt(prefix + ".size", -1); + if (count == -1) { + return false; + } + + for (int i = 0; i < count; i++) { + String pre = prefix + "." + i; + + int start = sp.getInt(pre + ".start", -1); + String before = + sp.getString(pre + ".before", null); + String after = + sp.getString(pre + ".after", null); + + if (start == -1 + || before == null + || after == null) { + return false; + } + mEditHistory.add( + new EditItem(start, before, after)); + } + + mEditHistory.mmPosition = + sp.getInt(prefix + ".position", -1); + return mEditHistory.mmPosition != -1; + + } + + /** + * Class that listens to changes in the text. + */ + private final class EditTextChangeListener + implements TextWatcher { + + /** + * The text that will be removed by the + * change event. + */ + private CharSequence mBeforeChange; + + /** + * The text that was inserted by the change + * event. + */ + private CharSequence mAfterChange; + + public void beforeTextChanged( + CharSequence s, int start, int count, + int after) { + if (mIsUndoOrRedo) { + return; + } + + mBeforeChange = + s.subSequence(start, start + count); + } + + public void onTextChanged(CharSequence s, + int start, int before, + int count) { + if (mIsUndoOrRedo) { + return; + } + + mAfterChange = + s.subSequence(start, start + count); + mEditHistory.add( + new EditItem(start, mBeforeChange, + mAfterChange)); + } + + public void afterTextChanged(Editable s) { + boolean showUndo = getCanUndo(); + boolean showRedo = getCanRedo(); + if (!canSaveFile) + canSaveFile = getCanUndo(); + if (showUndo != mShowUndo || showRedo != mShowRedo) { + mShowUndo = showUndo; + mShowRedo = showRedo; + ((BaseHomeActivity) getContext()).invalidateOptionsMenu(); + } + + ((BaseHomeActivity) getContext()).updateTextSyntax(); + } + } + + //endregion + + //region EDIT HISTORY + + /** + * Keeps track of all the edit history of a + * text. + */ + private final class EditHistory { + + /** + * The list of edits in chronological + * order. + */ + private final LinkedList + mmHistory = new LinkedList<>(); + /** + * The position from which an EditItem will + * be retrieved when getNext() is called. If + * getPrevious() has not been called, this + * has the same value as mmHistory.size(). + */ + private int mmPosition = 0; + /** + * Maximum undo history size. + */ + private int mmMaxHistorySize = -1; + + private int size() { + return mmHistory.size(); + } + + /** + * Clear history. + */ + private void clear() { + mmPosition = 0; + mmHistory.clear(); + } + + /** + * Adds a new edit operation to the history + * at the current position. If executed + * after a call to getPrevious() removes all + * the future history (elements with + * positions >= current history position). + */ + private void add(EditItem item) { + while (mmHistory.size() > mmPosition) { + mmHistory.removeLast(); + } + mmHistory.add(item); + mmPosition++; + + if (mmMaxHistorySize >= 0) { + trimHistory(); + } + } + + /** + * Trim history when it exceeds max history + * size. + */ + private void trimHistory() { + while (mmHistory.size() + > mmMaxHistorySize) { + mmHistory.removeFirst(); + mmPosition--; + } + + if (mmPosition < 0) { + mmPosition = 0; + } + } + + /** + * Set the maximum history size. If size is + * negative, then history size is only + * limited by the device memory. + */ + private void setMaxHistorySize( + int maxHistorySize) { + mmMaxHistorySize = maxHistorySize; + if (mmMaxHistorySize >= 0) { + trimHistory(); + } + } + + /** + * Traverses the history backward by one + * position, returns and item at that + * position. + */ + private EditItem getPrevious() { + if (mmPosition == 0) { + return null; + } + mmPosition--; + return mmHistory.get(mmPosition); + } + + /** + * Traverses the history forward by one + * position, returns and item at that + * position. + */ + private EditItem getNext() { + if (mmPosition >= mmHistory.size()) { + return null; + } + + EditItem item = mmHistory.get(mmPosition); + mmPosition++; + return item; + } + } + + /** + * Represents the changes performed by a + * single edit operation. + */ + private final class EditItem { + private final int mmStart; + private final CharSequence mmBefore; + private final CharSequence mmAfter; + + /** + * Constructs EditItem of a modification + * that was applied at position start and + * replaced CharSequence before with + * CharSequence after. + */ + public EditItem(int start, + CharSequence before, CharSequence after) { + mmStart = start; + mmBefore = before; + mmAfter = after; + } + } + //endregion + + + } } diff --git a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/activity/LicensesActivity.java b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/activity/LicensesActivity.java index 62285c1..ebaf9a7 100644 --- a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/activity/LicensesActivity.java +++ b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/activity/LicensesActivity.java @@ -30,19 +30,14 @@ import android.widget.ListView; import android.widget.TextView; import sharedcode.turboeditor.R; -import sharedcode.turboeditor.preferences.PreferenceHelper; +import sharedcode.turboeditor.util.ThemeUtils; public class LicensesActivity extends Activity implements AdapterView.OnItemClickListener { @Override protected void onCreate(Bundle savedInstanceState) { - boolean light = PreferenceHelper.getLightTheme(this); - if (light) { - setTheme(R.style.AppTheme_Light); - } else { - setTheme(R.style.AppTheme_Dark); - } + ThemeUtils.setTheme(this); super.onCreate(savedInstanceState); setContentView(R.layout.activity_licenses); diff --git a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/activity/MyApp.java b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/activity/MyApp.java index 5116d24..45f36b8 100644 --- a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/activity/MyApp.java +++ b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/activity/MyApp.java @@ -33,7 +33,7 @@ public class MyApp extends Application { try { ViewConfiguration config = ViewConfiguration.get(this); Field menuKeyField = ViewConfiguration.class.getDeclaredField("sHasPermanentMenuKey"); - if(menuKeyField != null) { + if (menuKeyField != null) { menuKeyField.setAccessible(true); menuKeyField.setBoolean(config, false); } diff --git a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/activity/PreferenceAbout.java b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/activity/PreferenceAbout.java deleted file mode 100644 index ac2867a..0000000 --- a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/activity/PreferenceAbout.java +++ /dev/null @@ -1,238 +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 . - */ - -package sharedcode.turboeditor.activity; - -import android.app.Activity; -import android.app.PendingIntent; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.content.pm.PackageManager; -import android.net.Uri; -import android.os.Bundle; -import android.os.IBinder; -import android.view.View; -import android.widget.TextView; - -import com.android.vending.billing.IInAppBillingService; - -import org.json.JSONException; -import org.json.JSONObject; -import org.solovyev.android.checkout.ActivityCheckout; -import org.solovyev.android.checkout.Checkout; -import org.solovyev.android.checkout.Inventory; -import org.solovyev.android.checkout.Products; - -import java.util.ArrayList; -import java.util.Arrays; - -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.setText(ProCheckUtils.isPro(getBaseContext()) ? getString(R.string.donate) : getString(R.string.pro_version)); - - Intent serviceIntent = new Intent("com.android.vending.billing.InAppBillingService.BIND"); - serviceIntent.setPackage("com.android.vending"); - bindService(serviceIntent, mServiceConn, Context.BIND_AUTO_CREATE); - } - - IInAppBillingService mService; - - ServiceConnection mServiceConn = new ServiceConnection() { - @Override - public void onServiceDisconnected(ComponentName name) { - mService = null; - } - - @Override - public void onServiceConnected(ComponentName name, - IBinder service) { - mService = IInAppBillingService.Stub.asInterface(service); - } - }; - - @Override - public void onDestroy() { - super.onDestroy(); - if (mService != null) { - unbindService(mServiceConn); - } - } - - 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 { - if(ProCheckUtils.isPro(getBaseContext())) { - startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=26VWS2TSAMUJA")) - .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); - } else { - //startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=com.maskyn.fileeditorpro")) - // .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); - - Bundle buyIntentBundle = mService.getBuyIntent(3, getPackageName(), - "fileeditor.proversion", "inapp", "bGoa+V7g/yqDXvKRqq+JTFn4uQZbPiQJo4pf9RzJ"); - - PendingIntent pendingIntent = buyIntentBundle.getParcelable("BUY_INTENT"); - - startIntentSenderForResult(pendingIntent.getIntentSender(), - 1001, new Intent(), Integer.valueOf(0), Integer.valueOf(0), - Integer.valueOf(0)); - } - - } catch (Exception e) { - } - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - if (requestCode == 1001) { - int responseCode = data.getIntExtra("RESPONSE_CODE", 0); - String purchaseData = data.getStringExtra("INAPP_PURCHASE_DATA"); - String dataSignature = data.getStringExtra("INAPP_DATA_SIGNATURE"); - - if (resultCode == RESULT_OK) { - try { - JSONObject jo = new JSONObject(purchaseData); - String sku = jo.getString("productId"); - //alert("You have bought the " + sku + ". Excellent choice, - // adventurer!"); - } - catch (JSONException e) { - //alert("Failed to parse purchase data."); - e.printStackTrace(); - } - } - } - } - - public void OpenGithub(View view) { - String url = "http://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 = "http://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; - } - }); - } - }*/ -} diff --git a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/activity/SelectFileActivity.java b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/activity/SelectFileActivity.java index 97ef712..63cbf94 100644 --- a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/activity/SelectFileActivity.java +++ b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/activity/SelectFileActivity.java @@ -24,8 +24,6 @@ import android.content.Intent; import android.os.AsyncTask; import android.os.Bundle; import android.support.v4.view.MenuItemCompat; -import android.widget.PopupMenu; -import android.widget.SearchView; import android.text.TextUtils; import android.view.Menu; import android.view.MenuItem; @@ -33,6 +31,8 @@ import android.view.View; import android.widget.AdapterView; import android.widget.Filter; import android.widget.ListView; +import android.widget.PopupMenu; +import android.widget.SearchView; import android.widget.TextView; import android.widget.Toast; @@ -54,13 +54,14 @@ import java.util.concurrent.TimeoutException; import sharedcode.turboeditor.R; import sharedcode.turboeditor.adapter.AdapterDetailedList; -import sharedcode.turboeditor.fragment.EditDialogFragment; +import sharedcode.turboeditor.fragment.EditTextDialog; import sharedcode.turboeditor.preferences.PreferenceHelper; +import sharedcode.turboeditor.root.RootUtils; import sharedcode.turboeditor.util.AlphanumComparator; -import sharedcode.turboeditor.util.Constants; -import sharedcode.turboeditor.util.RootUtils; +import sharedcode.turboeditor.util.Build; +import sharedcode.turboeditor.util.ThemeUtils; -public class SelectFileActivity extends Activity implements SearchView.OnQueryTextListener, AdapterView.OnItemClickListener, EditDialogFragment.EditDialogListener { +public class SelectFileActivity extends Activity implements SearchView.OnQueryTextListener, AdapterView.OnItemClickListener, EditTextDialog.EditDialogListener { private String currentFolder = PreferenceHelper.SD_CARD_ROOT; private ListView listView; private boolean wantAFile = true; @@ -73,12 +74,7 @@ public class SelectFileActivity extends Activity implements SearchView.OnQueryTe protected void onCreate(Bundle savedInstanceState) { - boolean light = PreferenceHelper.getLightTheme(this); - if (light) { - setTheme(R.style.AppTheme_Light); - } else { - setTheme(R.style.AppTheme_Dark); - } + ThemeUtils.setTheme(this); super.onCreate(savedInstanceState); setContentView(R.layout.activity_select_file); @@ -105,14 +101,14 @@ public class SelectFileActivity extends Activity implements SearchView.OnQueryTe popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { - final EditDialogFragment dialogFrag; + final EditTextDialog dialogFrag; int i = item.getItemId(); if (i == R.id.im_new_file) { - dialogFrag = EditDialogFragment.newInstance(EditDialogFragment.Actions.NewFile); + dialogFrag = EditTextDialog.newInstance(EditTextDialog.Actions.NewFile); dialogFrag.show(getFragmentManager().beginTransaction(), "dialog"); return true; } 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"); return true; } else { @@ -151,7 +147,7 @@ public class SelectFileActivity extends Activity implements SearchView.OnQueryTe } public boolean onQueryTextChange(String newText) { - if(filter == null) + if (filter == null) return true; if (TextUtils.isEmpty(newText)) { @@ -261,11 +257,11 @@ public class SelectFileActivity extends Activity implements SearchView.OnQueryTe @Override - public void onFinishEditDialog(final String inputText, final String hint, final EditDialogFragment.Actions actions) { - if (actions == EditDialogFragment.Actions.NewFile && !TextUtils.isEmpty(inputText)) { + public void onFinishEditDialog(final String inputText, final String hint, final EditTextDialog.Actions actions) { + if (actions == EditTextDialog.Actions.NewFile && !TextUtils.isEmpty(inputText)) { File file = new File(currentFolder, inputText); 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.mkdirs(); new UpdateList().execute(currentFolder); @@ -340,7 +336,7 @@ public class SelectFileActivity extends Activity implements SearchView.OnQueryTe "")); } else if (f.isFile() && !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(); SimpleDateFormat format = new SimpleDateFormat("MMM dd, yyyy hh:mm a"); String date = format.format(f.lastModified()); diff --git a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/adapter/AdapterDrawer.java b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/adapter/AdapterDrawer.java index 9249343..44338bf 100644 --- a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/adapter/AdapterDrawer.java +++ b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/adapter/AdapterDrawer.java @@ -83,8 +83,7 @@ public class AdapterDrawer extends hold.nameLabel.setTypeface(hold.nameLabel.getTypeface(), Typeface.BOLD); convertView.setBackgroundColor((convertView.getResources() .getColor(R.color.item_selected))); - } - else { + } else { hold.nameLabel.setTypeface(hold.nameLabel.getTypeface(), Typeface.NORMAL); convertView.setBackgroundColor((convertView.getResources() .getColor(android.R.color.transparent))); @@ -108,8 +107,7 @@ public class AdapterDrawer extends hold.nameLabel.setTypeface(null, Typeface.BOLD); convertView.setBackgroundColor((convertView.getResources() .getColor(R.color.item_selected))); - } - else { + } else { hold.nameLabel.setTypeface(null, Typeface.NORMAL); convertView.setBackgroundColor((convertView.getResources() .getColor(android.R.color.transparent))); diff --git a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/adapter/AdapterTwoItem.java b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/adapter/AdapterTwoItem.java index 06a01bc..bb2782c 100644 --- a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/adapter/AdapterTwoItem.java +++ b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/adapter/AdapterTwoItem.java @@ -20,18 +20,12 @@ package sharedcode.turboeditor.adapter; import android.content.Context; -import android.graphics.Typeface; -import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; -import android.widget.ImageView; import android.widget.TextView; -import java.io.File; -import java.util.ArrayList; - import sharedcode.turboeditor.R; public class AdapterTwoItem extends @@ -39,11 +33,11 @@ public class AdapterTwoItem extends private final LayoutInflater inflater; private final String[] lines1; - private final String[] lines2; + private final String[] lines2; public AdapterTwoItem(Context context, - String[] lines1, - String[] lines2) { + String[] lines1, + String[] lines2) { super(context, R.layout.item_two_lines, lines1); this.lines1 = lines1; this.lines2 = lines2; diff --git a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/AboutDialog.java b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/AboutDialog.java new file mode 100644 index 0000000..64747b1 --- /dev/null +++ b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/AboutDialog.java @@ -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 . + */ +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("\\-", "-") + ""; + } + } 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(); + } +} diff --git a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/ChangelogDialogFragment.java b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/ChangelogDialog.java similarity index 91% rename from libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/ChangelogDialogFragment.java rename to libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/ChangelogDialog.java index eeef46e..3c6dbe2 100644 --- a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/ChangelogDialogFragment.java +++ b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/ChangelogDialog.java @@ -22,30 +22,30 @@ import android.app.AlertDialog; import android.app.Dialog; import android.app.DialogFragment; import android.app.Fragment; +import android.app.FragmentManager; +import android.app.FragmentTransaction; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.net.Uri; import android.os.Bundle; -import android.app.FragmentManager; -import android.app.FragmentTransaction; import android.view.LayoutInflater; import it.gmariotti.changelibs.library.view.ChangeLogListView; import sharedcode.turboeditor.R; -import sharedcode.turboeditor.util.Constants; +import sharedcode.turboeditor.util.Build; -public class ChangelogDialogFragment extends DialogFragment { +public class ChangelogDialog extends DialogFragment { public static void showChangeLogDialog(FragmentManager fragmentManager) { - ChangelogDialogFragment changelogDialogFragment = new ChangelogDialogFragment(); + ChangelogDialog changelogDialog = new ChangelogDialog(); FragmentTransaction ft = fragmentManager.beginTransaction(); Fragment prev = fragmentManager.findFragmentByTag("changelogdemo_dialog"); if (prev != null) { ft.remove(prev); } ft.addToBackStack(null); - changelogDialogFragment.show(ft, "changelogdemo_dialog"); + changelogDialog.show(ft, "changelogdemo_dialog"); } @@ -72,7 +72,7 @@ public class ChangelogDialogFragment extends DialogFragment { @Override public void onClick(final DialogInterface dialog, final int which) { try { - if (Constants.FOR_AMAZON) { + if (Build.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)); diff --git a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/EditDialogFragment.java b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/EditTextDialog.java similarity index 91% rename from libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/EditDialogFragment.java rename to libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/EditTextDialog.java index 0a0fb89..f7e457d 100644 --- a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/EditDialogFragment.java +++ b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/EditTextDialog.java @@ -34,16 +34,16 @@ import android.widget.TextView; import sharedcode.turboeditor.R; // ... -public class EditDialogFragment extends DialogFragment implements TextView.OnEditorActionListener { +public class EditTextDialog extends DialogFragment implements TextView.OnEditorActionListener { private EditText mEditText; - public static EditDialogFragment newInstance(final Actions action) { - return EditDialogFragment.newInstance(action, ""); + public static EditTextDialog newInstance(final Actions action) { + return EditTextDialog.newInstance(action, ""); } - public static EditDialogFragment newInstance(final Actions action, final String hint) { - final EditDialogFragment f = new EditDialogFragment(); + public static EditTextDialog newInstance(final Actions action, final String hint) { + final EditTextDialog f = new EditTextDialog(); final Bundle args = new Bundle(); args.putSerializable("action", action); args.putString("hint", hint); diff --git a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/EditorFragment.java b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/EditorFragment.java deleted file mode 100644 index de4dcc0..0000000 --- a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/EditorFragment.java +++ /dev/null @@ -1,1642 +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 . - */ - -package sharedcode.turboeditor.fragment; - -import android.app.ActionBar; -import android.app.Fragment; -import android.content.ActivityNotFoundException; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.res.Configuration; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.Typeface; -import android.net.Uri; -import android.os.Bundle; -import android.os.Handler; -import android.text.Editable; -import android.text.InputFilter; -import android.text.InputType; -import android.text.Selection; -import android.text.Spannable; -import android.text.Spanned; -import android.text.TextPaint; -import android.text.TextWatcher; -import android.text.method.KeyListener; -import android.text.style.ForegroundColorSpan; -import android.text.style.UnderlineSpan; -import android.util.AttributeSet; -import android.view.KeyEvent; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.view.WindowManager; -import android.view.inputmethod.InputMethodManager; -import android.widget.EditText; -import android.widget.HorizontalScrollView; -import android.widget.ScrollView; -import android.widget.Toast; - -import com.faizmalkani.floatingactionbutton.FloatingActionButton; -import sharedcode.turboeditor.R; - -import sharedcode.turboeditor.preferences.SettingsFragment; -import sharedcode.turboeditor.util.ApiHelper; -import sharedcode.turboeditor.util.EditorInterface; -import sharedcode.turboeditor.util.EdittextPadding; -import sharedcode.turboeditor.util.EventBusEvents; -import sharedcode.turboeditor.util.LineUtils; -import sharedcode.turboeditor.views.GoodScrollView; -import sharedcode.turboeditor.util.MimeTypes; -import sharedcode.turboeditor.util.PageSystem; -import sharedcode.turboeditor.util.PageSystemButtons; -import sharedcode.turboeditor.util.Patterns; -import sharedcode.turboeditor.preferences.PreferenceHelper; -import sharedcode.turboeditor.util.SaveFileTask; -import sharedcode.turboeditor.util.SearchResult; - -import org.apache.commons.io.FilenameUtils; - -import java.io.File; -import java.io.UnsupportedEncodingException; -import java.util.Arrays; -import java.util.LinkedList; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import de.greenrobot.event.EventBus; - -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.LINE_NUMERS; -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.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; - -public class EditorFragment extends Fragment implements FindTextDialogFragment.SearchDialogInterface, GoodScrollView.ScrollInterface, EditorInterface, PageSystem.PageSystemInterface, PageSystemButtons.PageButtonsInterface, SeekbarDialogFragment.onSeekbarDialogDismissed { - - //region VARIABLES - private GoodScrollView verticalScroll; - private String sFilePath; - private Editor mEditor; - private HorizontalScrollView horizontalScroll; - private boolean searchingText; - private SearchResult searchResult; - private PageSystem pageSystem; - private PageSystemButtons pageSystemButtons; - private String currentEncoding; - - private static final int SYNTAX_DELAY_MILLIS_SHORT = 350; - private static final int SYNTAX_DELAY_MILLIS_LONG = 1500; - static final int - ID_SELECT_ALL = android.R.id.selectAll; - static final int ID_CUT = android.R.id.cut; - static final int ID_COPY = android.R.id.copy; - static final int ID_PASTE = android.R.id.paste; - private static final int ID_UNDO = R.id.im_undo; - private static final int ID_REDO = R.id.im_redo; - private static final int CHARS_TO_COLOR = 2500; - - private final Handler updateHandler = new Handler(); - private final Runnable colorRunnable_duringEditing = - new Runnable() { - @Override - public void run() { - mEditor.replaceTextKeepCursor(null, true); - } - }; - private final Runnable colorRunnable_duringScroll = - new Runnable() { - @Override - public void run() { - mEditor.replaceTextKeepCursor(null, false); - } - }; - //endregion - - public static EditorFragment newInstance(String filePath, String fileText, String encoding) { - EditorFragment frag = new EditorFragment(); - Bundle args = new Bundle(); - args.putString("filePath", filePath); - args.putString("fileText", fileText); - args.putString("encoding", encoding); - frag.setArguments(args); - return frag; - } - - //region ACTIVITY LIFECYCLE - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - String fileText = getArguments().getString("fileText"); - if(fileText == null) - fileText = ""; - - setHasOptionsMenu(true); - - sFilePath = getArguments().getString("filePath"); - pageSystem = new PageSystem(getActivity(), this, fileText); - currentEncoding = getArguments().getString("encoding"); - } - - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View rootView = inflater.inflate(R.layout.fragment_editor, container, false); - verticalScroll = (GoodScrollView) rootView.findViewById(R.id.vertical_scroll); - horizontalScroll = (HorizontalScrollView) rootView.findViewById(R.id.horizontal_scroll); - mEditor = (Editor) rootView.findViewById(R.id.editor); - - mEditor.setEditorInterface(this); - - if (PreferenceHelper.getWrapContent(getActivity())) { - horizontalScroll.removeView(mEditor); - verticalScroll.removeView(horizontalScroll); - verticalScroll.addView(mEditor); - } else { - // else show what is in the xml file fragment_editor.xml- - } - if (PreferenceHelper.getLightTheme(getActivity())) { - mEditor.setTextColor(getResources().getColor(R.color.textColorInverted)); - } else { - mEditor.setTextColor(getResources().getColor(R.color.textColor)); - } - if (PreferenceHelper.getLineNumbers(getActivity())) { - mEditor.setPadding(EdittextPadding.getPaddingWithLineNumbers(getActivity(), PreferenceHelper.getFontSize(getActivity())), EdittextPadding.getPaddingTop(getActivity()), 0, 0); - } else { - mEditor.setPadding(EdittextPadding.getPaddingWithoutLineNumbers(getActivity()), EdittextPadding.getPaddingTop(getActivity()), 0, 0); - } - - if(PreferenceHelper.getReadOnly(getActivity())) { - getActivity().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); - mEditor.setReadOnly(true); - } else { - getActivity().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED); - mEditor.setReadOnly(false); - if (PreferenceHelper.getSuggestionActive(getActivity())) { - mEditor.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE | InputType.TYPE_TEXT_FLAG_IME_MULTI_LINE); - } else { - mEditor.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD | InputType.TYPE_TEXT_FLAG_IME_MULTI_LINE); - } - } - - if (PreferenceHelper.getUseMonospace(getActivity())) { - mEditor.setTypeface(Typeface.MONOSPACE); - } else { - mEditor.setTypeface(Typeface.DEFAULT); - } - mEditor.setTextSize(PreferenceHelper.getFontSize(getActivity())); - - mEditor.setFocusable(true); - mEditor.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if(!PreferenceHelper.getReadOnly(getActivity())) { - getVerticalScrollView().tempDisableListener(1000); - ((InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE)) - .showSoftInput(mEditor, InputMethodManager.SHOW_IMPLICIT); - } - - } - }); - mEditor.setOnFocusChangeListener(new View.OnFocusChangeListener() { - @Override - public void onFocusChange(View v, boolean hasFocus) { - if(hasFocus && !PreferenceHelper.getReadOnly(getActivity())) { - getVerticalScrollView().tempDisableListener(1000); - ((InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE)) - .showSoftInput(mEditor, InputMethodManager.SHOW_IMPLICIT); - } - } - }); - - mEditor.setMaxHistorySize(30); - - verticalScroll.setScrollInterface(this); - - pageSystemButtons = new PageSystemButtons(getActivity(), this, (FloatingActionButton)rootView.findViewById(R.id.fabPrev), (FloatingActionButton)rootView.findViewById(R.id.fabNext)); - - mEditor.disableTextChangedListener(); - mEditor.replaceTextKeepCursor(pageSystem.getCurrentPageText(), false); - mEditor.enableTextChangedListener(); - return rootView; - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - String name = FilenameUtils.getName(sFilePath); - if (name.isEmpty()) - getActivity().getActionBar().setTitle("*"); - else - getActivity().getActionBar().setTitle(name); - } - - @Override - public void onResume() { - super.onResume(); - // Register the Event Bus for events - EventBus.getDefault().register(this); - } - - - @Override - public void onPause() { - super.onPause(); - // Unregister the Event Bus - EventBus.getDefault().unregister(this); - - if(PreferenceHelper.getAutoSave(getActivity()) && mEditor.canSaveFile()) { - onEvent(new EventBusEvents.SaveAFile()); - mEditor.fileSaved(); // so it doesn't ask to save in onDetach - } - } - - @Override - public void onDetach() { - super.onDetach(); - - if (!getActivity().isFinishing() && mEditor.canSaveFile()) - SaveFileDialogFragment.newInstance(sFilePath, pageSystem.getAllText(mEditor.getText().toString()), currentEncoding).show(getFragmentManager(), "dialog"); - } - - //endregion - - //region MENU - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - if (searchingText) - inflater.inflate(R.menu.fragment_editor_search, menu); - else - inflater.inflate(R.menu.fragment_editor, menu); - super.onCreateOptionsMenu(menu, inflater); - } - - @Override - public void onPrepareOptionsMenu(Menu menu) { - super.onPrepareOptionsMenu(menu); - - ActionBar ab = getActivity().getActionBar(); - if (ab == null) - return; - - - if (searchingText) { - MenuItem imReplace = menu.findItem(R.id.im_replace); - MenuItem imPrev = menu.findItem(R.id.im_previous_item); - MenuItem imNext = menu.findItem(R.id.im_next_item); - - if (imReplace != null) - imReplace.setVisible(searchResult.canReplaceSomething()); - - if (imPrev != null) - imPrev.setVisible(searchResult.hasPrevious()); - - if (imNext != null) - imNext.setVisible(searchResult.hasNext()); - - - } else { - MenuItem imSave = menu.findItem(R.id.im_save); - MenuItem imUndo = menu.findItem(R.id.im_undo); - MenuItem imRedo = menu.findItem(R.id.im_redo); - if (mEditor != null) { - if (imSave != null) - imSave.setVisible(mEditor.canSaveFile()); - if (imUndo != null) - imUndo.setVisible(mEditor.getCanUndo()); - if (imRedo != null) - imRedo.setVisible(mEditor.getCanRedo()); - } else { - imSave.setVisible(false); - imUndo.setVisible(false); - imRedo.setVisible(false); - } - } - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - int i = item.getItemId(); - if (i == R.id.im_save) { - EventBus.getDefault().post(new EventBusEvents.SaveAFile()); - - } else if (i == R.id.im_undo) { - this.mEditor.onTextContextMenuItem(ID_UNDO); - - } else if (i == R.id.im_redo) { - this.mEditor.onTextContextMenuItem(ID_REDO); - - } else if (i == R.id.im_search) { - FindTextDialogFragment dialogFrag = FindTextDialogFragment.newInstance(pageSystem.getCurrentPageText()); - dialogFrag.setTargetFragment(EditorFragment.this, 0); - dialogFrag.show(getFragmentManager().beginTransaction(), "dialog"); - - } else if (i == R.id.im_cancel) { - searchingText = false; - getActivity().invalidateOptionsMenu(); - - } else if (i == R.id.im_replace) { - replaceText(); - - } else if (i == R.id.im_next_item) { - nextResult(); - - } else if (i == R.id.im_previous_item) { - previousResult(); - - } - else if(i == R.id.im_goto_line){ - int min = mEditor.getLineUtils().firstReadLine(); - int max = mEditor.getLineUtils().lastReadLine(); - SeekbarDialogFragment dialogFrag = SeekbarDialogFragment.newInstance(SeekbarDialogFragment.Actions.GoToLine, min, min, max); - dialogFrag.setTargetFragment(EditorFragment.this, 0); - dialogFrag.show(getFragmentManager().beginTransaction(), "dialog"); - } - else if (i == R.id.im_view_it_on_browser) { - Intent browserIntent = null; - try { - browserIntent = new Intent(Intent.ACTION_VIEW); - browserIntent.setDataAndType(Uri.fromFile(new File(sFilePath)), "text/*"); - startActivity(browserIntent); - } catch (ActivityNotFoundException ex2) { - // - } - - } else if (i == R.id.im_share) { - File f = new File(sFilePath); - Intent shareIntent = new Intent(); - shareIntent.setAction(Intent.ACTION_SEND); - shareIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(f)); - shareIntent.setType("text/plain"); - - startActivity(Intent.createChooser(shareIntent, getString(R.string.share))); - - } - else if (i == R.id.im_info) { - FileInfoDialogFragment dialogFrag = FileInfoDialogFragment.newInstance(sFilePath); - dialogFrag.setTargetFragment(EditorFragment.this, 0); - dialogFrag.show(getFragmentManager().beginTransaction(), "dialog"); - } - return super.onOptionsItemSelected(item); - } - //endregion - - //region Interfaces - @Override - public String getFilePath() { - return sFilePath; - } - - @Override - public GoodScrollView getVerticalScrollView() { - return verticalScroll; - } - - @Override - public PageSystem getPageSystem() { - return pageSystem; - } - - @Override - public void updateTextSyntax() { - if (!PreferenceHelper.getSyntaxHiglight(getActivity()) || mEditor.hasSelection() || updateHandler == null || colorRunnable_duringEditing == null) - return; - - updateHandler.removeCallbacks(colorRunnable_duringEditing); - updateHandler.removeCallbacks(colorRunnable_duringScroll); - updateHandler.postDelayed(colorRunnable_duringEditing, SYNTAX_DELAY_MILLIS_LONG); - } - - @Override - public void nextPageClicked() { - pageSystem.savePage(mEditor.getText().toString()); - pageSystem.nextPage(); - mEditor.disableTextChangedListener(); - mEditor.replaceTextKeepCursor(pageSystem.getCurrentPageText(), false); - mEditor.enableTextChangedListener(); - - verticalScroll.postDelayed(new Runnable() { - @Override - public void run() { - verticalScroll.smoothScrollTo(0, 0); - } - }, 200); - - if(PreferenceHelper.getPageSystemButtonsPopupShown(getActivity()) == false){ - PreferenceHelper.setPageSystemButtonsPopupShown(getActivity(), true); - Toast.makeText(getActivity(), getString(R.string.long_click_for_more_options), Toast.LENGTH_LONG).show(); - } - } - - @Override - public void pageSystemButtonLongClicked() { - int maxPages = pageSystem.getMaxPage(); - int currentPage = pageSystem.getCurrentPage(); - SeekbarDialogFragment dialogFrag = SeekbarDialogFragment.newInstance(SeekbarDialogFragment.Actions.SelectPage, 0, currentPage, maxPages); - dialogFrag.setTargetFragment(EditorFragment.this, 0); - dialogFrag.show(getFragmentManager().beginTransaction(), "dialog"); - } - - @Override - public void prevPageClicked() { - pageSystem.savePage(mEditor.getText().toString()); - pageSystem.prevPage(); - mEditor.disableTextChangedListener(); - mEditor.replaceTextKeepCursor(pageSystem.getCurrentPageText(), false); - mEditor.enableTextChangedListener(); - - verticalScroll.postDelayed(new Runnable() { - @Override - public void run() { - verticalScroll.smoothScrollTo(0,0); - } - }, 200); - - if(PreferenceHelper.getPageSystemButtonsPopupShown(getActivity()) == false){ - PreferenceHelper.setPageSystemButtonsPopupShown(getActivity(), true); - Toast.makeText(getActivity(), getString(R.string.long_click_for_more_options), Toast.LENGTH_LONG).show(); - } - } - - @Override - public boolean canReadNextPage() { - return pageSystem.canReadNextPage(); - } - - @Override - public boolean canReadPrevPage() { - return pageSystem.canReadPrevPage(); - } - - @Override - public void onSearchDone(SearchResult searchResult) { - this.searchResult = searchResult; - searchingText = true; - getActivity().invalidateOptionsMenu(); - - final int line = mEditor.getLineUtils().getLineFromIndex(searchResult.foundIndex.getFirst(), mEditor.getLineCount(), mEditor.getLayout()); - verticalScroll.post(new Runnable() { - @Override - public void run() { - int y = mEditor.getLayout().getLineTop(line); - if(y > 100) - y -= 100; - else - y = 0; - - verticalScroll.scrollTo(0, y); - } - }); - - mEditor.setSelection(searchResult.foundIndex.getFirst(), searchResult.foundIndex.getFirst() + searchResult.textLength); - - } - - @Override - public void onPageChanged(int page) { - pageSystemButtons.updateVisibility(false); - searchingText = false; - mEditor.clearHistory(); - getActivity().invalidateOptionsMenu(); - } - - @Override - public void onScrollChanged(int l, int t, int oldl, int oldt) { - pageSystemButtons.updateVisibility(Math.abs(t) > 10); - - if (!PreferenceHelper.getSyntaxHiglight(getActivity()) || (mEditor.hasSelection() && !searchingText) || updateHandler == null || colorRunnable_duringScroll == null) - return; - - updateHandler.removeCallbacks(colorRunnable_duringEditing); - updateHandler.removeCallbacks(colorRunnable_duringScroll); - updateHandler.postDelayed(colorRunnable_duringScroll, SYNTAX_DELAY_MILLIS_SHORT); - } - - @Override - public void onSeekbarDialogDismissed(SeekbarDialogFragment.Actions action, int value) { - if(action == SeekbarDialogFragment.Actions.SelectPage) { - pageSystem.savePage(mEditor.getText().toString()); - pageSystem.goToPage(value); - mEditor.disableTextChangedListener(); - mEditor.replaceTextKeepCursor(pageSystem.getCurrentPageText(), true); - mEditor.enableTextChangedListener(); - - verticalScroll.postDelayed(new Runnable() { - @Override - public void run() { - verticalScroll.smoothScrollTo(0, 0); - } - }, 200); - - } else if(action == SeekbarDialogFragment.Actions.GoToLine) { - - int fakeLine = mEditor.getLineUtils().fakeLineFromRealLine(value); - final int y = mEditor.getLineUtils().getYAtLine(verticalScroll, mEditor.getLineCount(), fakeLine); - - verticalScroll.postDelayed(new Runnable() { - @Override - public void run() { - verticalScroll.smoothScrollTo(0, y); - } - }, 200); - } - - } - - public void nextResult() { - if (searchResult.index == mEditor.getLineCount() - 1) // last result of page - { - return; - } - - - if (searchResult.index < searchResult.numberOfResults() - 1) { // equal zero is not good - searchResult.index++; - final int line = mEditor.getLineUtils().getLineFromIndex(searchResult.foundIndex.get(searchResult.index), mEditor.getLineCount(), mEditor.getLayout()); - - - verticalScroll.post(new Runnable() { - @Override - public void run() { - int y = mEditor.getLayout().getLineTop(line); - if(y > 100) - y -= 100; - else - y = 0; - - verticalScroll.scrollTo(0, y); - } - }); - - mEditor.setSelection(searchResult.foundIndex.get(searchResult.index), searchResult.foundIndex.get(searchResult.index) + searchResult.textLength); - } - - getActivity().invalidateOptionsMenu(); - } - - public void previousResult() { - if (searchResult.index == 0) - return; - if (searchResult.index > 0) { - searchResult.index--; - final int line = mEditor.getLineUtils().getLineFromIndex(searchResult.foundIndex.get(searchResult.index), mEditor.getLineCount(), mEditor.getLayout()); - verticalScroll.post(new Runnable() { - @Override - public void run() { - int y = mEditor.getLayout().getLineTop(line); - if(y > 100) - y -= 100; - else - y = 0; - verticalScroll.scrollTo(0, y); - } - }); - - mEditor.setSelection(searchResult.foundIndex.get(searchResult.index), searchResult.foundIndex.get(searchResult.index) + searchResult.textLength); - } - - getActivity().invalidateOptionsMenu(); - } - - public void replaceText() { - mEditor.setText(mEditor.getText().replace(searchResult.foundIndex.get(searchResult.index), searchResult.foundIndex.get(searchResult.index) + searchResult.textLength, searchResult.textToReplace)); - searchResult.doneReplace(); - - getActivity().invalidateOptionsMenu(); - - if(searchResult.hasNext()) - nextResult(); - else if(searchResult.hasPrevious()) - previousResult(); - } - //endregion - - //region Eventbus - public void onEvent(EventBusEvents.APreferenceValueWasChanged event) { - - if (event.hasType(WRAP_CONTENT)) { - if (PreferenceHelper.getWrapContent(getActivity())) { - horizontalScroll.removeView(mEditor); - verticalScroll.removeView(horizontalScroll); - verticalScroll.addView(mEditor); - } else { - verticalScroll.removeView(mEditor); - verticalScroll.addView(horizontalScroll); - horizontalScroll.addView(mEditor); - } - } else if (event.hasType(LINE_NUMERS)) { - mEditor.disableTextChangedListener(); - mEditor.replaceTextKeepCursor(null, true); - mEditor.enableTextChangedListener(); - if (PreferenceHelper.getLineNumbers(getActivity())) { - mEditor.setPadding(EdittextPadding.getPaddingWithLineNumbers(getActivity(), PreferenceHelper.getFontSize(getActivity())), EdittextPadding.getPaddingTop(getActivity()), 0, 0); - } else { - mEditor.setPadding(EdittextPadding.getPaddingWithoutLineNumbers(getActivity()), EdittextPadding.getPaddingTop(getActivity()), 0, 0); - } - } else if (event.hasType(SYNTAX)) { - mEditor.disableTextChangedListener(); - mEditor.replaceTextKeepCursor(null, true); - mEditor.enableTextChangedListener(); - } else if (event.hasType(MONOSPACE)) { - if (PreferenceHelper.getUseMonospace(getActivity())) - this.mEditor.setTypeface(Typeface.MONOSPACE); - else - this.mEditor.setTypeface(Typeface.DEFAULT); - } else if (event.hasType(THEME_CHANGE)) { - if (PreferenceHelper.getLightTheme(getActivity())) { - mEditor.setTextColor(getResources().getColor(R.color.textColorInverted)); - } else { - mEditor.setTextColor(getResources().getColor(R.color.textColor)); - } - } else if (event.hasType(TEXT_SUGGESTIONS) || event.hasType(READ_ONLY)) { - if(PreferenceHelper.getReadOnly(getActivity())) { - getActivity().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); - mEditor.setReadOnly(true); - } else { - getActivity().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED); - mEditor.setReadOnly(false); - if (PreferenceHelper.getSuggestionActive(getActivity())) { - mEditor.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE | InputType.TYPE_TEXT_FLAG_IME_MULTI_LINE); - } else { - mEditor.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD | InputType.TYPE_TEXT_FLAG_IME_MULTI_LINE); - } - } - // sometimes it becomes monospace after setting the input type - if (PreferenceHelper.getUseMonospace(getActivity())) - this.mEditor.setTypeface(Typeface.MONOSPACE); - else - this.mEditor.setTypeface(Typeface.DEFAULT); - } else if (event.hasType(FONT_SIZE)) { - if (PreferenceHelper.getLineNumbers(getActivity())) { - mEditor.setPadding(EdittextPadding.getPaddingWithLineNumbers(getActivity(), PreferenceHelper.getFontSize(getActivity())), EdittextPadding.getPaddingTop(getActivity()), 0, 0); - } else { - mEditor.setPadding(EdittextPadding.getPaddingWithoutLineNumbers(getActivity()), EdittextPadding.getPaddingTop(getActivity()), 0, 0); - } - this.mEditor.setTextSize(PreferenceHelper.getFontSize(getActivity())); - } else if (event.hasType(ENCODING)) { - String oldEncoding, newEncoding; - oldEncoding = currentEncoding; - newEncoding = PreferenceHelper.getEncoding(getActivity()); - try { - final byte[] oldText = this.mEditor.getText().toString().getBytes(oldEncoding); - mEditor.disableTextChangedListener(); - mEditor.replaceTextKeepCursor(new String(oldText, newEncoding), true); - mEditor.enableTextChangedListener(); - currentEncoding = newEncoding; - } catch (UnsupportedEncodingException ignored) { - try { - final byte[] oldText = this.mEditor.getText().toString().getBytes(oldEncoding); - mEditor.disableTextChangedListener(); - mEditor.replaceTextKeepCursor(new String(oldText, "UTF-8"), true); - mEditor.enableTextChangedListener(); - } catch (UnsupportedEncodingException ignored2) { - } - } - } - } - - public void onEvent(EventBusEvents.SaveAFile event) { - File file = new File(sFilePath); - if(!file.getName().isEmpty()) - new SaveFileTask(getActivity(), sFilePath, pageSystem.getAllText(mEditor.getText().toString()), currentEncoding).execute(); - else { - NewFileDetailsDialogFragment dialogFrag = NewFileDetailsDialogFragment.newInstance(pageSystem.getAllText(mEditor.getText().toString()), currentEncoding); - dialogFrag.show(getFragmentManager().beginTransaction(), "dialog"); - } - } - - public void onEvent(EventBusEvents.SavedAFile event) { - mEditor.clearHistory(); - mEditor.fileSaved(); - getActivity().invalidateOptionsMenu(); - } - - public void onEvent(EventBusEvents.InvalideTheMenu event) { - getActivity().invalidateOptionsMenu(); - } - //endregion - - public static class Editor extends EditText { - - //region VARIABLES - private EditorInterface editorInterface; - private final TextPaint mPaintNumbers = new TextPaint(); - /** - * The edit history. - */ - private final EditHistory mEditHistory; - /** - * The change listener. - */ - private final EditTextChangeListener - mChangeListener; - private int lineCount, realLine, startingLine; - private LineUtils lineUtils; - private boolean modified = true; - /** - * Is undo/redo being performed? This member - * signals if an undo/redo operation is - * currently being performed. Changes in the - * text during undo/redo are not recorded - * because it would mess up the undo history. - */ - private boolean mIsUndoOrRedo = false; - private Matcher m; - private boolean mShowUndo = false, mShowRedo = false; - private boolean canSaveFile = false; - private KeyListener keyListener; - private int firstVisibleIndex = 0, firstColoredIndex = 0; - private int deviceHeight; - //endregion - - //region CONSTRUCTOR - public Editor(final Context context, AttributeSet attrs) { - super(context, attrs); - mEditHistory = new EditHistory(); - mChangeListener = new EditTextChangeListener(); - lineUtils = new LineUtils(); - - deviceHeight = getResources().getDisplayMetrics().heightPixels; - - this.mPaintNumbers.setAntiAlias(true); - this.mPaintNumbers.setDither(false); - this.mPaintNumbers.setTextAlign(Paint.Align.RIGHT); - - // Syntax editor - setFilters(new InputFilter[]{ - new InputFilter() { - @Override - public CharSequence filter( - CharSequence source, - int start, - int end, - Spanned dest, - int dstart, - int dend) { - if (modified) { - return autoIndent( - source, - start, - end, - dest, - dstart, - dend); - } - - return source; - } - }}); - } - //endregion - - //region OVERRIDES - @Override - public void setTextSize(float size) { - super.setTextSize(size); - final float scale = getContext().getResources().getDisplayMetrics().density; - this.mPaintNumbers.setTextSize((int) (size * scale * 0.65f)); - } - - @Override - public void setTextColor(int color) { - super.setTextColor(color); - //this.mPaintNumbers.setColor(getTextColors().getDefaultColor()); - this.mPaintNumbers.setColor(getResources().getColor(R.color.file_text)); - } - - @Override - public boolean onKeyUp(int keyCode, KeyEvent event) { - if (event.isCtrlPressed()) { - switch (keyCode) { - case KeyEvent.KEYCODE_A: - case KeyEvent.KEYCODE_X: - case KeyEvent.KEYCODE_C: - case KeyEvent.KEYCODE_V: - case KeyEvent.KEYCODE_Z: - case KeyEvent.KEYCODE_Y: - case KeyEvent.KEYCODE_S: - return true; - default: - return false; - } - } else { - switch (keyCode) { - case KeyEvent.KEYCODE_TAB: - return true; - default: - return false; - } - } - } - - @Override - public boolean onKeyDown(int keyCode, KeyEvent event) { - - if (ApiHelper.is11() && event.isCtrlPressed()) { - switch (keyCode) { - case KeyEvent.KEYCODE_A: - return onTextContextMenuItem(ID_SELECT_ALL); - case KeyEvent.KEYCODE_X: - return onTextContextMenuItem(ID_CUT); - case KeyEvent.KEYCODE_C: - return onTextContextMenuItem(ID_COPY); - case KeyEvent.KEYCODE_V: - return onTextContextMenuItem(ID_PASTE); - case KeyEvent.KEYCODE_Z: - if (getCanUndo()) { - return onTextContextMenuItem(ID_UNDO); - } - case KeyEvent.KEYCODE_Y: - if (getCanRedo()) { - return onTextContextMenuItem(ID_REDO); - } - case KeyEvent.KEYCODE_S: - EventBus.getDefault().post(new EventBusEvents.SaveAFile()); - return true; - default: - return super.onKeyDown(keyCode, event); - } - } else { - switch (keyCode) { - case KeyEvent.KEYCODE_TAB: - String textToInsert = " "; - int start, end; - start = Math.max(getSelectionStart(), 0); - end = Math.max(getSelectionEnd(), 0); - getText().replace(Math.min(start, end), Math.max(start, end), - textToInsert, 0, textToInsert.length()); - return true; - default: - return super.onKeyDown(keyCode, event); - } - } - } - - /** - * {@inheritDoc} - */ - @Override - public boolean onTextContextMenuItem( - final int id) { - if (id == ID_UNDO) { - undo(); - return true; - } else if (id == ID_REDO) { - redo(); - return true; - } else { - return super.onTextContextMenuItem(id); - } - } - - @Override - public void onDraw(final Canvas canvas) { - - if (PreferenceHelper.getLineNumbers(getContext())) { - if (lineCount != getLineCount() || startingLine != editorInterface.getPageSystem().getStartingLine()) { - startingLine = editorInterface.getPageSystem().getStartingLine(); - lineCount = getLineCount(); - - lineUtils.updateHasNewLineArray(editorInterface.getPageSystem().getStartingLine(), lineCount, getLayout(), getText().toString()); - } - - int editorHeight = getHeight(); - int i = lineUtils.getFirstVisibleLine(editorInterface.getVerticalScrollView(), editorHeight, lineCount); - int lastLine = lineUtils.getLastVisibleLine(editorInterface.getVerticalScrollView(), editorHeight, lineCount, deviceHeight); - boolean[] hasNewLineArray = lineUtils.getToCountLinesArray(); - int[] realLines = lineUtils.getRealLines(); - boolean wrapContent = PreferenceHelper.getWrapContent(getContext()); - int numbersWidth = (int) (EdittextPadding.getPaddingWithLineNumbers(getContext(), PreferenceHelper.getFontSize(getContext())) * 0.8); - int paddingTop = EdittextPadding.getPaddingTop(getContext()); - - while (i < lastLine) { - // if last line we count it anyway - if (!wrapContent - || hasNewLineArray[i] - || i == lastLine - 1) { - if (i == lastLine - 1) - realLine = realLines[i] + 1; - else - realLine = realLines[i]; - - canvas.drawText(String.valueOf(realLine), - numbersWidth, // they all center aligned - paddingTop + getLineHeight() * (i + 1), - mPaintNumbers); - } - i++; - } - } - - - super.onDraw(canvas); - } - - - //endregion - - //region Other - - public boolean canSaveFile() { - return canSaveFile; - } - - public void fileSaved() { - canSaveFile = false; - } - - public void setEditorInterface(EditorInterface editorInterface){ - this.editorInterface = editorInterface; - } - - public LineUtils getLineUtils() { - return lineUtils; - } - - private CharSequence autoIndent( - CharSequence source, - int start, - int end, - Spanned dest, - int dstart, - int dend) { - if (end - start != 1 || - start >= source.length() || - source.charAt(start) != '\n' || - dstart >= dest.length()) { - return source; - } - - int istart = dstart; - int iend; - String indent = ""; - - // skip end of line if cursor is at the end of a line - if (dest.charAt(istart) == '\n') { - --istart; - } - - // indent next line if this one isn't terminated - if (istart > -1) { - // skip white space - for (; istart > -1; --istart) { - char c = dest.charAt(istart); - - if (c != ' ' && - c != '\t') { - break; - } - } - - if (istart > -1) { - char c = dest.charAt(istart); - - if (c != ';' && - c != '\n') { - indent = "\t"; - } - } - } - - // find start of previous line - for (; istart > -1; --istart) { - if (dest.charAt(istart) == '\n') { - break; - } - } - - // cursor is in the first line - if (istart < 0) { - return source; - } - - // span over previous indent - for (iend = ++istart; - iend < dend; - ++iend) { - char c = dest.charAt(iend); - - if (c != ' ' && - c != '\t') { - break; - } - } - - // copy white space of previous lines and append new indent - return "\n" + dest.subSequence( - istart, - iend) + indent; - } - - public void replaceTextKeepCursor(String textToUpdate, boolean mantainCursorPos) { - - int cursorPos; - int cursorPosEnd; - if(textToUpdate != null) { - cursorPos = 0; - cursorPosEnd = 0; - } else { - cursorPos = getSelectionStart(); - cursorPosEnd = getSelectionEnd(); - } - disableTextChangedListener(); - modified = false; - - - - - if(PreferenceHelper.getSyntaxHiglight(getContext())) - setText(highlight(textToUpdate == null ? getEditableText() : Editable.Factory.getInstance().newEditable(textToUpdate))); - else - setText(textToUpdate == null ? getText().toString() : textToUpdate); - - modified = true; - enableTextChangedListener(); - - if(mantainCursorPos) - firstVisibleIndex = cursorPos; - - if (firstVisibleIndex > -1) { - if(cursorPosEnd != cursorPos) - setSelection(cursorPos, cursorPosEnd); - else - setSelection(firstVisibleIndex); - } - } - - public CharSequence highlight(Editable editable) { - final String fileExtension = FilenameUtils.getExtension(editorInterface.getFilePath()).toLowerCase(); - editable.clearSpans(); - - if (editable.length() == 0) { - return editable; - } - - int end; - int height = getHeight(); - - if(height > 0) { - firstVisibleIndex = getLayout().getLineStart(getLineUtils().getFirstVisibleLine(editorInterface.getVerticalScrollView(), height, getLineCount())); - end = getLayout().getLineStart(getLineUtils().getLastVisibleLine(editorInterface.getVerticalScrollView(), height, getLineCount(), deviceHeight)); - } else { - firstVisibleIndex = 0; - end = CHARS_TO_COLOR; - } - - firstColoredIndex = firstVisibleIndex - (CHARS_TO_COLOR / 5); - - // normalize - if (firstColoredIndex < 0) - firstColoredIndex = 0; - if (end > editable.length()) - end = editable.length(); - if(firstColoredIndex > end) - firstColoredIndex = end; - - - CharSequence textToHighlight = editable.subSequence(firstColoredIndex, end); - - if (fileExtension.contains("htm") - || fileExtension.contains("xml")) { - color(Patterns.HTML_OPEN_TAGS, editable, textToHighlight, firstColoredIndex); - color(Patterns.HTML_CLOSE_TAGS, editable, textToHighlight, firstColoredIndex); - color(Patterns.HTML_ATTRS, editable, textToHighlight, firstColoredIndex); - color(Patterns.GENERAL_STRINGS, editable, textToHighlight, firstColoredIndex); - color(Patterns.XML_COMMENTS, editable, textToHighlight, firstColoredIndex); - } else if (fileExtension.equals("css")) { - //color(CSS_STYLE_NAME, editable); - color(Patterns.CSS_ATTRS, editable, textToHighlight, firstColoredIndex); - color(Patterns.CSS_ATTR_VALUE, editable, textToHighlight, firstColoredIndex); - color(Patterns.SYMBOLS, editable, textToHighlight, firstColoredIndex); - color(Patterns.GENERAL_COMMENTS, editable, textToHighlight, firstColoredIndex); - } else if (Arrays.asList(MimeTypes.MIME_CODE).contains(fileExtension)) { - if(fileExtension.equals("lua")) - color(Patterns.LUA_KEYWORDS, editable, textToHighlight, firstColoredIndex); - else if(fileExtension.equals("py")) - color(Patterns.PY_KEYWORDS, editable, textToHighlight, firstColoredIndex); - else - color(Patterns.GENERAL_KEYWORDS, editable, textToHighlight, firstColoredIndex); - color(Patterns.NUMBERS, editable, textToHighlight, firstColoredIndex); - color(Patterns.SYMBOLS, editable, textToHighlight, firstColoredIndex); - color(Patterns.GENERAL_STRINGS, editable, textToHighlight, firstColoredIndex); - color(Patterns.GENERAL_COMMENTS, editable, textToHighlight, firstColoredIndex); - if (fileExtension.equals("php")) - color(Patterns.PHP_VARIABLES, editable, textToHighlight, firstColoredIndex); - } else if (Arrays.asList(MimeTypes.MIME_SQL).contains(fileExtension)) { - color(Patterns.SYMBOLS, editable, textToHighlight, firstColoredIndex); - color(Patterns.GENERAL_STRINGS, editable, textToHighlight, firstColoredIndex); - color(Patterns.SQL_KEYWORDS, editable, textToHighlight, firstColoredIndex); - } else { - if(!fileExtension.contains("md")) - color(Patterns.GENERAL_KEYWORDS, editable, textToHighlight, firstColoredIndex); - color(Patterns.NUMBERS, editable, textToHighlight, firstColoredIndex); - color(Patterns.SYMBOLS, editable, textToHighlight, firstColoredIndex); - color(Patterns.GENERAL_STRINGS, editable, textToHighlight, firstColoredIndex); - if (fileExtension.equals("prop") || fileExtension.contains("conf") || fileExtension.contains("md")) - color(Patterns.GENERAL_COMMENTS_NO_SLASH, editable, textToHighlight, firstColoredIndex); - else - color(Patterns.GENERAL_COMMENTS, editable, textToHighlight, firstColoredIndex); - - if(fileExtension.contains("md")) - color(Patterns.LINK, editable, textToHighlight, firstColoredIndex); - } - - return editable; - } - - private void color(Pattern pattern, - Editable allText, - CharSequence textToHighlight, - int start) { - int color = 0; - if (pattern.equals(Patterns.HTML_OPEN_TAGS) - || pattern.equals(Patterns.HTML_CLOSE_TAGS) - || pattern.equals(Patterns.GENERAL_KEYWORDS) - || pattern.equals(Patterns.SQL_KEYWORDS) - || pattern.equals(Patterns.PY_KEYWORDS) - || pattern.equals(Patterns.LUA_KEYWORDS) - - ) { - color = getResources().getColor(R.color.syntax_keyword); - } else if (pattern.equals(Patterns.HTML_ATTRS) - || pattern.equals(Patterns.CSS_ATTRS) - || pattern.equals(Patterns.LINK)) { - color = getResources().getColor(R.color.syntax_attr); - } else if (pattern.equals(Patterns.CSS_ATTR_VALUE)) { - color = getResources().getColor(R.color.syntax_attr_value); - } else if (pattern.equals(Patterns.XML_COMMENTS) - || pattern.equals(Patterns.GENERAL_COMMENTS) - || pattern.equals(Patterns.GENERAL_COMMENTS_NO_SLASH)) { - color = getResources().getColor(R.color.syntax_comment); - } else if (pattern.equals(Patterns.GENERAL_STRINGS)) { - color = getResources().getColor(R.color.syntax_string); - } else if (pattern.equals(Patterns.NUMBERS) || pattern.equals(Patterns.SYMBOLS)) { - color = getResources().getColor(R.color.syntax_number); - } else if (pattern.equals(Patterns.PHP_VARIABLES)) { - color = getResources().getColor(R.color.syntax_variable); - } - - m = pattern.matcher(textToHighlight); - - while (m.find()) { - allText.setSpan( - new ForegroundColorSpan(color), - start + m.start(), - start + m.end(), - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - } - } - - /** - * Disconnect this undo/redo from the text - * view. - */ - boolean enabledChangeListener = false; - public void disableTextChangedListener() { - enabledChangeListener = false; - removeTextChangedListener(mChangeListener); - } - public void enableTextChangedListener() { - if(!enabledChangeListener) { - addTextChangedListener(mChangeListener); - enabledChangeListener = true; - } - } - - public void setReadOnly(boolean value){ - if(value) { - keyListener = getKeyListener(); - setKeyListener(null); - } else { - if(keyListener != null) - setKeyListener(keyListener); - } - } - //endregion - - //region UNDO REDO - - /** - * Set the maximum history size. If size is - * negative, then history size is only limited - * by the device memory. - */ - public void setMaxHistorySize( - int maxHistorySize) { - mEditHistory.setMaxHistorySize( - maxHistorySize); - } - - /** - * Clear history. - */ - public void clearHistory() { - mEditHistory.clear(); - mShowUndo = getCanUndo(); - mShowRedo = getCanRedo(); - } - - /** - * Can undo be performed? - */ - public boolean getCanUndo() { - return (mEditHistory.mmPosition > 0); - } - - /** - * Perform undo. - */ - public void undo() { - EditItem edit = mEditHistory.getPrevious(); - if (edit == null) { - return; - } - - Editable text = getEditableText(); - int start = edit.mmStart; - int end = start + (edit.mmAfter != null - ? edit.mmAfter.length() : 0); - - mIsUndoOrRedo = true; - text.replace(start, end, edit.mmBefore); - mIsUndoOrRedo = false; - - // This will get rid of underlines inserted when editor tries to come - // up with a suggestion. - for (Object o : text.getSpans(0, - text.length(), UnderlineSpan.class)) { - text.removeSpan(o); - } - - Selection.setSelection(text, - edit.mmBefore == null ? start - : (start + edit.mmBefore.length())); - } - - /** - * Can redo be performed? - */ - public boolean getCanRedo() { - return (mEditHistory.mmPosition - < mEditHistory.mmHistory.size()); - } - - /** - * Perform redo. - */ - public void redo() { - EditItem edit = mEditHistory.getNext(); - if (edit == null) { - return; - } - - Editable text = getEditableText(); - int start = edit.mmStart; - int end = start + (edit.mmBefore != null - ? edit.mmBefore.length() : 0); - - mIsUndoOrRedo = true; - text.replace(start, end, edit.mmAfter); - mIsUndoOrRedo = false; - - // This will get rid of underlines inserted when editor tries to come - // up with a suggestion. - for (Object o : text.getSpans(0, - text.length(), UnderlineSpan.class)) { - text.removeSpan(o); - } - - Selection.setSelection(text, - edit.mmAfter == null ? start - : (start + edit.mmAfter.length())); - } - - /** - * Store preferences. - */ - public void storePersistentState( - SharedPreferences.Editor editor, - String prefix) { - // Store hash code of text in the editor so that we can check if the - // editor contents has changed. - editor.putString(prefix + ".hash", - String.valueOf( - getText().toString().hashCode())); - editor.putInt(prefix + ".maxSize", - mEditHistory.mmMaxHistorySize); - editor.putInt(prefix + ".position", - mEditHistory.mmPosition); - editor.putInt(prefix + ".size", - mEditHistory.mmHistory.size()); - - int i = 0; - for (EditItem ei : mEditHistory.mmHistory) { - String pre = prefix + "." + i; - - editor.putInt(pre + ".start", ei.mmStart); - editor.putString(pre + ".before", - ei.mmBefore.toString()); - editor.putString(pre + ".after", - ei.mmAfter.toString()); - - i++; - } - } - - /** - * Restore preferences. - * - * @param prefix The preference key prefix - * used when state was stored. - * @return did restore succeed? If this is - * false, the undo history will be empty. - */ - public boolean restorePersistentState( - SharedPreferences sp, String prefix) - throws IllegalStateException { - - boolean ok = - doRestorePersistentState(sp, prefix); - if (!ok) { - mEditHistory.clear(); - } - - return ok; - } - - private boolean doRestorePersistentState( - SharedPreferences sp, String prefix) { - - String hash = - sp.getString(prefix + ".hash", null); - if (hash == null) { - // No state to be restored. - return true; - } - - if (Integer.valueOf(hash) - != getText().toString().hashCode()) { - return false; - } - - mEditHistory.clear(); - mEditHistory.mmMaxHistorySize = - sp.getInt(prefix + ".maxSize", -1); - - int count = sp.getInt(prefix + ".size", -1); - if (count == -1) { - return false; - } - - for (int i = 0; i < count; i++) { - String pre = prefix + "." + i; - - int start = sp.getInt(pre + ".start", -1); - String before = - sp.getString(pre + ".before", null); - String after = - sp.getString(pre + ".after", null); - - if (start == -1 - || before == null - || after == null) { - return false; - } - mEditHistory.add( - new EditItem(start, before, after)); - } - - mEditHistory.mmPosition = - sp.getInt(prefix + ".position", -1); - return mEditHistory.mmPosition != -1; - - } - - /** - * Class that listens to changes in the text. - */ - private final class EditTextChangeListener - implements TextWatcher { - - /** - * The text that will be removed by the - * change event. - */ - private CharSequence mBeforeChange; - - /** - * The text that was inserted by the change - * event. - */ - private CharSequence mAfterChange; - - public void beforeTextChanged( - CharSequence s, int start, int count, - int after) { - if (mIsUndoOrRedo) { - return; - } - - mBeforeChange = - s.subSequence(start, start + count); - } - - public void onTextChanged(CharSequence s, - int start, int before, - int count) { - if (mIsUndoOrRedo) { - return; - } - - mAfterChange = - s.subSequence(start, start + count); - mEditHistory.add( - new EditItem(start, mBeforeChange, - mAfterChange)); - } - - public void afterTextChanged(Editable s) { - boolean showUndo = getCanUndo(); - boolean showRedo = getCanRedo(); - if(!canSaveFile) - canSaveFile = getCanUndo(); - if (showUndo != mShowUndo || showRedo != mShowRedo) { - mShowUndo = showUndo; - mShowRedo = showRedo; - EventBus.getDefault().post(new EventBusEvents.InvalideTheMenu()); - } - - editorInterface.updateTextSyntax(); - } - } - - //endregion - - //region EDIT HISTORY - - /** - * Keeps track of all the edit history of a - * text. - */ - private final class EditHistory { - - /** - * The list of edits in chronological - * order. - */ - private final LinkedList - mmHistory = new LinkedList<>(); - /** - * The position from which an EditItem will - * be retrieved when getNext() is called. If - * getPrevious() has not been called, this - * has the same value as mmHistory.size(). - */ - private int mmPosition = 0; - /** - * Maximum undo history size. - */ - private int mmMaxHistorySize = -1; - - private int size() { - return mmHistory.size(); - } - - /** - * Clear history. - */ - private void clear() { - mmPosition = 0; - mmHistory.clear(); - } - - /** - * Adds a new edit operation to the history - * at the current position. If executed - * after a call to getPrevious() removes all - * the future history (elements with - * positions >= current history position). - */ - private void add(EditItem item) { - while (mmHistory.size() > mmPosition) { - mmHistory.removeLast(); - } - mmHistory.add(item); - mmPosition++; - - if (mmMaxHistorySize >= 0) { - trimHistory(); - } - } - - /** - * Set the maximum history size. If size is - * negative, then history size is only - * limited by the device memory. - */ - private void setMaxHistorySize( - int maxHistorySize) { - mmMaxHistorySize = maxHistorySize; - if (mmMaxHistorySize >= 0) { - trimHistory(); - } - } - - /** - * Trim history when it exceeds max history - * size. - */ - private void trimHistory() { - while (mmHistory.size() - > mmMaxHistorySize) { - mmHistory.removeFirst(); - mmPosition--; - } - - if (mmPosition < 0) { - mmPosition = 0; - } - } - - /** - * Traverses the history backward by one - * position, returns and item at that - * position. - */ - private EditItem getPrevious() { - if (mmPosition == 0) { - return null; - } - mmPosition--; - return mmHistory.get(mmPosition); - } - - /** - * Traverses the history forward by one - * position, returns and item at that - * position. - */ - private EditItem getNext() { - if (mmPosition >= mmHistory.size()) { - return null; - } - - EditItem item = mmHistory.get(mmPosition); - mmPosition++; - return item; - } - } - - /** - * Represents the changes performed by a - * single edit operation. - */ - private final class EditItem { - private final int mmStart; - private final CharSequence mmBefore; - private final CharSequence mmAfter; - - /** - * Constructs EditItem of a modification - * that was applied at position start and - * replaced CharSequence before with - * CharSequence after. - */ - public EditItem(int start, - CharSequence before, CharSequence after) { - mmStart = start; - mmBefore = before; - mmAfter = after; - } - } - //endregion - - - } -} \ No newline at end of file diff --git a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/EncodingDialogFragment.java b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/EncodingDialog.java similarity index 94% rename from libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/EncodingDialogFragment.java rename to libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/EncodingDialog.java index b93981d..a09bb91 100644 --- a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/EncodingDialogFragment.java +++ b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/EncodingDialog.java @@ -35,7 +35,7 @@ import org.mozilla.universalchardet.Constants; import sharedcode.turboeditor.R; import sharedcode.turboeditor.preferences.PreferenceHelper; -public class EncodingDialogFragment extends DialogFragment implements AdapterView.OnItemClickListener { +public class EncodingDialog extends DialogFragment implements AdapterView.OnItemClickListener { private final String[] encodings = new String[]{ Constants.CHARSET_BIG5, @@ -67,8 +67,8 @@ public class EncodingDialogFragment extends DialogFragment implements AdapterVie }; private ListView list; - public static EncodingDialogFragment newInstance() { - final EncodingDialogFragment f = new EncodingDialogFragment(); + public static EncodingDialog newInstance() { + final EncodingDialog f = new EncodingDialog(); return f; } diff --git a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/FileInfoDialogFragment.java b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/FileInfoDialog.java similarity index 88% rename from libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/FileInfoDialogFragment.java rename to libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/FileInfoDialog.java index 541f800..b6d2c08 100644 --- a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/FileInfoDialogFragment.java +++ b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/FileInfoDialog.java @@ -25,26 +25,21 @@ import android.app.DialogFragment; import android.content.DialogInterface; import android.os.Bundle; import android.view.View; -import android.widget.ArrayAdapter; import android.widget.ListView; -import android.widget.NumberPicker; import org.apache.commons.io.FileUtils; -import org.apache.commons.io.FilenameUtils; -import org.apache.commons.io.IOUtils; import java.io.File; -import java.util.ArrayList; import java.util.Date; import sharedcode.turboeditor.R; import sharedcode.turboeditor.adapter.AdapterTwoItem; // ... -public class FileInfoDialogFragment extends DialogFragment { +public class FileInfoDialog extends DialogFragment { - public static FileInfoDialogFragment newInstance(String filePath) { - final FileInfoDialogFragment f = new FileInfoDialogFragment(); + public static FileInfoDialog newInstance(String filePath) { + final FileInfoDialog f = new FileInfoDialog(); final Bundle args = new Bundle(); args.putString("filePath", filePath); f.setArguments(args); diff --git a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/FindTextDialogFragment.java b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/FindTextDialog.java similarity index 90% rename from libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/FindTextDialogFragment.java rename to libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/FindTextDialog.java index 7dd660b..22b7947 100644 --- a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/FindTextDialogFragment.java +++ b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/FindTextDialog.java @@ -37,16 +37,17 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import sharedcode.turboeditor.R; -import sharedcode.turboeditor.util.SearchResult; +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 CheckBox regexCheck, replaceCheck, matchCaseCheck; - public static FindTextDialogFragment newInstance(String allText) { - final FindTextDialogFragment f = new FindTextDialogFragment(); + public static FindTextDialog newInstance(String allText) { + final FindTextDialog f = new FindTextDialog(); final Bundle args = new Bundle(); args.putString("allText", allText); f.setArguments(args); @@ -56,7 +57,13 @@ public class FindTextDialogFragment extends DialogFragment { @Override 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.textToReplace = (EditText) view.findViewById(R.id.text_to_replace); this.regexCheck = (CheckBox) view.findViewById(R.id.regex_check); @@ -79,13 +86,7 @@ public class FindTextDialogFragment extends DialogFragment { } } ) - .setNegativeButton(android.R.string.cancel, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - } - } - ) + .setNegativeButton(android.R.string.cancel, null) .create(); } @@ -200,7 +201,7 @@ public class FindTextDialogFragment extends DialogFragment { } Toast.makeText(getActivity(), String.format(getString(R.string.occurrences_found), foundIndex.size()), Toast.LENGTH_SHORT).show(); // dismiss the dialog - FindTextDialogFragment.this.dismiss(); + FindTextDialog.this.dismiss(); } } } \ No newline at end of file diff --git a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/NavigationDrawerListFragment.java b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/NavigationDrawer.java similarity index 94% rename from libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/NavigationDrawerListFragment.java rename to libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/NavigationDrawer.java index ebcee8a..1f77d16 100644 --- a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/NavigationDrawerListFragment.java +++ b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/NavigationDrawer.java @@ -37,7 +37,7 @@ 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; @@ -90,7 +90,6 @@ public class NavigationDrawerListFragment extends Fragment implements AdapterVie String filePath = savedPaths[position]; // Send the event that a file was selected EventBus.getDefault().post(new EventBusEvents.NewFileToOpen(new File(filePath))); - EventBus.getDefault().post(new EventBusEvents.AFileIsSelected(filePath)); } public void onEvent(EventBusEvents.AFileIsSelected event) { @@ -112,7 +111,7 @@ public class NavigationDrawerListFragment extends Fragment implements AdapterVie } } // Add the path if it wasn't added before - if(!pathAlreadyExist) + if (!pathAlreadyExist) addPath(selectedPath); EventBus.getDefault().removeStickyEvent(event); @@ -196,9 +195,4 @@ public class NavigationDrawerListFragment extends Fragment implements AdapterVie if (andCloseOpenedFile) EventBus.getDefault().post(new EventBusEvents.CannotOpenAFile()); } - - /*@Override - public void ItemSelected(String path) { - EventBus.getDefault().post(new EventBusEvents.AFileIsSelected(path)); - }*/ } diff --git a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/NewFileDetailsDialogFragment.java b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/NewFileDetailsDialog.java similarity index 83% rename from libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/NewFileDetailsDialogFragment.java rename to libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/NewFileDetailsDialog.java index 5cebbb5..2fd01a1 100644 --- a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/NewFileDetailsDialogFragment.java +++ b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/NewFileDetailsDialog.java @@ -32,16 +32,17 @@ import java.io.File; import sharedcode.turboeditor.R; 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 mFolder; - public static NewFileDetailsDialogFragment newInstance(String fileText, String fileEncoding) { - final NewFileDetailsDialogFragment f = new NewFileDetailsDialogFragment(); + public static NewFileDetailsDialog newInstance(String fileText, String fileEncoding) { + final NewFileDetailsDialog f = new NewFileDetailsDialog(); final Bundle args = new Bundle(); args.putString("fileText", fileText); args.putString("fileEncoding", fileEncoding); @@ -52,7 +53,11 @@ public class NewFileDetailsDialogFragment extends DialogFragment { @Override 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.mFolder = (EditText) view.findViewById(android.R.id.text2); @@ -71,6 +76,7 @@ public class NewFileDetailsDialogFragment extends DialogFragment { if (!mName.getText().toString().isEmpty() && !mFolder.getText().toString().isEmpty()) { File file = new File(mFolder.getText().toString(), mName.getText().toString()); new SaveFileTask(getActivity(), file.getPath(), getArguments().getString("fileText"), getArguments().getString("fileEncoding")).execute(); + PreferenceHelper.setWorkingFolder(getActivity(), file.getParent()); } } } diff --git a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/NoFileOpenedFragment.java b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/NoFileOpenedFragment.java deleted file mode 100644 index dd738dc..0000000 --- a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/NoFileOpenedFragment.java +++ /dev/null @@ -1,37 +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 . - */ - -package sharedcode.turboeditor.fragment; - -import android.os.Bundle; -import android.app.Fragment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import sharedcode.turboeditor.R; - -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); - } - -} diff --git a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/SaveFileDialogFragment.java b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/SaveFileDialog.java similarity index 55% rename from libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/SaveFileDialogFragment.java rename to libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/SaveFileDialog.java index 97f102c..3f4149a 100644 --- a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/SaveFileDialogFragment.java +++ b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/SaveFileDialog.java @@ -24,26 +24,36 @@ import android.app.Dialog; import android.app.DialogFragment; import android.content.DialogInterface; import android.os.Bundle; +import android.view.View; import org.apache.commons.io.FilenameUtils; import java.io.File; import sharedcode.turboeditor.R; -import sharedcode.turboeditor.util.SaveFileTask; +import sharedcode.turboeditor.task.SaveFileTask; +import sharedcode.turboeditor.views.DialogHelper; -public class SaveFileDialogFragment extends DialogFragment { +public class SaveFileDialog extends DialogFragment { - public static SaveFileDialogFragment newInstance(String filePath, String text, String encoding) { - 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(); args.putString("filePath", filePath); args.putString("text", text); args.putString("encoding", encoding); + args.putBoolean("openNewFileAfter", openNewFileAfter); + args.putString("pathOfNewFile", pathOfNewFile); frag.setArguments(args); return frag; } + + @Override public Dialog onCreateDialog(Bundle savedInstanceState) { final String filePath = getArguments().getString("filePath"); @@ -52,33 +62,51 @@ public class SaveFileDialogFragment extends DialogFragment { final String fileName = FilenameUtils.getName(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)) - .setPositiveButton(android.R.string.yes, + .createCommonView(); + + return new AlertDialog.Builder(getActivity()) + .setView(view) + .setPositiveButton(R.string.salva, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if (!fileName.isEmpty()) - new SaveFileTask(getActivity(), filePath, text, encoding).execute(); + new SaveFileTask(getActivity(), filePath, text, + encoding).execute(); else { - NewFileDetailsDialogFragment dialogFrag = NewFileDetailsDialogFragment.newInstance(text, encoding); - dialogFrag.show(getFragmentManager().beginTransaction(), "dialog"); + NewFileDetailsDialog dialogFrag = + 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() { @Override 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(); } - public static enum Action { - SaveAFile + public interface ISaveDialog { + void userDoesntWantToSave(boolean openNewFile, String pathOfNewFile); } } \ No newline at end of file diff --git a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/SeekbarDialogFragment.java b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/SeekbarDialog.java similarity index 68% rename from libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/SeekbarDialogFragment.java rename to libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/SeekbarDialog.java index e8dc917..da41492 100644 --- a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/SeekbarDialogFragment.java +++ b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/fragment/SeekbarDialog.java @@ -28,18 +28,19 @@ import android.view.View; import android.widget.NumberPicker; import sharedcode.turboeditor.R; +import sharedcode.turboeditor.views.DialogHelper; // ... -public class SeekbarDialogFragment extends DialogFragment { +public class SeekbarDialog extends DialogFragment { private NumberPicker mSeekBar; - public static SeekbarDialogFragment newInstance(final Actions action) { - return SeekbarDialogFragment.newInstance(action, 0, 50, 100); + public static SeekbarDialog newInstance(final Actions action) { + return SeekbarDialog.newInstance(action, 0, 50, 100); } - public static SeekbarDialogFragment newInstance(final Actions action, final int min, final int current, final int max) { - final SeekbarDialogFragment f = new SeekbarDialogFragment(); + public static SeekbarDialog newInstance(final Actions action, final int min, final int current, final int max) { + final SeekbarDialog f = new SeekbarDialog(); final Bundle args = new Bundle(); args.putSerializable("action", action); args.putInt("min", min); @@ -52,9 +53,28 @@ public class SeekbarDialogFragment extends DialogFragment { @Override 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.setMaxValue(getArguments().getInt("max")); this.mSeekBar.setMinValue(getArguments().getInt("min")); @@ -82,9 +102,9 @@ public class SeekbarDialogFragment extends DialogFragment { } void returnData() { - onSeekbarDialogDismissed target = (onSeekbarDialogDismissed) getTargetFragment(); + ISeekbarDialog target = (ISeekbarDialog) getTargetFragment(); if (target == null) { - target = (onSeekbarDialogDismissed) getActivity(); + target = (ISeekbarDialog) getActivity(); } target.onSeekbarDialogDismissed( (Actions) getArguments().getSerializable("action"), @@ -94,10 +114,10 @@ public class SeekbarDialogFragment extends DialogFragment { } public enum Actions { - FileSize, SelectPage, GoToLine + FontSize, SelectPage, GoToLine } - public interface onSeekbarDialogDismissed { + public interface ISeekbarDialog { void onSeekbarDialogDismissed(Actions action, int value); } } \ No newline at end of file diff --git a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/iab/Donation.java b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/iab/Donation.java new file mode 100644 index 0000000..2a0bc10 --- /dev/null +++ b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/iab/Donation.java @@ -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 . + */ + +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(); + } + +} diff --git a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/iab/DonationAdapter.java b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/iab/DonationAdapter.java new file mode 100644 index 0000000..958f807 --- /dev/null +++ b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/iab/DonationAdapter.java @@ -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 . + */ + +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 { + + private final HashSet mInventorySet; + private final LayoutInflater mInflater; + private final String mDonationAmountLabel; + + private final int mColorNormal; + private final int mColorPurchased; + + public DonationAdapter(Context context, Donation[] items, HashSet 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; + } +} diff --git a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/iab/DonationFragment.java b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/iab/DonationFragment.java new file mode 100644 index 0000000..12e592f --- /dev/null +++ b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/iab/DonationFragment.java @@ -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 . + */ + +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 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.
+ * 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; + } + } + + /** + * Make sure you call {@link #disposeBilling()}! + * + * @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 shouldn’t need to add just + // to cover for the deficiencies of the IAB API. + return true; + } + + private void complain(String message) { + ToastUtils.showShort(getActivity(), message); + } + +} diff --git a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/iab/DonationItems.java b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/iab/DonationItems.java new file mode 100644 index 0000000..34a3db1 --- /dev/null +++ b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/iab/DonationItems.java @@ -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 . + */ + +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; + } + +} diff --git a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/iab/utils/Base64.java b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/iab/utils/Base64.java new file mode 100644 index 0000000..94b7d50 --- /dev/null +++ b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/iab/utils/Base64.java @@ -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: + *

+ * 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 + * http://iharder.net/xmlizable + * periodically to check for updates or to contribute improvements. + *

+ * + * @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. + *

+ *

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 source + * and writes the resulting four Base64 bytes to destination. + * The source and destination arrays can be manipulated + * anywhere along their length by specifying + * srcOffset and destOffset. + * This method does not check to make sure your arrays + * are large enough to accommodate srcOffset + 3 for + * the source array or destOffset + 4 for + * the destination array. + * The actual number of significant bytes in your array is + * given by numSigBytes. + * + * @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 destination 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 source + * and writes the resulting bytes (up to three of them) + * to destination. + * The source and destination arrays can be manipulated + * anywhere along their length by specifying + * srcOffset and destOffset. + * This method does not check to make sure your arrays + * are large enough to accommodate srcOffset + 4 for + * the source array or destOffset + 3 for + * the destination 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; + } +} diff --git a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/ApiHelper.java b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/iab/utils/Base64DecoderException.java similarity index 66% rename from libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/ApiHelper.java rename to libraries/sharedCode/src/main/java/sharedcode/turboeditor/iab/utils/Base64DecoderException.java index df7e17e..8f4c024 100644 --- a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/ApiHelper.java +++ b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/iab/utils/Base64DecoderException.java @@ -17,12 +17,21 @@ * along with this program. If not, see . */ -package sharedcode.turboeditor.util; +package sharedcode.turboeditor.iab.utils; -import android.os.Build; +/** + * Exception thrown when encountering an invalid Base64 input character. + * + * @author nelson + */ +public class Base64DecoderException extends Exception { + private static final long serialVersionUID = 1L; -public class ApiHelper { - public static boolean is11(){ - return Build.VERSION.SDK_INT >= 11; + public Base64DecoderException() { + super(); + } + + public Base64DecoderException(String s) { + super(s); } } diff --git a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/iab/utils/IabException.java b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/iab/utils/IabException.java new file mode 100644 index 0000000..2e00564 --- /dev/null +++ b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/iab/utils/IabException.java @@ -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 . + */ + +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; + } +} \ No newline at end of file diff --git a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/iab/utils/IabHelper.java b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/iab/utils/IabHelper.java new file mode 100644 index 0000000..61060f0 --- /dev/null +++ b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/iab/utils/IabHelper.java @@ -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 . + */ + +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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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 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 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 moreItemSkus, + List 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 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 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 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 ownedSkus = ownedItems.getStringArrayList( + RESPONSE_INAPP_ITEM_LIST); + ArrayList purchaseDataList = ownedItems.getStringArrayList( + RESPONSE_INAPP_PURCHASE_DATA_LIST); + ArrayList 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 moreSkus) + throws RemoteException, JSONException { + logDebug("Querying SKU details."); + ArrayList 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 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 purchases, + final OnConsumeFinishedListener singleListener, + final OnConsumeMultiFinishedListener multiListener) { + final Handler handler = new Handler(); + flagStartAsync("consume"); + (new Thread(new Runnable() { + public void run() { + final List 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 purchases, List results); + } +} diff --git a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/iab/utils/IabResult.java b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/iab/utils/IabResult.java new file mode 100644 index 0000000..c01c194 --- /dev/null +++ b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/iab/utils/IabResult.java @@ -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 . + */ + +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(); + } +} + diff --git a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/iab/utils/Inventory.java b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/iab/utils/Inventory.java new file mode 100644 index 0000000..c3b3b23 --- /dev/null +++ b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/iab/utils/Inventory.java @@ -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 . + */ + +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 mSkuMap = new HashMap<>(); + Map 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 getAllOwnedSkus() { + return new ArrayList<>(mPurchaseMap.keySet()); + } + + /** + * Returns a list of all owned product IDs of a given type + */ + List getAllOwnedSkus(String itemType) { + List 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 getAllPurchases() { + return new ArrayList<>(mPurchaseMap.values()); + } + + void addSkuDetails(SkuDetails d) { + mSkuMap.put(d.getSku(), d); + } + + void addPurchase(Purchase p) { + mPurchaseMap.put(p.getSku(), p); + } +} diff --git a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/iab/utils/Purchase.java b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/iab/utils/Purchase.java new file mode 100644 index 0000000..d63575b --- /dev/null +++ b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/iab/utils/Purchase.java @@ -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 . + */ + +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; + } +} diff --git a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/iab/utils/Security.java b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/iab/utils/Security.java new file mode 100644 index 0000000..adaed71 --- /dev/null +++ b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/iab/utils/Security.java @@ -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 . + */ + +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; + } +} diff --git a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/iab/utils/SkuDetails.java b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/iab/utils/SkuDetails.java new file mode 100644 index 0000000..acdd42f --- /dev/null +++ b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/iab/utils/SkuDetails.java @@ -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 . + */ + +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; + } +} diff --git a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/preferences/ExtraSettingsActivity.java b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/preferences/ExtraSettingsActivity.java index e61b918..19aab21 100644 --- a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/preferences/ExtraSettingsActivity.java +++ b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/preferences/ExtraSettingsActivity.java @@ -35,6 +35,7 @@ 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 @@ -107,12 +108,7 @@ public class ExtraSettingsActivity extends PreferenceActivity { @Override protected void onCreate(Bundle savedInstanceState) { - boolean light = PreferenceHelper.getLightTheme(this); - if (light) { - setTheme(R.style.AppTheme_Light); - } else { - setTheme(R.style.AppTheme_Dark); - } + ThemeUtils.setTheme(this); super.onCreate(savedInstanceState); } diff --git a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/preferences/PreferenceHelper.java b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/preferences/PreferenceHelper.java index 23f193c..96502b1 100644 --- a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/preferences/PreferenceHelper.java +++ b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/preferences/PreferenceHelper.java @@ -109,6 +109,9 @@ public final class PreferenceHelper { return getPrefs(context).getBoolean("page_system_active", true); } + public static boolean hasDonated(Context context) { + return getPrefs(context).getBoolean("has_donated", false); + } // Setter methods public static void setUseMonospace(Context context, boolean value) { @@ -151,4 +154,8 @@ public final class PreferenceHelper { getEditor(context).putBoolean("read_only", value).commit(); } + public static void setHasDonated(Context context, boolean value) { + getEditor(context).putBoolean("has_donated", value); + } + } diff --git a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/preferences/SettingsFragment.java b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/preferences/SettingsFragment.java index d539b4f..cedfde2 100644 --- a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/preferences/SettingsFragment.java +++ b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/preferences/SettingsFragment.java @@ -21,7 +21,6 @@ package sharedcode.turboeditor.preferences; import android.app.Fragment; import android.content.Intent; -import android.net.Uri; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; @@ -32,8 +31,11 @@ import android.widget.TextView; import de.greenrobot.event.EventBus; import sharedcode.turboeditor.R; -import sharedcode.turboeditor.fragment.SeekbarDialogFragment; +import sharedcode.turboeditor.fragment.SeekbarDialog; +import sharedcode.turboeditor.util.AnimationUtils; 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.Type.FONT_SIZE; @@ -43,7 +45,7 @@ import static sharedcode.turboeditor.util.EventBusEvents.APreferenceValueWasChan import static sharedcode.turboeditor.util.EventBusEvents.APreferenceValueWasChanged.Type.SYNTAX; import static sharedcode.turboeditor.util.EventBusEvents.APreferenceValueWasChanged.Type.WRAP_CONTENT; -public class SettingsFragment extends Fragment implements SeekbarDialogFragment.onSeekbarDialogDismissed { +public class SettingsFragment extends Fragment implements SeekbarDialog.ISeekbarDialog { // Editor Variables private boolean sLineNumbers; @@ -67,7 +69,7 @@ public class SettingsFragment extends Fragment implements SeekbarDialogFragment. Bundle savedInstanceState) { // Our custom layout 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); switchSyntax = (CheckBox) rootView.findViewById(R.id.switch_syntax); switchWrapContent = (CheckBox) rootView.findViewById(R.id.switch_wrap_content); @@ -80,10 +82,13 @@ public class SettingsFragment extends Fragment implements SeekbarDialogFragment. switchMonospace.setChecked(sUseMonospace); switchReadOnly.setChecked(sReadOnly); - TextView fontSizeView, goProView, extraOptionsView; + TextView fontSizeView, donateView, extraOptionsView; fontSizeView = (TextView) rootView.findViewById(R.id.drawer_button_font_size); extraOptionsView = (TextView) rootView.findViewById(R.id.drawer_button_extra_options); - goProView = (TextView) rootView.findViewById(R.id.drawer_button_go_pro); + donateView = (TextView) rootView.findViewById(R.id.drawer_button_go_pro); + + if(ProCheckUtils.isPro(getActivity(), false)) + ViewUtils.setVisible(donateView, false); switchLineNumbers.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override @@ -139,7 +144,8 @@ public class SettingsFragment extends Fragment implements SeekbarDialogFragment. int fontCurrent = //(int) (mEditor.getTextSize() / scaledDensity); //fontMax / 2; 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.show(getFragmentManager().beginTransaction(), "dialog"); } @@ -148,27 +154,23 @@ public class SettingsFragment extends Fragment implements SeekbarDialogFragment. extraOptionsView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - startActivity(new Intent(getActivity(), ExtraSettingsActivity.class)); + AnimationUtils.startActivityWithScale(getActivity(), new Intent(getActivity(), + ExtraSettingsActivity.class), false, 0, v); } }); - goProView.setOnClickListener(new View.OnClickListener() { + donateView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - try { - startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=com.maskyn.fileeditorpro")) - .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); - } catch (Exception e) { - } + DialogHelper.showDonateDialog(getActivity()); } }); - goProView.setVisibility(ProCheckUtils.isPro(getActivity()) ? View.GONE : View.VISIBLE); return rootView; } @Override - public void onSeekbarDialogDismissed(SeekbarDialogFragment.Actions action, int value) { + public void onSeekbarDialogDismissed(SeekbarDialog.Actions action, int value) { PreferenceHelper.setFontSize(getActivity(), value); EventBus.getDefault().post(new APreferenceValueWasChanged(FONT_SIZE)); diff --git a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/LinuxShell.java b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/root/LinuxShell.java similarity index 98% rename from libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/LinuxShell.java rename to libraries/sharedCode/src/main/java/sharedcode/turboeditor/root/LinuxShell.java index 64a59e2..82e2e7c 100644 --- a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/LinuxShell.java +++ b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/root/LinuxShell.java @@ -17,7 +17,9 @@ * along with this program. If not, see . */ -package sharedcode.turboeditor.util; +package sharedcode.turboeditor.root; + +import android.util.Log; import java.io.BufferedReader; import java.io.DataOutputStream; @@ -26,8 +28,6 @@ import java.io.InputStreamReader; import java.util.regex.Matcher; import java.util.regex.Pattern; -import android.util.Log; - public class LinuxShell { private static final String UNIX_ESCAPE_EXPRESSION = "(\\(|\\)|\\[|\\]|\\s|\'|\"|`|\\{|\\}|&|\\\\|\\?)"; diff --git a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/RootUtils.java b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/root/RootUtils.java similarity index 98% rename from libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/RootUtils.java rename to libraries/sharedCode/src/main/java/sharedcode/turboeditor/root/RootUtils.java index b1d7654..7ed8115 100644 --- a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/RootUtils.java +++ b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/root/RootUtils.java @@ -32,7 +32,7 @@ * along with 920 Text Editor. If not, see . */ -package sharedcode.turboeditor.util; +package sharedcode.turboeditor.root; import android.content.Context; diff --git a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/SaveFileTask.java b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/task/SaveFileTask.java similarity index 95% rename from libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/SaveFileTask.java rename to libraries/sharedCode/src/main/java/sharedcode/turboeditor/task/SaveFileTask.java index 2c94b8e..bc4d5a0 100644 --- a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/SaveFileTask.java +++ b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/task/SaveFileTask.java @@ -17,7 +17,7 @@ * along with this program. If not, see . */ -package sharedcode.turboeditor.util; +package sharedcode.turboeditor.task; import android.content.Context; import android.os.AsyncTask; @@ -32,6 +32,8 @@ import java.util.concurrent.TimeoutException; import de.greenrobot.event.EventBus; import sharedcode.turboeditor.R; +import sharedcode.turboeditor.root.RootUtils; +import sharedcode.turboeditor.util.EventBusEvents; public class SaveFileTask extends AsyncTask { diff --git a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/EdittextPadding.java b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/texteditor/EditTextPadding.java similarity index 90% rename from libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/EdittextPadding.java rename to libraries/sharedCode/src/main/java/sharedcode/turboeditor/texteditor/EditTextPadding.java index d4bc9e5..b378e7e 100644 --- a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/EdittextPadding.java +++ b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/texteditor/EditTextPadding.java @@ -17,11 +17,13 @@ * along with this program. If not, see . */ -package sharedcode.turboeditor.util; +package sharedcode.turboeditor.texteditor; import android.content.Context; -public class EdittextPadding { +import sharedcode.turboeditor.util.PixelDipConverter; + +public class EditTextPadding { public static int getPaddingWithoutLineNumbers(Context context) { return (int) PixelDipConverter.convertDpToPixel(5, context); diff --git a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/FileUtils.java b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/texteditor/FileUtils.java similarity index 97% rename from libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/FileUtils.java rename to libraries/sharedCode/src/main/java/sharedcode/turboeditor/texteditor/FileUtils.java index f820f67..5ad2a3e 100644 --- a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/FileUtils.java +++ b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/texteditor/FileUtils.java @@ -17,7 +17,7 @@ * along with this program. If not, see . */ -package sharedcode.turboeditor.util; +package sharedcode.turboeditor.texteditor; import org.mozilla.universalchardet.UniversalDetector; diff --git a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/LineUtils.java b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/texteditor/LineUtils.java similarity index 93% rename from libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/LineUtils.java rename to libraries/sharedCode/src/main/java/sharedcode/turboeditor/texteditor/LineUtils.java index 4472f96..080b42b 100644 --- a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/LineUtils.java +++ b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/texteditor/LineUtils.java @@ -17,7 +17,7 @@ * along with this program. If not, see . */ -package sharedcode.turboeditor.util; +package sharedcode.turboeditor.texteditor; import android.text.Layout; import android.widget.ScrollView; @@ -34,11 +34,11 @@ public class LineUtils { 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; } - public int getFirstVisibleLine(ScrollView scrollView, int childHeight, int lineCount) throws ArithmeticException{ + public int getFirstVisibleLine(ScrollView scrollView, int childHeight, int lineCount) throws ArithmeticException { int line = (scrollView.getScrollY() * lineCount) / childHeight; if (line < 0) line = 0; return line; @@ -85,6 +85,7 @@ public class LineUtils { /** * Gets the line from the index of the letter in the text + * * @param index * @param lineCount * @param layout @@ -109,14 +110,14 @@ public class LineUtils { } public int lastReadLine() { - return realLines[realLines.length-1]; + return realLines[realLines.length - 1]; } public int fakeLineFromRealLine(int realLine) { int i; int fakeLine = 0; - for(i = 0; i < realLines.length; i++) { - if (realLine == realLines[i]){ + for (i = 0; i < realLines.length; i++) { + if (realLine == realLines[i]) { fakeLine = i; break; } diff --git a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/PageSystem.java b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/texteditor/PageSystem.java similarity index 99% rename from libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/PageSystem.java rename to libraries/sharedCode/src/main/java/sharedcode/turboeditor/texteditor/PageSystem.java index ba90010..d860604 100644 --- a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/PageSystem.java +++ b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/texteditor/PageSystem.java @@ -17,7 +17,7 @@ * along with this program. If not, see . */ -package sharedcode.turboeditor.util; +package sharedcode.turboeditor.texteditor; import android.content.Context; diff --git a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/PageSystemButtons.java b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/texteditor/PageSystemButtons.java similarity index 99% rename from libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/PageSystemButtons.java rename to libraries/sharedCode/src/main/java/sharedcode/turboeditor/texteditor/PageSystemButtons.java index c7a104d..0f6a50e 100644 --- a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/PageSystemButtons.java +++ b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/texteditor/PageSystemButtons.java @@ -17,7 +17,7 @@ * along with this program. If not, see . */ -package sharedcode.turboeditor.util; +package sharedcode.turboeditor.texteditor; import android.content.Context; import android.os.Handler; diff --git a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/Patterns.java b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/texteditor/Patterns.java similarity index 99% rename from libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/Patterns.java rename to libraries/sharedCode/src/main/java/sharedcode/turboeditor/texteditor/Patterns.java index 06af62f..9c2b630 100644 --- a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/Patterns.java +++ b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/texteditor/Patterns.java @@ -17,7 +17,7 @@ * along with this program. If not, see . */ -package sharedcode.turboeditor.util; +package sharedcode.turboeditor.texteditor; import java.util.regex.Pattern; diff --git a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/SearchResult.java b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/texteditor/SearchResult.java similarity index 97% rename from libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/SearchResult.java rename to libraries/sharedCode/src/main/java/sharedcode/turboeditor/texteditor/SearchResult.java index 634c805..5991dc9 100644 --- a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/SearchResult.java +++ b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/texteditor/SearchResult.java @@ -17,7 +17,7 @@ * along with this program. If not, see . */ -package sharedcode.turboeditor.util; +package sharedcode.turboeditor.texteditor; import java.util.LinkedList; diff --git a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/AccessStorageApi.java b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/AccessStorageApi.java index 7674655..cadedd9 100644 --- a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/AccessStorageApi.java +++ b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/AccessStorageApi.java @@ -19,12 +19,7 @@ package sharedcode.turboeditor.util; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; - import android.annotation.SuppressLint; -import android.annotation.TargetApi; import android.content.ContentUris; import android.content.Context; import android.database.Cursor; @@ -36,174 +31,178 @@ 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 { + public static Bitmap loadPrescaledBitmap(String filename) throws IOException { // Facebook image size - final int IMAGE_MAX_SIZE = 630; + final int IMAGE_MAX_SIZE = 630; - File file = null; - FileInputStream fis; + File file = null; + FileInputStream fis; - BitmapFactory.Options opts; - int resizeScale; - Bitmap bmp; + BitmapFactory.Options opts; + int resizeScale; + Bitmap bmp; - file = new File(filename); + 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(); + // 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; + // 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))); - } + 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); + // Load pre-scaled bitmap + opts = new BitmapFactory.Options(); + opts.inSampleSize = resizeScale; + fis = new FileInputStream(file); + bmp = BitmapFactory.decodeStream(fis, null, opts); - fis.close(); + 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 - */ + 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; + final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; - // DocumentProvider + // 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]; + // 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]; - } + if ("primary".equalsIgnoreCase(type)) { + return Environment.getExternalStorageDirectory() + "/" + split[1]; + } - // TODO handle non-primary volumes - } - // DownloadsProvider - else if (isDownloadsDocument(uri)) { + // 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)); + 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]; + 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; - } + 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] - }; + 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 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; - } + 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) { + /** + * 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 - }; + 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; - } + 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 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 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()); - } + /** + * @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()); + } } diff --git a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/AnimationUtils.java b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/AnimationUtils.java new file mode 100644 index 0000000..f89bb6b --- /dev/null +++ b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/AnimationUtils.java @@ -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 . + */ + +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); + } + } +} diff --git a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/Build.java b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/Build.java new file mode 100644 index 0000000..c656ccc --- /dev/null +++ b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/Build.java @@ -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 . + */ +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"; + + } + +} diff --git a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/Constants.java b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/Constants.java deleted file mode 100644 index d8c5959..0000000 --- a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/Constants.java +++ /dev/null @@ -1,25 +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 . - */ - -package sharedcode.turboeditor.util; - -public class Constants { - public static final int MAX_FILE_SIZE = 20_000; - public static final boolean FOR_AMAZON = false; -} diff --git a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/Device.java b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/Device.java new file mode 100644 index 0000000..ca464f6 --- /dev/null +++ b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/Device.java @@ -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 . + */ +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; + } + +} diff --git a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/EditorInterface.java b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/EditorInterface.java deleted file mode 100644 index 491f042..0000000 --- a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/EditorInterface.java +++ /dev/null @@ -1,32 +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 . - */ - -package sharedcode.turboeditor.util; - -import sharedcode.turboeditor.views.GoodScrollView; - -public interface EditorInterface { - public GoodScrollView getVerticalScrollView(); - - public String getFilePath(); - - public PageSystem getPageSystem(); - - public void updateTextSyntax(); -} \ No newline at end of file diff --git a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/ProCheckUtils.java b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/ProCheckUtils.java index 40b9040..f909309 100644 --- a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/ProCheckUtils.java +++ b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/ProCheckUtils.java @@ -21,16 +21,24 @@ package sharedcode.turboeditor.util; import android.content.Context; +import sharedcode.turboeditor.preferences.PreferenceHelper; + public class ProCheckUtils { - public static boolean isPro(Context context) { + public static boolean isPro(Context context, boolean includeDonations) { String packageName = context.getPackageName(); - if (Constants.FOR_AMAZON) + if (Build.FOR_AMAZON) return true; else if (packageName.equals("com.maskyn.fileeditorpro")) return true; + else if (includeDonations && PreferenceHelper.hasDonated(context)) + return true; else return false; } + + public static boolean isPro(Context context) { + return isPro(context, true); + } } diff --git a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/ThemeHelper.java b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/ThemeUtils.java similarity index 80% rename from libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/ThemeHelper.java rename to libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/ThemeUtils.java index 7bbf6ce..ac76d63 100644 --- a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/ThemeHelper.java +++ b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/ThemeUtils.java @@ -24,7 +24,16 @@ import android.app.Activity; import sharedcode.turboeditor.R; 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) { boolean whiteTheme = PreferenceHelper.getLightTheme(activity); diff --git a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/ToastUtils.java b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/ToastUtils.java new file mode 100644 index 0000000..ff6a5bb --- /dev/null +++ b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/ToastUtils.java @@ -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 . + */ +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; + } + +} diff --git a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/ViewUtils.java b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/ViewUtils.java new file mode 100644 index 0000000..8459586 --- /dev/null +++ b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/util/ViewUtils.java @@ -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 . + */ +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; + } + } + +} diff --git a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/views/DialogHelper.java b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/views/DialogHelper.java new file mode 100644 index 0000000..5e1dac3 --- /dev/null +++ b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/views/DialogHelper.java @@ -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 . + */ +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)); + } + + } + +} \ No newline at end of file diff --git a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/views/GoodScrollView.java b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/views/GoodScrollView.java index 459fbba..a1d9942 100644 --- a/libraries/sharedCode/src/main/java/sharedcode/turboeditor/views/GoodScrollView.java +++ b/libraries/sharedCode/src/main/java/sharedcode/turboeditor/views/GoodScrollView.java @@ -22,7 +22,6 @@ package sharedcode.turboeditor.views; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; -import android.view.MotionEvent; import android.view.View; import android.widget.ScrollView; @@ -61,10 +60,10 @@ public class GoodScrollView extends ScrollView { } - public boolean hasReachedBottom(){ - View firstChild = getChildAt(getChildCount()-1); + public boolean hasReachedBottom() { + View firstChild = getChildAt(getChildCount() - 1); - int diff = (firstChild.getBottom()-(getHeight()+getScrollY()+firstChild.getTop()));// Calculate the scrolldiff + int diff = (firstChild.getBottom() - (getHeight() + getScrollY() + firstChild.getTop()));// Calculate the scrolldiff return diff <= 0; } diff --git a/libraries/sharedCode/src/main/res/drawable-hdpi/ic_action_paypal.png b/libraries/sharedCode/src/main/res/drawable-hdpi/ic_action_paypal.png new file mode 100644 index 0000000000000000000000000000000000000000..d11ddbc079de267717a61b8fc5cecdc4e8ec0b6a GIT binary patch literal 875 zcmV-x1C;!UP)bczF3x?t>vZpV=id8f zUIXvDIrpC5`JMOup7T5B{LXzN?RMKr#;QRRXo{hLL1QRj&=?9BG*-NT(T4u-V^f9p zZTyB?xQ>~Izi*_$NxTN%sy_&AoW{rayDo_)A+%ky863eIIEC@Loiq{fVguhi#&NK& z^Z!8rypCt?sB{VMFmZDDQvl}e(nfTzUc_9U(@syl|`cqZaE zk1uc-vnfRChEQzu|HaLM`aAe4;@83hf%u21-0BLLir9X_LW%7`#IKc=3)qQQQiv?Y z3|x=cc0_E`S@}d(?oJ_6N5JYHM9Ktg!>c8>SzODWH)Q4Z6f$)LynwNY{YF9EOE{u@ z(>RlrPhv8AzMDd(&Mwqz0l&GZ|800#IC+ckC(dQh`-K@MY^gobZvmrtw?bbFIHXJV zK+bk6rGq*l)UOk`C1kUmkb4bVbGEZ7om8EH$1zdSrwBKNEcE;AeHT8+*{|S_l#Z$j zm{K0gz3`8a|DV9uLdrlmnLol~xwfZLJFO$248P*Dg8F&P;dfoqupaN=ZLH1Je~!zk zgsKXN#s7D>*u#Gu&t?6*C4^)i7liFL8C3*~=zIC81ve zrUk+qbT2J4_@pMUWK=oGVgfqhvolvWUDUAONEI+9BynZv2$(FWTT-Nk&;)*t*gIsW z3hI^=O+IWD;Tz#Xa!93F!A4Saz)V{|ANw292SBL1QRj&{**T9s=c6fCK_(SZ@FT002ovPDHLkV1lC^ BpQr!; literal 0 HcmV?d00001 diff --git a/libraries/sharedCode/src/main/res/drawable-mdpi/ic_action_paypal.png b/libraries/sharedCode/src/main/res/drawable-mdpi/ic_action_paypal.png new file mode 100644 index 0000000000000000000000000000000000000000..7844e6d5410d2b5a7a8fe846adbbfdfd1f14c4c6 GIT binary patch literal 596 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 I_%qJ^10a9l@Bjb+ literal 0 HcmV?d00001 diff --git a/libraries/sharedCode/src/main/res/drawable/item_background_holo_dark.xml b/libraries/sharedCode/src/main/res/drawable/item_background_holo_dark.xml index c9797ae..b548cc6 100644 --- a/libraries/sharedCode/src/main/res/drawable/item_background_holo_dark.xml +++ b/libraries/sharedCode/src/main/res/drawable/item_background_holo_dark.xml @@ -26,5 +26,5 @@ - + diff --git a/libraries/sharedCode/src/main/res/layout/activity_about.xml b/libraries/sharedCode/src/main/res/layout/activity_about.xml deleted file mode 100644 index 2a45a32..0000000 --- a/libraries/sharedCode/src/main/res/layout/activity_about.xml +++ /dev/null @@ -1,149 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/libraries/sharedCode/src/main/res/layout/activity_home.xml b/libraries/sharedCode/src/main/res/layout/activity_home.xml index 8b83f1e..e24b75a 100644 --- a/libraries/sharedCode/src/main/res/layout/activity_home.xml +++ b/libraries/sharedCode/src/main/res/layout/activity_home.xml @@ -25,14 +25,81 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - + android:layout_height="match_parent" + android:gravity="center" + android:textSize="@dimen/text_size_mega_title" + android:fontFamily="sans-serif-light" + android:text="@string/open_a_file" + android:background="@null" + android:textColor="@android:color/secondary_text_dark" + android:id="@id/no_file_opened_messagge"/> + + + + + + + + + + + + + + + + + + android:smoothScrollbar="true"/> + + + + + + + + \ No newline at end of file diff --git a/libraries/sharedCode/src/main/res/layout/dialog_fragment_find_text.xml b/libraries/sharedCode/src/main/res/layout/dialog_fragment_find_text.xml index ddd7abe..d7cf699 100644 --- a/libraries/sharedCode/src/main/res/layout/dialog_fragment_find_text.xml +++ b/libraries/sharedCode/src/main/res/layout/dialog_fragment_find_text.xml @@ -19,7 +19,7 @@ --> + + + + + + \ No newline at end of file diff --git a/libraries/sharedCode/src/main/res/layout/donation_dialog.xml b/libraries/sharedCode/src/main/res/layout/donation_dialog.xml new file mode 100644 index 0000000..2132d23 --- /dev/null +++ b/libraries/sharedCode/src/main/res/layout/donation_dialog.xml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/libraries/sharedCode/src/main/res/layout/donation_iab_item.xml b/libraries/sharedCode/src/main/res/layout/donation_iab_item.xml new file mode 100644 index 0000000..f74296b --- /dev/null +++ b/libraries/sharedCode/src/main/res/layout/donation_iab_item.xml @@ -0,0 +1,50 @@ + + + + + + + + + + \ No newline at end of file diff --git a/libraries/sharedCode/src/main/res/layout/fragment_editor.xml b/libraries/sharedCode/src/main/res/layout/fragment_editor.xml deleted file mode 100644 index 6b163ec..0000000 --- a/libraries/sharedCode/src/main/res/layout/fragment_editor.xml +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/libraries/sharedCode/src/main/res/layout/fragment_settings.xml b/libraries/sharedCode/src/main/res/layout/fragment_settings.xml index 5af7835..1102951 100644 --- a/libraries/sharedCode/src/main/res/layout/fragment_settings.xml +++ b/libraries/sharedCode/src/main/res/layout/fragment_settings.xml @@ -31,16 +31,11 @@ android:orientation="vertical" > - - + + Turbo Editor is a free and open source app. Now you can show your appreciation and support development by donating :) + New visual changes + New save dialog when you are about to close a file + Many enchantments and fixes + + Many enchantments and fixes diff --git a/libraries/sharedCode/src/main/res/values-sw600dp/dimens.xml b/libraries/sharedCode/src/main/res/values-w820dp/dimens.xml similarity index 96% rename from libraries/sharedCode/src/main/res/values-sw600dp/dimens.xml rename to libraries/sharedCode/src/main/res/values-w820dp/dimens.xml index b43a9db..d1b4929 100644 --- a/libraries/sharedCode/src/main/res/values-sw600dp/dimens.xml +++ b/libraries/sharedCode/src/main/res/values-w820dp/dimens.xml @@ -20,7 +20,6 @@ 64dp - 64dp diff --git a/libraries/sharedCode/src/main/res/layout/fragment_no_file_open.xml b/libraries/sharedCode/src/main/res/values/attrs.xml similarity index 66% rename from libraries/sharedCode/src/main/res/layout/fragment_no_file_open.xml rename to libraries/sharedCode/src/main/res/values/attrs.xml index 35ea9fa..573577d 100644 --- a/libraries/sharedCode/src/main/res/layout/fragment_no_file_open.xml +++ b/libraries/sharedCode/src/main/res/values/attrs.xml @@ -18,12 +18,12 @@ ~ along with this program. If not, see . --> - + + + + + + + + + \ No newline at end of file diff --git a/libraries/sharedCode/src/main/res/values/colors.xml b/libraries/sharedCode/src/main/res/values/colors.xml index 9e92015..e54a7c7 100644 --- a/libraries/sharedCode/src/main/res/values/colors.xml +++ b/libraries/sharedCode/src/main/res/values/colors.xml @@ -61,4 +61,7 @@ #40969696 #50000000 #30000000 + + #b6cc45 + #DFED47 \ No newline at end of file diff --git a/libraries/sharedCode/src/main/res/values/dimens.xml b/libraries/sharedCode/src/main/res/values/dimens.xml index 69dca58..09562e1 100644 --- a/libraries/sharedCode/src/main/res/values/dimens.xml +++ b/libraries/sharedCode/src/main/res/values/dimens.xml @@ -19,8 +19,8 @@ - 0dp - 0dp + 16dp + 16dp diff --git a/libraries/sharedCode/src/main/res/values/ids.xml b/libraries/sharedCode/src/main/res/values/ids.xml index 14c2aae..008c7a1 100644 --- a/libraries/sharedCode/src/main/res/values/ids.xml +++ b/libraries/sharedCode/src/main/res/values/ids.xml @@ -50,7 +50,6 @@ - @@ -59,6 +58,9 @@ + + + diff --git a/libraries/sharedCode/src/main/res/values/strings.xml b/libraries/sharedCode/src/main/res/values/strings.xml index b281ae6..0cb3be0 100644 --- a/libraries/sharedCode/src/main/res/values/strings.xml +++ b/libraries/sharedCode/src/main/res/values/strings.xml @@ -30,7 +30,8 @@ File Folder Light Theme - Go to Line + Go to Line… + Go to Page… Find Replace Share @@ -46,12 +47,11 @@ Previous Please wait… %s occurrences was found - Version %s + v%s Translate Changelog Match case Long click for more options - Pro version Auto save Read only Send error reports @@ -59,116 +59,18 @@ Split the text if too long Ignore back button Donate - - New account - Active - Delete - Deleting files… - Loading… - Current local folder - Private Key - Light Encoding Share - New local folder - New remote folder - New remote file - New local file - Disconnect - Default local folder - Where to download? - Download - Download completed - Duplicate - Done - Home - Host Info - Local - Logging in… - Edit - Move - Hide - Turbo Client Turbo Editor - Username - Passive - Passphrase - Password - Leave it empty to prompt for it every session - Port Preferences - Remote - To change the theme, restart the application - Rename - Default remote folder Save - Dark - Select - Select an account - Are you sure? - Something failed - Do not transfer same file - App theme - Connection type - Protocol type - Another folder - Use a passphrase - Upload - Upload completed - What do you want to do? Syntax highlight Undo Redo - Sync - Remote folder to sync - Local folder to sync - Rate - Cannot contact Google Play - Support the development of other great features. - Upgrade to Premium - Upgrade to Premium and support the development of Turbo Client! - Download unlocked version - What is Turbo Client worth to you? Set your price! - Upgrade to unlock this features: - Power to open and modify any type of file. - Backup service to backup and restore your data safely. - Unlock the Premium features - I really like this app! - I love this app! - Backup the accounts - Restore the accounts - Backup and share the accounts - Importing the accounts… - Exporting the accounts... - No backups found - Cannot open the file - Temporary folder does not exist - An error occurred - UI - Remove - Modification date - Name - Size - Sort Open - The file %1$s was modified, do you want to upload it? The file %1$s was saved with success! - Create a new account - Create a new account to start. - Type - Send feedback - Copy URL - Cut - paste - Advanced - Auto - Bytes - Unit of measurement for file size - Open Source licenses - Show open source licenses Open a file - Open this time only - Change the list type + No diff --git a/libraries/sharedCode/src/main/res/values/strings_dialogs.xml b/libraries/sharedCode/src/main/res/values/strings_dialogs.xml new file mode 100644 index 0000000..c9a4647 --- /dev/null +++ b/libraries/sharedCode/src/main/res/values/strings_dialogs.xml @@ -0,0 +1,40 @@ + + + + + Close + + + About + %1$s v%2$s + ]]> + open source app. + Copyright 2013-2014 Vlad Mihalachi. All Rights Reserved.
+
+ Many thanks to all who + helped with translations or + donated to me.
+
+ If you want to send feedback here is the XDA thread + ]]>
+ + \ No newline at end of file diff --git a/libraries/sharedCode/src/main/res/values/strings_donation.xml b/libraries/sharedCode/src/main/res/values/strings_donation.xml new file mode 100644 index 0000000..c3b062f --- /dev/null +++ b/libraries/sharedCode/src/main/res/values/strings_donation.xml @@ -0,0 +1,45 @@ + + + + + + Donate + Donate to developer + open source app. + You can show your appreciation and support development by donating: + ]]> + + ﹩%1$s + You\'ve donated for this item already. + + An ice cream + Cup of coffee + Electricity bills + The right pillow + Solid-state drive + Sound system + + Failed to setup in-app-billing service! + Notice that Google is not responsible for that payments method. + + PayPal + + \ No newline at end of file diff --git a/libraries/sharedCode/src/main/res/values/strings_turbo_client.xml b/libraries/sharedCode/src/main/res/values/strings_turbo_client.xml new file mode 100644 index 0000000..4bbf011 --- /dev/null +++ b/libraries/sharedCode/src/main/res/values/strings_turbo_client.xml @@ -0,0 +1,104 @@ + + + + + Change the list type + Copy URL + Cut + Paste + Advanced + Auto + Bytes + Unit of measurement for file size + Create a new account + Create a new account to start. + Type + The file %1$s was modified, do you want to upload it? + Sync + Remote folder to sync + Local folder to sync + Rate + Backup the accounts + Restore the accounts + Backup and share the accounts + Importing the accounts… + Exporting the accounts… + No backups found + Cannot open the file + Temporary folder does not exist + An error occurred + UI + Remove + Modification date + Name + Size + Sort + Dark + Select + Select an account + Are you sure? + Something failed + Do not transfer same file + App theme + Connection type + Protocol type + Another folder + Use a passphrase + Upload + Upload completed + What do you want to do? + Remote + To change the theme, restart the application + Rename + Default remote folder + Username + Passive + Passphrase + Password + Leave it empty to prompt for it every session + Port + Local + Logging in… + Edit + Move + Hide + Turbo Client + Host + New local folder + New remote folder + New remote file + New local file + Disconnect + Default local folder + Where to download? + Download + Download completed + Duplicate + Done + Home + Light + New account + Active + Delete + Deleting files… + Loading… + Current local folder + Private Key + \ No newline at end of file diff --git a/libraries/sharedCode/src/main/res/values/styles.xml b/libraries/sharedCode/src/main/res/values/styles.xml index 03d1a68..b62e93b 100644 --- a/libraries/sharedCode/src/main/res/values/styles.xml +++ b/libraries/sharedCode/src/main/res/values/styles.xml @@ -19,14 +19,14 @@ - - + + @style/TextAppearance.Dialog.Title + @style/TextAppearance.Dialog.Message + @style/TextAppearance.Dialog.Info - - - + + + + + +