Release: 1.1.6
This commit is contained in:
@ -17,8 +17,8 @@ android {
|
|||||||
applicationId "io.neoterm"
|
applicationId "io.neoterm"
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 25
|
targetSdkVersion 25
|
||||||
versionCode 6
|
versionCode 8
|
||||||
versionName "1.1.4"
|
versionName "1.1.6"
|
||||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
resConfigs "zh"
|
resConfigs "zh"
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
|
@ -6,21 +6,21 @@
|
|||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".App"
|
android:name=".App"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:fullBackupContent="@xml/backup_config"
|
|
||||||
android:extractNativeLibs="true"
|
android:extractNativeLibs="true"
|
||||||
|
android:fullBackupContent="@xml/backup_config"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/AppTheme">
|
android:theme="@style/AppTheme">
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.NeoTermActivity"
|
android:name=".ui.term.NeoTermActivity"
|
||||||
android:configChanges="orientation|keyboardHidden"
|
android:configChanges="orientation|keyboardHidden"
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
android:screenOrientation="portrait"
|
|
||||||
android:theme="@style/AppTheme.NoActionBar"
|
android:theme="@style/AppTheme.NoActionBar"
|
||||||
android:windowSoftInputMode="adjustResize|stateHidden">
|
android:windowSoftInputMode="adjustResize|stateHidden">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
@ -29,9 +29,18 @@
|
|||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".ui.crash.CrashActivity"
|
||||||
|
android:exported="false"
|
||||||
|
android:label="@string/error"
|
||||||
|
android:theme="@style/AppTheme.NoActionBar" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.setup.SetupActivity"
|
android:name=".ui.setup.SetupActivity"
|
||||||
android:theme="@style/AppTheme.NoActionBar" />
|
android:theme="@style/AppTheme.NoActionBar" />
|
||||||
|
<activity
|
||||||
|
android:name=".ui.bonus.BonusActivity"
|
||||||
|
android:theme="@style/Theme.AppCompat.NoActionBar"
|
||||||
|
android:configChanges="orientation|keyboardHidden"/>
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.pm.PackageManagerActivity"
|
android:name=".ui.pm.PackageManagerActivity"
|
||||||
android:label="@string/package_settings"
|
android:label="@string/package_settings"
|
||||||
@ -39,8 +48,7 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name=".ui.customization.CustomizationActivity"
|
android:name=".ui.customization.CustomizationActivity"
|
||||||
android:label="@string/customization_settings"
|
android:label="@string/customization_settings"
|
||||||
android:theme="@style/Theme.AppCompat.NoActionBar"
|
android:theme="@style/Theme.AppCompat.NoActionBar" />
|
||||||
android:windowSoftInputMode="adjustResize|stateHidden" />
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.settings.SettingActivity"
|
android:name=".ui.settings.SettingActivity"
|
||||||
android:theme="@style/Theme.AppCompat" />
|
android:theme="@style/Theme.AppCompat" />
|
||||||
@ -53,9 +61,10 @@
|
|||||||
|
|
||||||
<activity-alias
|
<activity-alias
|
||||||
android:name=".NeoLotMainActivity"
|
android:name=".NeoLotMainActivity"
|
||||||
android:targetActivity="io.neoterm.ui.NeoTermActivity">
|
android:targetActivity="io.neoterm.ui.term.NeoTermActivity">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.IOT_LAUNCHER" />
|
<category android:name="android.intent.category.IOT_LAUNCHER" />
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
@ -65,7 +74,9 @@
|
|||||||
android:name=".services.NeoTermService"
|
android:name=".services.NeoTermService"
|
||||||
android:enabled="true" />
|
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>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
@ -3,6 +3,7 @@ package io.neoterm
|
|||||||
import android.app.Application
|
import android.app.Application
|
||||||
import io.neoterm.customize.color.ColorSchemeManager
|
import io.neoterm.customize.color.ColorSchemeManager
|
||||||
import io.neoterm.customize.font.FontManager
|
import io.neoterm.customize.font.FontManager
|
||||||
|
import io.neoterm.utils.CrashHandler
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author kiva
|
* @author kiva
|
||||||
@ -11,13 +12,15 @@ class App : Application() {
|
|||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
app = this
|
app = this
|
||||||
|
CrashHandler.init()
|
||||||
|
|
||||||
// ensure that we can access these any time
|
// ensure that we can access these any time
|
||||||
ColorSchemeManager.init(this)
|
ColorSchemeManager.init(this)
|
||||||
FontManager.init(this)
|
FontManager.init(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
var app: App? = null
|
private var app: App? = null
|
||||||
|
|
||||||
fun get(): App {
|
fun get(): App {
|
||||||
return app!!
|
return app!!
|
||||||
|
@ -8,7 +8,7 @@ import java.io.*
|
|||||||
*/
|
*/
|
||||||
class EksConfigParser {
|
class EksConfigParser {
|
||||||
companion object {
|
companion object {
|
||||||
const val PARSER_VERSION = 2
|
const val PARSER_VERSION = 4
|
||||||
}
|
}
|
||||||
|
|
||||||
private lateinit var source: BufferedReader
|
private lateinit var source: BufferedReader
|
||||||
|
@ -16,9 +16,11 @@ import java.io.File
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
object NeoPreference {
|
object NeoPreference {
|
||||||
|
const val KEY_HAPPY_EGG = "neoterm_fun_happy"
|
||||||
const val KEY_FONT_SIZE = "neoterm_general_font_size"
|
const val KEY_FONT_SIZE = "neoterm_general_font_size"
|
||||||
const val KEY_CURRENT_SESSION = "neoterm_service_current_session"
|
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_ONLY = "NeoTermOnly"
|
||||||
const val VALUE_NEOTERM_FIRST = "NeoTermFirst"
|
const val VALUE_NEOTERM_FIRST = "NeoTermFirst"
|
||||||
const val VALUE_SYSTEM_FIRST = "SystemFirst"
|
const val VALUE_SYSTEM_FIRST = "SystemFirst"
|
||||||
|
@ -1,22 +1,26 @@
|
|||||||
package io.neoterm.services
|
package io.neoterm.services
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.app.Notification
|
import android.app.Notification
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.app.Service
|
import android.app.Service
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.net.wifi.WifiManager
|
||||||
import android.os.Binder
|
import android.os.Binder
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
|
import android.os.PowerManager
|
||||||
import android.support.v4.content.WakefulBroadcastReceiver
|
import android.support.v4.content.WakefulBroadcastReceiver
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import io.neoterm.R
|
import io.neoterm.R
|
||||||
import io.neoterm.backend.EmulatorDebug
|
import io.neoterm.backend.EmulatorDebug
|
||||||
import io.neoterm.backend.TerminalSession
|
import io.neoterm.backend.TerminalSession
|
||||||
import io.neoterm.ui.NeoTermActivity
|
import io.neoterm.ui.term.NeoTermActivity
|
||||||
import io.neoterm.utils.TerminalUtils
|
import io.neoterm.utils.TerminalUtils
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author kiva
|
* @author kiva
|
||||||
*/
|
*/
|
||||||
@ -28,6 +32,8 @@ class NeoTermService : Service() {
|
|||||||
|
|
||||||
private val neoTermBinder = NeoTermBinder()
|
private val neoTermBinder = NeoTermBinder()
|
||||||
private val mTerminalSessions = ArrayList<TerminalSession>()
|
private val mTerminalSessions = ArrayList<TerminalSession>()
|
||||||
|
private var mWakeLock: PowerManager.WakeLock? = null
|
||||||
|
private var mWifiLock: WifiManager.WifiLock? = null
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
@ -40,12 +46,18 @@ class NeoTermService : Service() {
|
|||||||
|
|
||||||
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
|
||||||
val action = intent.action
|
val action = intent.action
|
||||||
if (ACTION_SERVICE_STOP == action) {
|
when (action) {
|
||||||
|
ACTION_SERVICE_STOP -> {
|
||||||
for (i in mTerminalSessions.indices)
|
for (i in mTerminalSessions.indices)
|
||||||
mTerminalSessions[i].finishIfRunning()
|
mTerminalSessions[i].finishIfRunning()
|
||||||
stopSelf()
|
stopSelf()
|
||||||
} else if (action != null) {
|
}
|
||||||
Log.e(EmulatorDebug.LOG_TAG, "Unknown NeoTermService action: '$action'")
|
|
||||||
|
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) {
|
if (flags and Service.START_FLAG_REDELIVERY == 0) {
|
||||||
@ -76,8 +88,10 @@ class NeoTermService : Service() {
|
|||||||
|
|
||||||
fun removeTermSession(sessionToRemove: TerminalSession): Int {
|
fun removeTermSession(sessionToRemove: TerminalSession): Int {
|
||||||
val indexOfRemoved = mTerminalSessions.indexOf(sessionToRemove)
|
val indexOfRemoved = mTerminalSessions.indexOf(sessionToRemove)
|
||||||
|
if (indexOfRemoved >= 0) {
|
||||||
mTerminalSessions.removeAt(indexOfRemoved)
|
mTerminalSessions.removeAt(indexOfRemoved)
|
||||||
updateNotification()
|
updateNotification()
|
||||||
|
}
|
||||||
return indexOfRemoved
|
return indexOfRemoved
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,7 +107,10 @@ class NeoTermService : Service() {
|
|||||||
val pendingIntent = PendingIntent.getActivity(this, 0, notifyIntent, 0)
|
val pendingIntent = PendingIntent.getActivity(this, 0, notifyIntent, 0)
|
||||||
|
|
||||||
val sessionCount = mTerminalSessions.size
|
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)
|
val builder = Notification.Builder(this)
|
||||||
builder.setContentTitle(getText(R.string.app_name))
|
builder.setContentTitle(getText(R.string.app_name))
|
||||||
@ -104,14 +121,54 @@ class NeoTermService : Service() {
|
|||||||
builder.setShowWhen(false)
|
builder.setShowWhen(false)
|
||||||
builder.setColor(0xFF000000.toInt())
|
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)
|
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))
|
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()
|
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 {
|
companion object {
|
||||||
val ACTION_SERVICE_STOP = "neoterm.action.service.stop"
|
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
|
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)
|
addPreferencesFromResource(R.xml.settings_main)
|
||||||
findPreference(getString(R.string.about)).setOnPreferenceClickListener {
|
findPreference(getString(R.string.about)).setOnPreferenceClickListener {
|
||||||
AlertDialog.Builder(this@SettingActivity)
|
AlertDialog.Builder(this@SettingActivity)
|
||||||
.setTitle(R.string.about)
|
.setTitle("为什么我们选择开发NeoTerm?")
|
||||||
.setMessage("Hello World!")
|
.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)
|
.setPositiveButton(android.R.string.yes, null)
|
||||||
.show()
|
.show()
|
||||||
return@setOnPreferenceClickListener true
|
return@setOnPreferenceClickListener true
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package io.neoterm.ui.setup
|
package io.neoterm.ui.setup
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.support.v4.content.ContextCompat
|
import android.support.v4.content.ContextCompat
|
||||||
@ -7,6 +8,7 @@ import android.support.v7.app.AlertDialog
|
|||||||
import android.support.v7.app.AppCompatActivity
|
import android.support.v7.app.AppCompatActivity
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.Button
|
import android.widget.Button
|
||||||
|
import android.widget.Toast
|
||||||
import com.igalata.bubblepicker.BubblePickerListener
|
import com.igalata.bubblepicker.BubblePickerListener
|
||||||
import com.igalata.bubblepicker.adapter.BubblePickerAdapter
|
import com.igalata.bubblepicker.adapter.BubblePickerAdapter
|
||||||
import com.igalata.bubblepicker.model.BubbleGradient
|
import com.igalata.bubblepicker.model.BubbleGradient
|
||||||
@ -29,13 +31,14 @@ import java.util.*
|
|||||||
class SetupActivity : AppCompatActivity() {
|
class SetupActivity : AppCompatActivity() {
|
||||||
companion object {
|
companion object {
|
||||||
private val DEFAULT_PACKAGES = arrayOf(
|
private val DEFAULT_PACKAGES = arrayOf(
|
||||||
"zsh", "aria2", "tmux", "lua", "netcat", "nodejs",
|
"zsh", "neoterm-core", "tmux", "nodejs",
|
||||||
"fish", "make", "gdb", "unrar", "clang", "vim", "emacs",
|
"fish", "make", "gdb", "clang", "vim", "emacs", "nano",
|
||||||
"curl", "git", "python", "perl", "zip", "p7zip")
|
"curl", "git", "python", "p7zip", "oh-my-zsh")
|
||||||
}
|
}
|
||||||
|
|
||||||
lateinit var picker: BubblePicker
|
lateinit var picker: BubblePicker
|
||||||
lateinit var nextButton: Button
|
lateinit var nextButton: Button
|
||||||
|
lateinit var toast: Toast
|
||||||
var aptUpdated = false
|
var aptUpdated = false
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
@ -125,6 +128,7 @@ class SetupActivity : AppCompatActivity() {
|
|||||||
.show("apt update")
|
.show("apt update")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("ShowToast")
|
||||||
private fun setupBubbles() {
|
private fun setupBubbles() {
|
||||||
val titles =
|
val titles =
|
||||||
if (intent.getBooleanExtra("setup", false))
|
if (intent.getBooleanExtra("setup", false))
|
||||||
@ -133,10 +137,11 @@ class SetupActivity : AppCompatActivity() {
|
|||||||
randomPackageList()
|
randomPackageList()
|
||||||
val colors = resources.obtainTypedArray(R.array.bubble_colors)
|
val colors = resources.obtainTypedArray(R.array.bubble_colors)
|
||||||
|
|
||||||
|
toast = Toast.makeText(this, null, Toast.LENGTH_LONG)
|
||||||
|
|
||||||
picker.bubbleSize = 25
|
picker.bubbleSize = 25
|
||||||
picker.adapter = object : BubblePickerAdapter {
|
picker.adapter = object : BubblePickerAdapter {
|
||||||
override val totalCount = titles.size
|
override val totalCount = titles.size
|
||||||
|
|
||||||
override fun getItem(position: Int): PickerItem {
|
override fun getItem(position: Int): PickerItem {
|
||||||
return PickerItem().apply {
|
return PickerItem().apply {
|
||||||
title = titles[position]
|
title = titles[position]
|
||||||
@ -148,9 +153,17 @@ class SetupActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
picker.listener = object : BubblePickerListener {
|
picker.listener = object : BubblePickerListener {
|
||||||
override fun onBubbleSelected(item: PickerItem) {
|
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) {
|
override fun onBubbleDeselected(item: PickerItem) {
|
||||||
|
toast.cancel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
colors.recycle()
|
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.Activity
|
||||||
import android.app.AlertDialog
|
import android.app.AlertDialog
|
||||||
import android.content.*
|
import android.content.*
|
||||||
@ -13,8 +14,10 @@ import android.support.v4.view.ViewCompat
|
|||||||
import android.support.v7.app.AppCompatActivity
|
import android.support.v7.app.AppCompatActivity
|
||||||
import android.support.v7.widget.Toolbar
|
import android.support.v7.widget.Toolbar
|
||||||
import android.view.*
|
import android.view.*
|
||||||
|
import android.view.animation.AccelerateDecelerateInterpolator
|
||||||
import android.view.inputmethod.InputMethodManager
|
import android.view.inputmethod.InputMethodManager
|
||||||
import android.widget.ImageButton
|
import android.widget.ImageButton
|
||||||
|
import android.widget.Toast
|
||||||
import de.mrapp.android.tabswitcher.*
|
import de.mrapp.android.tabswitcher.*
|
||||||
import io.neoterm.R
|
import io.neoterm.R
|
||||||
import io.neoterm.backend.TerminalSession
|
import io.neoterm.backend.TerminalSession
|
||||||
@ -25,12 +28,18 @@ import io.neoterm.customize.setup.BaseFileInstaller
|
|||||||
import io.neoterm.preference.NeoPermission
|
import io.neoterm.preference.NeoPermission
|
||||||
import io.neoterm.preference.NeoPreference
|
import io.neoterm.preference.NeoPreference
|
||||||
import io.neoterm.services.NeoTermService
|
import io.neoterm.services.NeoTermService
|
||||||
|
import io.neoterm.ui.bonus.BonusActivity
|
||||||
import io.neoterm.ui.pm.PackageManagerActivity
|
import io.neoterm.ui.pm.PackageManagerActivity
|
||||||
import io.neoterm.ui.settings.SettingActivity
|
import io.neoterm.ui.settings.SettingActivity
|
||||||
import io.neoterm.ui.setup.SetupActivity
|
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.utils.FullScreenHelper
|
||||||
import io.neoterm.view.eks.StatedControlButton
|
import io.neoterm.view.eks.StatedControlButton
|
||||||
import io.neoterm.view.tab.*
|
|
||||||
import org.greenrobot.eventbus.EventBus
|
import org.greenrobot.eventbus.EventBus
|
||||||
import org.greenrobot.eventbus.Subscribe
|
import org.greenrobot.eventbus.Subscribe
|
||||||
import org.greenrobot.eventbus.ThreadMode
|
import org.greenrobot.eventbus.ThreadMode
|
||||||
@ -70,7 +79,8 @@ class NeoTermActivity : AppCompatActivity(), ServiceConnection, SharedPreference
|
|||||||
setSupportActionBar(toolbar)
|
setSupportActionBar(toolbar)
|
||||||
|
|
||||||
fullScreenHelper = FullScreenHelper.injectActivity(this, fullscreen, peekRecreating())
|
fullScreenHelper = FullScreenHelper.injectActivity(this, fullscreen, peekRecreating())
|
||||||
fullScreenHelper.setKeyBoardListener({ isShow, _ ->
|
fullScreenHelper.setKeyBoardListener(object : FullScreenHelper.KeyBoardListener {
|
||||||
|
override fun onKeyboardChange(isShow: Boolean, keyboardHeight: Int) {
|
||||||
var tab: TermTab? = null
|
var tab: TermTab? = null
|
||||||
|
|
||||||
if (tabSwitcher.selectedTab is TermTab) {
|
if (tabSwitcher.selectedTab is TermTab) {
|
||||||
@ -81,20 +91,13 @@ class NeoTermActivity : AppCompatActivity(), ServiceConnection, SharedPreference
|
|||||||
|| NeoPreference.loadBoolean(R.string.key_ui_hide_toolbar, false)) {
|
|| NeoPreference.loadBoolean(R.string.key_ui_hide_toolbar, false)) {
|
||||||
tab?.toolbar?.visibility = if (isShow) View.GONE else View.VISIBLE
|
tab?.toolbar?.visibility = if (isShow) View.GONE else View.VISIBLE
|
||||||
}
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
fullScreenToggleButton = object : StatedControlButton("FS", fullscreen) {
|
fullScreenToggleButton = object : StatedControlButton("FS", fullscreen) {
|
||||||
override fun onClick(view: View?) {
|
override fun onClick(view: View) {
|
||||||
super.onClick(view)
|
super.onClick(view)
|
||||||
if (tabSwitcher.selectedTab is TermTab) {
|
setFullScreenMode(super.toggleButton?.isChecked ?: false)
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,11 +119,11 @@ class NeoTermActivity : AppCompatActivity(), ServiceConnection, SharedPreference
|
|||||||
if (!tabSwitcher.isSwitcherShown) {
|
if (!tabSwitcher.isSwitcherShown) {
|
||||||
if (imm.isActive && tabSwitcher.selectedTab is TermTab) {
|
if (imm.isActive && tabSwitcher.selectedTab is TermTab) {
|
||||||
val tab = tabSwitcher.selectedTab as TermTab
|
val tab = tabSwitcher.selectedTab as TermTab
|
||||||
tab.hideIme()
|
tab.requireHideIme()
|
||||||
}
|
}
|
||||||
tabSwitcher.showSwitcher()
|
toggleSwitcher(showSwitcher = true)
|
||||||
} else {
|
} else {
|
||||||
tabSwitcher.hideSwitcher()
|
toggleSwitcher(showSwitcher = false)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return true
|
return true
|
||||||
@ -149,7 +152,7 @@ class NeoTermActivity : AppCompatActivity(), ServiceConnection, SharedPreference
|
|||||||
}
|
}
|
||||||
R.id.menu_item_new_session -> {
|
R.id.menu_item_new_session -> {
|
||||||
if (!tabSwitcher.isSwitcherShown) {
|
if (!tabSwitcher.isSwitcherShown) {
|
||||||
tabSwitcher.showSwitcher()
|
toggleSwitcher(showSwitcher = true)
|
||||||
}
|
}
|
||||||
val index = tabSwitcher.count
|
val index = tabSwitcher.count
|
||||||
addNewSession("NeoTerm #" + index, systemShell, createRevealAnimation())
|
addNewSession("NeoTerm #" + index, systemShell, createRevealAnimation())
|
||||||
@ -232,7 +235,7 @@ class NeoTermActivity : AppCompatActivity(), ServiceConnection, SharedPreference
|
|||||||
when (keyCode) {
|
when (keyCode) {
|
||||||
KeyEvent.KEYCODE_BACK -> {
|
KeyEvent.KEYCODE_BACK -> {
|
||||||
if (event?.action == KeyEvent.ACTION_DOWN && tabSwitcher.isSwitcherShown && tabSwitcher.count > 0) {
|
if (event?.action == KeyEvent.ACTION_DOWN && tabSwitcher.isSwitcherShown && tabSwitcher.count > 0) {
|
||||||
tabSwitcher.hideSwitcher()
|
toggleSwitcher(showSwitcher = false)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -311,7 +314,7 @@ class NeoTermActivity : AppCompatActivity(), ServiceConnection, SharedPreference
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun enterSystemShell() {
|
private fun enterSystemShell() {
|
||||||
tabSwitcher.showSwitcher()
|
toggleSwitcher(showSwitcher = true)
|
||||||
addNewSession("NeoTerm #0", systemShell, createRevealAnimation())
|
addNewSession("NeoTerm #0", systemShell, createRevealAnimation())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -324,7 +327,7 @@ class NeoTermActivity : AppCompatActivity(), ServiceConnection, SharedPreference
|
|||||||
}
|
}
|
||||||
switchToSession(getStoredCurrentSessionOrLast())
|
switchToSession(getStoredCurrentSessionOrLast())
|
||||||
} else {
|
} else {
|
||||||
tabSwitcher.showSwitcher()
|
toggleSwitcher(showSwitcher = true)
|
||||||
addNewSession("NeoTerm #0", systemShell, createRevealAnimation())
|
addNewSession("NeoTerm #0", systemShell, createRevealAnimation())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -353,12 +356,13 @@ class NeoTermActivity : AppCompatActivity(), ServiceConnection, SharedPreference
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun setFullScreenMode(fullScreen: Boolean) {
|
private fun setFullScreenMode(fullScreen: Boolean) {
|
||||||
fullScreenHelper.setFullScreen(fullScreen)
|
fullScreenHelper.fullScreen = fullScreen
|
||||||
if (fullScreen) {
|
if (tabSwitcher.selectedTab is TermTab) {
|
||||||
tabSwitcher.systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN
|
val tab = tabSwitcher.selectedTab as TermTab
|
||||||
} else {
|
tab.requireHideIme()
|
||||||
tabSwitcher.systemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE
|
|
||||||
}
|
}
|
||||||
|
NeoPreference.store(R.string.key_ui_fullscreen, fullScreen)
|
||||||
|
this@NeoTermActivity.recreate()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addNewSession(session: TerminalSession?) {
|
private fun addNewSession(session: TerminalSession?) {
|
||||||
@ -366,6 +370,14 @@ class NeoTermActivity : AppCompatActivity(), ServiceConnection, SharedPreference
|
|||||||
return
|
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
|
val tab = createTab(session.mSessionName) as TermTab
|
||||||
tab.sessionCallback = session.sessionChangedCallback as TermSessionChangedCallback
|
tab.sessionCallback = session.sessionChangedCallback as TermSessionChangedCallback
|
||||||
tab.viewClient = TermViewClient(this)
|
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")
|
@Suppress("unused")
|
||||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||||
fun onTabCloseEvent(tabCloseEvent: TabCloseEvent) {
|
fun onTabCloseEvent(tabCloseEvent: TabCloseEvent) {
|
||||||
val tab = tabCloseEvent.termTab
|
val tab = tabCloseEvent.termTab
|
||||||
tabSwitcher.showSwitcher()
|
toggleSwitcher(showSwitcher = true)
|
||||||
tabSwitcher.removeTab(tab)
|
tabSwitcher.removeTab(tab)
|
||||||
|
|
||||||
if (tabSwitcher.count > 1) {
|
if (tabSwitcher.count > 1) {
|
||||||
@ -510,4 +549,11 @@ class NeoTermActivity : AppCompatActivity(), ServiceConnection, SharedPreference
|
|||||||
switchToSession(tabSwitcher.getTab(index))
|
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.ClipData
|
||||||
import android.content.ClipboardManager
|
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.content.Context
|
||||||
import android.graphics.Color
|
|
||||||
import android.support.v7.widget.Toolbar
|
import android.support.v7.widget.Toolbar
|
||||||
import android.view.inputmethod.InputMethodManager
|
import android.view.inputmethod.InputMethodManager
|
||||||
import de.mrapp.android.tabswitcher.Tab
|
import de.mrapp.android.tabswitcher.Tab
|
||||||
import io.neoterm.R
|
import io.neoterm.R
|
||||||
import io.neoterm.backend.TerminalSession
|
import io.neoterm.backend.TerminalSession
|
||||||
import io.neoterm.customize.color.ColorSchemeManager
|
import io.neoterm.customize.color.ColorSchemeManager
|
||||||
import io.neoterm.customize.color.NeoColorScheme
|
|
||||||
import io.neoterm.preference.NeoPreference
|
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
|
import org.greenrobot.eventbus.EventBus
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -38,6 +38,7 @@ class TermTab(title: CharSequence) : Tab(title) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun updateTitle(title: String) {
|
fun updateTitle(title: String) {
|
||||||
|
if (title.isNotEmpty()) {
|
||||||
this.title = title
|
this.title = title
|
||||||
toolbar?.title = title
|
toolbar?.title = title
|
||||||
if (NeoPreference.loadBoolean(R.string.key_ui_suggestions, true)) {
|
if (NeoPreference.loadBoolean(R.string.key_ui_suggestions, true)) {
|
||||||
@ -46,17 +47,18 @@ class TermTab(title: CharSequence) : Tab(title) {
|
|||||||
viewClient?.removeSuggestions()
|
viewClient?.removeSuggestions()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun onSessionFinished() {
|
fun onSessionFinished() {
|
||||||
viewClient?.sessionFinished = true
|
viewClient?.sessionFinished = true
|
||||||
}
|
}
|
||||||
|
|
||||||
fun requiredCloseTab() {
|
fun requireCloseTab() {
|
||||||
hideIme()
|
requireHideIme()
|
||||||
EventBus.getDefault().post(TabCloseEvent(this))
|
EventBus.getDefault().post(TabCloseEvent(this))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun hideIme() {
|
fun requireHideIme() {
|
||||||
val terminalView = viewClient?.termView
|
val terminalView = viewClient?.termView
|
||||||
if (terminalView != null) {
|
if (terminalView != null) {
|
||||||
val imm = terminalView.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
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.content.Context
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
@ -11,7 +11,7 @@ import de.mrapp.android.tabswitcher.TabSwitcherDecorator
|
|||||||
import io.neoterm.R
|
import io.neoterm.R
|
||||||
import io.neoterm.customize.color.ColorSchemeManager
|
import io.neoterm.customize.color.ColorSchemeManager
|
||||||
import io.neoterm.preference.NeoPreference
|
import io.neoterm.preference.NeoPreference
|
||||||
import io.neoterm.ui.NeoTermActivity
|
import io.neoterm.ui.term.NeoTermActivity
|
||||||
import io.neoterm.utils.TerminalUtils
|
import io.neoterm.utils.TerminalUtils
|
||||||
import io.neoterm.view.ExtraKeysView
|
import io.neoterm.view.ExtraKeysView
|
||||||
import io.neoterm.view.TerminalView
|
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();
|
if (action == MotionEvent.ACTION_DOWN) showContextMenu();
|
||||||
return true;
|
return true;
|
||||||
} else if (ev.isButtonPressed(MotionEvent.BUTTON_TERTIARY)) {
|
} else if (ev.isButtonPressed(MotionEvent.BUTTON_TERTIARY)) {
|
||||||
ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
|
pasteFromClipboard();
|
||||||
ClipData clipData = clipboard.getPrimaryClip();
|
|
||||||
if (clipData != null) {
|
|
||||||
CharSequence paste = clipData.getItemAt(0).coerceToText(getContext());
|
|
||||||
if (!TextUtils.isEmpty(paste)) mEmulator.paste(paste.toString());
|
|
||||||
}
|
|
||||||
} else if (mEmulator.isMouseTrackingActive()) { // BUTTON_PRIMARY.
|
} else if (mEmulator.isMouseTrackingActive()) { // BUTTON_PRIMARY.
|
||||||
switch (ev.getAction()) {
|
switch (ev.getAction()) {
|
||||||
case MotionEvent.ACTION_DOWN:
|
case MotionEvent.ACTION_DOWN:
|
||||||
@ -589,6 +584,15 @@ public final class TerminalView extends View {
|
|||||||
return true;
|
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
|
@Override
|
||||||
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
|
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
|
||||||
if (LOG_KEY_EVENTS)
|
if (LOG_KEY_EVENTS)
|
||||||
@ -892,12 +896,7 @@ public final class TerminalView extends View {
|
|||||||
mTermSession.clipboardText(selectedText);
|
mTermSession.clipboardText(selectedText);
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
|
pasteFromClipboard();
|
||||||
ClipData clipData = clipboard.getPrimaryClip();
|
|
||||||
if (clipData != null) {
|
|
||||||
CharSequence paste = clipData.getItemAt(0).coerceToText(getContext());
|
|
||||||
if (!TextUtils.isEmpty(paste)) mEmulator.paste(paste.toString());
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
showContextMenu();
|
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"
|
android:id="@+id/package_loading_progress_bar"
|
||||||
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
|
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="1dp"
|
android:layout_height="4dp"
|
||||||
android:visibility="gone" />
|
android:visibility="gone" />
|
||||||
|
|
||||||
<com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView
|
<com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView
|
||||||
|
@ -71,6 +71,14 @@
|
|||||||
<string name="setup_hello">发现</string>
|
<string name="setup_hello">发现</string>
|
||||||
<string name="setup_info">你好,NeoTerm</string>
|
<string name="setup_info">你好,NeoTerm</string>
|
||||||
<string name="setup_info2">轻触以选择你的最爱</string>
|
<string name="setup_info2">轻触以选择你的最爱</string>
|
||||||
<string name="setup_next">下一步</string>
|
<string name="setup_next">安装</string>
|
||||||
<string name="discovery">发现</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>
|
</resources>
|
@ -6,11 +6,15 @@
|
|||||||
|
|
||||||
<string name="toggle_tab_switcher_menu_item">Toggle switcher</string>
|
<string name="toggle_tab_switcher_menu_item">Toggle switcher</string>
|
||||||
<string name="new_session">New session</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_hello">Discovery</string>
|
||||||
<string name="setup_info">Hello, NeoTerm</string>
|
<string name="setup_info">Hello, NeoTerm</string>
|
||||||
<string name="setup_info2">Tap to choose your favorites</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="about">About</string>
|
||||||
<string name="discovery">Discovery</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="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="fullscreen_mode_changed">FullScreen mode changed, please restart NeoTerm.</string>
|
||||||
<string name="permission_denied">NeoTerm cannot get essential permissions, exiting.</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="use_system_shell">System Shell</string>
|
||||||
<string name="retry">Retry</string>
|
<string name="retry">Retry</string>
|
||||||
<string name="source_changed">APT source changed, you may get it updated by executing: apt update</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_font">Install Font</string>
|
||||||
<string name="install_color">Install Color Scheme</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_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>
|
<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.
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
ext.kotlin_version = '1.1.2-4'
|
ext.kotlin_version = '1.1.3'
|
||||||
repositories {
|
repositories {
|
||||||
maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }
|
maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }
|
||||||
jcenter()
|
jcenter()
|
||||||
|
Reference in New Issue
Block a user