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