From c9973393ff905e77ae0d9f62959274e4559f9c27 Mon Sep 17 00:00:00 2001 From: Kr328 <39107975+Kr328@users.noreply.github.com> Date: Thu, 5 Dec 2019 00:11:04 +0800 Subject: [PATCH] add import from url (1/3) --- app/src/main/AndroidManifest.xml | 4 + .../kr328/clash/CreateProfileActivity.kt | 11 +- .../github/kr328/clash/ImportFileActivity.kt | 132 ++++++---------- .../github/kr328/clash/ImportUrlActivity.kt | 41 +++++ .../github/kr328/clash/adapter/FormAdapter.kt | 145 +++++++++++++++--- .../kr328/clash/fragment/FormFragment.kt | 45 ------ app/src/main/res/drawable/ic_link.xml | 9 ++ .../main/res/layout/activity_import_file.xml | 103 +------------ .../main/res/layout/activity_import_url.xml | 50 ++++++ app/src/main/res/layout/adapter_form_text.xml | 54 +++++++ app/src/main/res/layout/adapter_from_text.xml | 27 ---- app/src/main/res/values/strings.xml | 4 + 12 files changed, 346 insertions(+), 279 deletions(-) create mode 100644 app/src/main/java/com/github/kr328/clash/ImportUrlActivity.kt delete mode 100644 app/src/main/java/com/github/kr328/clash/fragment/FormFragment.kt create mode 100644 app/src/main/res/drawable/ic_link.xml create mode 100644 app/src/main/res/layout/activity_import_url.xml create mode 100644 app/src/main/res/layout/adapter_form_text.xml delete mode 100644 app/src/main/res/layout/adapter_from_text.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 31bfcad..7c42ed5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -40,6 +40,10 @@ android:label="@string/clash_import_from_file" android:exported="false" android:screenOrientation="portrait"/> + when (index) { - 0 -> + 0 -> { startActivity( Intent( this@CreateProfileActivity, ImportFileActivity::class.java ) ) + } + 1 -> { + startActivity( + Intent( + this@CreateProfileActivity, + ImportUrlActivity::class.java + ) + ) + } } } } diff --git a/app/src/main/java/com/github/kr328/clash/ImportFileActivity.kt b/app/src/main/java/com/github/kr328/clash/ImportFileActivity.kt index c4a593b..a2bb2eb 100644 --- a/app/src/main/java/com/github/kr328/clash/ImportFileActivity.kt +++ b/app/src/main/java/com/github/kr328/clash/ImportFileActivity.kt @@ -1,28 +1,30 @@ package com.github.kr328.clash -import android.app.Activity import android.content.Intent import android.net.Uri import android.os.Bundle import android.view.View -import android.webkit.MimeTypeMap -import android.widget.EditText -import androidx.appcompat.app.AlertDialog +import androidx.recyclerview.widget.LinearLayoutManager import com.charleskorn.kaml.Yaml import com.charleskorn.kaml.YamlConfiguration +import com.github.kr328.clash.adapter.FormAdapter import com.github.kr328.clash.model.ClashProfile import com.github.kr328.clash.service.data.ClashProfileEntity import com.github.kr328.clash.utils.FileUtils -import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.snackbar.Snackbar import kotlinx.android.synthetic.main.activity_import_file.* import java.io.FileOutputStream import kotlin.concurrent.thread class ImportFileActivity : BaseActivity() { - companion object { - const val REQUEST_FILE_CODE = 185 - } + private val elements: List = listOf( + FormAdapter.TextType(R.drawable.ic_about, + R.string.clash_profile_name, + R.string.clash_profile_name_hint), + FormAdapter.FilePickerType(R.drawable.ic_new_profile_file, + R.string.clash_profile_file, + R.string.clash_profile_file_hint) + ) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -30,93 +32,53 @@ class ImportFileActivity : BaseActivity() { setSupportActionBar(activity_import_file_toolbar) + activity_import_file_form.also { + it.layoutManager = LinearLayoutManager(this) + it.adapter = FormAdapter(this, elements) + } + activity_import_file_save.setOnClickListener { - when { - activity_import_file_name_text.text.isEmpty() -> { - Snackbar.make( - activity_import_file_root, - R.string.clash_import_file_empty_name, - Snackbar.LENGTH_LONG - ).show() - } - activity_import_file_path_text.tag == null -> { - Snackbar.make( - activity_import_file_root, - R.string.clash_import_file_empty_path, - Snackbar.LENGTH_LONG - ).show() - } - else -> { - it.visibility = View.GONE - activity_import_file_saving.visibility = View.VISIBLE + activity_import_file_save.visibility = View.GONE + activity_import_file_saving.visibility = View.VISIBLE - thread { - checkAndInsert() - - runOnUiThread { - it.visibility = View.VISIBLE - activity_import_file_saving.visibility = View.GONE - } - } - } + thread { + checkAndInsert() } } - - val name = activity_import_file_name_text - - activity_import_file_name.setOnClickListener { - MaterialAlertDialogBuilder(this) - .setTitle(R.string.clash_profile_name) - .setView(R.layout.dialog_text_edit) - .setPositiveButton(R.string.ok) { _, _ -> } - .setNegativeButton(R.string.cancel) { _, _ -> } - .create() - .apply { - setOnShowListener { - val data = findViewById(R.id.dialog_text_edit).also { - it?.hint = name.hint - it?.setText(name.text) - } - getButton(AlertDialog.BUTTON_POSITIVE)?.apply { - setTextColor(getColor(R.color.colorAccent)) - setOnClickListener { - name.text = data?.text ?: "" - dismiss() - } - } - getButton(AlertDialog.BUTTON_NEGATIVE)?.apply { - setTextColor(getColor(R.color.colorAccent)) - } - } - } - .show() - } - - activity_import_file_path.setOnClickListener { - startActivityForResult( - Intent(Intent.ACTION_GET_CONTENT) - .setType( - MimeTypeMap.getSingleton() - .getMimeTypeFromExtension("yaml") ?: "*/*" - ), - REQUEST_FILE_CODE - ) - } } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - if (requestCode == REQUEST_FILE_CODE && resultCode == Activity.RESULT_OK) { - activity_import_file_path_text.tag = data?.data - activity_import_file_path_text.text = data?.data?.pathSegments?.last() ?: "" - } else { - super.onActivityResult(requestCode, resultCode, data) - } + if (data != null && (activity_import_file_form.adapter as FormAdapter).onActivityResult(requestCode, resultCode, data) ) + return + + super.onActivityResult(requestCode, resultCode, data) } private fun checkAndInsert() { try { + val name = elements[0] as FormAdapter.TextType + val file = elements[1] as FormAdapter.FilePickerType + + if ( name.content.isEmpty() ) { + runOnUiThread { + activity_import_file_save.visibility = View.VISIBLE + activity_import_file_saving.visibility = View.GONE + Snackbar.make(activity_import_file_root, R.string.clash_import_file_empty_name, Snackbar.LENGTH_LONG).show() + } + return + } + + if ( file.content == null || file.content == Uri.EMPTY ) { + runOnUiThread { + activity_import_file_save.visibility = View.VISIBLE + activity_import_file_saving.visibility = View.GONE + Snackbar.make(activity_import_file_root, R.string.clash_import_file_empty_path, Snackbar.LENGTH_LONG).show() + } + return + } + val data = - contentResolver.openInputStream(activity_import_file_path_text.tag as Uri)?.use { + contentResolver.openInputStream(file.content!!)?.use { it.readBytes().toString(Charsets.UTF_8) } ?: throw NullPointerException("Unable to open config file") @@ -134,8 +96,8 @@ class ImportFileActivity : BaseActivity() { runClash { it.profileService.addProfile( ClashProfileEntity( - name = activity_import_file_name_text.text.toString(), - token = "file|" + activity_import_file_path_text.tag.toString(), + name = name.content, + token = "file|" + file.content!!.toString(), cache = cache.absolutePath, active = false, proxies = parsed.proxies.size, diff --git a/app/src/main/java/com/github/kr328/clash/ImportUrlActivity.kt b/app/src/main/java/com/github/kr328/clash/ImportUrlActivity.kt new file mode 100644 index 0000000..75ef8dd --- /dev/null +++ b/app/src/main/java/com/github/kr328/clash/ImportUrlActivity.kt @@ -0,0 +1,41 @@ +package com.github.kr328.clash + +import android.content.Intent +import android.os.Bundle +import androidx.recyclerview.widget.LinearLayoutManager +import com.github.kr328.clash.adapter.FormAdapter +import kotlinx.android.synthetic.main.activity_import_url.* + +class ImportUrlActivity : BaseActivity() { + private val elements = listOf( + FormAdapter.TextType( + R.drawable.ic_about, + R.string.clash_profile_name, + R.string.clash_profile_name_hint), + FormAdapter.TextType( + R.drawable.ic_link, + R.string.clash_profile_url, + R.string.clash_profile_url_hint + ) + ) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_import_url) + + setSupportActionBar(activity_import_url_toolbar) + + activity_import_url_form.also { + it.layoutManager = LinearLayoutManager(this) + it.adapter = FormAdapter(this, elements) + } + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + if (data != null && (activity_import_url_form.adapter as FormAdapter) + .onActivityResult(requestCode, resultCode, data)) + return + + super.onActivityResult(requestCode, resultCode, data) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/kr328/clash/adapter/FormAdapter.kt b/app/src/main/java/com/github/kr328/clash/adapter/FormAdapter.kt index 27de5ae..d0891d5 100644 --- a/app/src/main/java/com/github/kr328/clash/adapter/FormAdapter.kt +++ b/app/src/main/java/com/github/kr328/clash/adapter/FormAdapter.kt @@ -1,30 +1,116 @@ package com.github.kr328.clash.adapter -import android.content.Context +import android.app.Activity +import android.app.Activity.RESULT_OK +import android.content.Intent +import android.net.Uri import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.webkit.MimeTypeMap +import android.widget.EditText import android.widget.ImageView +import android.widget.TextView +import androidx.appcompat.app.AlertDialog import androidx.recyclerview.widget.RecyclerView import com.github.kr328.clash.R -import com.github.kr328.clash.fragment.FormFragment -import com.google.android.material.textfield.TextInputEditText +import com.google.android.material.dialog.MaterialAlertDialogBuilder class FormAdapter( - private val context: Context, - private val elements: List + private val activity: Activity, + private val elements: List ) : RecyclerView.Adapter() { + companion object { + const val BASE_REQUEST_CODE = 14654 + } + + interface Type { + val content: Any? + } + + data class TextType( + val icon: Int, + val title: Int, + val hint: Int, + override var content: String = "" + ) : Type + + data class FilePickerType( + val icon: Int, + val title: Int, + val hint: Int, + override var content: Uri? = Uri.EMPTY + ) : Type + class TextHolder(view: View) : RecyclerView.ViewHolder(view) { - val icon: ImageView = view.findViewById(R.id.adapter_from_text_icon) - val edit: TextInputEditText = view.findViewById(R.id.adapter_from_text_text) + val icon: ImageView = view.findViewById(R.id.adapter_form_text_icon) + val title: TextView = view.findViewById(R.id.adapter_form_text_title) + val text: TextView = view.findViewById(R.id.adapter_form_text_text) + val clickable: View = view.findViewById(R.id.adapter_form_text_clickable) + } + + fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent): Boolean { + val position = requestCode - BASE_REQUEST_CODE + + if (position in 0..elements.size) { + val element = elements[position] + + if (element is FilePickerType) { + if (resultCode == RESULT_OK) { + element.content = data.data + notifyItemChanged(position) + } + + return true + } + } + + return false + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + when (val current = elements[position]) { + is TextType -> { + val castedHolder = holder as TextHolder + + castedHolder.icon.setImageResource(current.icon) + castedHolder.title.text = activity.getText(current.title) + castedHolder.text.hint = activity.getText(current.hint) + castedHolder.text.text = current.content + castedHolder.clickable.setOnClickListener { + showTextEditDialog( + castedHolder.text.text.toString(), + castedHolder.text.hint.toString() + ) { + current.content = it + notifyItemChanged(position) + } + } + } + is FilePickerType -> { + val castedHolder = holder as TextHolder + + castedHolder.icon.setImageResource(current.icon) + castedHolder.title.text = activity.getText(current.title) + castedHolder.text.hint = activity.getText(current.hint) + castedHolder.text.text = current.content?.pathSegments?.lastOrNull() ?: "" + castedHolder.clickable.setOnClickListener { + activity.startActivityForResult( + Intent(Intent.ACTION_GET_CONTENT) + .setType("*/*"), + BASE_REQUEST_CODE + position + ) + } + } + } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { return when (viewType) { - FormFragment.FormType.STRING.ordinal, FormFragment.FormType.FILE.ordinal, FormFragment.FormType.URL.ordinal -> + TextType::class.java.hashCode(), FilePickerType::class.java.hashCode() -> TextHolder( - LayoutInflater.from(context).inflate( - R.layout.adapter_from_text, + LayoutInflater.from(activity).inflate( + R.layout.adapter_form_text, parent, false ) @@ -37,18 +123,35 @@ class FormAdapter( return elements.size } - override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { - val current = elements[position] - - when (holder) { - is TextHolder -> { - holder.icon.setImageResource(current.icon) - holder.edit.hint = context.getString(current.hint) - } - } + override fun getItemViewType(position: Int): Int { + return elements[position].javaClass.hashCode() } - override fun getItemViewType(position: Int): Int { - return elements[position].type.ordinal + private fun showTextEditDialog(initial: String, hint: String, callback: (String) -> Unit) { + MaterialAlertDialogBuilder(activity) + .setTitle(R.string.clash_profile_name) + .setView(R.layout.dialog_text_edit) + .setPositiveButton(R.string.ok) { _, _ -> } + .setNegativeButton(R.string.cancel) { _, _ -> } + .create() + .apply { + setOnShowListener { + val data = findViewById(R.id.dialog_text_edit).also { + it?.hint = hint + it?.setText(initial) + } + getButton(AlertDialog.BUTTON_POSITIVE)?.apply { + setTextColor(context.getColor(R.color.colorAccent)) + setOnClickListener { + callback(data?.text?.toString() ?: "") + dismiss() + } + } + getButton(AlertDialog.BUTTON_NEGATIVE)?.apply { + setTextColor(context.getColor(R.color.colorAccent)) + } + } + } + .show() } } \ No newline at end of file diff --git a/app/src/main/java/com/github/kr328/clash/fragment/FormFragment.kt b/app/src/main/java/com/github/kr328/clash/fragment/FormFragment.kt deleted file mode 100644 index 147b606..0000000 --- a/app/src/main/java/com/github/kr328/clash/fragment/FormFragment.kt +++ /dev/null @@ -1,45 +0,0 @@ -package com.github.kr328.clash.fragment - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.Fragment -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView -import com.github.kr328.clash.adapter.FormAdapter - -class FormFragment(private val elements: List) : Fragment() { - enum class FormType { - STRING, URL, FILE - } - - interface ResultDumper { - class DumpException(message: String) : Exception(message) - - fun dump(element: FormElement, data: Any) - } - - data class FormElement( - val type: FormType, - val icon: Int, - val title: Int, - val hint: Int, - val result: ResultDumper - ) - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - return RecyclerView(requireActivity()).apply { - layoutParams = ViewGroup.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT - ) - layoutManager = LinearLayoutManager(requireActivity()) - adapter = FormAdapter(requireActivity(), elements) - } - } -} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_link.xml b/app/src/main/res/drawable/ic_link.xml new file mode 100644 index 0000000..538c5bd --- /dev/null +++ b/app/src/main/res/drawable/ic_link.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/activity_import_file.xml b/app/src/main/res/layout/activity_import_file.xml index 5127bd7..dcfd8ae 100644 --- a/app/src/main/res/layout/activity_import_file.xml +++ b/app/src/main/res/layout/activity_import_file.xml @@ -43,105 +43,8 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - + android:layout_height="wrap_content" /> \ No newline at end of file diff --git a/app/src/main/res/layout/activity_import_url.xml b/app/src/main/res/layout/activity_import_url.xml new file mode 100644 index 0000000..fd5ebfe --- /dev/null +++ b/app/src/main/res/layout/activity_import_url.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/adapter_form_text.xml b/app/src/main/res/layout/adapter_form_text.xml new file mode 100644 index 0000000..56b79bd --- /dev/null +++ b/app/src/main/res/layout/adapter_form_text.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/adapter_from_text.xml b/app/src/main/res/layout/adapter_from_text.xml deleted file mode 100644 index c8b6d6a..0000000 --- a/app/src/main/res/layout/adapter_from_text.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 42247a7..b55e9a4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -28,6 +28,7 @@ From Internet Import From File + Import From URL Name Unlabeled @@ -35,6 +36,9 @@ File content:// + URL + https:// + OK Cancel