AutoComplete: Popup version.

This commit is contained in:
zt515 2017-07-18 00:08:08 +08:00
parent f8f6fce0a5
commit c9d59c643f
24 changed files with 416 additions and 18 deletions

View File

@ -0,0 +1,37 @@
package io.neoterm.customize.completion
/**
* @author kiva
*/
object AutoCompleteManager {
private val completeCandidates = mutableListOf<CompleteCandidate>()
init {
val programs = arrayOf("ls", "clean", "exit", "apt", "neoterm-normalize-binary", "less", "ln", "lsof")
val desc = arrayOf("List files and directories",
"Clear screen",
"Exit current shell",
"Installing, Updating, Upgrading packages",
"Fix program error caused by linux shebang",
"View files",
"Create symlinks or hardlinks",
"List opened files")
for (i in programs.indices) {
val candidate = CompleteCandidate(programs[i])
candidate.description = desc[i]
completeCandidates.add(candidate)
}
}
fun filter(text: String) : List<CompleteCandidate> {
val result = mutableListOf<CompleteCandidate>()
if (text.isNotEmpty()) {
completeCandidates.forEach {
if (it.completeString.startsWith(text, ignoreCase = true)) {
result.add(it)
}
}
}
return result
}
}

View File

@ -0,0 +1,9 @@
package io.neoterm.customize.completion
/**
* @author kiva
*/
class CompleteCandidate(var completeString: String) {
var displayName: String = completeString
var description: String? = null
}

View File

@ -25,7 +25,7 @@ object NeoPreference {
const val VALUE_NEOTERM_FIRST = "NeoTermFirst" const val VALUE_NEOTERM_FIRST = "NeoTermFirst"
const val VALUE_SYSTEM_FIRST = "SystemFirst" const val VALUE_SYSTEM_FIRST = "SystemFirst"
var preference: SharedPreferences? = null private var preference: SharedPreferences? = null
fun init(context: Context) { fun init(context: Context) {
preference = PreferenceManager.getDefaultSharedPreferences(context) preference = PreferenceManager.getDefaultSharedPreferences(context)

View File

@ -56,8 +56,6 @@ class NeoTermService : Service() {
ACTION_ACQUIRE_LOCK -> acquireLock() ACTION_ACQUIRE_LOCK -> acquireLock()
ACTION_RELEASE_LOCK -> releaseLock() 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) {
@ -124,14 +122,15 @@ class NeoTermService : Service() {
builder.setPriority(if (lockAcquired) Notification.PRIORITY_HIGH else Notification.PRIORITY_MIN) 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, getString(R.string.exit), PendingIntent.getService(this, 0, exitIntent, 0))
val newWakeAction = if (lockAcquired) ACTION_RELEASE_LOCK else ACTION_ACQUIRE_LOCK val newWakeAction = if (lockAcquired) ACTION_RELEASE_LOCK else ACTION_ACQUIRE_LOCK
val toggleWakeLockIntent = Intent(this, NeoTermService::class.java).setAction(newWakeAction) val toggleWakeLockIntent = Intent(this, NeoTermService::class.java).setAction(newWakeAction)
val actionTitle = getString(if (lockAcquired) val actionTitle = getString(
R.string.service_release_lock if (lockAcquired)
else R.string.service_release_lock
R.string.service_acquire_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 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)) builder.addAction(actionIcon, actionTitle, PendingIntent.getService(this, 0, toggleWakeLockIntent, 0))

View File

@ -21,7 +21,7 @@ class PackageAdapter(context: Context, comparator: Comparator<PackageModel>, pri
} }
override fun onCreateViewHolder(inflater: LayoutInflater, parent: ViewGroup, viewType: Int): SortedListAdapter.ViewHolder<out PackageModel> { override fun onCreateViewHolder(inflater: LayoutInflater, parent: ViewGroup, viewType: Int): SortedListAdapter.ViewHolder<out PackageModel> {
val rootView = inflater.inflate(R.layout.package_item, parent, false) val rootView = inflater.inflate(R.layout.item_package, parent, false)
return PackageViewHolder(rootView, listener) return PackageViewHolder(rootView, listener)
} }
} }

View File

@ -6,6 +6,7 @@ import android.app.Activity
import android.app.AlertDialog import android.app.AlertDialog
import android.content.* import android.content.*
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.graphics.Rect
import android.os.Bundle import android.os.Bundle
import android.os.IBinder import android.os.IBinder
import android.preference.PreferenceManager import android.preference.PreferenceManager
@ -77,6 +78,7 @@ class NeoTermActivity : AppCompatActivity(), ServiceConnection, SharedPreference
} }
setContentView(R.layout.ui_main) setContentView(R.layout.ui_main)
toolbar = findViewById(R.id.terminal_toolbar) as Toolbar toolbar = findViewById(R.id.terminal_toolbar) as Toolbar
setSupportActionBar(toolbar) setSupportActionBar(toolbar)
@ -361,6 +363,7 @@ class NeoTermActivity : AppCompatActivity(), ServiceConnection, SharedPreference
if (tabSwitcher.selectedTab is TermTab) { if (tabSwitcher.selectedTab is TermTab) {
val tab = tabSwitcher.selectedTab as TermTab val tab = tabSwitcher.selectedTab as TermTab
tab.requireHideIme() tab.requireHideIme()
tab.onFullScreenModeChanged(fullScreen)
} }
NeoPreference.store(R.string.key_ui_fullscreen, fullScreen) NeoPreference.store(R.string.key_ui_fullscreen, fullScreen)
this@NeoTermActivity.recreate() this@NeoTermActivity.recreate()

