/
githubmirror
/
shadowsocks-android
Обзор
Документация
Войти
/
githubmirror
/
shadowsocks-android
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
mobile/src/main/java/com/github/shadowsocks/subscription/SubscriptionFragment.kt
262 строки
11 KB
Mygod
Properly support predictive back
05 фев 2023, 07:55
05 фев 2023, 07:55
f3dcec5
Код
Авторство
О чём код?
/******************************************************************************* * * * Copyright (C) 2020 by Max Lv <max.c.lv@gmail.com> * * Copyright (C) 2020 by Mygod Studio <contact-shadowsocks-android@mygod.be> * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see <http://www.gnu.org/licenses/>. * * * *******************************************************************************/ package com.github.shadowsocks.subscription import android.annotation.SuppressLint import android.content.DialogInterface import android.content.Intent import android.os.Bundle import android.os.Parcelable import android.text.Editable import android.text.TextWatcher import android.view.LayoutInflater import android.view.MenuItem import android.view.View import android.view.ViewGroup import android.widget.AdapterView import android.widget.EditText import android.widget.TextView import androidx.appcompat.app.AlertDialog import androidx.appcompat.widget.Toolbar import androidx.core.view.ViewCompat import androidx.recyclerview.widget.DefaultItemAnimator import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.github.shadowsocks.MainActivity import com.github.shadowsocks.R import com.github.shadowsocks.ToolbarFragment import com.github.shadowsocks.plugin.fragment.AlertDialogFragment import com.github.shadowsocks.utils.readableMessage import com.github.shadowsocks.widget.ListHolderListener import com.github.shadowsocks.widget.MainListListener import com.github.shadowsocks.widget.UndoSnackbarManager import com.google.android.material.textfield.TextInputLayout import kotlinx.parcelize.Parcelize import me.zhanghai.android.fastscroll.FastScrollerBuilder import java.net.MalformedURLException import java.net.URL class SubscriptionFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { @Parcelize data class SubItem(val item: String? = null) : Parcelable @Parcelize data class SubEditResult(val edited: String?, val replacing: String?) : Parcelable class SubDialogFragment : AlertDialogFragment<SubItem, SubEditResult>(), TextWatcher, AdapterView.OnItemSelectedListener { private lateinit var editText: EditText private lateinit var inputLayout: TextInputLayout private val positive by lazy { (dialog as AlertDialog).getButton(AlertDialog.BUTTON_POSITIVE) } override fun AlertDialog.Builder.prepare(listener: DialogInterface.OnClickListener) { val activity = requireActivity() @SuppressLint("InflateParams") val view = activity.layoutInflater.inflate(R.layout.dialog_subscription, null) editText = view.findViewById(R.id.content) inputLayout = view.findViewById(R.id.content_layout) editText.setText(arg.item) editText.addTextChangedListener(this@SubDialogFragment) setTitle(R.string.edit_subscription) setPositiveButton(android.R.string.ok, listener) setNegativeButton(android.R.string.cancel, null) if (!arg.item.isNullOrEmpty()) setNeutralButton(R.string.delete, listener) setView(view) } override fun onStart() { super.onStart() validate() } override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {} override fun afterTextChanged(s: Editable) = validate(value = s) override fun onNothingSelected(parent: AdapterView<*>?) = check(false) override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) = validate() private fun validate(value: Editable = editText.text) { var message = "" positive.isEnabled = try { val url = URL(value.toString()) if ("http".equals(url.protocol, true)) message = getString(R.string.cleartext_http_warning) true } catch (e: MalformedURLException) { message = e.readableMessage false } inputLayout.error = message } override fun ret(which: Int) = when (which) { DialogInterface.BUTTON_POSITIVE -> SubEditResult(editText.text.toString(), arg.item) DialogInterface.BUTTON_NEUTRAL -> SubEditResult(null, arg.item) else -> null } override fun onClick(dialog: DialogInterface?, which: Int) { if (which != DialogInterface.BUTTON_NEGATIVE) super.onClick(dialog, which) } } private inner class SubViewHolder(view: View) : RecyclerView.ViewHolder(view), View.OnClickListener { lateinit var item: URL private val text = view.findViewById<TextView>(android.R.id.text1) init { view.isFocusable = true view.setOnClickListener(this) view.setBackgroundResource(R.drawable.background_selectable) } fun bind(url: URL) { item = url text.text = url.toString() } override fun onClick(v: View?) { SubDialogFragment().apply { arg(SubItem(item.toString())) key() }.show(parentFragmentManager, null) } } private inner class SubscriptionAdapter : RecyclerView.Adapter<SubViewHolder>() { private val subscription = Subscription.instance private var savePending = false override fun onBindViewHolder(holder: SubViewHolder, i: Int) { holder.bind(subscription.urls[i]) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = SubViewHolder(LayoutInflater .from(parent.context).inflate(android.R.layout.simple_list_item_1, parent, false)) override fun getItemCount(): Int = subscription.urls.size() private fun apply() { if (!savePending) { savePending = true list.post { Subscription.instance = subscription savePending = false } } } fun add(url: URL): Int { val old = subscription.urls.size() val index = subscription.urls.add(url) if (old != subscription.urls.size()) { notifyItemInserted(index) apply() } return index } fun remove(i: Int) { undoManager.remove(Pair(i, subscription.urls[i])) subscription.urls.removeItemAt(i) notifyItemRemoved(i) apply() } fun remove(item: URL) { notifyItemRemoved(subscription.urls.indexOf(item)) subscription.urls.remove(item) apply() } fun undo(actions: List<Pair<Int, Any>>) { for ((_, item) in actions) when (item) { is URL -> { add(item) } } } } private val adapter by lazy { SubscriptionAdapter() } private lateinit var list: RecyclerView private lateinit var undoManager: UndoSnackbarManager<URL> override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = inflater.inflate(R.layout.layout_subscriptions, container, false) override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) ViewCompat.setOnApplyWindowInsetsListener(view, ListHolderListener) AlertDialogFragment.setResultListener<SubDialogFragment, SubEditResult>(this) { which, ret -> val (edited, replacing) = ret ?: return@setResultListener replacing?.let { item -> val url = URL(item) adapter.remove(url) if (which == DialogInterface.BUTTON_NEUTRAL) undoManager.remove(-1 to url) } if (edited != null) adapter.add(URL(edited)).also { list.post { list.scrollToPosition(it) } } } toolbar.setTitle(R.string.subscriptions) toolbar.inflateMenu(R.menu.subscription_menu) toolbar.setOnMenuItemClickListener(this) SubscriptionService.idle.observe(viewLifecycleOwner) { toolbar.menu.findItem(R.id.action_update_subscription).isEnabled = it } val activity = activity as MainActivity list = view.findViewById(R.id.list) ViewCompat.setOnApplyWindowInsetsListener(list, MainListListener) list.layoutManager = LinearLayoutManager(activity, RecyclerView.VERTICAL, false) list.itemAnimator = DefaultItemAnimator() list.adapter = adapter FastScrollerBuilder(list).useMd2Style().build() undoManager = UndoSnackbarManager(activity, adapter::undo) ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.START) { override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) = adapter.remove(viewHolder.bindingAdapterPosition) override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean = false }).attachToRecyclerView(list) } override fun onMenuItemClick(item: MenuItem): Boolean = when (item.itemId) { R.id.action_manual_settings -> { SubDialogFragment().apply { arg(SubItem()) key() }.show(parentFragmentManager, null) true } R.id.action_update_subscription -> { val context = requireContext() context.startService(Intent(context, SubscriptionService::class.java)) true } else -> false } override fun onDetach() { undoManager.flush() super.onDetach() } }