Feature: Customize fonts

This commit is contained in:
zt515
2017-07-03 13:16:31 +08:00
parent e104be3fc3
commit 9ae461433e
10 changed files with 276 additions and 29 deletions

View File

@ -59,7 +59,7 @@ class NeoTermActivity : AppCompatActivity(), ServiceConnection, SharedPreference
WindowManager.LayoutParams.FLAG_FULLSCREEN)
}
setContentView(R.layout.tab_main)
setContentView(R.layout.ui_main)
toolbar = findViewById(R.id.terminal_toolbar) as Toolbar
setSupportActionBar(toolbar)

View File

@ -1,5 +1,7 @@
package io.neoterm.ui.customization
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.Toolbar
@ -8,24 +10,32 @@ import android.view.View
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.Spinner
import android.widget.Toast
import io.neoterm.R
import io.neoterm.backend.TerminalSession
import io.neoterm.customize.NeoTermPath
import io.neoterm.customize.font.FontManager
import io.neoterm.utils.FileUtils
import io.neoterm.utils.MediaUtils
import io.neoterm.utils.TerminalUtils
import io.neoterm.view.BasicSessionCallback
import io.neoterm.view.BasicViewClient
import io.neoterm.view.TerminalView
import java.io.File
import java.io.FileInputStream
/**
* @author kiva
*/
class CustomizationActivity: AppCompatActivity() {
class CustomizationActivity : AppCompatActivity() {
lateinit var terminalView: TerminalView
lateinit var viewClient: BasicViewClient
lateinit var sessionCallback: BasicSessionCallback
lateinit var session: TerminalSession
val REQUEST_SELECT_FONT = 22222
val REQUEST_SELECT_COLOR = 22223
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.ui_customization)
@ -41,6 +51,23 @@ class CustomizationActivity: AppCompatActivity() {
arrayOf("echo", "Hello NeoTerm."), null, null, sessionCallback, false)
terminalView.attachSession(session)
findViewById(R.id.custom_install_font_button).setOnClickListener {
val intent = Intent()
intent.action = Intent.ACTION_GET_CONTENT
intent.type = "*/*"
startActivityForResult(Intent.createChooser(intent, getString(R.string.install_font)), REQUEST_SELECT_FONT)
}
findViewById(R.id.custom_install_color_button).setOnClickListener {
val intent = Intent()
intent.action = Intent.ACTION_GET_CONTENT
intent.type = "*/*"
startActivityForResult(Intent.createChooser(intent, getString(R.string.install_color)), REQUEST_SELECT_COLOR)
}
}
private fun setupSpinners() {
FontManager.refreshFontList()
setupSpinner(R.id.custom_font_spinner, FontManager.getFontNames(), FontManager.getCurrentFontName(), object : AdapterView.OnItemSelectedListener {
override fun onNothingSelected(parent: AdapterView<*>?) {
}
@ -62,11 +89,52 @@ class CustomizationActivity: AppCompatActivity() {
spinner.setSelection(if (data.contains(selected)) data.indexOf(selected) else 0)
}
override fun onResume() {
super.onResume()
setupSpinners()
}
override fun onDestroy() {
super.onDestroy()
session.finishIfRunning()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (resultCode == Activity.RESULT_OK && data != null) {
val selected = MediaUtils.getPath(this, data.data)
if (selected != null && selected.isNotEmpty()) {
when (requestCode) {
REQUEST_SELECT_FONT -> installFont(selected)
REQUEST_SELECT_COLOR -> installColor(selected)
}
}
}
super.onActivityResult(requestCode, resultCode, data)
}
private fun installColor(selected: String) {
installFileTo(selected, NeoTermPath.COLORS_PATH)
setupSpinners()
}
private fun installFont(selected: String) {
installFileTo(selected, NeoTermPath.FONT_PATH)
setupSpinners()
}
private fun installFileTo(file: String, targetDir: String) {
try {
val fileObject = File(file)
val input = FileInputStream(fileObject.absolutePath)
val targetFile = File(targetDir, fileObject.name)
input.use {
FileUtils.writeFile(targetFile, it)
}
} catch (e: Exception) {
Toast.makeText(this, getString(R.string.error) + ": ${e.localizedMessage}", Toast.LENGTH_LONG).show()
}
}
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
when (item?.itemId) {
android.R.id.home -> finish()

View File

@ -0,0 +1,139 @@
package io.neoterm.utils
import android.content.ContentUris
import android.content.Context
import android.database.Cursor
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.DocumentsContract
import android.provider.MediaStore
/**
* @author kiva
*/
object MediaUtils {
/**
* Get a file path from a Uri. This will get the the path for Storage Access
* Framework Documents, as well as the _data field for the MediaStore and
* other file-based ContentProviders.
* @param context The context.
* *
* @param uri The Uri to query.
*/
fun getPath(context: Context, uri: Uri): String? {
val isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
// DocumentProvider
if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
// ExternalStorageProvider
if (isExternalStorageDocument(uri)) {
val docId = DocumentsContract.getDocumentId(uri)
val split = docId.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
val type = split[0]
if ("primary".equals(type, ignoreCase = true)) {
return Environment.getExternalStorageDirectory().toString() + "/" + split[1]
}
// TODO handle non-primary volumes
} else if (isDownloadsDocument(uri)) {
val id = DocumentsContract.getDocumentId(uri)
val contentUri = ContentUris.withAppendedId(
Uri.parse("content://downloads/public_downloads"), java.lang.Long.valueOf(id)!!)
return getDataColumn(context, contentUri, null, null)
} else if (isMediaDocument(uri)) {
val docId = DocumentsContract.getDocumentId(uri)
val split = docId.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
val type = split[0]
var contentUri: Uri? = null
if ("image" == type) {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
} else if ("video" == type) {
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI
} else if ("audio" == type) {
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
}
val selection = "_id=?"
val selectionArgs = arrayOf(split[1])
return getDataColumn(context, contentUri!!, selection, selectionArgs)
}// MediaProvider
// DownloadsProvider
} else if ("content".equals(uri.scheme, ignoreCase = true)) {
return getDataColumn(context, uri, null, null)
} else if ("file".equals(uri.scheme, ignoreCase = true)) {
return uri.path
}
return null
}
/**
* Get the value of the data column for this Uri. This is useful for
* MediaStore Uris, and other file-based ContentProviders.
* @param context The context.
* *
* @param uri The Uri to query.
* *
* @param selection (Optional) Filter used in the query.
* *
* @param selectionArgs (Optional) Selection arguments used in the query.
* *
* @return The value of the _data column, which is typically a file path.
*/
fun getDataColumn(context: Context, uri: Uri, selection: String?,
selectionArgs: Array<String>?): String? {
var cursor: Cursor? = null
val column = "_data"
val projection = arrayOf(column)
try {
cursor = context.contentResolver.query(uri, projection, selection, selectionArgs, null)
if (cursor != null && cursor.moveToFirst()) {
val column_index = cursor.getColumnIndexOrThrow(column)
return cursor.getString(column_index)
}
} finally {
if (cursor != null)
cursor.close()
}
return null
}
/**
* @param uri The Uri to check.
* *
* @return Whether the Uri authority is ExternalStorageProvider.
*/
fun isExternalStorageDocument(uri: Uri): Boolean {
return "com.android.externalstorage.documents" == uri.authority
}
/**
* @param uri The Uri to check.
* *
* @return Whether the Uri authority is DownloadsProvider.
*/
fun isDownloadsDocument(uri: Uri): Boolean {
return "com.android.providers.downloads.documents" == uri.authority
}
/**
* @param uri The Uri to check.
* *
* @return Whether the Uri authority is MediaProvider.
*/
fun isMediaDocument(uri: Uri): Boolean {
return "com.android.providers.media.documents" == uri.authority
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 637 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 691 B

View File

@ -14,6 +14,7 @@
app:popupTheme="@style/ThemeOverlay.AppCompat.Dark" />
<LinearLayout
android:layout_marginBottom="@dimen/text_margin"
android:id="@+id/custom_editor_layout"
style="?buttonBarStyle"
android:layout_width="match_parent"
@ -22,40 +23,71 @@
android:layout_margin="@dimen/text_margin"
android:orientation="vertical">
<LinearLayout
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/text_margin"
android:layout_height="@dimen/custom_editor_line_height"
android:layout_gravity="center"
android:gravity="center"
android:orientation="horizontal">
<ImageButton
style="?buttonBarButtonStyle"
android:id="@+id/custom_install_font_button"
android:layout_width="@dimen/custom_install_icon_width"
android:layout_height="@dimen/custom_editor_line_height"
android:layout_alignParentEnd="true"
android:src="@drawable/ic_install_white_36" />
<TextView
android:gravity="center"
android:id="@+id/custom_font_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentStart="true"
android:text="@string/pref_customization_font" />
<Spinner
android:id="@+id/custom_font_spinner"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/text_margin" />
</LinearLayout>
android:layout_height="match_parent"
android:layout_marginStart="@dimen/text_margin"
android:layout_toEndOf="@id/custom_font_text"
android:layout_toStartOf="@id/custom_install_font_button" />
<LinearLayout
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="@dimen/custom_editor_line_height"
android:layout_gravity="center"
android:gravity="center"
android:orientation="horizontal">
<ImageButton
style="?buttonBarButtonStyle"
android:id="@+id/custom_install_color_button"
android:layout_width="@dimen/custom_install_icon_width"
android:layout_height="@dimen/custom_editor_line_height"
android:layout_alignParentEnd="true"
android:src="@drawable/ic_install_white_36" />
<TextView
android:gravity="center"
android:id="@+id/custom_color_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentStart="true"
android:text="@string/pref_customization_color_scheme" />
<Spinner
android:id="@+id/custom_color_spinner"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/text_margin" />
</LinearLayout>
android:layout_height="match_parent"
android:layout_marginStart="@dimen/text_margin"
android:layout_toEndOf="@id/custom_color_text"
android:layout_toStartOf="@id/custom_install_color_button" />
</RelativeLayout>
</LinearLayout>
<RelativeLayout

View File

@ -66,4 +66,6 @@
<string name="general_settings_desc">响铃振动Shell</string>
<string name="ui_settings_desc">全屏,标题栏,快捷键盘</string>
<string name="package_settings_desc">源,更新,升级</string>
<string name="install_font">安装字体</string>
<string name="install_color">安装配色方案</string>
</resources>

View File

@ -7,4 +7,7 @@
<dimen name="eks_height_two_line">72dp</dimen>
<dimen name="eks_height_one_line">36dp</dimen>
<dimen name="terminal_dialog_height">256dp</dimen>
<dimen name="custom_editor_line_height">48dp</dimen>
<dimen name="custom_install_icon_width">36dp</dimen>
<dimen name="custom_install_icon_height">36dp</dimen>
</resources>

View File

@ -18,6 +18,23 @@
<string name="customization_settings_desc">Font, ColorScheme, ExtraKeys</string>
<string name="customization_settings">Customization</string>
<string name="toggle_ime">Toggle IME</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="permission_denied">NeoTerm cannot get essential permissions, exiting.</string>
<string name="error">Error</string>
<string name="use_system_shell">System Shell</string>
<string name="retry">Retry</string>
<string name="source_changed">APT source changed, you may get it updated by executing: apt update</string>
<string name="done">Done</string>
<string name="install">Install</string>
<string name="package_details">Package: %s\nVersion: %s\nDepends: %s\nInstalled Size: %s\nDescription: %s\nHome Page: %s</string>
<string name="package_list_empty">Package list is empty, please check out your source.</string>
<string name="menu_refresh_list">Refresh</string>
<string name="menu_update">Update, Refresh</string>
<string name="install_font">Install Font</string>
<string name="install_color">Install Color Scheme</string>
<string name="installer_message">Installing</string>
<string name="installer_install_zsh_manually">You may install zsh and setup suggestions by executing: ~/install-zsh.sh</string>
@ -44,20 +61,6 @@
<string name="pref_customization_color_scheme">Color Scheme</string>
<string name="pref_customization_eks">Extra Keys</string>
<string name="pref_package_source">Source</string>
<string name="toggle_ime">Toggle IME</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="permission_denied">NeoTerm cannot get essential permissions, exiting.</string>
<string name="error">Error</string>
<string name="use_system_shell">System Shell</string>
<string name="retry">Retry</string>
<string name="source_changed">APT source changed, you may get it updated by executing: apt update</string>
<string name="done">Done</string>
<string name="install">Install</string>
<string name="package_details">Package: %s\nVersion: %s\nDepends: %s\nInstalled Size: %s\nDescription: %s\nHome Page: %s</string>
<string name="package_list_empty">Package list is empty, please check out your source.</string>
<string name="menu_refresh_list">Refresh</string>
<string name="menu_update">Update, Refresh</string>
<string-array name="pref_general_shell_entries" translatable="false">
<item>sh</item>