View File

@ -0,0 +1,93 @@
package io.neoterm.ui.term.tab
import android.util.Log
import android.view.KeyEvent
import io.neoterm.customize.completion.AutoCompleteManager
import io.neoterm.customize.completion.CompleteCandidate
import io.neoterm.view.AutoCompletePopupWindow
import io.neoterm.view.OnAutoCompleteListener
import io.neoterm.view.TerminalView
import java.util.*
/**
* @author kiva
*/
class TermCompleteListener(var terminalView: TerminalView?) : OnAutoCompleteListener {
private val inputStack = Stack<Char>()
private val popupWindow = AutoCompletePopupWindow(terminalView!!.context)
override fun onKeyCode(keyCode: Int, keyMod: Int) {
when (keyCode) {
KeyEvent.KEYCODE_DEL -> {
Log.e("NeoTerm-AC", "BackSpace")
popChar()
activateAutoCompletion()
}
KeyEvent.KEYCODE_ENTER -> {
Log.e("NeoTerm-AC", "Clear Chars")
clearChars()
popupWindow.dismiss()
}
}
}
override fun onAutoComplete(newText: String?) {
if (newText == null || newText.isEmpty()) {
return
}
newText.toCharArray().forEach { pushChar(it) }
activateAutoCompletion()
}
override fun onCleanUp() {
popupWindow.cleanup()
terminalView = null
}
private fun activateAutoCompletion() {
val text = getCurrentEditingText()
if (text.isEmpty()) {
return
}
val candidates = AutoCompleteManager.filter(text)
Log.e("NeoTerm-AC", "Completing for $text")
candidates.forEach {
Log.e("NeoTerm-AC", " Candidate: ${it.completeString}")
}
if (candidates.isNotEmpty()) {
showAutoCompleteCandidates(candidates)
}
}
private fun showAutoCompleteCandidates(candidates: List<CompleteCandidate>) {
popupWindow.candidates = candidates
popupWindow.show(terminalView!!)
}
private fun getCurrentEditingText(): String {
val builder = StringBuilder()
val size = inputStack.size
(0..(size - 1))
.map { inputStack[it] }
.takeWhile { !(it == 0.toChar() || it == ' ') }
.forEach { builder.append(it) }
return builder.toString()
}
private fun clearChars() {
inputStack.clear()
}
private fun popChar() {
if (inputStack.isNotEmpty()) {
inputStack.pop()
}
}
private fun pushChar(char: Char) {
inputStack.push(char)
}
}

View File

