From d7ea4352ac5de56202268fd6128185c588667189 Mon Sep 17 00:00:00 2001 From: zt515 Date: Tue, 11 Jul 2017 02:57:33 +0800 Subject: [PATCH] Release: 1.1.6 --- app/build.gradle | 4 +- app/src/main/AndroidManifest.xml | 31 ++- app/src/main/java/io/neoterm/App.kt | 5 +- .../neoterm/customize/eks/EksConfigParser.kt | 2 +- .../io/neoterm/preference/NeoPreference.kt | 2 + .../io/neoterm/services/NeoTermService.kt | 77 +++++- .../io/neoterm/ui/bonus/BonusActivity.java | 193 +++++++++++++++ .../java/io/neoterm/ui/crash/CrashActivity.kt | 46 ++++ .../io/neoterm/ui/floating/FloatingNeoTerm.kt | 6 + .../io/neoterm/ui/settings/SettingActivity.kt | 30 ++- .../java/io/neoterm/ui/setup/SetupActivity.kt | 21 +- .../neoterm/ui/{ => term}/NeoTermActivity.kt | 112 ++++++--- .../term}/tab/TermSessionChangedCallback.kt | 2 +- .../neoterm/{view => ui/term}/tab/TermTab.kt | 34 ++- .../{view => ui/term}/tab/TermTabDecorator.kt | 4 +- .../io/neoterm/ui/term/tab/TermViewClient.kt | 204 ++++++++++++++++ .../ui/term/tab/event/TabCloseEvent.kt | 9 + .../term/tab/event/ToggleFullScreenEvent.kt | 6 + .../java/io/neoterm/utils/CrashHandler.kt | 26 ++ .../io/neoterm/utils/FullScreenHelper.java | 126 ---------- .../java/io/neoterm/utils/FullScreenHelper.kt | 120 ++++++++++ app/src/main/java/io/neoterm/utils/Shaker.kt | 52 ++++ .../java/io/neoterm/view/ExtraKeysView.java | 214 ----------------- .../java/io/neoterm/view/ExtraKeysView.kt | 223 ++++++++++++++++++ .../view/GestureAndScaleRecognizer.java | 111 --------- .../neoterm/view/GestureAndScaleRecognizer.kt | 95 ++++++++ .../java/io/neoterm/view/TerminalView.java | 23 +- .../io/neoterm/view/eks/ControlButton.java | 20 -- .../java/io/neoterm/view/eks/ControlButton.kt | 19 ++ .../java/io/neoterm/view/eks/ExtraButton.java | 82 ------- .../java/io/neoterm/view/eks/ExtraButton.kt | 62 +++++ .../neoterm/view/eks/StatedControlButton.java | 44 ---- .../neoterm/view/eks/StatedControlButton.kt | 33 +++ .../java/io/neoterm/view/eks/TextButton.java | 30 --- .../java/io/neoterm/view/eks/TextButton.kt | 25 ++ .../java/io/neoterm/view/tab/TabCloseEvent.kt | 7 - .../io/neoterm/view/tab/TermViewClient.kt | 124 ---------- app/src/main/res/drawable/plat_logo.png | Bin 0 -> 3318 bytes app/src/main/res/layout/ui_crash.xml | 72 ++++++ .../main/res/layout/ui_package_manager.xml | 2 +- app/src/main/res/values-zh/strings.xml | 12 +- app/src/main/res/values/strings.xml | 15 +- build.gradle | 2 +- 43 files changed, 1472 insertions(+), 855 deletions(-) create mode 100644 app/src/main/java/io/neoterm/ui/bonus/BonusActivity.java create mode 100644 app/src/main/java/io/neoterm/ui/crash/CrashActivity.kt create mode 100644 app/src/main/java/io/neoterm/ui/floating/FloatingNeoTerm.kt rename app/src/main/java/io/neoterm/ui/{ => term}/NeoTermActivity.kt (82%) rename app/src/main/java/io/neoterm/{view => ui/term}/tab/TermSessionChangedCallback.kt (98%) rename app/src/main/java/io/neoterm/{view => ui/term}/tab/TermTab.kt (69%) rename app/src/main/java/io/neoterm/{view => ui/term}/tab/TermTabDecorator.kt (97%) create mode 100644 app/src/main/java/io/neoterm/ui/term/tab/TermViewClient.kt create mode 100644 app/src/main/java/io/neoterm/ui/term/tab/event/TabCloseEvent.kt create mode 100644 app/src/main/java/io/neoterm/ui/term/tab/event/ToggleFullScreenEvent.kt create mode 100644 app/src/main/java/io/neoterm/utils/CrashHandler.kt delete mode 100644 app/src/main/java/io/neoterm/utils/FullScreenHelper.java create mode 100644 app/src/main/java/io/neoterm/utils/FullScreenHelper.kt create mode 100644 app/src/main/java/io/neoterm/utils/Shaker.kt delete mode 100755 app/src/main/java/io/neoterm/view/ExtraKeysView.java create mode 100755 app/src/main/java/io/neoterm/view/ExtraKeysView.kt delete mode 100755 app/src/main/java/io/neoterm/view/GestureAndScaleRecognizer.java create mode 100755 app/src/main/java/io/neoterm/view/GestureAndScaleRecognizer.kt delete mode 100644 app/src/main/java/io/neoterm/view/eks/ControlButton.java create mode 100644 app/src/main/java/io/neoterm/view/eks/ControlButton.kt delete mode 100644 app/src/main/java/io/neoterm/view/eks/ExtraButton.java create mode 100644 app/src/main/java/io/neoterm/view/eks/ExtraButton.kt delete mode 100644 app/src/main/java/io/neoterm/view/eks/StatedControlButton.java create mode 100644 app/src/main/java/io/neoterm/view/eks/StatedControlButton.kt delete mode 100644 app/src/main/java/io/neoterm/view/eks/TextButton.java create mode 100644 app/src/main/java/io/neoterm/view/eks/TextButton.kt delete mode 100644 app/src/main/java/io/neoterm/view/tab/TabCloseEvent.kt delete mode 100644 app/src/main/java/io/neoterm/view/tab/TermViewClient.kt create mode 100644 app/src/main/res/drawable/plat_logo.png create mode 100644 app/src/main/res/layout/ui_crash.xml diff --git a/app/build.gradle b/app/build.gradle index a798251..31cf3eb 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -17,8 +17,8 @@ android { applicationId "io.neoterm" minSdkVersion 21 targetSdkVersion 25 - versionCode 6 - versionName "1.1.4" + versionCode 8 + versionName "1.1.6" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" resConfigs "zh" externalNativeBuild { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f5c6cae..ec482e4 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,21 +6,21 @@ + @@ -29,9 +29,18 @@ + + + android:theme="@style/Theme.AppCompat.NoActionBar" /> @@ -53,11 +61,12 @@ + android:targetActivity="io.neoterm.ui.term.NeoTermActivity"> - - - + + + + @@ -65,7 +74,9 @@ android:name=".services.NeoTermService" android:enabled="true" /> - + \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/App.kt b/app/src/main/java/io/neoterm/App.kt index 9ab0bb3..d25af5a 100644 --- a/app/src/main/java/io/neoterm/App.kt +++ b/app/src/main/java/io/neoterm/App.kt @@ -3,6 +3,7 @@ package io.neoterm import android.app.Application import io.neoterm.customize.color.ColorSchemeManager import io.neoterm.customize.font.FontManager +import io.neoterm.utils.CrashHandler /** * @author kiva @@ -11,13 +12,15 @@ class App : Application() { override fun onCreate() { super.onCreate() app = this + CrashHandler.init() + // ensure that we can access these any time ColorSchemeManager.init(this) FontManager.init(this) } companion object { - var app: App? = null + private var app: App? = null fun get(): App { return app!! diff --git a/app/src/main/java/io/neoterm/customize/eks/EksConfigParser.kt b/app/src/main/java/io/neoterm/customize/eks/EksConfigParser.kt index c31810e..b15e098 100644 --- a/app/src/main/java/io/neoterm/customize/eks/EksConfigParser.kt +++ b/app/src/main/java/io/neoterm/customize/eks/EksConfigParser.kt @@ -8,7 +8,7 @@ import java.io.* */ class EksConfigParser { companion object { - const val PARSER_VERSION = 2 + const val PARSER_VERSION = 4 } private lateinit var source: BufferedReader diff --git a/app/src/main/java/io/neoterm/preference/NeoPreference.kt b/app/src/main/java/io/neoterm/preference/NeoPreference.kt index dc36810..890c5a4 100644 --- a/app/src/main/java/io/neoterm/preference/NeoPreference.kt +++ b/app/src/main/java/io/neoterm/preference/NeoPreference.kt @@ -16,9 +16,11 @@ import java.io.File */ object NeoPreference { + const val KEY_HAPPY_EGG = "neoterm_fun_happy" const val KEY_FONT_SIZE = "neoterm_general_font_size" const val KEY_CURRENT_SESSION = "neoterm_service_current_session" + const val VALUE_HAPPY_EGG_TRIGGER = 8 const val VALUE_NEOTERM_ONLY = "NeoTermOnly" const val VALUE_NEOTERM_FIRST = "NeoTermFirst" const val VALUE_SYSTEM_FIRST = "SystemFirst" diff --git a/app/src/main/java/io/neoterm/services/NeoTermService.kt b/app/src/main/java/io/neoterm/services/NeoTermService.kt index 96c72ea..adc142b 100644 --- a/app/src/main/java/io/neoterm/services/NeoTermService.kt +++ b/app/src/main/java/io/neoterm/services/NeoTermService.kt @@ -1,22 +1,26 @@ package io.neoterm.services +import android.annotation.SuppressLint import android.app.Notification import android.app.NotificationManager import android.app.PendingIntent import android.app.Service import android.content.Context import android.content.Intent +import android.net.wifi.WifiManager import android.os.Binder import android.os.IBinder +import android.os.PowerManager import android.support.v4.content.WakefulBroadcastReceiver import android.util.Log import io.neoterm.R import io.neoterm.backend.EmulatorDebug import io.neoterm.backend.TerminalSession -import io.neoterm.ui.NeoTermActivity +import io.neoterm.ui.term.NeoTermActivity import io.neoterm.utils.TerminalUtils import java.util.* + /** * @author kiva */ @@ -28,6 +32,8 @@ class NeoTermService : Service() { private val neoTermBinder = NeoTermBinder() private val mTerminalSessions = ArrayList() + private var mWakeLock: PowerManager.WakeLock? = null + private var mWifiLock: WifiManager.WifiLock? = null override fun onCreate() { super.onCreate() @@ -40,12 +46,18 @@ class NeoTermService : Service() { override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { val action = intent.action - if (ACTION_SERVICE_STOP == action) { - for (i in mTerminalSessions.indices) - mTerminalSessions[i].finishIfRunning() - stopSelf() - } else if (action != null) { - Log.e(EmulatorDebug.LOG_TAG, "Unknown NeoTermService action: '$action'") + when (action) { + ACTION_SERVICE_STOP -> { + for (i in mTerminalSessions.indices) + mTerminalSessions[i].finishIfRunning() + stopSelf() + } + + ACTION_ACQUIRE_LOCK -> acquireLock() + + ACTION_RELEASE_LOCK -> releaseLock() + + null -> Log.e(EmulatorDebug.LOG_TAG, "Unknown NeoTermService action: '$action'") } if (flags and Service.START_FLAG_REDELIVERY == 0) { @@ -76,8 +88,10 @@ class NeoTermService : Service() { fun removeTermSession(sessionToRemove: TerminalSession): Int { val indexOfRemoved = mTerminalSessions.indexOf(sessionToRemove) - mTerminalSessions.removeAt(indexOfRemoved) - updateNotification() + if (indexOfRemoved >= 0) { + mTerminalSessions.removeAt(indexOfRemoved) + updateNotification() + } return indexOfRemoved } @@ -93,7 +107,10 @@ class NeoTermService : Service() { val pendingIntent = PendingIntent.getActivity(this, 0, notifyIntent, 0) val sessionCount = mTerminalSessions.size - val contentText = sessionCount.toString() + " session" + if (sessionCount == 1) "" else "s" + var contentText = getString(R.string.service_status_text, sessionCount) + + val lockAcquired = mWakeLock != null + if (lockAcquired) contentText += getString(R.string.service_lock_acquired) val builder = Notification.Builder(this) builder.setContentTitle(getText(R.string.app_name)) @@ -104,14 +121,54 @@ class NeoTermService : Service() { builder.setShowWhen(false) builder.setColor(0xFF000000.toInt()) + builder.setPriority(if (lockAcquired) Notification.PRIORITY_HIGH else Notification.PRIORITY_MIN) + val exitIntent = Intent(this, NeoTermService::class.java).setAction(ACTION_SERVICE_STOP) builder.addAction(android.R.drawable.ic_delete, "Exit", PendingIntent.getService(this, 0, exitIntent, 0)) + val newWakeAction = if (lockAcquired) ACTION_RELEASE_LOCK else ACTION_ACQUIRE_LOCK + val toggleWakeLockIntent = Intent(this, NeoTermService::class.java).setAction(newWakeAction) + val actionTitle = getString(if (lockAcquired) + R.string.service_release_lock + else + R.string.service_acquire_lock) + val actionIcon = if (lockAcquired) android.R.drawable.ic_lock_idle_lock else android.R.drawable.ic_lock_lock + builder.addAction(actionIcon, actionTitle, PendingIntent.getService(this, 0, toggleWakeLockIntent, 0)) + return builder.build() } + @SuppressLint("WakelockTimeout") + private fun acquireLock() { + if (mWakeLock == null) { + val pm = getSystemService(Context.POWER_SERVICE) as PowerManager + mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, EmulatorDebug.LOG_TAG) + mWakeLock!!.acquire() + + val wm = applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager + mWifiLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, EmulatorDebug.LOG_TAG) + mWifiLock!!.acquire() + + updateNotification() + } + } + + private fun releaseLock() { + if (mWakeLock != null) { + mWakeLock!!.release() + mWakeLock = null + + mWifiLock!!.release() + mWifiLock = null + + updateNotification() + } + } + companion object { val ACTION_SERVICE_STOP = "neoterm.action.service.stop" + val ACTION_ACQUIRE_LOCK = "neoterm.action.service.lock.acquire" + val ACTION_RELEASE_LOCK = "neoterm.action.service.lock.release" private val NOTIFICATION_ID = 52019 } } diff --git a/app/src/main/java/io/neoterm/ui/bonus/BonusActivity.java b/app/src/main/java/io/neoterm/ui/bonus/BonusActivity.java new file mode 100644 index 0000000..3e38e7e --- /dev/null +++ b/app/src/main/java/io/neoterm/ui/bonus/BonusActivity.java @@ -0,0 +1,193 @@ +package io.neoterm.ui.bonus; + +import android.animation.ObjectAnimator; +import android.content.res.ColorStateList; +import android.graphics.Canvas; +import android.graphics.Outline; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.GradientDrawable; +import android.graphics.drawable.RippleDrawable; +import android.graphics.drawable.ShapeDrawable; +import android.graphics.drawable.shapes.OvalShape; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.util.DisplayMetrics; +import android.view.Gravity; +import android.view.KeyEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewOutlineProvider; +import android.view.WindowManager; +import android.view.animation.PathInterpolator; +import android.widget.FrameLayout; +import android.widget.ImageView; + +import io.neoterm.R; + +/** + * @author kiva + */ + +public class BonusActivity extends AppCompatActivity { + final static int[] FLAVORS = { + 0xFF9C27B0, 0xFFBA68C8, // grape + 0xFFFF9800, 0xFFFFB74D, // orange + 0xFFF06292, 0xFFF8BBD0, // bubblegum + 0xFFAFB42B, 0xFFCDDC39, // lime + 0xFF795548, 0xFFA1887F, // mystery flavor + }; + + FrameLayout mLayout; + int mTapCount; + int mKeyCount; + PathInterpolator mInterpolator = new PathInterpolator(0f, 0f, 0.5f, 1f); + + static int newColorIndex() { + return 2 * ((int) (Math.random() * FLAVORS.length / 2)); + } + + Drawable makeRipple() { + final int idx = newColorIndex(); + final ShapeDrawable popbg = new ShapeDrawable(new OvalShape()); + popbg.getPaint().setColor(FLAVORS[idx]); + final RippleDrawable ripple = new RippleDrawable( + ColorStateList.valueOf(FLAVORS[idx + 1]), + popbg, null); + return ripple; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mLayout = new FrameLayout(this); + getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN); + setContentView(mLayout); + } + + @Override + public void onAttachedToWindow() { + final DisplayMetrics dm = getResources().getDisplayMetrics(); + final float dp = dm.density; + final int size = (int) + (Math.min(Math.min(dm.widthPixels, dm.heightPixels), 600 * dp) - 100 * dp); + final View stick = new View(this) { + Paint mPaint = new Paint(); + Path mShadow = new Path(); + + @Override + public void onAttachedToWindow() { + super.onAttachedToWindow(); + setWillNotDraw(false); + setOutlineProvider(new ViewOutlineProvider() { + @Override + public void getOutline(View view, Outline outline) { + outline.setRect(0, getHeight() / 2, getWidth(), getHeight()); + } + }); + } + + @Override + public void onDraw(Canvas c) { + final int w = c.getWidth(); + final int h = c.getHeight() / 2; + c.translate(0, h); + final GradientDrawable g = new GradientDrawable(); + g.setOrientation(GradientDrawable.Orientation.LEFT_RIGHT); + g.setGradientCenter(w * 0.75f, 0); + g.setColors(new int[]{0xFFFFFFFF, 0xFFAAAAAA}); + g.setBounds(0, 0, w, h); + g.draw(c); + mPaint.setColor(0xFFAAAAAA); + mShadow.reset(); + mShadow.moveTo(0, 0); + mShadow.lineTo(w, 0); + mShadow.lineTo(w, size / 2 + 1.5f * w); + mShadow.lineTo(0, size / 2); + mShadow.close(); + c.drawPath(mShadow, mPaint); + } + }; + mLayout.addView(stick, new FrameLayout.LayoutParams((int) (32 * dp), + ViewGroup.LayoutParams.MATCH_PARENT, Gravity.CENTER_HORIZONTAL)); + stick.setAlpha(0f); + + final ImageView im = new ImageView(this); + im.setTranslationZ(20); + im.setScaleX(0); + im.setScaleY(0); + final Drawable platlogo = getDrawable(R.drawable.plat_logo); + platlogo.setAlpha(0); + im.setImageDrawable(platlogo); + im.setBackground(makeRipple()); + im.setClickable(true); + final ShapeDrawable highlight = new ShapeDrawable(new OvalShape()); + highlight.getPaint().setColor(0x10FFFFFF); + highlight.setBounds((int) (size * .15f), (int) (size * .15f), + (int) (size * .6f), (int) (size * .6f)); + im.getOverlay().add(highlight); + im.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (mTapCount == 0) { + im.animate() + .translationZ(40) + .scaleX(1) + .scaleY(1) + .setInterpolator(mInterpolator) + .setDuration(700) + .setStartDelay(500) + .start(); + + final ObjectAnimator a = ObjectAnimator.ofInt(platlogo, "alpha", 0, 255); + a.setInterpolator(mInterpolator); + a.setStartDelay(1000); + a.start(); + + stick.animate() + .translationZ(20) + .alpha(1) + .setInterpolator(mInterpolator) + .setDuration(700) + .setStartDelay(750) + .start(); + } else { + im.setBackground(makeRipple()); + } + mTapCount++; + } + }); + + // Enable hardware keyboard input for TV compatibility. + im.setFocusable(true); + im.requestFocus(); + im.setOnKeyListener(new View.OnKeyListener() { + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + if (keyCode != KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_DOWN) { + ++mKeyCount; + if (mKeyCount > 2) { + if (mTapCount > 5) { + im.performLongClick(); + } else { + im.performClick(); + } + } + return true; + } else { + return false; + } + } + }); + + mLayout.addView(im, new FrameLayout.LayoutParams(size, size, Gravity.CENTER)); + + im.animate().scaleX(0.3f).scaleY(0.3f) + .setInterpolator(mInterpolator) + .setDuration(500) + .setStartDelay(800) + .start(); + } +} diff --git a/app/src/main/java/io/neoterm/ui/crash/CrashActivity.kt b/app/src/main/java/io/neoterm/ui/crash/CrashActivity.kt new file mode 100644 index 0000000..13cfe29 --- /dev/null +++ b/app/src/main/java/io/neoterm/ui/crash/CrashActivity.kt @@ -0,0 +1,46 @@ +package io.neoterm.ui.crash + +import android.os.Build +import android.os.Bundle +import android.support.v7.app.AppCompatActivity +import android.support.v7.widget.Toolbar +import android.widget.TextView +import io.neoterm.R + +/** + * @author kiva + */ +class CrashActivity : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.ui_crash) + setSupportActionBar(findViewById(R.id.crash_toolbar) as Toolbar) + + (findViewById(R.id.crash_model) as TextView).text = getString(R.string.crash_model, collectModelInfo()) + (findViewById(R.id.crash_app_version) as TextView).text = getString(R.string.crash_app, collectAppInfo()) + (findViewById(R.id.crash_details) as TextView).text = collectExceptionInfo() + } + + private fun collectExceptionInfo(): String { + val extra = intent.getSerializableExtra("exception") + if (extra != null && extra is Throwable) { + val s = StringBuilder(extra.toString() + "\n\n") + for ((index, trace) in extra.stackTrace.withIndex()) { + s.append(String.format(" #%02d ", index)) + s.append("${trace.className}(${trace.fileName}:${trace.lineNumber})(native=${trace.isNativeMethod})") + return s.toString() + } + } + return "are.you.kidding.me.NoExceptionFoundException: This is a bug, please contact me!" + } + + fun collectAppInfo(): String { + val pm = packageManager + val info = pm.getPackageInfo(packageName, 0) + return "${info.versionName} (${info.versionCode})" + } + + private fun collectModelInfo(): String { + return "${Build.MODEL} (Android ${Build.VERSION.RELEASE})" + } +} \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/ui/floating/FloatingNeoTerm.kt b/app/src/main/java/io/neoterm/ui/floating/FloatingNeoTerm.kt new file mode 100644 index 0000000..d9df1e0 --- /dev/null +++ b/app/src/main/java/io/neoterm/ui/floating/FloatingNeoTerm.kt @@ -0,0 +1,6 @@ +package io.neoterm.ui.floating + +/** + * @author kiva + */ +class FloatingNeoTerm \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/ui/settings/SettingActivity.kt b/app/src/main/java/io/neoterm/ui/settings/SettingActivity.kt index fe98cc2..c2ace81 100644 --- a/app/src/main/java/io/neoterm/ui/settings/SettingActivity.kt +++ b/app/src/main/java/io/neoterm/ui/settings/SettingActivity.kt @@ -18,8 +18,34 @@ class SettingActivity : AppCompatPreferenceActivity() { addPreferencesFromResource(R.xml.settings_main) findPreference(getString(R.string.about)).setOnPreferenceClickListener { AlertDialog.Builder(this@SettingActivity) - .setTitle(R.string.about) - .setMessage("Hello World!") + .setTitle("为什么我们选择开发NeoTerm?") + .setMessage("安卓上的终端,一直以来就有这样的诟病:" + + "只能用安卓自带的程序,要想用用户体验更好的终端程序," + + "你需要额外配置一堆烦琐的环境," + + "甚至需要自己动手编译符合安卓设备的版本。" + + "就在这时,Termux出现了," + + "很完美,一行命令就能安装一个原来想都不敢想的软件," + + "比如MySQL, clang。\n" + + "但用着用着,感觉这样的终端还差点什么," + + "仅仅有丰富的软件包是不够的," + + "Termux在部分功能上可以说是欠妥甚至缺乏," + + "再者安卓并非自带键盘," + + "在小小的屏幕上触摸虚拟键盘来跟命令行打交道实属残忍," + + "但我们又没法强迫所有用户在使用终端的时候再额外接一个键盘," + + "我们只能从终端的层面来解决问题," + + "于是我们开发了这款app," + + "并取名Neo Term(Neo正是new的意思)," + + "并希望它可以改善用户在终端下的体验," + + "让软件包与终端功能两不误。" + + "不可否认,开发过程中Termux给了我们很大帮助," + + "不仅提供了很好的terminal-view," + + "还提供了庞大的软件包仓库," + + "这一点我们对原作者表示忠诚的感谢。" + + "日后的开发中,我们会紧跟用户反馈," + + "努力打造安卓上最好用的终端。\n" + + "\n" + + "NeoTerm 开发者们\n" + + "2017.6.19 00:16") .setPositiveButton(android.R.string.yes, null) .show() return@setOnPreferenceClickListener true diff --git a/app/src/main/java/io/neoterm/ui/setup/SetupActivity.kt b/app/src/main/java/io/neoterm/ui/setup/SetupActivity.kt index 899a395..ecb970e 100644 --- a/app/src/main/java/io/neoterm/ui/setup/SetupActivity.kt +++ b/app/src/main/java/io/neoterm/ui/setup/SetupActivity.kt @@ -1,5 +1,6 @@ package io.neoterm.ui.setup +import android.annotation.SuppressLint import android.app.Activity import android.os.Bundle import android.support.v4.content.ContextCompat @@ -7,6 +8,7 @@ import android.support.v7.app.AlertDialog import android.support.v7.app.AppCompatActivity import android.view.View import android.widget.Button +import android.widget.Toast import com.igalata.bubblepicker.BubblePickerListener import com.igalata.bubblepicker.adapter.BubblePickerAdapter import com.igalata.bubblepicker.model.BubbleGradient @@ -29,13 +31,14 @@ import java.util.* class SetupActivity : AppCompatActivity() { companion object { private val DEFAULT_PACKAGES = arrayOf( - "zsh", "aria2", "tmux", "lua", "netcat", "nodejs", - "fish", "make", "gdb", "unrar", "clang", "vim", "emacs", - "curl", "git", "python", "perl", "zip", "p7zip") + "zsh", "neoterm-core", "tmux", "nodejs", + "fish", "make", "gdb", "clang", "vim", "emacs", "nano", + "curl", "git", "python", "p7zip", "oh-my-zsh") } lateinit var picker: BubblePicker lateinit var nextButton: Button + lateinit var toast: Toast var aptUpdated = false override fun onCreate(savedInstanceState: Bundle?) { @@ -125,6 +128,7 @@ class SetupActivity : AppCompatActivity() { .show("apt update") } + @SuppressLint("ShowToast") private fun setupBubbles() { val titles = if (intent.getBooleanExtra("setup", false)) @@ -133,10 +137,11 @@ class SetupActivity : AppCompatActivity() { randomPackageList() val colors = resources.obtainTypedArray(R.array.bubble_colors) + toast = Toast.makeText(this, null, Toast.LENGTH_LONG) + picker.bubbleSize = 25 picker.adapter = object : BubblePickerAdapter { override val totalCount = titles.size - override fun getItem(position: Int): PickerItem { return PickerItem().apply { title = titles[position] @@ -148,9 +153,17 @@ class SetupActivity : AppCompatActivity() { } picker.listener = object : BubblePickerListener { override fun onBubbleSelected(item: PickerItem) { + val packageName = item.title + val pm = NeoPackageManager.get() + val packageInfo = pm.getPackageInfo(packageName) + val packageDesc = packageInfo.description + toast.cancel() + toast.setText(packageDesc) + toast.show() } override fun onBubbleDeselected(item: PickerItem) { + toast.cancel() } } colors.recycle() diff --git a/app/src/main/java/io/neoterm/ui/NeoTermActivity.kt b/app/src/main/java/io/neoterm/ui/term/NeoTermActivity.kt similarity index 82% rename from app/src/main/java/io/neoterm/ui/NeoTermActivity.kt rename to app/src/main/java/io/neoterm/ui/term/NeoTermActivity.kt index 3d696f8..13939e0 100644 --- a/app/src/main/java/io/neoterm/ui/NeoTermActivity.kt +++ b/app/src/main/java/io/neoterm/ui/term/NeoTermActivity.kt @@ -1,5 +1,6 @@ -package io.neoterm.ui +package io.neoterm.ui.term +import android.animation.ObjectAnimator import android.app.Activity import android.app.AlertDialog import android.content.* @@ -13,8 +14,10 @@ import android.support.v4.view.ViewCompat import android.support.v7.app.AppCompatActivity import android.support.v7.widget.Toolbar import android.view.* +import android.view.animation.AccelerateDecelerateInterpolator import android.view.inputmethod.InputMethodManager import android.widget.ImageButton +import android.widget.Toast import de.mrapp.android.tabswitcher.* import io.neoterm.R import io.neoterm.backend.TerminalSession @@ -25,12 +28,18 @@ import io.neoterm.customize.setup.BaseFileInstaller import io.neoterm.preference.NeoPermission import io.neoterm.preference.NeoPreference import io.neoterm.services.NeoTermService +import io.neoterm.ui.bonus.BonusActivity import io.neoterm.ui.pm.PackageManagerActivity import io.neoterm.ui.settings.SettingActivity import io.neoterm.ui.setup.SetupActivity +import io.neoterm.ui.term.tab.TermSessionChangedCallback +import io.neoterm.ui.term.tab.TermTab +import io.neoterm.ui.term.tab.TermTabDecorator +import io.neoterm.ui.term.tab.TermViewClient +import io.neoterm.ui.term.tab.event.TabCloseEvent +import io.neoterm.ui.term.tab.event.ToggleFullScreenEvent import io.neoterm.utils.FullScreenHelper import io.neoterm.view.eks.StatedControlButton -import io.neoterm.view.tab.* import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode @@ -70,31 +79,25 @@ class NeoTermActivity : AppCompatActivity(), ServiceConnection, SharedPreference setSupportActionBar(toolbar) fullScreenHelper = FullScreenHelper.injectActivity(this, fullscreen, peekRecreating()) - fullScreenHelper.setKeyBoardListener({ isShow, _ -> - var tab: TermTab? = null + fullScreenHelper.setKeyBoardListener(object : FullScreenHelper.KeyBoardListener { + override fun onKeyboardChange(isShow: Boolean, keyboardHeight: Int) { + var tab: TermTab? = null - if (tabSwitcher.selectedTab is TermTab) { - tab = tabSwitcher.selectedTab as TermTab - } + if (tabSwitcher.selectedTab is TermTab) { + tab = tabSwitcher.selectedTab as TermTab + } - if (NeoPreference.loadBoolean(R.string.key_ui_fullscreen, false) - || NeoPreference.loadBoolean(R.string.key_ui_hide_toolbar, false)) { - tab?.toolbar?.visibility = if (isShow) View.GONE else View.VISIBLE + if (NeoPreference.loadBoolean(R.string.key_ui_fullscreen, false) + || NeoPreference.loadBoolean(R.string.key_ui_hide_toolbar, false)) { + tab?.toolbar?.visibility = if (isShow) View.GONE else View.VISIBLE + } } }) fullScreenToggleButton = object : StatedControlButton("FS", fullscreen) { - override fun onClick(view: View?) { + override fun onClick(view: View) { super.onClick(view) - if (tabSwitcher.selectedTab is TermTab) { - val tab = tabSwitcher.selectedTab as TermTab - tab.hideIme() - } - NeoPreference.store(R.string.key_ui_fullscreen, super.toggleButton.isChecked) - // FIXME: Cannot toggle full screen mode - // FIXME: We still need to recreate the activity. -// setFullScreenMode(super.toggleButton.isChecked) - this@NeoTermActivity.recreate() + setFullScreenMode(super.toggleButton?.isChecked ?: false) } } @@ -116,11 +119,11 @@ class NeoTermActivity : AppCompatActivity(), ServiceConnection, SharedPreference if (!tabSwitcher.isSwitcherShown) { if (imm.isActive && tabSwitcher.selectedTab is TermTab) { val tab = tabSwitcher.selectedTab as TermTab - tab.hideIme() + tab.requireHideIme() } - tabSwitcher.showSwitcher() + toggleSwitcher(showSwitcher = true) } else { - tabSwitcher.hideSwitcher() + toggleSwitcher(showSwitcher = false) } }) return true @@ -149,7 +152,7 @@ class NeoTermActivity : AppCompatActivity(), ServiceConnection, SharedPreference } R.id.menu_item_new_session -> { if (!tabSwitcher.isSwitcherShown) { - tabSwitcher.showSwitcher() + toggleSwitcher(showSwitcher = true) } val index = tabSwitcher.count addNewSession("NeoTerm #" + index, systemShell, createRevealAnimation()) @@ -232,7 +235,7 @@ class NeoTermActivity : AppCompatActivity(), ServiceConnection, SharedPreference when (keyCode) { KeyEvent.KEYCODE_BACK -> { if (event?.action == KeyEvent.ACTION_DOWN && tabSwitcher.isSwitcherShown && tabSwitcher.count > 0) { - tabSwitcher.hideSwitcher() + toggleSwitcher(showSwitcher = false) return true } } @@ -311,7 +314,7 @@ class NeoTermActivity : AppCompatActivity(), ServiceConnection, SharedPreference } private fun enterSystemShell() { - tabSwitcher.showSwitcher() + toggleSwitcher(showSwitcher = true) addNewSession("NeoTerm #0", systemShell, createRevealAnimation()) } @@ -324,7 +327,7 @@ class NeoTermActivity : AppCompatActivity(), ServiceConnection, SharedPreference } switchToSession(getStoredCurrentSessionOrLast()) } else { - tabSwitcher.showSwitcher() + toggleSwitcher(showSwitcher = true) addNewSession("NeoTerm #0", systemShell, createRevealAnimation()) } } @@ -353,12 +356,13 @@ class NeoTermActivity : AppCompatActivity(), ServiceConnection, SharedPreference } private fun setFullScreenMode(fullScreen: Boolean) { - fullScreenHelper.setFullScreen(fullScreen) - if (fullScreen) { - tabSwitcher.systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN - } else { - tabSwitcher.systemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE + fullScreenHelper.fullScreen = fullScreen + if (tabSwitcher.selectedTab is TermTab) { + val tab = tabSwitcher.selectedTab as TermTab + tab.requireHideIme() } + NeoPreference.store(R.string.key_ui_fullscreen, fullScreen) + this@NeoTermActivity.recreate() } private fun addNewSession(session: TerminalSession?) { @@ -366,6 +370,14 @@ class NeoTermActivity : AppCompatActivity(), ServiceConnection, SharedPreference return } + // Do not add the same session again + // Or app will crash when rotate + val tabCount = tabSwitcher.count + (0..(tabCount - 1)) + .map { tabSwitcher.getTab(it) } + .filter { it is TermTab && it.termSession == session } + .forEach { return } + val tab = createTab(session.mSessionName) as TermTab tab.sessionCallback = session.sessionChangedCallback as TermSessionChangedCallback tab.viewClient = TermViewClient(this) @@ -490,12 +502,39 @@ class NeoTermActivity : AppCompatActivity(), ServiceConnection, SharedPreference } } + private fun toggleSwitcher(showSwitcher: Boolean) { + if (tabSwitcher.count > 0) { + val transparentAnimator = ObjectAnimator.ofFloat(toolbar, View.ALPHA, 1.0f, 0.0f, 1.0f) + transparentAnimator.interpolator = AccelerateDecelerateInterpolator() + transparentAnimator.start() + } else { + val happyCount = NeoPreference.loadInt(NeoPreference.KEY_HAPPY_EGG, 0) + 1 + NeoPreference.store(NeoPreference.KEY_HAPPY_EGG, happyCount) + + val trigger = NeoPreference.VALUE_HAPPY_EGG_TRIGGER + + if (happyCount == trigger / 2) { + val toast = Toast.makeText(this, "Emm...", Toast.LENGTH_LONG) + toast.setGravity(Gravity.CENTER, 0, 0) + toast.show() + } else if (happyCount > trigger) { + NeoPreference.store(NeoPreference.KEY_HAPPY_EGG, 0) + startActivity(Intent(this, BonusActivity::class.java)) + } + return + } + if (showSwitcher) { + tabSwitcher.showSwitcher() + } else { + tabSwitcher.hideSwitcher() + } + } @Suppress("unused") @Subscribe(threadMode = ThreadMode.MAIN) fun onTabCloseEvent(tabCloseEvent: TabCloseEvent) { val tab = tabCloseEvent.termTab - tabSwitcher.showSwitcher() + toggleSwitcher(showSwitcher = true) tabSwitcher.removeTab(tab) if (tabSwitcher.count > 1) { @@ -510,4 +549,11 @@ class NeoTermActivity : AppCompatActivity(), ServiceConnection, SharedPreference switchToSession(tabSwitcher.getTab(index)) } } + + @Suppress("unused", "UNUSED_PARAMETER") + @Subscribe(threadMode = ThreadMode.MAIN) + fun onToggleFullScreenEvent(toggleFullScreenEvent: ToggleFullScreenEvent) { + val fullScreen = fullScreenHelper.fullScreen + setFullScreenMode(!fullScreen) + } } diff --git a/app/src/main/java/io/neoterm/view/tab/TermSessionChangedCallback.kt b/app/src/main/java/io/neoterm/ui/term/tab/TermSessionChangedCallback.kt similarity index 98% rename from app/src/main/java/io/neoterm/view/tab/TermSessionChangedCallback.kt rename to app/src/main/java/io/neoterm/ui/term/tab/TermSessionChangedCallback.kt index d767830..bc08316 100644 --- a/app/src/main/java/io/neoterm/view/tab/TermSessionChangedCallback.kt +++ b/app/src/main/java/io/neoterm/ui/term/tab/TermSessionChangedCallback.kt @@ -1,4 +1,4 @@ -package io.neoterm.view.tab +package io.neoterm.ui.term.tab import android.content.ClipData import android.content.ClipboardManager diff --git a/app/src/main/java/io/neoterm/view/tab/TermTab.kt b/app/src/main/java/io/neoterm/ui/term/tab/TermTab.kt similarity index 69% rename from app/src/main/java/io/neoterm/view/tab/TermTab.kt rename to app/src/main/java/io/neoterm/ui/term/tab/TermTab.kt index 05e3b98..9036ad1 100644 --- a/app/src/main/java/io/neoterm/view/tab/TermTab.kt +++ b/app/src/main/java/io/neoterm/ui/term/tab/TermTab.kt @@ -1,15 +1,15 @@ -package io.neoterm.view.tab +package io.neoterm.ui.term.tab import android.content.Context -import android.graphics.Color import android.support.v7.widget.Toolbar import android.view.inputmethod.InputMethodManager import de.mrapp.android.tabswitcher.Tab import io.neoterm.R import io.neoterm.backend.TerminalSession import io.neoterm.customize.color.ColorSchemeManager -import io.neoterm.customize.color.NeoColorScheme import io.neoterm.preference.NeoPreference +import io.neoterm.ui.term.tab.event.TabCloseEvent +import io.neoterm.ui.term.tab.event.ToggleFullScreenEvent import org.greenrobot.eventbus.EventBus /** @@ -38,12 +38,14 @@ class TermTab(title: CharSequence) : Tab(title) { } fun updateTitle(title: String) { - this.title = title - toolbar?.title = title - if (NeoPreference.loadBoolean(R.string.key_ui_suggestions, true)) { - viewClient?.updateSuggestions(title) - } else { - viewClient?.removeSuggestions() + if (title.isNotEmpty()) { + this.title = title + toolbar?.title = title + if (NeoPreference.loadBoolean(R.string.key_ui_suggestions, true)) { + viewClient?.updateSuggestions(title) + } else { + viewClient?.removeSuggestions() + } } } @@ -51,12 +53,12 @@ class TermTab(title: CharSequence) : Tab(title) { viewClient?.sessionFinished = true } - fun requiredCloseTab() { - hideIme() + fun requireCloseTab() { + requireHideIme() EventBus.getDefault().post(TabCloseEvent(this)) } - fun hideIme() { + fun requireHideIme() { val terminalView = viewClient?.termView if (terminalView != null) { val imm = terminalView.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager @@ -65,4 +67,12 @@ class TermTab(title: CharSequence) : Tab(title) { } } } + + fun requireToggleFullScreen() { + EventBus.getDefault().post(ToggleFullScreenEvent()) + } + + fun requirePaste() { + viewClient?.termView?.pasteFromClipboard() + } } diff --git a/app/src/main/java/io/neoterm/view/tab/TermTabDecorator.kt b/app/src/main/java/io/neoterm/ui/term/tab/TermTabDecorator.kt similarity index 97% rename from app/src/main/java/io/neoterm/view/tab/TermTabDecorator.kt rename to app/src/main/java/io/neoterm/ui/term/tab/TermTabDecorator.kt index 0d66c7b..bb95ba7 100644 --- a/app/src/main/java/io/neoterm/view/tab/TermTabDecorator.kt +++ b/app/src/main/java/io/neoterm/ui/term/tab/TermTabDecorator.kt @@ -1,4 +1,4 @@ -package io.neoterm.view.tab +package io.neoterm.ui.term.tab import android.content.Context import android.os.Bundle @@ -11,7 +11,7 @@ import de.mrapp.android.tabswitcher.TabSwitcherDecorator import io.neoterm.R import io.neoterm.customize.color.ColorSchemeManager import io.neoterm.preference.NeoPreference -import io.neoterm.ui.NeoTermActivity +import io.neoterm.ui.term.NeoTermActivity import io.neoterm.utils.TerminalUtils import io.neoterm.view.ExtraKeysView import io.neoterm.view.TerminalView diff --git a/app/src/main/java/io/neoterm/ui/term/tab/TermViewClient.kt b/app/src/main/java/io/neoterm/ui/term/tab/TermViewClient.kt new file mode 100644 index 0000000..a420144 --- /dev/null +++ b/app/src/main/java/io/neoterm/ui/term/tab/TermViewClient.kt @@ -0,0 +1,204 @@ +package io.neoterm.ui.term.tab + +import android.content.Context +import android.media.AudioManager +import android.view.InputDevice +import android.view.KeyEvent +import android.view.MotionEvent +import android.view.inputmethod.InputMethodManager +import io.neoterm.R +import io.neoterm.backend.KeyHandler +import io.neoterm.backend.TerminalSession +import io.neoterm.customize.eks.EksKeysManager +import io.neoterm.preference.NeoPreference +import io.neoterm.view.ExtraKeysView +import io.neoterm.view.TerminalView +import io.neoterm.view.TerminalViewClient + + +/** + * @author kiva + */ +class TermViewClient(val context: Context) : TerminalViewClient { + private var mVirtualControlKeyDown: Boolean = false + private var mVirtualFnKeyDown: Boolean = false + private var lastTitle: String = "" + + var sessionFinished: Boolean = false + + var termTab: TermTab? = null + var termView: TerminalView? = null + var extraKeysView: ExtraKeysView? = null + + override fun onScale(scale: Float): Float { + if (scale < 0.9f || scale > 1.1f) { + val increase = scale > 1f + changeFontSize(increase) + return 1.0f + } + return scale + } + + override fun onSingleTapUp(e: MotionEvent?) { + (context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager) + .showSoftInput(termView, InputMethodManager.SHOW_IMPLICIT) + } + + override fun shouldBackButtonBeMappedToEscape(): Boolean { + return NeoPreference.loadBoolean(R.string.key_generaL_backspace_map_to_esc, false) + } + + override fun copyModeChanged(copyMode: Boolean) { + // TODO + } + + override fun onKeyDown(keyCode: Int, e: KeyEvent?, session: TerminalSession?): Boolean { + when (keyCode) { + KeyEvent.KEYCODE_ENTER -> { + if (e?.action == KeyEvent.ACTION_DOWN && sessionFinished) { + termTab?.requireCloseTab() + return true + } + return false + } + } + if (e != null && e.isCtrlPressed && e.isAltPressed) { + // Get the unmodified code point: + val unicodeChar = e.getUnicodeChar(0).toChar() + + if (unicodeChar == 'f'/* full screen */) { + termTab?.requireToggleFullScreen() + } else if (unicodeChar == 'v') { + termTab?.requirePaste() + } else if (unicodeChar == '+' || e.getUnicodeChar(KeyEvent.META_SHIFT_ON).toChar() == '+') { + // We also check for the shifted char here since shift may be required to produce '+', + // see https://github.com/termux/termux-api/issues/2 + changeFontSize(true) + } else if (unicodeChar == '-') { + changeFontSize(false) + } + } + return false + } + + override fun onKeyUp(keyCode: Int, e: KeyEvent?): Boolean { + return handleVirtualKeys(keyCode, e, false) + } + + override fun readControlKey(): Boolean { + return (extraKeysView != null && extraKeysView!!.readControlButton()) || mVirtualControlKeyDown + } + + override fun readAltKey(): Boolean { + return (extraKeysView != null && extraKeysView!!.readAltButton()) || mVirtualFnKeyDown + } + + override fun onCodePoint(codePoint: Int, ctrlDown: Boolean, session: TerminalSession?): Boolean { + if (mVirtualFnKeyDown) { + var resultingKeyCode: Int = -1 + var resultingCodePoint: Int = -1 + var altDown = false + val lowerCase = Character.toLowerCase(codePoint) + when (lowerCase.toChar()) { + // Arrow keys. + 'w' -> resultingKeyCode = KeyEvent.KEYCODE_DPAD_UP + 'a' -> resultingKeyCode = KeyEvent.KEYCODE_DPAD_LEFT + 's' -> resultingKeyCode = KeyEvent.KEYCODE_DPAD_DOWN + 'd' -> resultingKeyCode = KeyEvent.KEYCODE_DPAD_RIGHT + + // Page up and down. + 'p' -> resultingKeyCode = KeyEvent.KEYCODE_PAGE_UP + 'n' -> resultingKeyCode = KeyEvent.KEYCODE_PAGE_DOWN + + // Some special keys: + 't' -> resultingKeyCode = KeyEvent.KEYCODE_TAB + 'i' -> resultingKeyCode = KeyEvent.KEYCODE_INSERT + 'h' -> resultingCodePoint = '~'.toInt() + + // Special characters to input. + 'u' -> resultingCodePoint = '_'.toInt() + 'l' -> resultingCodePoint = '|'.toInt() + + // Function keys. + '1', '2', '3', '4', '5', '6', '7', '8', '9' -> resultingKeyCode = codePoint - '1'.toInt() + KeyEvent.KEYCODE_F1 + '0' -> resultingKeyCode = KeyEvent.KEYCODE_F10 + + // Other special keys. + 'e' -> resultingCodePoint = 27 /*Escape*/ + '.' -> resultingCodePoint = 28 /*^.*/ + + 'b' // alt+b, jumping backward in readline. + , 'f' // alf+f, jumping forward in readline. + , 'x' // alt+x, common in emacs. + -> { + resultingCodePoint = lowerCase + altDown = true + } + + // Volume control. + 'v' -> { + resultingCodePoint = -1 + val audio = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager + audio.adjustSuggestedStreamVolume(AudioManager.ADJUST_SAME, AudioManager.USE_DEFAULT_STREAM_TYPE, AudioManager.FLAG_SHOW_UI) + } + } + + if (resultingKeyCode != -1) { + if (session != null) { + val term = session.emulator + session.write(KeyHandler.getCode(resultingKeyCode, 0, term.isCursorKeysApplicationMode, term.isKeypadApplicationMode)) + } + } else if (resultingCodePoint != -1) { + session?.writeCodePoint(altDown, resultingCodePoint) + } + return true + } + return false + } + + override fun onLongPress(event: MotionEvent?): Boolean { + // TODO + return false + } + + private fun handleVirtualKeys(keyCode: Int, event: KeyEvent?, down: Boolean): Boolean { + if (event == null) { + return false + } + val inputDevice = event.device + if (inputDevice != null && inputDevice.keyboardType == InputDevice.KEYBOARD_TYPE_ALPHABETIC) { + return false + } else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) { + mVirtualControlKeyDown = down + return true + } else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) { + mVirtualFnKeyDown = down + return true + } + return false + } + + fun updateSuggestions(title: String?, force: Boolean = false) { + if (extraKeysView == null || title == null || title.isEmpty()) { + return + } + + if (lastTitle != title || force) { + removeSuggestions() + EksKeysManager.showShortcutKeys(title, extraKeysView) + extraKeysView?.updateButtons() + lastTitle = title + } + } + + fun removeSuggestions() { + extraKeysView?.clearUserDefinedButton() + } + + private fun changeFontSize(increase: Boolean) { + val changedSize = (if (increase) 1 else -1) * 2 + val fontSize = termView!!.textSize + changedSize + termView!!.textSize = fontSize + NeoPreference.store(NeoPreference.KEY_FONT_SIZE, fontSize) + } +} \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/ui/term/tab/event/TabCloseEvent.kt b/app/src/main/java/io/neoterm/ui/term/tab/event/TabCloseEvent.kt new file mode 100644 index 0000000..7e0ac51 --- /dev/null +++ b/app/src/main/java/io/neoterm/ui/term/tab/event/TabCloseEvent.kt @@ -0,0 +1,9 @@ +package io.neoterm.ui.term.tab.event + +import io.neoterm.ui.term.tab.TermTab + +/** + * @author kiva + */ + +class TabCloseEvent(var termTab: TermTab) diff --git a/app/src/main/java/io/neoterm/ui/term/tab/event/ToggleFullScreenEvent.kt b/app/src/main/java/io/neoterm/ui/term/tab/event/ToggleFullScreenEvent.kt new file mode 100644 index 0000000..fcc8400 --- /dev/null +++ b/app/src/main/java/io/neoterm/ui/term/tab/event/ToggleFullScreenEvent.kt @@ -0,0 +1,6 @@ +package io.neoterm.ui.term.tab.event + +/** + * @author kiva + */ +class ToggleFullScreenEvent() \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/utils/CrashHandler.kt b/app/src/main/java/io/neoterm/utils/CrashHandler.kt new file mode 100644 index 0000000..9966cb4 --- /dev/null +++ b/app/src/main/java/io/neoterm/utils/CrashHandler.kt @@ -0,0 +1,26 @@ +package io.neoterm.utils + +import android.content.Intent +import android.os.Process +import io.neoterm.App +import io.neoterm.ui.crash.CrashActivity + +/** + * @author kiva + */ +object CrashHandler : Thread.UncaughtExceptionHandler { + private lateinit var defaultHandler: Thread.UncaughtExceptionHandler + + fun init() { + defaultHandler = Thread.getDefaultUncaughtExceptionHandler() + Thread.setDefaultUncaughtExceptionHandler(this) + } + + override fun uncaughtException(t: Thread?, e: Throwable?) { + val intent = Intent(App.get(), CrashActivity::class.java) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + intent.putExtra("exception", e) + App.get().startActivity(intent) + Process.killProcess(Process.myPid()) + } +} \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/utils/FullScreenHelper.java b/app/src/main/java/io/neoterm/utils/FullScreenHelper.java deleted file mode 100644 index d3dfb50..0000000 --- a/app/src/main/java/io/neoterm/utils/FullScreenHelper.java +++ /dev/null @@ -1,126 +0,0 @@ -package io.neoterm.utils; - -import android.app.Activity; -import android.content.Context; -import android.graphics.Rect; -import android.view.View; -import android.view.ViewTreeObserver; -import android.widget.FrameLayout; - -/** - * Helper class to "adjustResize" Activity when we are in full screen mode and check IME status. - * Android Bug 5497: https://code.google.com/p/android/issues/detail?id=5497 - */ -public class FullScreenHelper { - public static FullScreenHelper injectActivity(Activity activity, boolean fullScreen, boolean recreate) { - return new FullScreenHelper(activity, fullScreen, recreate); - } - - public interface KeyBoardListener { - /** - * call back - * - * @param isShow true is show else hidden - * @param keyboardHeight keyboard height - */ - void onKeyboardChange(boolean isShow, int keyboardHeight); - } - - private View mChildOfContent; - private int usableHeightPrevious; - private FrameLayout.LayoutParams frameLayoutParams; - - private int mOriginHeight; - private int mPreHeight; - private KeyBoardListener mKeyBoardListener; - private boolean fullScreen; - private boolean shouldSkipFirst; - - public void setKeyBoardListener(KeyBoardListener mKeyBoardListener) { - this.mKeyBoardListener = mKeyBoardListener; - } - - private FullScreenHelper(Activity activity, final boolean fullScreen, boolean recreate) { - this.fullScreen = fullScreen; - this.shouldSkipFirst = recreate; - FrameLayout content = (FrameLayout) activity.findViewById(android.R.id.content); - mChildOfContent = content.getChildAt(0); - mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { - public void onGlobalLayout() { - if (FullScreenHelper.this.fullScreen) { - possiblyResizeChildOfContent(); - } - monitorImeStatus(); - } - }); - frameLayoutParams = (FrameLayout.LayoutParams) mChildOfContent.getLayoutParams(); - } - - private void monitorImeStatus() { - int currHeight = mChildOfContent.getHeight(); - if (currHeight == 0 && shouldSkipFirst) { - // First time - return; - } - - shouldSkipFirst = false; - boolean hasChange = false; - if (mPreHeight == 0) { - mPreHeight = currHeight; - mOriginHeight = currHeight; - } else { - if (mPreHeight != currHeight) { - hasChange = true; - mPreHeight = currHeight; - } else { - hasChange = false; - } - } - if (hasChange) { - int keyboardHeight = 0; - boolean keyBoardIsShowing; - if (Math.abs(mOriginHeight - currHeight) < 100) { - //hidden - keyBoardIsShowing = false; - } else { - //show - keyboardHeight = mOriginHeight - currHeight; - keyBoardIsShowing = true; - } - - if (mKeyBoardListener != null) { - mKeyBoardListener.onKeyboardChange(keyBoardIsShowing, keyboardHeight); - } - } - } - - private void possiblyResizeChildOfContent() { - int usableHeightNow = computeUsableHeight(); - int currentHeightLayoutHeight; - - if (usableHeightNow != usableHeightPrevious) { - int usableHeightSansKeyboard = mChildOfContent.getRootView().getHeight(); - int heightDifference = usableHeightSansKeyboard - usableHeightNow; - if (heightDifference > (usableHeightSansKeyboard / 4)) { - // keyboard probably just became visible - currentHeightLayoutHeight = usableHeightSansKeyboard - heightDifference; - } else { - // keyboard probably just became hidden - currentHeightLayoutHeight = usableHeightSansKeyboard; - } - frameLayoutParams.height = currentHeightLayoutHeight; - mChildOfContent.requestLayout(); - usableHeightPrevious = usableHeightNow; - } - } - - private int computeUsableHeight() { - Rect r = new Rect(); - mChildOfContent.getWindowVisibleDisplayFrame(r); - return r.bottom - r.top; - } - - public void setFullScreen(boolean fullScreen) { - this.fullScreen = fullScreen; - } -} \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/utils/FullScreenHelper.kt b/app/src/main/java/io/neoterm/utils/FullScreenHelper.kt new file mode 100644 index 0000000..c4384bc --- /dev/null +++ b/app/src/main/java/io/neoterm/utils/FullScreenHelper.kt @@ -0,0 +1,120 @@ +package io.neoterm.utils + +import android.app.Activity +import android.content.Context +import android.graphics.Rect +import android.view.View +import android.view.ViewTreeObserver +import android.widget.FrameLayout + +/** + * Helper class to "adjustResize" Activity when we are in full screen mode and check IME status. + * Android Bug 5497: https://code.google.com/p/android/issues/detail?id=5497 + */ +class FullScreenHelper private constructor(activity: Activity, var fullScreen: Boolean, private var shouldSkipFirst: Boolean) { + + interface KeyBoardListener { + /** + * call back + + * @param isShow true is show else hidden + * * + * @param keyboardHeight keyboard height + */ + fun onKeyboardChange(isShow: Boolean, keyboardHeight: Int) + } + + private val mChildOfContent: View + private var usableHeightPrevious: Int = 0 + private val frameLayoutParams: FrameLayout.LayoutParams + + private var mOriginHeight: Int = 0 + private var mPreHeight: Int = 0 + private var mKeyBoardListener: KeyBoardListener? = null + + fun setKeyBoardListener(mKeyBoardListener: KeyBoardListener) { + this.mKeyBoardListener = mKeyBoardListener + } + + init { + val content = activity.findViewById(android.R.id.content) as FrameLayout + mChildOfContent = content.getChildAt(0) + mChildOfContent.viewTreeObserver.addOnGlobalLayoutListener { + if (this@FullScreenHelper.fullScreen) { + possiblyResizeChildOfContent() + } + monitorImeStatus() + } + frameLayoutParams = mChildOfContent.layoutParams as FrameLayout.LayoutParams + } + + private fun monitorImeStatus() { + val currHeight = mChildOfContent.height + if (currHeight == 0 && shouldSkipFirst) { + // First time + return + } + + shouldSkipFirst = false + var hasChange = false + if (mPreHeight == 0) { + mPreHeight = currHeight + mOriginHeight = currHeight + } else { + if (mPreHeight != currHeight) { + hasChange = true + mPreHeight = currHeight + } else { + hasChange = false + } + } + if (hasChange) { + var keyboardHeight = 0 + val keyBoardIsShowing: Boolean + if (Math.abs(mOriginHeight - currHeight) < 100) { + //hidden + keyBoardIsShowing = false + } else { + //show + keyboardHeight = mOriginHeight - currHeight + keyBoardIsShowing = true + } + + if (mKeyBoardListener != null) { + mKeyBoardListener!!.onKeyboardChange(keyBoardIsShowing, keyboardHeight) + } + } + } + + private fun possiblyResizeChildOfContent() { + val usableHeightNow = computeUsableHeight() + val currentHeightLayoutHeight: Int + + if (usableHeightNow != usableHeightPrevious) { + val usableHeightSansKeyboard = mChildOfContent.rootView.height + val heightDifference = usableHeightSansKeyboard - usableHeightNow + if (heightDifference > usableHeightSansKeyboard / 4) { + // keyboard probably just became visible + currentHeightLayoutHeight = usableHeightSansKeyboard - heightDifference + } else { + // keyboard probably just became hidden + currentHeightLayoutHeight = usableHeightSansKeyboard + } + frameLayoutParams.height = currentHeightLayoutHeight + mChildOfContent.requestLayout() + usableHeightPrevious = usableHeightNow + } + } + + private fun computeUsableHeight(): Int { + val r = Rect() + mChildOfContent.getWindowVisibleDisplayFrame(r) + return r.bottom - r.top + } + + companion object { + fun injectActivity(activity: Activity, fullScreen: Boolean, recreate: Boolean): FullScreenHelper { + return FullScreenHelper(activity, fullScreen, recreate) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/utils/Shaker.kt b/app/src/main/java/io/neoterm/utils/Shaker.kt new file mode 100644 index 0000000..c9e4575 --- /dev/null +++ b/app/src/main/java/io/neoterm/utils/Shaker.kt @@ -0,0 +1,52 @@ +package io.neoterm.utils + +import android.content.Context +import android.hardware.Sensor +import android.hardware.SensorEvent +import android.hardware.SensorEventListener +import android.hardware.SensorManager + +class Shaker(context: Context) : SensorEventListener { + companion object { + private val SHAKE_SENSITIVITY = 14 + } + + private val mSensorManager: SensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager + private var mOnShakeListener: OnShakeListener? = null + + fun setOnShakeListener(onShakeListener: OnShakeListener) { + mOnShakeListener = onShakeListener + } + + fun onResume() { + mSensorManager.registerListener(this, + mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), + SensorManager.SENSOR_DELAY_NORMAL) + } + + fun onPause() { + mSensorManager.unregisterListener(this) + } + + override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) { + + } + + override fun onSensorChanged(event: SensorEvent) { + val sensorType = event.sensor.type + //values[0]:X, values[1]:Y, values[2]:Z + val values = event.values + if (sensorType == Sensor.TYPE_ACCELEROMETER) { + //这里可以调节摇一摇的灵敏度 + if (Math.abs(values[0]) > SHAKE_SENSITIVITY || Math.abs(values[1]) > SHAKE_SENSITIVITY || Math.abs(values[2]) > SHAKE_SENSITIVITY) { + if (mOnShakeListener != null) { + mOnShakeListener!!.onShake() + } + } + } + } + + interface OnShakeListener { + fun onShake() + } +} \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/view/ExtraKeysView.java b/app/src/main/java/io/neoterm/view/ExtraKeysView.java deleted file mode 100755 index 7b5c4ff..0000000 --- a/app/src/main/java/io/neoterm/view/ExtraKeysView.java +++ /dev/null @@ -1,214 +0,0 @@ -package io.neoterm.view; - -import android.content.Context; -import android.graphics.Typeface; -import android.util.AttributeSet; -import android.view.Gravity; -import android.view.HapticFeedbackConstants; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Button; -import android.widget.HorizontalScrollView; -import android.widget.LinearLayout; -import android.widget.ToggleButton; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; - -import io.neoterm.customize.eks.EksConfig; -import io.neoterm.customize.eks.EksConfigParser; -import io.neoterm.customize.font.FontManager; -import io.neoterm.preference.NeoTermPath; -import io.neoterm.utils.FileUtils; -import io.neoterm.view.eks.ControlButton; -import io.neoterm.view.eks.ExtraButton; -import io.neoterm.view.eks.StatedControlButton; - -import static io.neoterm.view.eks.ExtraButton.KEY_ARROW_DOWN; -import static io.neoterm.view.eks.ExtraButton.KEY_ARROW_LEFT; -import static io.neoterm.view.eks.ExtraButton.KEY_ARROW_RIGHT; -import static io.neoterm.view.eks.ExtraButton.KEY_ARROW_UP; -import static io.neoterm.view.eks.ExtraButton.KEY_CTRL; -import static io.neoterm.view.eks.ExtraButton.KEY_END; -import static io.neoterm.view.eks.ExtraButton.KEY_ESC; -import static io.neoterm.view.eks.ExtraButton.KEY_HOME; -import static io.neoterm.view.eks.ExtraButton.KEY_PAGE_DOWN; -import static io.neoterm.view.eks.ExtraButton.KEY_PAGE_UP; -import static io.neoterm.view.eks.ExtraButton.KEY_TAB; - -/** - * A view showing extra keys (such as Escape, Ctrl, Alt) not normally available on an Android soft - * keyboard. - */ -public final class ExtraKeysView extends LinearLayout { - - public static final StatedControlButton CTRL = new StatedControlButton(KEY_CTRL); - public static final ControlButton ESC = new ControlButton(KEY_ESC); - public static final ControlButton TAB = new ControlButton(KEY_TAB); - public static final ControlButton PAGE_UP = new ControlButton(KEY_PAGE_UP); - public static final ControlButton PAGE_DOWN = new ControlButton(KEY_PAGE_DOWN); - public static final ControlButton HOME = new ControlButton(KEY_HOME); - public static final ControlButton END = new ControlButton(KEY_END); - public static final ControlButton ARROW_UP = new ControlButton(KEY_ARROW_UP); - public static final ControlButton ARROW_DOWN = new ControlButton(KEY_ARROW_DOWN); - public static final ControlButton ARROW_LEFT = new ControlButton(KEY_ARROW_LEFT); - public static final ControlButton ARROW_RIGHT = new ControlButton(KEY_ARROW_RIGHT); - - public static final String DEFAULT_FILE_CONTENT = "version " + EksConfigParser.PARSER_VERSION + "\n" + - "program default\n" + - "define - false\n" + - "define / false\n" + - "define | false\n"; - - public static int NORMAL_TEXT_COLOR = 0xFFFFFFFF; - public static int SELECTED_TEXT_COLOR = 0xFF80DEEA; - - private List builtinExtraKeys; - private List userDefinedExtraKeys; - - private LinearLayout lineOne; - private LinearLayout lineTwo; - private Typeface typeface; - - public ExtraKeysView(Context context, AttributeSet attrs) { - super(context, attrs); - setGravity(Gravity.TOP); - setOrientation(VERTICAL); - HorizontalScrollView scrollOne = new HorizontalScrollView(context); - HorizontalScrollView scrollTwo = new HorizontalScrollView(context); - loadDefaultBuiltinExtraKeys(); - loadDefaultUserDefinedExtraKeys(); - lineOne = initLine(scrollOne); - lineTwo = initLine(scrollTwo); - addView(scrollOne); - addView(scrollTwo); - updateButtons(); - } - - private LinearLayout initLine(HorizontalScrollView scroll) { - scroll.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0, 1f)); - scroll.setFillViewport(true); - scroll.setHorizontalScrollBarEnabled(false); - LinearLayout line = new LinearLayout(getContext()); - line.setGravity(Gravity.START); - line.setOrientation(LinearLayout.HORIZONTAL); - scroll.addView(line); - return line; - } - - public boolean readControlButton() { - return CTRL.readState(); - } - - public boolean readAltButton() { - return false; - } - - public void addUserDefinedButton(ExtraButton button) { - addButton(userDefinedExtraKeys, button); - } - - public void addBuiltinButton(ExtraButton button) { - addButton(builtinExtraKeys, button); - } - - private void addButton(List buttons, ExtraButton button) { - if (!buttons.contains(button)) { - buttons.add(button); - } - } - - public void clearUserDefinedButton() { - userDefinedExtraKeys.clear(); - } - - public void loadDefaultUserDefinedExtraKeys() { - userDefinedExtraKeys = new ArrayList<>(7); - File defaultFile = new File(NeoTermPath.EKS_DEFAULT_FILE); - if (!defaultFile.exists()) { - generateDefaultFile(defaultFile); - } - - clearUserDefinedButton(); - try { - EksConfigParser parser = new EksConfigParser(); - parser.setInput(defaultFile); - EksConfig config = parser.parse(); - userDefinedExtraKeys.addAll(config.getShortcutKeys()); - } catch (Exception e) { - e.printStackTrace(); - } - } - - private void generateDefaultFile(File defaultFile) { - FileUtils.INSTANCE.writeFile(defaultFile, DEFAULT_FILE_CONTENT.getBytes()); - } - - void loadDefaultBuiltinExtraKeys() { - builtinExtraKeys = new ArrayList<>(7); - builtinExtraKeys.clear(); - builtinExtraKeys.add(ESC); - builtinExtraKeys.add(CTRL); - builtinExtraKeys.add(TAB); - builtinExtraKeys.add(ARROW_UP); - builtinExtraKeys.add(ARROW_DOWN); - builtinExtraKeys.add(ARROW_LEFT); - builtinExtraKeys.add(ARROW_RIGHT); - builtinExtraKeys.add(PAGE_UP); - builtinExtraKeys.add(PAGE_DOWN); - builtinExtraKeys.add(HOME); - builtinExtraKeys.add(END); - } - - public void updateButtons() { - lineOne.removeAllViews(); - lineTwo.removeAllViews(); - for (final ExtraButton extraButton : userDefinedExtraKeys) { - addExtraButton(lineOne, extraButton); - } - for (final ExtraButton extraButton : builtinExtraKeys) { - addExtraButton(lineTwo, extraButton); - } - } - - private void addExtraButton(LinearLayout contentView, final ExtraButton extraButton) { - final Button button; - if (extraButton instanceof StatedControlButton) { - StatedControlButton btn = ((StatedControlButton) extraButton); - button = btn.toggleButton = new ToggleButton(getContext(), null, android.R.attr.buttonBarButtonStyle); - button.setClickable(true); - if (btn.initState) { - btn.toggleButton.setChecked(true); - btn.toggleButton.setTextColor(SELECTED_TEXT_COLOR); - } - } else { - button = new Button(getContext(), null, android.R.attr.buttonBarButtonStyle); - } - - button.setTypeface(typeface); - button.setText(extraButton.buttonText); - button.setTextColor(NORMAL_TEXT_COLOR); - button.setAllCaps(false); - - button.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - button.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP); - View root = getRootView(); - extraButton.onClick(root); - } - }); - contentView.addView(button); - } - - public void setTextColor(int textColor) { - NORMAL_TEXT_COLOR = textColor; - updateButtons(); - } - - public void setTypeface(Typeface typeface) { - this.typeface = typeface; - updateButtons(); - } -} diff --git a/app/src/main/java/io/neoterm/view/ExtraKeysView.kt b/app/src/main/java/io/neoterm/view/ExtraKeysView.kt new file mode 100755 index 0000000..0318eec --- /dev/null +++ b/app/src/main/java/io/neoterm/view/ExtraKeysView.kt @@ -0,0 +1,223 @@ +package io.neoterm.view + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.Typeface +import android.util.AttributeSet +import android.view.Gravity +import android.view.HapticFeedbackConstants +import android.view.View +import android.view.ViewGroup +import android.widget.Button +import android.widget.HorizontalScrollView +import android.widget.LinearLayout +import android.widget.ToggleButton + +import java.io.File +import java.util.ArrayList + +import io.neoterm.customize.eks.EksConfig +import io.neoterm.customize.eks.EksConfigParser +import io.neoterm.customize.font.FontManager +import io.neoterm.preference.NeoTermPath +import io.neoterm.utils.FileUtils +import io.neoterm.view.eks.ControlButton +import io.neoterm.view.eks.ExtraButton +import io.neoterm.view.eks.StatedControlButton + +import io.neoterm.view.eks.ExtraButton.Companion.KEY_ARROW_DOWN +import io.neoterm.view.eks.ExtraButton.Companion.KEY_ARROW_LEFT +import io.neoterm.view.eks.ExtraButton.Companion.KEY_ARROW_RIGHT +import io.neoterm.view.eks.ExtraButton.Companion.KEY_ARROW_UP +import io.neoterm.view.eks.ExtraButton.Companion.KEY_CTRL +import io.neoterm.view.eks.ExtraButton.Companion.KEY_END +import io.neoterm.view.eks.ExtraButton.Companion.KEY_ESC +import io.neoterm.view.eks.ExtraButton.Companion.KEY_HOME +import io.neoterm.view.eks.ExtraButton.Companion.KEY_PAGE_DOWN +import io.neoterm.view.eks.ExtraButton.Companion.KEY_PAGE_UP +import io.neoterm.view.eks.ExtraButton.Companion.KEY_TAB + +/** + * A view showing extra keys (such as Escape, Ctrl, Alt) not normally available on an Android soft + * keyboard. + */ +class ExtraKeysView(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) { + + private var builtinExtraKeys: MutableList? = null + private var userDefinedExtraKeys: MutableList? = null + + private val lineOne: LinearLayout + private val lineTwo: LinearLayout + private var typeface: Typeface? = null + + init { + gravity = Gravity.TOP + orientation = LinearLayout.VERTICAL + val scrollOne = HorizontalScrollView(context) + val scrollTwo = HorizontalScrollView(context) + loadDefaultBuiltinExtraKeys() + loadDefaultUserDefinedExtraKeys() + lineOne = initLine(scrollOne) + lineTwo = initLine(scrollTwo) + addView(scrollOne) + addView(scrollTwo) + updateButtons() + } + + private fun initLine(scroll: HorizontalScrollView): LinearLayout { + scroll.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0, 1f) + scroll.isFillViewport = true + scroll.isHorizontalScrollBarEnabled = false + val line = LinearLayout(context) + line.gravity = Gravity.START + line.orientation = LinearLayout.HORIZONTAL + scroll.addView(line) + return line + } + + fun readControlButton(): Boolean { + return CTRL.readState() + } + + fun readAltButton(): Boolean { + return ALT.readState() + } + + fun addUserDefinedButton(button: ExtraButton) { + addButton(userDefinedExtraKeys, button) + } + + fun addBuiltinButton(button: ExtraButton) { + addButton(builtinExtraKeys, button) + } + + private fun addButton(buttons: MutableList?, button: ExtraButton) { + if (buttons != null && !buttons.contains(button)) { + buttons.add(button) + } + } + + fun clearUserDefinedButton() { + userDefinedExtraKeys!!.clear() + } + + fun loadDefaultUserDefinedExtraKeys() { + userDefinedExtraKeys = ArrayList(7) + val defaultFile = File(NeoTermPath.EKS_DEFAULT_FILE) + if (!defaultFile.exists()) { + generateDefaultFile(defaultFile) + } + + clearUserDefinedButton() + try { + val parser = EksConfigParser() + parser.setInput(defaultFile) + val config = parser.parse() + userDefinedExtraKeys!!.addAll(config.shortcutKeys) + } catch (e: Exception) { + e.printStackTrace() + } + + } + + private fun generateDefaultFile(defaultFile: File) { + FileUtils.writeFile(defaultFile, DEFAULT_FILE_CONTENT.toByteArray()) + } + + internal fun loadDefaultBuiltinExtraKeys() { + builtinExtraKeys = ArrayList(7) + builtinExtraKeys!!.clear() + builtinExtraKeys!!.add(ESC) + builtinExtraKeys!!.add(CTRL) + builtinExtraKeys!!.add(ALT) + builtinExtraKeys!!.add(TAB) + builtinExtraKeys!!.add(ARROW_UP) + builtinExtraKeys!!.add(ARROW_DOWN) + builtinExtraKeys!!.add(ARROW_LEFT) + builtinExtraKeys!!.add(ARROW_RIGHT) + builtinExtraKeys!!.add(PAGE_UP) + builtinExtraKeys!!.add(PAGE_DOWN) + builtinExtraKeys!!.add(HOME) + builtinExtraKeys!!.add(END) + } + + fun updateButtons() { + lineOne.removeAllViews() + lineTwo.removeAllViews() + for (extraButton in userDefinedExtraKeys!!) { + addExtraButton(lineOne, extraButton) + } + for (extraButton in builtinExtraKeys!!) { + addExtraButton(lineTwo, extraButton) + } + } + + private fun addExtraButton(contentView: LinearLayout, extraButton: ExtraButton) { + val button: Button + if (extraButton is StatedControlButton) { + val btn = extraButton + val toggleButton = ToggleButton(context, null, android.R.attr.buttonBarButtonStyle) + btn.toggleButton = toggleButton + button = toggleButton + + button.setClickable(true) + if (btn.initState) { + btn.toggleButton!!.isChecked = true + btn.toggleButton!!.setTextColor(SELECTED_TEXT_COLOR) + } + } else { + button = Button(context, null, android.R.attr.buttonBarButtonStyle) + } + + button.typeface = typeface + button.text = extraButton.buttonText + button.setTextColor(NORMAL_TEXT_COLOR) + button.setAllCaps(false) + + button.setOnClickListener { + button.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP) + val root = rootView + extraButton.onClick(root) + } + contentView.addView(button) + } + + fun setTextColor(textColor: Int) { + NORMAL_TEXT_COLOR = textColor + updateButtons() + } + + fun setTypeface(typeface: Typeface?) { + this.typeface = typeface + updateButtons() + } + + companion object { + @SuppressLint("StaticFieldLeak") + val CTRL = StatedControlButton(ExtraButton.KEY_CTRL) + @SuppressLint("StaticFieldLeak") + val ALT = StatedControlButton(ExtraButton.KEY_ALT) + val ESC = ControlButton(ExtraButton.KEY_ESC) + val TAB = ControlButton(ExtraButton.KEY_TAB) + val PAGE_UP = ControlButton(ExtraButton.KEY_PAGE_UP) + val PAGE_DOWN = ControlButton(ExtraButton.KEY_PAGE_DOWN) + val HOME = ControlButton(ExtraButton.KEY_HOME) + val END = ControlButton(ExtraButton.KEY_END) + val ARROW_UP = ControlButton(ExtraButton.KEY_ARROW_UP) + val ARROW_DOWN = ControlButton(ExtraButton.KEY_ARROW_DOWN) + val ARROW_LEFT = ControlButton(ExtraButton.KEY_ARROW_LEFT) + val ARROW_RIGHT = ControlButton(ExtraButton.KEY_ARROW_RIGHT) + + val DEFAULT_FILE_CONTENT = "version " + EksConfigParser.PARSER_VERSION + "\n" + + "program default\n" + + "define - false\n" + + "define / false\n" + + "define | false\n" + + "define $ false\n" + + "define < false\n" + + "define > false\n" + + var NORMAL_TEXT_COLOR = 0xFFFFFFFF.toInt() + var SELECTED_TEXT_COLOR = 0xFF80DEEA.toInt() + } +} diff --git a/app/src/main/java/io/neoterm/view/GestureAndScaleRecognizer.java b/app/src/main/java/io/neoterm/view/GestureAndScaleRecognizer.java deleted file mode 100755 index 0da557b..0000000 --- a/app/src/main/java/io/neoterm/view/GestureAndScaleRecognizer.java +++ /dev/null @@ -1,111 +0,0 @@ -package io.neoterm.view; - -import android.content.Context; -import android.view.GestureDetector; -import android.view.MotionEvent; -import android.view.ScaleGestureDetector; - -/** A combination of {@link GestureDetector} and {@link ScaleGestureDetector}. */ -final class GestureAndScaleRecognizer { - - public interface Listener { - boolean onSingleTapUp(MotionEvent e); - - boolean onDoubleTap(MotionEvent e); - - boolean onScroll(MotionEvent e2, float dx, float dy); - - boolean onFling(MotionEvent e, float velocityX, float velocityY); - - boolean onScale(float focusX, float focusY, float scale); - - boolean onDown(float x, float y); - - boolean onUp(MotionEvent e); - - void onLongPress(MotionEvent e); - } - - private final GestureDetector mGestureDetector; - private final ScaleGestureDetector mScaleDetector; - final Listener mListener; - boolean isAfterLongPress; - - public GestureAndScaleRecognizer(Context context, Listener listener) { - mListener = listener; - - mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() { - @Override - public boolean onScroll(MotionEvent e1, MotionEvent e2, float dx, float dy) { - return mListener.onScroll(e2, dx, dy); - } - - @Override - public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { - return mListener.onFling(e2, velocityX, velocityY); - } - - @Override - public boolean onDown(MotionEvent e) { - return mListener.onDown(e.getX(), e.getY()); - } - - @Override - public void onLongPress(MotionEvent e) { - mListener.onLongPress(e); - isAfterLongPress = true; - } - }, null, true /* ignoreMultitouch */); - - mGestureDetector.setOnDoubleTapListener(new GestureDetector.OnDoubleTapListener() { - @Override - public boolean onSingleTapConfirmed(MotionEvent e) { - return mListener.onSingleTapUp(e); - } - - @Override - public boolean onDoubleTap(MotionEvent e) { - return mListener.onDoubleTap(e); - } - - @Override - public boolean onDoubleTapEvent(MotionEvent e) { - return true; - } - }); - - mScaleDetector = new ScaleGestureDetector(context, new ScaleGestureDetector.SimpleOnScaleGestureListener() { - @Override - public boolean onScaleBegin(ScaleGestureDetector detector) { - return true; - } - - @Override - public boolean onScale(ScaleGestureDetector detector) { - return mListener.onScale(detector.getFocusX(), detector.getFocusY(), detector.getScaleFactor()); - } - }); - } - - public void onTouchEvent(MotionEvent event) { - mGestureDetector.onTouchEvent(event); - mScaleDetector.onTouchEvent(event); - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - isAfterLongPress = false; - break; - case MotionEvent.ACTION_UP: - if (!isAfterLongPress) { - // This behaviour is desired when in e.g. vim with mouse events, where we do not - // want to move the cursor when lifting finger after a long press. - mListener.onUp(event); - } - break; - } - } - - public boolean isInProgress() { - return mScaleDetector.isInProgress(); - } - -} diff --git a/app/src/main/java/io/neoterm/view/GestureAndScaleRecognizer.kt b/app/src/main/java/io/neoterm/view/GestureAndScaleRecognizer.kt new file mode 100755 index 0000000..27539a0 --- /dev/null +++ b/app/src/main/java/io/neoterm/view/GestureAndScaleRecognizer.kt @@ -0,0 +1,95 @@ +package io.neoterm.view + +import android.content.Context +import android.view.GestureDetector +import android.view.MotionEvent +import android.view.ScaleGestureDetector + +/** A combination of [GestureDetector] and [ScaleGestureDetector]. */ +internal class GestureAndScaleRecognizer(context: Context, val mListener: Listener) { + + interface Listener { + fun onSingleTapUp(e: MotionEvent): Boolean + + fun onDoubleTap(e: MotionEvent): Boolean + + fun onScroll(e2: MotionEvent, dx: Float, dy: Float): Boolean + + fun onFling(e: MotionEvent, velocityX: Float, velocityY: Float): Boolean + + fun onScale(focusX: Float, focusY: Float, scale: Float): Boolean + + fun onDown(x: Float, y: Float): Boolean + + fun onUp(e: MotionEvent): Boolean + + fun onLongPress(e: MotionEvent) + } + + private val mGestureDetector: GestureDetector + private val mScaleDetector: ScaleGestureDetector + var isAfterLongPress: Boolean = false + + init { + + mGestureDetector = GestureDetector(context, object : GestureDetector.SimpleOnGestureListener() { + override fun onScroll(e1: MotionEvent, e2: MotionEvent, dx: Float, dy: Float): Boolean { + return mListener.onScroll(e2, dx, dy) + } + + override fun onFling(e1: MotionEvent, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean { + return mListener.onFling(e2, velocityX, velocityY) + } + + override fun onDown(e: MotionEvent): Boolean { + return mListener.onDown(e.x, e.y) + } + + override fun onLongPress(e: MotionEvent) { + mListener.onLongPress(e) + isAfterLongPress = true + } + }, null, true /* ignoreMultitouch */) + + mGestureDetector.setOnDoubleTapListener(object : GestureDetector.OnDoubleTapListener { + override fun onSingleTapConfirmed(e: MotionEvent): Boolean { + return mListener.onSingleTapUp(e) + } + + override fun onDoubleTap(e: MotionEvent): Boolean { + return mListener.onDoubleTap(e) + } + + override fun onDoubleTapEvent(e: MotionEvent): Boolean { + return true + } + }) + + mScaleDetector = ScaleGestureDetector(context, object : ScaleGestureDetector.SimpleOnScaleGestureListener() { + override fun onScaleBegin(detector: ScaleGestureDetector): Boolean { + return true + } + + override fun onScale(detector: ScaleGestureDetector): Boolean { + return mListener.onScale(detector.focusX, detector.focusY, detector.scaleFactor) + } + }) + } + + fun onTouchEvent(event: MotionEvent) { + mGestureDetector.onTouchEvent(event) + mScaleDetector.onTouchEvent(event) + when (event.action) { + MotionEvent.ACTION_DOWN -> isAfterLongPress = false + MotionEvent.ACTION_UP -> if (!isAfterLongPress) { + // This behaviour is desired when in e.g. vim with mouse events, where we do not + // want to move the cursor when lifting finger after a long press. + mListener.onUp(event) + } + } + } + + val isInProgress: Boolean + get() = mScaleDetector.isInProgress + +} diff --git a/app/src/main/java/io/neoterm/view/TerminalView.java b/app/src/main/java/io/neoterm/view/TerminalView.java index 95a7f6c..d763172 100755 --- a/app/src/main/java/io/neoterm/view/TerminalView.java +++ b/app/src/main/java/io/neoterm/view/TerminalView.java @@ -565,12 +565,7 @@ public final class TerminalView extends View { if (action == MotionEvent.ACTION_DOWN) showContextMenu(); return true; } else if (ev.isButtonPressed(MotionEvent.BUTTON_TERTIARY)) { - ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE); - ClipData clipData = clipboard.getPrimaryClip(); - if (clipData != null) { - CharSequence paste = clipData.getItemAt(0).coerceToText(getContext()); - if (!TextUtils.isEmpty(paste)) mEmulator.paste(paste.toString()); - } + pasteFromClipboard(); } else if (mEmulator.isMouseTrackingActive()) { // BUTTON_PRIMARY. switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: @@ -589,6 +584,15 @@ public final class TerminalView extends View { return true; } + public void pasteFromClipboard() { + ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE); + ClipData clipData = clipboard.getPrimaryClip(); + if (clipData != null) { + CharSequence paste = clipData.getItemAt(0).coerceToText(getContext()); + if (!TextUtils.isEmpty(paste)) mEmulator.paste(paste.toString()); + } + } + @Override public boolean onKeyPreIme(int keyCode, KeyEvent event) { if (LOG_KEY_EVENTS) @@ -892,12 +896,7 @@ public final class TerminalView extends View { mTermSession.clipboardText(selectedText); break; case 2: - ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE); - ClipData clipData = clipboard.getPrimaryClip(); - if (clipData != null) { - CharSequence paste = clipData.getItemAt(0).coerceToText(getContext()); - if (!TextUtils.isEmpty(paste)) mEmulator.paste(paste.toString()); - } + pasteFromClipboard(); break; case 3: showContextMenu(); diff --git a/app/src/main/java/io/neoterm/view/eks/ControlButton.java b/app/src/main/java/io/neoterm/view/eks/ControlButton.java deleted file mode 100644 index 142cb30..0000000 --- a/app/src/main/java/io/neoterm/view/eks/ControlButton.java +++ /dev/null @@ -1,20 +0,0 @@ -package io.neoterm.view.eks; - -import android.view.View; - -import io.neoterm.view.ExtraKeysView; - -/** - * @author kiva - */ - -public class ControlButton extends ExtraButton { - public ControlButton(String text) { - buttonText = text; - } - - @Override - public void onClick(View view) { - sendKey(view, buttonText); - } -} diff --git a/app/src/main/java/io/neoterm/view/eks/ControlButton.kt b/app/src/main/java/io/neoterm/view/eks/ControlButton.kt new file mode 100644 index 0000000..6dbcaf3 --- /dev/null +++ b/app/src/main/java/io/neoterm/view/eks/ControlButton.kt @@ -0,0 +1,19 @@ +package io.neoterm.view.eks + +import android.view.View + +import io.neoterm.view.ExtraKeysView + +/** + * @author kiva + */ + +open class ControlButton(text: String) : ExtraButton() { + init { + buttonText = text + } + + override fun onClick(view: View) { + ExtraButton.Companion.sendKey(view, buttonText!!) + } +} diff --git a/app/src/main/java/io/neoterm/view/eks/ExtraButton.java b/app/src/main/java/io/neoterm/view/eks/ExtraButton.java deleted file mode 100644 index 274ddef..0000000 --- a/app/src/main/java/io/neoterm/view/eks/ExtraButton.java +++ /dev/null @@ -1,82 +0,0 @@ -package io.neoterm.view.eks; - -import android.view.KeyEvent; -import android.view.View; - -import io.neoterm.R; -import io.neoterm.backend.TerminalSession; -import io.neoterm.view.TerminalView; - -/** - * @author kiva - */ - -public abstract class ExtraButton implements View.OnClickListener { - public static final String KEY_ESC = "Esc"; - public static final String KEY_TAB = "Tab"; - public static final String KEY_CTRL = "Ctrl"; - public static final String KEY_PAGE_UP = "PgUp"; - public static final String KEY_PAGE_DOWN = "PgDn"; - public static final String KEY_HOME = "Home"; - public static final String KEY_END = "End"; - public static final String KEY_ARROW_UP = "▲"; - public static final String KEY_ARROW_DOWN = "▼"; - public static final String KEY_ARROW_LEFT = "◀"; - public static final String KEY_ARROW_RIGHT = "▶"; - - public String buttonText; - - @Override - public abstract void onClick(View view); - - public static void sendKey(View view, String keyName) { - int keyCode = 0; - String chars = null; - switch (keyName) { - case KEY_ESC: - keyCode = KeyEvent.KEYCODE_ESCAPE; - break; - case KEY_TAB: - keyCode = KeyEvent.KEYCODE_TAB; - break; - case KEY_ARROW_UP: - keyCode = KeyEvent.KEYCODE_DPAD_UP; - break; - case KEY_ARROW_LEFT: - keyCode = KeyEvent.KEYCODE_DPAD_LEFT; - break; - case KEY_ARROW_RIGHT: - keyCode = KeyEvent.KEYCODE_DPAD_RIGHT; - break; - case KEY_ARROW_DOWN: - keyCode = KeyEvent.KEYCODE_DPAD_DOWN; - break; - case KEY_PAGE_UP: - keyCode = KeyEvent.KEYCODE_PAGE_UP; - break; - case KEY_PAGE_DOWN: - keyCode = KeyEvent.KEYCODE_PAGE_DOWN; - break; - case KEY_HOME: - keyCode = KeyEvent.KEYCODE_MOVE_HOME; - break; - case KEY_END: - keyCode = KeyEvent.KEYCODE_MOVE_END; - break; - case "―": - chars = "-"; - break; - default: - chars = keyName; - } - - if (keyCode > 0) { - view.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode)); - view.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keyCode)); - } else { - TerminalView terminalView = (TerminalView) view.findViewById(R.id.terminal_view); - TerminalSession session = terminalView.getCurrentSession(); - if (session != null) session.write(chars); - } - } -} \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/view/eks/ExtraButton.kt b/app/src/main/java/io/neoterm/view/eks/ExtraButton.kt new file mode 100644 index 0000000..07f24da --- /dev/null +++ b/app/src/main/java/io/neoterm/view/eks/ExtraButton.kt @@ -0,0 +1,62 @@ +package io.neoterm.view.eks + +import android.view.KeyEvent +import android.view.View + +import io.neoterm.R +import io.neoterm.backend.TerminalSession +import io.neoterm.view.TerminalView + +/** + * @author kiva + */ + +abstract class ExtraButton : View.OnClickListener { + + var buttonText: String? = null + + abstract override fun onClick(view: View) + + companion object { + val KEY_ESC = "Esc" + val KEY_TAB = "Tab" + val KEY_CTRL = "Ctrl" + val KEY_ALT = "Alt" + val KEY_PAGE_UP = "PgUp" + val KEY_PAGE_DOWN = "PgDn" + val KEY_HOME = "Home" + val KEY_END = "End" + val KEY_ARROW_UP = "▲" + val KEY_ARROW_DOWN = "▼" + val KEY_ARROW_LEFT = "◀" + val KEY_ARROW_RIGHT = "▶" + + fun sendKey(view: View, keyName: String) { + var keyCode = 0 + var chars: String? = null + when (keyName) { + KEY_ESC -> keyCode = KeyEvent.KEYCODE_ESCAPE + KEY_TAB -> keyCode = KeyEvent.KEYCODE_TAB + KEY_ARROW_UP -> keyCode = KeyEvent.KEYCODE_DPAD_UP + KEY_ARROW_LEFT -> keyCode = KeyEvent.KEYCODE_DPAD_LEFT + KEY_ARROW_RIGHT -> keyCode = KeyEvent.KEYCODE_DPAD_RIGHT + KEY_ARROW_DOWN -> keyCode = KeyEvent.KEYCODE_DPAD_DOWN + KEY_PAGE_UP -> keyCode = KeyEvent.KEYCODE_PAGE_UP + KEY_PAGE_DOWN -> keyCode = KeyEvent.KEYCODE_PAGE_DOWN + KEY_HOME -> keyCode = KeyEvent.KEYCODE_MOVE_HOME + KEY_END -> keyCode = KeyEvent.KEYCODE_MOVE_END + "―" -> chars = "-" + else -> chars = keyName + } + + if (keyCode > 0) { + view.dispatchKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, keyCode)) + view.dispatchKeyEvent(KeyEvent(KeyEvent.ACTION_UP, keyCode)) + } else { + val terminalView = view.findViewById(R.id.terminal_view) as TerminalView + val session = terminalView.currentSession + session?.write(chars) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/io/neoterm/view/eks/StatedControlButton.java b/app/src/main/java/io/neoterm/view/eks/StatedControlButton.java deleted file mode 100644 index ab29e0a..0000000 --- a/app/src/main/java/io/neoterm/view/eks/StatedControlButton.java +++ /dev/null @@ -1,44 +0,0 @@ -package io.neoterm.view.eks; - -import android.view.View; -import android.widget.ToggleButton; - -import io.neoterm.view.ExtraKeysView; - -/** - * @author kiva - */ - -public class StatedControlButton extends ControlButton { - public ToggleButton toggleButton; - public boolean initState; - - public StatedControlButton(String text, boolean initState) { - super(text); - this.initState = initState; - } - - public StatedControlButton(String text) { - this(text, false); - } - - @Override - public void onClick(View view) { - setStatus(toggleButton.isChecked()); - } - - public void setStatus(boolean status) { - toggleButton.setChecked(status); - toggleButton.setTextColor(status ? ExtraKeysView.SELECTED_TEXT_COLOR : ExtraKeysView.NORMAL_TEXT_COLOR); - } - - public boolean readState() { - if (toggleButton.isPressed()) return true; - boolean result = toggleButton.isChecked(); - if (result) { - toggleButton.setChecked(false); - toggleButton.setTextColor(ExtraKeysView.NORMAL_TEXT_COLOR); - } - return result; - } -} diff --git a/app/src/main/java/io/neoterm/view/eks/StatedControlButton.kt b/app/src/main/java/io/neoterm/view/eks/StatedControlButton.kt new file mode 100644 index 0000000..019a9e5 --- /dev/null +++ b/app/src/main/java/io/neoterm/view/eks/StatedControlButton.kt @@ -0,0 +1,33 @@ +package io.neoterm.view.eks + +import android.view.View +import android.widget.ToggleButton + +import io.neoterm.view.ExtraKeysView + +/** + * @author kiva + */ + +open class StatedControlButton @JvmOverloads constructor(text: String, var initState: Boolean = false) : ControlButton(text) { + var toggleButton: ToggleButton? = null + + override fun onClick(view: View) { + setStatus(toggleButton!!.isChecked) + } + + fun setStatus(status: Boolean) { + toggleButton!!.isChecked = status + toggleButton!!.setTextColor(if (status) ExtraKeysView.SELECTED_TEXT_COLOR else ExtraKeysView.NORMAL_TEXT_COLOR) + } + + fun readState(): Boolean { + if (toggleButton!!.isPressed) return true + val result = toggleButton!!.isChecked + if (result) { + toggleButton!!.isChecked = false + toggleButton!!.setTextColor(ExtraKeysView.NORMAL_TEXT_COLOR) + } + return result + } +} diff --git a/app/src/main/java/io/neoterm/view/eks/TextButton.java b/app/src/main/java/io/neoterm/view/eks/TextButton.java deleted file mode 100644 index 6f7edaa..0000000 --- a/app/src/main/java/io/neoterm/view/eks/TextButton.java +++ /dev/null @@ -1,30 +0,0 @@ -package io.neoterm.view.eks; - -import android.view.View; - -import io.neoterm.view.ExtraKeysView; - -/** - * @author kiva - */ - -public class TextButton extends ExtraButton { - private boolean withEnter = false; - - public TextButton(String text) { - this(text, false); - } - - public TextButton(String text, boolean withEnter) { - this.buttonText = text; - this.withEnter = withEnter; - } - - @Override - public void onClick(View view) { - sendKey(view, buttonText); - if (withEnter) { - sendKey(view, "\n"); - } - } -} diff --git a/app/src/main/java/io/neoterm/view/eks/TextButton.kt b/app/src/main/java/io/neoterm/view/eks/TextButton.kt new file mode 100644 index 0000000..e32cc73 --- /dev/null +++ b/app/src/main/java/io/neoterm/view/eks/TextButton.kt @@ -0,0 +1,25 @@ +package io.neoterm.view.eks + +import android.view.View + +import io.neoterm.view.ExtraKeysView + +/** + * @author kiva + */ + +class TextButton @JvmOverloads constructor(text: String, withEnter: Boolean = false) : ExtraButton() { + private var withEnter = false + + init { + this.buttonText = text + this.withEnter = withEnter + } + + override fun onClick(view: View) { + ExtraButton.sendKey(view, buttonText!!) + if (withEnter) { + ExtraButton.sendKey(view, "\n") + } + } +} diff --git a/app/src/main/java/io/neoterm/view/tab/TabCloseEvent.kt b/app/src/main/java/io/neoterm/view/tab/TabCloseEvent.kt deleted file mode 100644 index 2203f0e..0000000 --- a/app/src/main/java/io/neoterm/view/tab/TabCloseEvent.kt +++ /dev/null @@ -1,7 +0,0 @@ -package io.neoterm.view.tab - -/** - * @author kiva - */ - -class TabCloseEvent(var termTab: TermTab) diff --git a/app/src/main/java/io/neoterm/view/tab/TermViewClient.kt b/app/src/main/java/io/neoterm/view/tab/TermViewClient.kt deleted file mode 100644 index ca2232f..0000000 --- a/app/src/main/java/io/neoterm/view/tab/TermViewClient.kt +++ /dev/null @@ -1,124 +0,0 @@ -package io.neoterm.view.tab - -import android.content.Context -import android.view.InputDevice -import android.view.KeyEvent -import android.view.MotionEvent -import android.view.inputmethod.InputMethodManager -import io.neoterm.R -import io.neoterm.backend.TerminalSession -import io.neoterm.customize.eks.EksKeysManager -import io.neoterm.preference.NeoPreference -import io.neoterm.view.ExtraKeysView -import io.neoterm.view.TerminalView -import io.neoterm.view.TerminalViewClient - -/** - * @author kiva - */ -class TermViewClient(val context: Context) : TerminalViewClient { - private var mVirtualControlKeyDown: Boolean = false - private var mVirtualFnKeyDown: Boolean = false - private var lastTitle: String = "" - - var sessionFinished: Boolean = false - - var termTab: TermTab? = null - var termView: TerminalView? = null - var extraKeysView: ExtraKeysView? = null - - override fun onScale(scale: Float): Float { - if (scale < 0.9f || scale > 1.1f) { - val increase = scale > 1f - val changedSize = (if (increase) 1 else -1) * 2 - val fontSize = termView!!.textSize + changedSize - termView!!.textSize = fontSize - NeoPreference.store(NeoPreference.KEY_FONT_SIZE, fontSize) - return 1.0f - } - return scale - } - - override fun onSingleTapUp(e: MotionEvent?) { - (context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager) - .showSoftInput(termView, InputMethodManager.SHOW_IMPLICIT) - } - - override fun shouldBackButtonBeMappedToEscape(): Boolean { - return NeoPreference.loadBoolean(R.string.key_generaL_backspace_map_to_esc, false) - } - - override fun copyModeChanged(copyMode: Boolean) { - // TODO - } - - override fun onKeyDown(keyCode: Int, e: KeyEvent?, session: TerminalSession?): Boolean { - when (keyCode) { - KeyEvent.KEYCODE_ENTER -> { - if (e?.action == KeyEvent.ACTION_DOWN && sessionFinished) { - termTab?.requiredCloseTab() - return true - } - return false - } - else -> return false - } - } - - override fun onKeyUp(keyCode: Int, e: KeyEvent?): Boolean { - return handleVirtualKeys(keyCode, e, false) - } - - override fun readControlKey(): Boolean { - return (extraKeysView != null && extraKeysView!!.readControlButton()) || mVirtualControlKeyDown - } - - override fun readAltKey(): Boolean { - return (extraKeysView != null && extraKeysView!!.readAltButton()) || mVirtualFnKeyDown - } - - override fun onCodePoint(codePoint: Int, ctrlDown: Boolean, session: TerminalSession?): Boolean { - // TODO - return false - } - - override fun onLongPress(event: MotionEvent?): Boolean { - // TODO - return false - } - - private fun handleVirtualKeys(keyCode: Int, event: KeyEvent?, down: Boolean): Boolean { - if (event == null) { - return false - } - val inputDevice = event.device - if (inputDevice != null && inputDevice.keyboardType == InputDevice.KEYBOARD_TYPE_ALPHABETIC) { - return false - } else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) { - mVirtualControlKeyDown = down - return true - } else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) { - mVirtualFnKeyDown = down - return true - } - return false - } - - fun updateSuggestions(title: String?, force: Boolean = false) { - if (extraKeysView == null || title == null || title.isEmpty()) { - return - } - - if (lastTitle != title || force) { - removeSuggestions() - EksKeysManager.showShortcutKeys(title, extraKeysView) - extraKeysView?.updateButtons() - lastTitle = title - } - } - - fun removeSuggestions() { - extraKeysView?.clearUserDefinedButton() - } - -} \ No newline at end of file diff --git a/app/src/main/res/drawable/plat_logo.png b/app/src/main/res/drawable/plat_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..6b8efe28dd5f926e0789e8526c9271be9000c4c9 GIT binary patch literal 3318 zcmeHJ`CAg$8a|k%WwwnuWf}E|T8_&orkM*(P7xVuE{O}ugj!~)iIN*gHIq(Sl$GWJ zKHU^kbKlC9%5p)az-3%e8&eZSOhgnxFLv)=G54PH!*_mozxO=v_r2e9&Y#YXNcAmR zTL1v+s58I10HCBeDgo6^im(vXEd^j}1M1gb+@i7sJi+;WZJL`#`)Wdza+r74_o(_W z+aMMQ(!Fw z?Q~=6-KOIQ-faO-ArNK^W+z~-Z3ew#=7Qtb!4B{;`85Ero#5p?LjX|9AQ+|wC;<0| zqmC-T*g}}cu`sLj+8y8=5|ldXd-nQJk|tZ&uuc_fVTaDE{!|Il&REF)kxi@ZaHy`7 zma$M|8W%aq%_+hUPLd;Py6hV~s5Ox{`Yydj&5LQ}e(G+4$3oEDAxoa@YQ{uPP=*E+ zL98F4+71`a9}CTx2u={y#gnOaEOY5z(qIV5%-W*Nt`k5xKepC@VCS0c|=8*e(y23AK&OkM;nRvnk(QdaB zYJAy=icVPML5JP%?7OGUIV=c&Nc>`!lTf*^O<~DuD~1BD+;rqv{ETIb35(M*C@1yXxb}iEm)_#sVx#9! z2;J?GA4#MxH_Xc5{?F!DTjds2Qt7ajZ4Oc83=)|1hZv6_ks2)(Z$ zhR`1x^gOXQ=N?q=j5e^5SXA91Jd*Fr6m{EWliKUVx>Xv}86gvu>rB@UijKW0^NVfI zzMa6&eOzR+ERXGv^i`rfRLsY-aJkW56SYz{|2D~*%X%wvDvvL9JtA~nG!gcub!)Ba z&iMEB?3zY;jF`!eu9AEv+0s`|xbQTD%7Fx#@ty|&r(NJl;UBIkl39LOc2NemS`Id3^7UWC$*ujRb&-ebe z|6G-qpU3%0JEZS4p^%eL8;?pmXH-UxINz`UPc^>TbHT4^<0d_TN#oz89FibQ?)yq; z*W+5Jf1-W$)#az(A+-Y;-oKq29z@>tp_n(EaTke#^7ri~xiR7P&^iG@f|~a3ls@&3Ui8 z*!0$m&Ht>0_0hz{TWRRqZpVn@vg5FE* zh9|1He1f_BVWSeIk!3CZ8e!n@5}NQ8pDN!`>wEvJ4*Xr>s&ypfX>;Ti+R7;@ddl6p zs4Rrc^#C6wu;Q~iG0CwI!!q*zi^W0n#Wcy#1ibY2R_1YcC;{I()|_Oac-D6AsJx_XWO(#)Hc!1NcQM`P%Ut&YDkbx+h;BAg*Cr15 zFQ@k%g-v*|qvmp+R19l#Pl2-IOUnH=`0-4?eRQ1Cs~UY4^FyAl_eDA7{Z25K%1^9G z;>X<IeuCi;rQ-~oc{`C3tK&vwjy2oUa7XyygWZ3L}X zv+H(+aEQ$aj^DdgUGZ+nNX1Ibs^>sgVT^Cj81%Mo?{ZGQOx|Qfv&t=u&da>B_TdU@jBqnf45{6tCH6JpqNWc z?fEpg=p}HQ<&ceg%(83|wU3uA#&{*aeFvNRgQZsc53eU=0Xbs}8U={svzIc^#K0J`dy34V8(2sLo^MGzw}oyuO`U4 zr-{>WvniIG-N;&b@FhEW@Uzhy4qv4s15oaomLNi{o8q~C_VF~L^77PjXtwo~EyGBN zu&o!`56r9c+Kl?w!M%C;YRum<80odDc&oOO&A2DLe&3MC@Sf(^g7R{--SE)l_#Mk0 uo^Xk`Yg$FPw17%e{%8H%{uk63cCCQ{?Eu|o+|7V>y{J=;zZRdw-TF85N#Ilf literal 0 HcmV?d00001 diff --git a/app/src/main/res/layout/ui_crash.xml b/app/src/main/res/layout/ui_crash.xml new file mode 100644 index 0000000..e1ac204 --- /dev/null +++ b/app/src/main/res/layout/ui_crash.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/ui_package_manager.xml b/app/src/main/res/layout/ui_package_manager.xml index b00cba9..fcf2a3a 100644 --- a/app/src/main/res/layout/ui_package_manager.xml +++ b/app/src/main/res/layout/ui_package_manager.xml @@ -18,7 +18,7 @@ android:id="@+id/package_loading_progress_bar" style="@style/Widget.AppCompat.ProgressBar.Horizontal" android:layout_width="match_parent" - android:layout_height="1dp" + android:layout_height="4dp" android:visibility="gone" /> - Neo Term + NeoTerm 关于 复制 通用设置 @@ -71,6 +71,14 @@ 发现 你好,NeoTerm 轻触以选择你的最爱 - 下一步 + 安装 发现 + 设备: %s + 应用: %s + 堆栈信息 + 我们正努力让这个 Activity 永不见天日… + %d 个实例 + (永不休眠) + 取得休眠锁 + 释放休眠锁 \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ae60227..3da6445 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,16 +1,20 @@ - Neo Term + NeoTerm Copy Paste More Toggle switcher New session + %d session(s) + (Wake Locked) + Acquire Lock + Release Lock Discovery Hello, NeoTerm Tap to choose your favorites - Next + Install About Discovery @@ -28,7 +32,7 @@ Shell %s not found, please install it first. FullScreen mode changed, please restart NeoTerm. NeoTerm cannot get essential permissions, exiting. - Error + Oops! System Shell Retry APT source changed, you may get it updated by executing: apt update @@ -41,6 +45,11 @@ Install Font Install Color Scheme + Model: %s + App: %s + We are trying to deprecate this Activity… + Stack Trace + Installing We are about to install oh-my-zsh and switch your login shell to zsh diff --git a/build.gradle b/build.gradle index 5514675..99537d0 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.1.2-4' + ext.kotlin_version = '1.1.3' repositories { maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' } jcenter()