Release: 1.1.6
This commit is contained in:
parent
c9de9541bf
commit
d7ea4352ac
@ -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 {
|
||||
|
@ -6,21 +6,21 @@
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
|
||||
<application
|
||||
android:name=".App"
|
||||
android:allowBackup="true"
|
||||
android:fullBackupContent="@xml/backup_config"
|
||||
android:extractNativeLibs="true"
|
||||
android:fullBackupContent="@xml/backup_config"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme">
|
||||
<activity
|
||||
android:name=".ui.NeoTermActivity"
|
||||
android:name=".ui.term.NeoTermActivity"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:launchMode="singleTask"
|
||||
android:screenOrientation="portrait"
|
||||
android:theme="@style/AppTheme.NoActionBar"
|
||||
android:windowSoftInputMode="adjustResize|stateHidden">
|
||||
<intent-filter>
|
||||
@ -29,9 +29,18 @@
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".ui.crash.CrashActivity"
|
||||
android:exported="false"
|
||||
android:label="@string/error"
|
||||
android:theme="@style/AppTheme.NoActionBar" />
|
||||
<activity
|
||||
android:name=".ui.setup.SetupActivity"
|
||||
android:theme="@style/AppTheme.NoActionBar" />
|
||||
<activity
|
||||
android:name=".ui.bonus.BonusActivity"
|
||||
android:theme="@style/Theme.AppCompat.NoActionBar"
|
||||
android:configChanges="orientation|keyboardHidden"/>
|
||||
<activity
|
||||
android:name=".ui.pm.PackageManagerActivity"
|
||||
android:label="@string/package_settings"
|
||||
@ -39,8 +48,7 @@
|
||||
<activity
|
||||
android:name=".ui.customization.CustomizationActivity"
|
||||
android:label="@string/customization_settings"
|
||||
android:theme="@style/Theme.AppCompat.NoActionBar"
|
||||
android:windowSoftInputMode="adjustResize|stateHidden" />
|
||||
android:theme="@style/Theme.AppCompat.NoActionBar" />
|
||||
<activity
|
||||
android:name=".ui.settings.SettingActivity"
|
||||
android:theme="@style/Theme.AppCompat" />
|
||||
@ -53,11 +61,12 @@
|
||||
|
||||
<activity-alias
|
||||
android:name=".NeoLotMainActivity"
|
||||
android:targetActivity="io.neoterm.ui.NeoTermActivity">
|
||||
android:targetActivity="io.neoterm.ui.term.NeoTermActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.IOT_LAUNCHER"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.IOT_LAUNCHER" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity-alias>
|
||||
|
||||
@ -65,7 +74,9 @@
|
||||
android:name=".services.NeoTermService"
|
||||
android:enabled="true" />
|
||||
|
||||
<meta-data android:name="com.sec.android.support.multiwindow" android:value="true" />
|
||||
<meta-data
|
||||
android:name="com.sec.android.support.multiwindow"
|
||||
android:value="true" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
@ -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!!
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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<TerminalSession>()
|
||||
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
|
||||
}
|
||||
}
|
||||
|
193
app/src/main/java/io/neoterm/ui/bonus/BonusActivity.java
Normal file
193
app/src/main/java/io/neoterm/ui/bonus/BonusActivity.java
Normal file
@ -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();
|
||||
}
|
||||
}
|
46
app/src/main/java/io/neoterm/ui/crash/CrashActivity.kt
Normal file
46
app/src/main/java/io/neoterm/ui/crash/CrashActivity.kt
Normal file
@ -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})"
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
package io.neoterm.ui.floating
|
||||
|
||||
/**
|
||||
* @author kiva
|
||||
*/
|
||||
class FloatingNeoTerm
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package io.neoterm.view.tab
|
||||
package io.neoterm.ui.term.tab
|
||||
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
@ -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()
|
||||
}
|
||||
}
|
@ -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
|
204
app/src/main/java/io/neoterm/ui/term/tab/TermViewClient.kt
Normal file
204
app/src/main/java/io/neoterm/ui/term/tab/TermViewClient.kt
Normal file
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
@ -0,0 +1,6 @@
|
||||
package io.neoterm.ui.term.tab.event
|
||||
|
||||
/**
|
||||
* @author kiva
|
||||
*/
|
||||
class ToggleFullScreenEvent()
|
26
app/src/main/java/io/neoterm/utils/CrashHandler.kt
Normal file
26
app/src/main/java/io/neoterm/utils/CrashHandler.kt
Normal file
@ -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())
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
120
app/src/main/java/io/neoterm/utils/FullScreenHelper.kt
Normal file
120
app/src/main/java/io/neoterm/utils/FullScreenHelper.kt
Normal file
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
52
app/src/main/java/io/neoterm/utils/Shaker.kt
Normal file
52
app/src/main/java/io/neoterm/utils/Shaker.kt
Normal file
@ -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()
|
||||
}
|
||||
}
|
@ -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<ExtraButton> builtinExtraKeys;
|
||||
private List<ExtraButton> 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<ExtraButton> 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();
|
||||
}
|
||||
}
|
223
app/src/main/java/io/neoterm/view/ExtraKeysView.kt
Executable file
223
app/src/main/java/io/neoterm/view/ExtraKeysView.kt
Executable file
@ -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<ExtraButton>? = null
|
||||
private var userDefinedExtraKeys: MutableList<ExtraButton>? = 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<ExtraButton>?, button: ExtraButton) {
|
||||
if (buttons != null && !buttons.contains(button)) {
|
||||
buttons.add(button)
|
||||
}
|
||||
}
|
||||
|
||||
fun clearUserDefinedButton() {
|
||||
userDefinedExtraKeys!!.clear()
|
||||
}
|
||||
|
||||
fun loadDefaultUserDefinedExtraKeys() {
|
||||
userDefinedExtraKeys = ArrayList<ExtraButton>(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<ExtraButton>(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()
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
95
app/src/main/java/io/neoterm/view/GestureAndScaleRecognizer.kt
Executable file
95
app/src/main/java/io/neoterm/view/GestureAndScaleRecognizer.kt
Executable file
@ -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
|
||||
|
||||
}
|
@ -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();
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
19
app/src/main/java/io/neoterm/view/eks/ControlButton.kt
Normal file
19
app/src/main/java/io/neoterm/view/eks/ControlButton.kt
Normal file
@ -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!!)
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
62
app/src/main/java/io/neoterm/view/eks/ExtraButton.kt
Normal file
62
app/src/main/java/io/neoterm/view/eks/ExtraButton.kt
Normal file
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
33
app/src/main/java/io/neoterm/view/eks/StatedControlButton.kt
Normal file
33
app/src/main/java/io/neoterm/view/eks/StatedControlButton.kt
Normal file
@ -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
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
25
app/src/main/java/io/neoterm/view/eks/TextButton.kt
Normal file
25
app/src/main/java/io/neoterm/view/eks/TextButton.kt
Normal file
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
package io.neoterm.view.tab
|
||||
|
||||
/**
|
||||
* @author kiva
|
||||
*/
|
||||
|
||||
class TabCloseEvent(var termTab: TermTab)
|
@ -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()
|
||||
}
|
||||
|
||||
}
|
BIN
app/src/main/res/drawable/plat_logo.png
Normal file
BIN
app/src/main/res/drawable/plat_logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.2 KiB |
72
app/src/main/res/layout/ui_crash.xml
Normal file
72
app/src/main/res/layout/ui_crash.xml
Normal file
@ -0,0 +1,72 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/terminal_background"
|
||||
android:orientation="vertical">
|
||||
|
||||
<android.support.v7.widget.Toolbar
|
||||
android:id="@+id/crash_toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="@color/colorPrimaryDark"
|
||||
android:theme="@style/ThemeOverlay.AppCompat.Dark"
|
||||
app:popupTheme="@style/ThemeOverlay.AppCompat.Dark" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="@dimen/text_margin"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
style="@style/TextAppearance.AppCompat.Medium"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/crash_tips"
|
||||
android:textColor="@color/colorAccent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/crash_model"
|
||||
style="@style/TextAppearance.AppCompat.Medium"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/textColorSecondary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/crash_app_version"
|
||||
style="@style/TextAppearance.AppCompat.Medium"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/textColorSecondary" />
|
||||
|
||||
<TextView
|
||||
style="@style/TextAppearance.AppCompat.Medium"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/text_margin"
|
||||
android:gravity="start|top"
|
||||
android:text="@string/crash_stack_trace"
|
||||
android:textColor="@color/textColor" />
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/crash_details"
|
||||
style="@style/TextAppearance.AppCompat.Medium"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="@dimen/text_margin"
|
||||
android:layout_marginStart="@dimen/text_margin"
|
||||
android:gravity="start|top"
|
||||
android:textColor="@color/textColor" />
|
||||
</ScrollView>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
</LinearLayout>
|
@ -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" />
|
||||
|
||||
<com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Neo Term</string>
|
||||
<string name="app_name">NeoTerm</string>
|
||||
<string name="about">关于</string>
|
||||
<string name="copy_text">复制</string>
|
||||
<string name="general_settings">通用设置</string>
|
||||
@ -71,6 +71,14 @@
|
||||
<string name="setup_hello">发现</string>
|
||||
<string name="setup_info">你好,NeoTerm</string>
|
||||
<string name="setup_info2">轻触以选择你的最爱</string>
|
||||
<string name="setup_next">下一步</string>
|
||||
<string name="setup_next">安装</string>
|
||||
<string name="discovery">发现</string>
|
||||
<string name="crash_model">设备: %s</string>
|
||||
<string name="crash_app">应用: %s</string>
|
||||
<string name="crash_stack_trace">堆栈信息</string>
|
||||
<string name="crash_tips">我们正努力让这个 Activity 永不见天日…</string>
|
||||
<string name="service_status_text">%d 个实例</string>
|
||||
<string name="service_lock_acquired"> (永不休眠)</string>
|
||||
<string name="service_acquire_lock">取得休眠锁</string>
|
||||
<string name="service_release_lock">释放休眠锁</string>
|
||||
</resources>
|
@ -1,16 +1,20 @@
|
||||
<resources>
|
||||
<string name="app_name">Neo Term</string>
|
||||
<string name="app_name">NeoTerm</string>
|
||||
<string name="copy_text">Copy</string>
|
||||
<string name="paste_text">Paste</string>
|
||||
<string name="text_selection_more">More</string>
|
||||
|
||||
<string name="toggle_tab_switcher_menu_item">Toggle switcher</string>
|
||||
<string name="new_session">New session</string>
|
||||
<string name="service_status_text">%d session(s)</string>
|
||||
<string name="service_lock_acquired"> (Wake Locked)</string>
|
||||
<string name="service_acquire_lock">Acquire Lock</string>
|
||||
<string name="service_release_lock">Release Lock</string>
|
||||
|
||||
<string name="setup_hello">Discovery</string>
|
||||
<string name="setup_info">Hello, NeoTerm</string>
|
||||
<string name="setup_info2">Tap to choose your favorites</string>
|
||||
<string name="setup_next">Next</string>
|
||||
<string name="setup_next">Install</string>
|
||||
|
||||
<string name="about">About</string>
|
||||
<string name="discovery">Discovery</string>
|
||||
@ -28,7 +32,7 @@
|
||||
<string name="shell_not_found">Shell %s not found, please install it first.</string>
|
||||
<string name="fullscreen_mode_changed">FullScreen mode changed, please restart NeoTerm.</string>
|
||||
<string name="permission_denied">NeoTerm cannot get essential permissions, exiting.</string>
|
||||
<string name="error">Error</string>
|
||||
<string name="error">Oops!</string>
|
||||
<string name="use_system_shell">System Shell</string>
|
||||
<string name="retry">Retry</string>
|
||||
<string name="source_changed">APT source changed, you may get it updated by executing: apt update</string>
|
||||
@ -41,6 +45,11 @@
|
||||
<string name="install_font">Install Font</string>
|
||||
<string name="install_color">Install Color Scheme</string>
|
||||
|
||||
<string name="crash_model">Model: %s</string>
|
||||
<string name="crash_app">App: %s</string>
|
||||
<string name="crash_tips">We are trying to deprecate this Activity…</string>
|
||||
<string name="crash_stack_trace">Stack Trace</string>
|
||||
|
||||
<string name="installer_message">Installing</string>
|
||||
<string name="installer_install_zsh_required">We are about to install oh-my-zsh and switch your login shell to zsh</string>
|
||||
|
||||
|
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user