@ -11,6 +11,7 @@ import io.neoterm.preference.NeoPreference
import io.neoterm.ui.term.tab.event.TabCloseEvent import io.neoterm.ui.term.tab.event.TabCloseEvent
import io.neoterm.ui.term.tab.event.TitleChangedEvent import io.neoterm.ui.term.tab.event.TitleChangedEvent
import io.neoterm.ui.term.tab.event.ToggleFullScreenEvent import io.neoterm.ui.term.tab.event.ToggleFullScreenEvent
import io.neoterm.view.OnAutoCompleteListener
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
/** /**
@ -21,6 +22,7 @@ class TermTab(title: CharSequence) : Tab(title) {
var termSession: TerminalSession? = null var termSession: TerminalSession? = null
var sessionCallback: TermSessionChangedCallback? = null var sessionCallback: TermSessionChangedCallback? = null
var viewClient: TermViewClient? = null var viewClient: TermViewClient? = null
var onAutoCompleteListener: OnAutoCompleteListener? = null
var toolbar: Toolbar? = null var toolbar: Toolbar? = null
fun updateColorScheme() { fun updateColorScheme() {
@ -29,6 +31,9 @@ class TermTab(title: CharSequence) : Tab(title) {
} }
fun cleanup() { fun cleanup() {
onAutoCompleteListener?.onCleanUp()
onAutoCompleteListener = null
viewClient?.termTab = null viewClient?.termTab = null
viewClient?.termView = null viewClient?.termView = null
viewClient?.extraKeysView = null viewClient?.extraKeysView = null
@ -54,6 +59,12 @@ class TermTab(title: CharSequence) : Tab(title) {
viewClient?.sessionFinished = true viewClient?.sessionFinished = true
} }
fun onFullScreenModeChanged(fullScreen: Boolean) {
// Window token changed, we need to recreate PopupWindow
onAutoCompleteListener?.onCleanUp()
onAutoCompleteListener = null
}
fun requireCloseTab() { fun requireCloseTab() {
requireHideIme() requireHideIme()
EventBus.getDefault().post(TabCloseEvent(this)) EventBus.getDefault().post(TabCloseEvent(this))

View File

@ -8,12 +8,14 @@ import android.view.ViewGroup
import de.mrapp.android.tabswitcher.Tab import de.mrapp.android.tabswitcher.Tab
import de.mrapp.android.tabswitcher.TabSwitcher import de.mrapp.android.tabswitcher.TabSwitcher
import de.mrapp.android.tabswitcher.TabSwitcherDecorator import de.mrapp.android.tabswitcher.TabSwitcherDecorator
import io.neoterm.BuildConfig
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.term.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.OnAutoCompleteListener
import io.neoterm.view.TerminalView import io.neoterm.view.TerminalView
/** /**
@ -72,11 +74,24 @@ class TermTabDecorator(val context: NeoTermActivity) : TabSwitcherDecorator() {
termTab.viewClient?.updateSuggestions(termTab.termSession?.title, true) termTab.viewClient?.updateSuggestions(termTab.termSession?.title, true)
} }
view.setOnKeyListener(termTab.viewClient) view.setTerminalViewClient(termTab.viewClient)
view.attachSession(termTab.termSession) view.attachSession(termTab.termSession)
// Still in progress
// Only available for developers.
if (BuildConfig.DEBUG) {
if (termTab.onAutoCompleteListener == null) {
termTab.onAutoCompleteListener = createAutoCompleteListener(view)
}
view.onAutoCompleteListener = termTab.onAutoCompleteListener
}
} }
} }
private fun createAutoCompleteListener(view: TerminalView): OnAutoCompleteListener? {
return TermCompleteListener(view)
}
override fun getViewTypeCount(): Int { override fun getViewTypeCount(): Int {
return 1 return 1
} }

View File

@ -4,7 +4,6 @@ import android.content.Context
import android.widget.Toast import android.widget.Toast
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.preference.NeoTermPath import io.neoterm.preference.NeoTermPath
import io.neoterm.customize.font.FontManager import io.neoterm.customize.font.FontManager
import io.neoterm.preference.NeoPreference import io.neoterm.preference.NeoPreference
@ -21,7 +20,7 @@ object TerminalUtils {
terminalView?.textSize = NeoPreference.loadInt(NeoPreference.KEY_FONT_SIZE, 30) terminalView?.textSize = NeoPreference.loadInt(NeoPreference.KEY_FONT_SIZE, 30)
terminalView?.setTypeface(FontManager.getCurrentFont().getTypeFace()) terminalView?.setTypeface(FontManager.getCurrentFont().getTypeFace())
if (terminalViewClient != null) { if (terminalViewClient != null) {
terminalView?.setOnKeyListener(terminalViewClient) terminalView?.setTerminalViewClient(terminalViewClient)
} }
} }

View File

@ -0,0 +1,117 @@
package io.neoterm.view
import android.content.Context
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.BaseAdapter
import android.widget.ListView
import android.widget.PopupWindow
import android.widget.TextView
import io.neoterm.R
import io.neoterm.backend.TerminalColors
import io.neoterm.customize.color.ColorSchemeManager
import io.neoterm.customize.completion.CompleteCandidate
/**
* @author kiva
*/
class AutoCompletePopupWindow(val context: Context) {
var candidates: List<CompleteCandidate>? = null
var popupWindow: PopupWindow? = null
var wantsToFinish = false
var candidateAdapter: CandidateAdapter? = null
fun show(terminalView: TerminalView) {
if (popupWindow == null && !wantsToFinish) {
popupWindow = createPopupWindow()
}
candidateAdapter?.notifyDataSetChanged()
if (!(popupWindow?.isShowing ?: false)) {
popupWindow?.showAtLocation(terminalView, Gravity.BOTTOM.and(Gravity.START),
terminalView.cursorAbsX,
terminalView.cursorAbsY)
}
}
fun dismiss() {
popupWindow?.dismiss()
}
private fun createPopupWindow(): PopupWindow {
val popupWindow = PopupWindow(context)
popupWindow.isOutsideTouchable = true
popupWindow.isTouchable = true
val contentView = LayoutInflater.from(context).inflate(R.layout.popup_auto_complete, null, false)
val candidateListView = contentView.findViewById(R.id.popup_complete_candidate_list) as ListView
candidateAdapter = CandidateAdapter(this)
candidateListView.adapter = candidateAdapter
popupWindow.contentView = contentView
return popupWindow
}
fun cleanup() {
wantsToFinish = true
popupWindow = null
candidateAdapter = null
candidates = null
}
class CandidateAdapter(val autoCompletePopupWindow: AutoCompletePopupWindow) : BaseAdapter() {
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
var convertView = convertView
val viewHolder: CandidateViewHolder =
if (convertView != null) {
convertView.tag as CandidateViewHolder
} else {
convertView = LayoutInflater.from(autoCompletePopupWindow.context)
.inflate(R.layout.item_complete_candidate, null, false)
val viewHolder = CandidateViewHolder(convertView)
convertView.tag = viewHolder
viewHolder
}
val candidate = getItem(position) as CompleteCandidate
viewHolder.apply {
display.text = candidate.displayName
if (candidate.description != null) {
splitView.visibility = View.VISIBLE
description.visibility = View.VISIBLE
description.text = candidate.description
} else {
splitView.visibility = View.GONE
description.visibility = View.GONE
}
}
return convertView!!
}
override fun getItem(position: Int): Any? {
return autoCompletePopupWindow.candidates?.get(position)
}
override fun getItemId(position: Int): Long {
return position.toLong()
}
override fun getCount(): Int {
return autoCompletePopupWindow.candidates?.size ?: 0
}
}
class CandidateViewHolder(rootView: View) {
val display: TextView = rootView.findViewById(R.id.complete_display) as TextView
val description: TextView = rootView.findViewById(R.id.complete_description) as TextView
val splitView: View = rootView.findViewById(R.id.complete_split)
init {
val colorScheme = ColorSchemeManager.getCurrentColorScheme()
val textColor = TerminalColors.parse(colorScheme.foregroundColor)
display.setTextColor(textColor)
description.setTextColor(textColor)
}
}
}

View File

@ -0,0 +1,13 @@
package io.neoterm.view;
/**
* @author Kiva
* @version 1.0
*/
public interface OnAutoCompleteListener {
void onAutoComplete(String newText);
void onKeyCode(int keyCode, int keyMod);
void onCleanUp();
}

View File

@ -34,7 +34,7 @@ class TerminalDialog(val context: Context) {
terminalViewClient = BasicViewClient(terminalView) terminalViewClient = BasicViewClient(terminalView)
TerminalUtils.setupTerminalView(terminalView, terminalViewClient) TerminalUtils.setupTerminalView(terminalView, terminalViewClient)
terminalView.setOnKeyListener(terminalViewClient) terminalView.setTerminalViewClient(terminalViewClient)
terminalSessionCallback = object : BasicSessionCallback(terminalView) { terminalSessionCallback = object : BasicSessionCallback(terminalView) {
override fun onSessionFinished(finishedSession: TerminalSession?) { override fun onSessionFinished(finishedSession: TerminalSession?) {
sessionFinishedCallback?.onSessionFinished(this@TerminalDialog, finishedSession) sessionFinishedCallback?.onSessionFinished(this@TerminalDialog, finishedSession)

View File

@ -31,6 +31,10 @@ final class TerminalRenderer {
/** The {@link #mFontLineSpacing} + {@link #mFontAscent}. */ /** The {@link #mFontLineSpacing} + {@link #mFontAscent}. */
final int mFontLineSpacingAndAscent; final int mFontLineSpacingAndAscent;
/** AutoCompletion PopupWindow need them to show popup window */
protected float savedLastDrawnLineX;
protected float savedLastDrawnLineY;
private final float[] asciiMeasures = new float[127]; private final float[] asciiMeasures = new float[127];
public TerminalRenderer(int textSize, Typeface typeface) { public TerminalRenderer(int textSize, Typeface typeface) {
@ -200,6 +204,8 @@ final class TerminalRenderer {
if (cursorStyle == TerminalEmulator.CURSOR_STYLE_UNDERLINE) cursorHeight /= 4.; if (cursorStyle == TerminalEmulator.CURSOR_STYLE_UNDERLINE) cursorHeight /= 4.;
else if (cursorStyle == TerminalEmulator.CURSOR_STYLE_BAR) right -= ((right - left) * 3) / 4.; else if (cursorStyle == TerminalEmulator.CURSOR_STYLE_BAR) right -= ((right - left) * 3) / 4.;
canvas.drawRect(left, y - cursorHeight, right, y, mTextPaint); canvas.drawRect(left, y - cursorHeight, right, y, mTextPaint);
savedLastDrawnLineX = left;
savedLastDrawnLineY = y;
} }
if ((effect & TextStyle.CHARACTER_ATTRIBUTE_INVISIBLE) == 0) { if ((effect & TextStyle.CHARACTER_ATTRIBUTE_INVISIBLE) == 0) {
@ -227,4 +233,12 @@ final class TerminalRenderer {
if (savedMatrix) canvas.restore(); if (savedMatrix) canvas.restore();
} }
float getCursorX() {
return savedLastDrawnLineX;
}
float getCursorY() {
return savedLastDrawnLineY;
}
} }

View File

@ -229,11 +229,11 @@ public final class TerminalView extends View {
} }
/** /**
* @param onKeyListener Listener for all kinds of key events, both hardware and IME (which makes it different from that * @param client Listener for all kinds of key events, both hardware and IME (which makes it different from that
* available with {@link View#setOnKeyListener(OnKeyListener)}. * available with {@link View#setOnKeyListener(OnKeyListener)}.
*/ */
public void setOnKeyListener(TerminalViewClient onKeyListener) { public void setTerminalViewClient(TerminalViewClient client) {
this.mClient = onKeyListener; this.mClient = client;
} }
/** /**
@ -296,6 +296,9 @@ public final class TerminalView extends View {
Editable content = getEditable(); Editable content = getEditable();
sendTextToTerminal(content); sendTextToTerminal(content);
if (onAutoCompleteListener != null) {
onAutoCompleteListener.onAutoComplete(content.toString());
}
content.clear(); content.clear();
return true; return true;
} }
@ -678,6 +681,16 @@ public final class TerminalView extends View {
if (mCombiningAccent != oldCombiningAccent) invalidate(); if (mCombiningAccent != oldCombiningAccent) invalidate();
if (onAutoCompleteListener != null) {
if (event.isPrintingKey()) {
char printingChar = (char) event.getUnicodeChar(metaState);
if (printingChar != '\b') {
// ASCII chars
onAutoCompleteListener.onAutoComplete(new String(new char[]{printingChar}));
}
}
}
return true; return true;
} }
@ -745,6 +758,9 @@ public final class TerminalView extends View {
String code = KeyHandler.getCode(keyCode, keyMod, term.isCursorKeysApplicationMode(), term.isKeypadApplicationMode()); String code = KeyHandler.getCode(keyCode, keyMod, term.isCursorKeysApplicationMode(), term.isKeypadApplicationMode());
if (code == null) return false; if (code == null) return false;
mTermSession.write(code); mTermSession.write(code);
if (onAutoCompleteListener != null) {
onAutoCompleteListener.onKeyCode(keyCode, keyMod);
}
return true; return true;
} }
@ -960,4 +976,24 @@ public final class TerminalView extends View {
return mTermSession; return mTermSession;
} }
private OnAutoCompleteListener onAutoCompleteListener;
public OnAutoCompleteListener getOnAutoCompleteListener() {
return onAutoCompleteListener;
}
public void setOnAutoCompleteListener(OnAutoCompleteListener onAutoCompleteListener) {
this.onAutoCompleteListener = onAutoCompleteListener;
}
public int getCursorAbsX() {
return (int) mRenderer.getCursorX();
}
public int getCursorAbsY() {
int[] locations = new int[2];
getLocationOnScreen(locations);
return (int) (mRenderer.getCursorY() + locations[1]);
}
} }

View File

@ -8,7 +8,7 @@ import io.neoterm.backend.TerminalSession;
/** /**
* Input and scale listener which may be set on a {@link TerminalView} through * Input and scale listener which may be set on a {@link TerminalView} through
* {@link TerminalView#setOnKeyListener(TerminalViewClient)}. * {@link TerminalView#setTerminalViewClient(TerminalViewClient)}.
* <p/> * <p/>
*/ */
public interface TerminalViewClient { public interface TerminalViewClient {

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:minHeight="@dimen/min_popup_height"
android:padding="@dimen/popup_padding"
android:orientation="vertical">
<TextView
android:id="@+id/complete_display"
style="@style/TextAppearance.AppCompat.Medium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:text="Display" />
<View
android:id="@+id/complete_split"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/popup_split_background" />
<TextView
android:id="@+id/complete_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/text_margin"
android:layout_marginStart="@dimen/text_margin"
tools:text="Description" />
</LinearLayout>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/popup_background"
android:orientation="vertical">
<ListView
android:id="@+id/popup_complete_candidate_list"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>

View File

@ -81,4 +81,5 @@
<string name="service_lock_acquired"> (永不休眠)</string> <string name="service_lock_acquired"> (永不休眠)</string>
<string name="service_acquire_lock">取得休眠锁</string> <string name="service_acquire_lock">取得休眠锁</string>
<string name="service_release_lock">释放休眠锁</string> <string name="service_release_lock">释放休眠锁</string>
<string name="exit">退出</string>
</resources> </resources>

View File

@ -15,6 +15,8 @@
<color name="roseEnd">#ef0276</color> <color name="roseEnd">#ef0276</color>
<color name="greenStart">#a7f56e</color> <color name="greenStart">#a7f56e</color>
<color name="greenEnd">#11c612</color> <color name="greenEnd">#11c612</color>
<color name="popup_background">#7f000000</color>
<color name="popup_split_background">#efefef</color>
<integer-array name="bubble_colors"> <integer-array name="bubble_colors">
<item>@color/roseStart</item> <item>@color/roseStart</item>

View File

@ -10,4 +10,6 @@
<dimen name="custom_editor_line_height">48dp</dimen> <dimen name="custom_editor_line_height">48dp</dimen>
<dimen name="custom_install_icon_width">36dp</dimen> <dimen name="custom_install_icon_width">36dp</dimen>
<dimen name="custom_install_icon_height">36dp</dimen> <dimen name="custom_install_icon_height">36dp</dimen>
<dimen name="min_popup_height">24dp</dimen>
<dimen name="popup_padding">4dp</dimen>
</resources> </resources>

View File

@ -76,6 +76,7 @@
<string name="pref_customization_color_scheme">Color Scheme</string> <string name="pref_customization_color_scheme">Color Scheme</string>
<string name="pref_customization_eks">Extra Keys</string> <string name="pref_customization_eks">Extra Keys</string>
<string name="pref_package_source">Source</string> <string name="pref_package_source">Source</string>
<string name="exit">Exit</string>
<string-array name="pref_general_shell_entries" translatable="false"> <string-array name="pref_general_shell_entries" translatable="false">
<item>sh</item> <item>sh</item>

View File

@ -254,7 +254,9 @@ public class TabSwitcher extends FrameLayout implements TabSwitcherLayout, Model
public void onGlobalLayout() { public void onGlobalLayout() {
ViewUtil.removeOnGlobalLayoutListener( ViewUtil.removeOnGlobalLayoutListener(
tabContainer.getViewTreeObserver(), this); tabContainer.getViewTreeObserver(), this);
TabSwitcher.this.layout.onGlobalLayout(); if (TabSwitcher.this.layout != null) {
TabSwitcher.this.layout.onGlobalLayout();
}
} }
}); });