diff --git a/.idea/dictionaries/fumiama.xml b/.idea/dictionaries/fumiama.xml index c80f935..211241b 100644 --- a/.idea/dictionaries/fumiama.xml +++ b/.idea/dictionaries/fumiama.xml @@ -3,6 +3,7 @@ alphae azurewebsites + catquit comancry comandy deviceinfo diff --git a/app/build.gradle b/app/build.gradle index a67021e..7fcf24c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -12,8 +12,8 @@ android { minSdkVersion 23 //noinspection OldTargetApi targetSdkVersion 34 - versionCode 80 - versionName '2.5.7' + versionCode 81 + versionName '2.5.8' resourceConfigurations += ['zh', 'zh-rCN'] testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -124,4 +124,5 @@ dependencies { implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.7.0' implementation 'com.airbnb.android:lottie:6.6.6' implementation 'net.java.dev.jna:jna:5.17.0@aar' + implementation 'top.fumiama:sdict:0.1.0' } diff --git a/app/src/main/java/top/fumiama/copymanga/MainActivity.kt b/app/src/main/java/top/fumiama/copymanga/MainActivity.kt index fe3afad..8f0d8b7 100644 --- a/app/src/main/java/top/fumiama/copymanga/MainActivity.kt +++ b/app/src/main/java/top/fumiama/copymanga/MainActivity.kt @@ -63,7 +63,7 @@ import top.fumiama.copymanga.api.update.Update import top.fumiama.copymanga.api.user.Member import top.fumiama.copymanga.lib.Comancry import top.fumiama.copymanga.lib.Comandy -import top.fumiama.copymanga.storage.DataLoader +import top.fumiama.copymanga.storage.ConfigLoader import top.fumiama.copymanga.strings.Base16384 import top.fumiama.dmzj.copymanga.BuildConfig import top.fumiama.dmzj.copymanga.R @@ -225,7 +225,7 @@ class MainActivity : AppCompatActivity() { toolsBox.buildInfo("备份管理", "可选择导出或导入base16384格式配置项", "导出", "导入", "取消", { // ok MaterialDialog(this).show { - input(prefill = Base16384.encode(DataLoader().toByteArray())) + input(prefill = Base16384.encode(ConfigLoader().toByteArray())) positiveButton(android.R.string.ok) title(null, "请复制配置文本并保存") } @@ -233,7 +233,7 @@ class MainActivity : AppCompatActivity() { MaterialDialog(this).show { input { _, c -> try { - DataLoader(Base16384.decode(c.toString())).settings.export() + ConfigLoader(Base16384.decode(c.toString())).settings.export() navController?.apply { currentDestination?.id?.let { popBackStack() diff --git a/app/src/main/java/top/fumiama/copymanga/api/Config.kt b/app/src/main/java/top/fumiama/copymanga/api/Config.kt index f7c9227..c967032 100644 --- a/app/src/main/java/top/fumiama/copymanga/api/Config.kt +++ b/app/src/main/java/top/fumiama/copymanga/api/Config.kt @@ -58,6 +58,7 @@ object Config { val proxyUrl = MainActivity.mainWeakReference?.get()?.getString(R.string.proxyUrl)!! val pc_ua get() = MainActivity.mainWeakReference?.get()?.getString(R.string.pc_ua)?.format(app_ver.value)?:"" + val default_ua get() = MainActivity.mainWeakReference?.get()?.getString(R.string.default_ua)?:"" val referer get() = MainActivity.mainWeakReference?.get()?.getString(R.string.referer)?.format(app_ver.value)?:"" val navTextInfo = UserPreferenceString("navTextInfo", R.string.navTextInfo) @@ -95,7 +96,7 @@ object Config { val net_img_resolution = PreferenceString(R.string.imgResolutionKeyID) val net_umstring = PreferenceString("settings_cat_net_et_umstring") val net_source = PreferenceString("settings_cat_net_et_source", R.string.source) - val net_ua = PreferenceString("settings_cat_net_et_ua", "__default_ua__") + val net_ua = PreferenceString("settings_cat_net_et_ua", R.string.default_ua) val view_manga_inverse_chapters = PreferenceBoolean("settings_cat_vm_sw_inverse_chapters", false) val view_manga_always_dark_bg = PreferenceBoolean("settings_cat_vm_sw_always_dark_bg", false) diff --git a/app/src/main/java/top/fumiama/copymanga/api/update/ByteArrayQueue.kt b/app/src/main/java/top/fumiama/copymanga/api/update/ByteArrayQueue.kt deleted file mode 100644 index 36656e0..0000000 --- a/app/src/main/java/top/fumiama/copymanga/api/update/ByteArrayQueue.kt +++ /dev/null @@ -1,27 +0,0 @@ -package top.fumiama.copymanga.api.update -//Fumiama 20210601 -//ByteArrayQueue.kt -//FIFO队列 -class ByteArrayQueue { - private var elements = byteArrayOf() - val size get() = elements.size - fun append(items: ByteArray) { - elements += items - } - fun pop(num: Int = 1): ByteArray? { - return if(num <= elements.size) { - val re = elements.copyOfRange(0, num) - elements = elements.copyOfRange(num, elements.size) - re - } else null - } - fun clear() { - elements = byteArrayOf() - } - fun popAll(): ByteArray { - val re = elements - clear() - return re - } - operator fun plusAssign(items: ByteArray) = append(items) -} \ No newline at end of file diff --git a/app/src/main/java/top/fumiama/copymanga/api/update/SimpleKanban.kt b/app/src/main/java/top/fumiama/copymanga/api/update/SimpleKanban.kt index a82fbef..23edf2e 100644 --- a/app/src/main/java/top/fumiama/copymanga/api/update/SimpleKanban.kt +++ b/app/src/main/java/top/fumiama/copymanga/api/update/SimpleKanban.kt @@ -1,7 +1,7 @@ package top.fumiama.copymanga.api.update import android.util.Log -import top.fumiama.copymanga.net.Client +import top.fumiama.sdict.io.Client class SimpleKanban(private val client: Client, private val pwd: String) { //must run in thread private val raw: ByteArray? @@ -51,13 +51,13 @@ class SimpleKanban(private val client: Client, private val pwd: String) { //mu client.sendMessage("${pwd}get${version}quit") client.receiveRawMessage(36) //Welcome to simple kanban server. get val r = try { - val firstRecv = client.receiveRawMessage(4) - if(firstRecv.decodeToString() == "null") "null" + val firstReceive = client.receiveRawMessage(4) + if(firstReceive.decodeToString() == "null") "null" else { - val length = convert2Int(firstRecv) + val length = convert2Int(firstReceive) Log.d("MySK", "Msg len: $length") var re = byteArrayOf() - if(firstRecv.size > 4) re += firstRecv.copyOfRange(4, firstRecv.size) + if(firstReceive.size > 4) re += firstReceive.copyOfRange(4, firstReceive.size) re += client.receiveRawMessage(length - re.size) if(re.isNotEmpty()) re.decodeToString() else "null" } diff --git a/app/src/main/java/top/fumiama/copymanga/api/update/Update.kt b/app/src/main/java/top/fumiama/copymanga/api/update/Update.kt index 1eee673..b3d2ce9 100644 --- a/app/src/main/java/top/fumiama/copymanga/api/update/Update.kt +++ b/app/src/main/java/top/fumiama/copymanga/api/update/Update.kt @@ -16,10 +16,11 @@ import kotlinx.android.synthetic.main.dialog_progress.view.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import top.fumiama.copymanga.net.Client +import top.fumiama.sdict.io.Client import top.fumiama.copymanga.view.interaction.UITools import top.fumiama.dmzj.copymanga.BuildConfig import top.fumiama.dmzj.copymanga.R +import top.fumiama.sdict.utils.Utils.toHexStr import java.io.File import java.security.MessageDigest @@ -63,7 +64,7 @@ object Update { fetch(client, kanban, this@apply) { lifecycleScope.launch { val md5 = msg.substringAfterLast("md5:") - if (md5 == UITools.toHexStr( + if (md5 == toHexStr( MessageDigest.getInstance("MD5").digest(it) ) ) { diff --git a/app/src/main/java/top/fumiama/copymanga/lib/template/LazyLibrary.kt b/app/src/main/java/top/fumiama/copymanga/lib/template/LazyLibrary.kt index ab8f4a4..9426f00 100644 --- a/app/src/main/java/top/fumiama/copymanga/lib/template/LazyLibrary.kt +++ b/app/src/main/java/top/fumiama/copymanga/lib/template/LazyLibrary.kt @@ -10,7 +10,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import top.fumiama.copymanga.MainActivity import top.fumiama.copymanga.json.ComandyVersion -import top.fumiama.copymanga.net.Client +import top.fumiama.sdict.io.Client import top.fumiama.copymanga.net.DownloadTools import top.fumiama.copymanga.storage.PreferenceBoolean import top.fumiama.copymanga.storage.UserPreferenceInt diff --git a/app/src/main/java/top/fumiama/copymanga/net/Client.kt b/app/src/main/java/top/fumiama/copymanga/net/Client.kt deleted file mode 100644 index 667bb0c..0000000 --- a/app/src/main/java/top/fumiama/copymanga/net/Client.kt +++ /dev/null @@ -1,124 +0,0 @@ -package top.fumiama.copymanga.net -//Fumiama 20210601 -//Client.kt -import android.util.Log -import top.fumiama.copymanga.api.update.ByteArrayQueue -import java.io.* -import java.lang.Thread.sleep -import java.net.Socket - -class Client(private val ip: String, private val port: Int) { - //普通数据交互接口 - private var sc: Socket? = null - - //普通交互流 - private var dout: OutputStream? = null - private var din: InputStream? = null - - //已连接标记 - private val isConnect get() = sc != null && din != null && dout != null - - /** - * 初始化普通交互连接 - */ - fun initConnect(depth: Int = 0): Boolean{ - if(depth > 3) Log.d("MyC", "connect server failed after $depth tries") - else try { - sc = Socket(ip, port) //通过socket连接服务器 - din = sc?.getInputStream() //获取输入流并转换为StreamReader,约定编码格式 - dout = sc?.getOutputStream() //获取输出流 - sc?.soTimeout = 10000 //设置连接超时限制 - return if (isConnect) { - Log.d("MyC", "connect server successful") - true - } else { - Log.d("MyC", "connect server failed, now retry...") - initConnect(depth + 1) - } - } catch (e: IOException) { //获取输入输出流是可能报IOException的,所以必须try-catch - e.printStackTrace() - } - return false - } - - /** - * 发送数据至服务器 - * @param message 要发送至服务器的字符串 - */ - fun sendMessage(message: String?): Boolean = sendMessage(message?.toByteArray()) - - fun sendMessage(message: ByteArray?): Boolean { - try { - if (isConnect) { - if (message != null) { //判断输出流或者消息是否为空,为空的话会产生null pointer错误 - dout?.write(message) - dout?.flush() - Log.d("MyC", "Send msg: ${message.decodeToString()}") - return true - } else Log.d("MyC", "The message to be sent is empty") - Log.d("MyC", "send message succeed") - } else Log.d("MyC", "send message failed: no connect") - } catch (e: IOException) { - Log.d("MyC", "send message failed: crash") - e.printStackTrace() - } - return false - } - - fun read(): Char? = din?.read()?.toChar() - - private var buffer = ByteArrayQueue() - private val receiveBuffer = ByteArray(65536) - - fun receiveRawMessage(totalSize: Int, setProgress: Boolean = false) : ByteArray { - if(totalSize == buffer.size) return buffer.popAll() - else { - try { - if (isConnect) { - Log.d("MyC", "开始接收服务端信息") - var prevP = 0 - while(totalSize > buffer.size) { - val count = din?.read(receiveBuffer)?:0 - if(count > 0) { - buffer += receiveBuffer.copyOfRange(0, count) - Log.d("MyC", "reply length:$count") - val p = 100 * buffer.size / totalSize - if(setProgress && totalSize > 0 && prevP != p) { - progress?.notify(p) - prevP = p - } - } else sleep(10) - } - } else Log.d("MyC", "no connect to receive message") - } catch (e: IOException) { - Log.d("MyC", "receive message failed") - e.printStackTrace() - } - return if(totalSize > 0) buffer.pop(totalSize)?:byteArrayOf() else buffer.popAll() - } - } - - fun receiveMessage(totalSize: Int) = receiveRawMessage(totalSize).decodeToString() - - /** - * 关闭连接 - */ - fun closeConnect() = try { - din?.close() - dout?.close() - sc?.close() - sc = null - din = null - dout = null - true - } catch (e: IOException) { - e.printStackTrace() - false - } - - var progress: Progress? = null - - interface Progress { - fun notify(progressPercentage: Int) - } -} diff --git a/app/src/main/java/top/fumiama/copymanga/net/DownloadTools.kt b/app/src/main/java/top/fumiama/copymanga/net/DownloadTools.kt index 22e0a2e..fddba23 100644 --- a/app/src/main/java/top/fumiama/copymanga/net/DownloadTools.kt +++ b/app/src/main/java/top/fumiama/copymanga/net/DownloadTools.kt @@ -7,6 +7,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext import org.json.JSONObject +import top.fumiama.sdict.io.Client import top.fumiama.copymanga.api.Config import top.fumiama.copymanga.api.Config.proxyUrl import top.fumiama.copymanga.json.ComandyCapsule @@ -34,7 +35,7 @@ object DownloadTools { connection.apply { Config.net_ua.value.let { if (it.isEmpty()) return@let - setRequestProperty("user-agent", if (it == "__default_ua__") Config.pc_ua else it) + setRequestProperty("user-agent", if (it == Config.default_ua) Config.pc_ua else it) } Config.net_source.value.let { if(it.isNotEmpty()) setRequestProperty("source", it) } // deviceinfo @@ -43,7 +44,7 @@ object DownloadTools { if (Config.net_use_gzip.value) setRequestProperty("accept-encoding", "gzip") setRequestProperty("authorization", "Token${Config.token.value?.let { tk -> if (tk.isNotEmpty()) " $tk" else "" - }}") + }?:""}") if (Config.net_platform.value) setRequestProperty("platform", Config.platform.value) if (Config.net_referer.value) setRequestProperty("referer", Config.referer) if (Config.net_use_json.value) setRequestProperty("accept", "application/json") @@ -66,7 +67,7 @@ object DownloadTools { capsule.headers = hashMapOf() Config.net_ua.value.let { if (it.isEmpty()) return@let - capsule.headers["user-agent"] = if (it == "__default_ua__") Config.pc_ua else it + capsule.headers["user-agent"] = if (it == Config.default_ua) Config.pc_ua else it } Config.net_source.value.let { if(it.isNotEmpty()) capsule.headers["source"] = it } // deviceinfo @@ -75,7 +76,7 @@ object DownloadTools { if (Config.net_use_gzip.value) capsule.headers["accept-encoding"] = "gzip" capsule.headers["authorization"] = "Token${Config.token.value?.let { tk -> if (tk.isNotEmpty()) " $tk" else "" - }}" + }?:""}" if (Config.net_platform.value) capsule.headers["platform"] = Config.platform.value if (Config.net_referer.value) capsule.headers["referer"] = Config.referer if (Config.net_use_json.value) capsule.headers["accept"] = "application/json" diff --git a/app/src/main/java/top/fumiama/copymanga/net/template/AutoDownloadHandler.kt b/app/src/main/java/top/fumiama/copymanga/net/template/AutoDownloadHandler.kt index 69dccfa..fe14333 100644 --- a/app/src/main/java/top/fumiama/copymanga/net/template/AutoDownloadHandler.kt +++ b/app/src/main/java/top/fumiama/copymanga/net/template/AutoDownloadHandler.kt @@ -11,11 +11,9 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import top.fumiama.copymanga.MainActivity.Companion.mainWeakReference import top.fumiama.copymanga.api.Config import top.fumiama.copymanga.json.ReturnBase import java.io.File -import java.security.MessageDigest open class AutoDownloadHandler( private val url: () -> String, private val jsonClass: Class<*>, @@ -24,6 +22,7 @@ open class AutoDownloadHandler( private val customCacheFile: File? = null): Handler(Looper.myLooper()!!) { private var checkTimes = 0 var exit = false + var raw: String? = null override fun handleMessage(msg: Message) { super.handleMessage(msg) when(msg.what){ @@ -45,26 +44,16 @@ open class AutoDownloadHandler( downloadCoroutine() check() } - private fun toHexStr(byteArray: ByteArray) = - with(StringBuilder()) { - byteArray.forEach { - val hex = it.toInt() and (0xFF) - val hexStr = Integer.toHexString(hex) - if (hexStr.length == 1) append("0").append(hexStr) - else append(hexStr) - } - toString() - } private suspend fun downloadCoroutine() = withContext(Dispatchers.IO) { - val cacheName = toHexStr(MessageDigest.getInstance("MD5").digest(url().encodeToByteArray())) - val cacheFile = customCacheFile?:(mainWeakReference?.get()?.externalCacheDir?.let { File(it, cacheName) }) if(loadFromCache) { - cacheFile?.let { + customCacheFile?.let { if (it.exists()) { var pass = true it.inputStream().use { fi-> try { - pass = setGsonItem(Gson().fromJson(fi.reader(), jsonClass)) + val data = fi.readBytes().decodeToString() + raw = data + pass = setGsonItem(Gson().fromJson(data, jsonClass)) } catch (e: Exception) { e.printStackTrace() } @@ -77,10 +66,11 @@ open class AutoDownloadHandler( while (cnt++ <= 3) { try { val data = Config.api.get(url()) + raw = data if(exit) return@withContext val pass = setGsonItem(Gson().fromJson(data, jsonClass)) if (pass && loadFromCache) { - cacheFile?.writeText(data) + customCacheFile?.writeText(data) } if(!pass) { delay(2000) diff --git a/app/src/main/java/top/fumiama/copymanga/storage/DataLoader.kt b/app/src/main/java/top/fumiama/copymanga/storage/ConfigLoader.kt similarity index 99% rename from app/src/main/java/top/fumiama/copymanga/storage/DataLoader.kt rename to app/src/main/java/top/fumiama/copymanga/storage/ConfigLoader.kt index 4b2bf8d..77c7ce3 100644 --- a/app/src/main/java/top/fumiama/copymanga/storage/DataLoader.kt +++ b/app/src/main/java/top/fumiama/copymanga/storage/ConfigLoader.kt @@ -4,7 +4,7 @@ import top.fumiama.copymanga.api.Config import java.nio.ByteBuffer import java.util.zip.CRC32 -class DataLoader { +class ConfigLoader { data class Settings( var appVer: String, var platform: String, diff --git a/app/src/main/java/top/fumiama/copymanga/ui/download/NewDownloadFragment.kt b/app/src/main/java/top/fumiama/copymanga/ui/download/NewDownloadFragment.kt index abbbf8a..a4222b9 100644 --- a/app/src/main/java/top/fumiama/copymanga/ui/download/NewDownloadFragment.kt +++ b/app/src/main/java/top/fumiama/copymanga/ui/download/NewDownloadFragment.kt @@ -21,7 +21,7 @@ import top.fumiama.copymanga.MainActivity import top.fumiama.copymanga.api.Config.manga_dl_show_0m_manga import top.fumiama.copymanga.api.manga.Downloader import top.fumiama.copymanga.api.manga.Reader -import top.fumiama.copymanga.net.Client +import top.fumiama.sdict.io.Client import top.fumiama.copymanga.storage.FileUtils import top.fumiama.copymanga.storage.FileUtils.compressToUserFile import top.fumiama.copymanga.view.interaction.Navigate diff --git a/app/src/main/java/top/fumiama/copymanga/ui/home/HomeFragment.kt b/app/src/main/java/top/fumiama/copymanga/ui/home/HomeFragment.kt index 5e32e09..9961e51 100644 --- a/app/src/main/java/top/fumiama/copymanga/ui/home/HomeFragment.kt +++ b/app/src/main/java/top/fumiama/copymanga/ui/home/HomeFragment.kt @@ -10,6 +10,7 @@ import android.view.View import android.view.ViewGroup import android.widget.ArrayAdapter import android.widget.ImageButton +import androidx.fragment.app.viewModels import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.LinearLayoutManager @@ -24,7 +25,6 @@ import kotlinx.android.synthetic.main.fragment_home.* import kotlinx.android.synthetic.main.line_word.view.* import kotlinx.android.synthetic.main.viewpage_horizonal.view.* import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import top.fumiama.copymanga.MainActivity @@ -40,150 +40,176 @@ import java.lang.ref.WeakReference import java.net.URLEncoder import java.nio.charset.Charset -class HomeFragment : NoBackRefreshFragment(R.layout.fragment_home) { +class HomeFragment : NoBackRefreshFragment(R.layout.fragment_home, true) { lateinit var homeHandler: HomeHandler + val vm: HomeViewModel by viewModels() @SuppressLint("ClickableViewAccessibility") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - if(isFirstInflate) { - val tb = (activity as MainActivity).toolsBox - val netInfo = tb.netInfo - if(netInfo != tb.transportStringNull && netInfo != tb.transportStringError) - MainActivity.member?.apply { lifecycleScope.launch { - withContext(Dispatchers.IO) { - Config.api.init() - try { - info() - } catch (e: Exception) { - Snackbar - .make(view, "${e::class.simpleName} ${e.message}", Snackbar.LENGTH_LONG) - .setTextMaxLines(10) - .show() - } - } - } } - homeHandler = HomeHandler(WeakReference(this)) - val theme = resources.newTheme() - swiperefresh?.setColorSchemeColors( - resources.getColor(R.color.colorAccent, theme), - resources.getColor(R.color.colorBlue2, theme), - resources.getColor(R.color.colorGreen, theme)) - swiperefresh?.isEnabled = true + val theme = resources.newTheme() + swiperefresh?.setColorSchemeColors( + resources.getColor(R.color.colorAccent, theme), + resources.getColor(R.color.colorBlue2, theme), + resources.getColor(R.color.colorGreen, theme) + ) - fhl?.setPadding(0, 0, 0, navBarHeight) + fhl?.setPadding(0, 0, 0, navBarHeight) - fhs?.apply { - isNestedScrollingEnabled = true - val recyclerView = findViewById(R.id.search_recycler_view) - recyclerView.isNestedScrollingEnabled = true - recyclerView.setPadding(0, 0, 0, navBarHeight) - setAdapterLayoutManager(LinearLayoutManager(context)) - val adapter = ListViewHolder(recyclerView).RecyclerViewAdapter() - setAdapter(adapter) - navigationIconSupport = SearchLayout.NavigationIconSupport.SEARCH - setMicIconImageResource(R.drawable.ic_setting_search) - val micView = findViewById(R.id.search_image_view_mic) - setClearFocusOnBackPressed(true) - setOnNavigationClickListener(object : SearchLayout.OnNavigationClickListener { - override fun onNavigationClick(hasFocus: Boolean) { - if (hasFocus()) { - clearFocus() - } - else requestFocus() - } - }) - setTextHint(android.R.string.search_go) - - var lastSearch = "" - setOnQueryTextListener(object : SearchLayout.OnQueryTextListener { - var lastChangeTime = 0L - override fun onQueryTextChange(newText: CharSequence): Boolean { - if (newText.contentEquals("__notice_focus_change__") || newText.contentEquals(lastSearch)) return true - lastSearch = newText.toString() - postDelayed({ - lifecycleScope.launch { - if (!newText.contentEquals(lastSearch)) return@launch - val diff = System.currentTimeMillis() - lastChangeTime - if(diff > 500) { - if (newText.isNotEmpty()) { - Log.d("MyHF", "new text: $newText") - adapter.refresh(newText) + lifecycleScope.launch { + withContext(Dispatchers.Main) { + vm.indexStructure.observe(viewLifecycleOwner) { + Log.d("MyHF", "get observed: $it") + if (it == null) { // init + val tb = (activity as MainActivity).toolsBox + val netInfo = tb.netInfo + lifecycleScope.launch net@ { + if (netInfo == tb.transportStringNull || netInfo == tb.transportStringError) { + (activity as MainActivity).toolsBox.toastError(getString(R.string.web_error)) + fhov?.swipeRefreshLayout = swiperefresh + swiperefresh.isRefreshing = false + swiperefresh.setOnRefreshListener { + Log.d("MyHFH", "Refresh items.") + vm.saveIndexStructure(null) + } + hideKanban() + return@net + } + MainActivity.member?.apply { + withContext(Dispatchers.IO) { + Config.api.init() + try { + info() + } catch (e: Exception) { + withContext(Dispatchers.Main) { + Snackbar + .make( + view, + "${e::class.simpleName} ${e.message}", + Snackbar.LENGTH_LONG + ) + .setTextMaxLines(10) + .show() + } } } - } - }, 1024) - lastChangeTime = System.currentTimeMillis() - return true - } + } } - override fun onQueryTextSubmit(query: CharSequence): Boolean { - /*if(query.isNotEmpty()) { - val key = query.toString() - Toast.makeText(context, key, Toast.LENGTH_SHORT).show() - }*/ - Log.d("MyHF", "recover text: $lastSearch") - setTextQuery(lastSearch, false) - return true - } - }) + homeHandler = HomeHandler(WeakReference(this@HomeFragment)) + homeHandler.obtainMessage(-1, true).sendToTarget() + homeHandler.startLoad() - setOnMicClickListener(object : SearchLayout.OnMicClickListener { - val types = arrayOf("", "name", "author", "local") - var i = 0 - override fun onMicClick() { - val typeNames = resources.getStringArray(R.array.search_types) - AlertDialog.Builder(context) - .setTitle(R.string.set_search_types) - .setIcon(R.mipmap.ic_launcher) - .setSingleChoiceItems(ArrayAdapter(context, R.layout.line_choice_list, typeNames), i){ d, p -> - adapter.type = types[p] - i = p - d.cancel() - }.show() + return@observe } - }) + if(homeHandler.exit) return@observe - var isInFocusWaiting = false - setOnFocusChangeListener(object : SearchLayout.OnFocusChangeListener { - override fun onFocusChange(hasFocus: Boolean) { - Log.d("MyHF", "fhs onFocusChange: $hasFocus") - if (isInFocusWaiting) return - isInFocusWaiting = true - postDelayed({ - navigationIconSupport = if (hasFocus) { - setTextQuery("__notice_focus_change__", true) - SearchLayout.NavigationIconSupport.ARROW - } - else { - if (lastSearch.isNotEmpty()) { - micView?.visibility = View.VISIBLE + swiperefresh?.isEnabled = true + + fhs?.apply { + isNestedScrollingEnabled = true + val recyclerView = findViewById(R.id.search_recycler_view) + recyclerView.isNestedScrollingEnabled = true + recyclerView.setPadding(0, 0, 0, navBarHeight) + setAdapterLayoutManager(LinearLayoutManager(context)) + val adapter = ListViewHolder(recyclerView).RecyclerViewAdapter() + setAdapter(adapter) + navigationIconSupport = SearchLayout.NavigationIconSupport.SEARCH + setMicIconImageResource(R.drawable.ic_setting_search) + val micView = findViewById(R.id.search_image_view_mic) + setClearFocusOnBackPressed(true) + setOnNavigationClickListener(object : SearchLayout.OnNavigationClickListener { + override fun onNavigationClick(hasFocus: Boolean) { + if (hasFocus()) { + clearFocus() } - SearchLayout.NavigationIconSupport.SEARCH + else requestFocus() } - isInFocusWaiting = false - }, 300) - } - }) + }) + setTextHint(android.R.string.search_go) - setOnTouchListener { _, e -> - Log.d("MyHF", "fhns on touch") - if (e.action == MotionEvent.ACTION_UP && mSearchEditText?.text?.isNotEmpty() == true) { - ime?.hideSoftInputFromWindow(activity?.window?.decorView?.windowToken, 0) - } - false - } - } + var lastSearch = "" + setOnQueryTextListener(object : SearchLayout.OnQueryTextListener { + var lastChangeTime = 0L + override fun onQueryTextChange(newText: CharSequence): Boolean { + if (newText.contentEquals("__notice_focus_change__") || newText.contentEquals(lastSearch)) return true + lastSearch = newText.toString() + postDelayed({ + lifecycleScope.launch { + if (!newText.contentEquals(lastSearch)) return@launch + val diff = System.currentTimeMillis() - lastChangeTime + if(diff > 500) { + if (newText.isNotEmpty()) { + Log.d("MyHF", "new text: $newText") + adapter.refresh(newText) + } + } + } + }, 1024) + lastChangeTime = System.currentTimeMillis() + return true + } - lifecycleScope.launch{ - withContext(Dispatchers.IO) { - homeHandler.obtainMessage(-1, true).sendToTarget() - while(!MainActivity.isDrawerClosed) delay(233) - //homeHandler.sendEmptyMessage(6) //removeAllViews - //homeHandler.fhib = null - delay(300) - homeHandler.startLoad() + override fun onQueryTextSubmit(query: CharSequence): Boolean { + /*if(query.isNotEmpty()) { + val key = query.toString() + Toast.makeText(context, key, Toast.LENGTH_SHORT).show() + }*/ + Log.d("MyHF", "recover text: $lastSearch") + setTextQuery(lastSearch, false) + return true + } + }) + + setOnMicClickListener(object : SearchLayout.OnMicClickListener { + val types = arrayOf("", "name", "author", "local") + var i = 0 + override fun onMicClick() { + val typeNames = resources.getStringArray(R.array.search_types) + AlertDialog.Builder(context) + .setTitle(R.string.set_search_types) + .setIcon(R.mipmap.ic_launcher) + .setSingleChoiceItems(ArrayAdapter(context, R.layout.line_choice_list, typeNames), i){ d, p -> + adapter.type = types[p] + i = p + d.cancel() + }.show() + } + }) + + var isInFocusWaiting = false + setOnFocusChangeListener(object : SearchLayout.OnFocusChangeListener { + override fun onFocusChange(hasFocus: Boolean) { + Log.d("MyHF", "fhs onFocusChange: $hasFocus") + if (isInFocusWaiting) return + isInFocusWaiting = true + postDelayed({ + navigationIconSupport = if (hasFocus) { + setTextQuery("__notice_focus_change__", true) + SearchLayout.NavigationIconSupport.ARROW + } + else { + if (lastSearch.isNotEmpty()) { + micView?.visibility = View.VISIBLE + } + SearchLayout.NavigationIconSupport.SEARCH + } + isInFocusWaiting = false + }, 300) + } + }) + + setOnTouchListener { _, e -> + Log.d("MyHF", "fhns on touch") + if (e.action == MotionEvent.ACTION_UP && mSearchEditText?.text?.isNotEmpty() == true) { + ime?.hideSoftInputFromWindow(activity?.window?.decorView?.windowToken, 0) + } + false + } + } + homeHandler.sendEmptyMessage(2) //setSwipe + homeHandler.sendEmptyMessage(7) //inflateBanner + homeHandler.sendEmptyMessage(1) //inflateCardLines } } } @@ -300,6 +326,7 @@ class HomeFragment : NoBackRefreshFragment(R.layout.fragment_home) { override fun getItemCount() = (results?.results?.list?.size?:0) + if (query?.isNotEmpty() == true) 1 else 0 + @SuppressLint("NotifyDataSetChanged") suspend fun refresh(q: CharSequence) = withContext(Dispatchers.IO) { query = q.toString() activity?.apply { diff --git a/app/src/main/java/top/fumiama/copymanga/ui/home/HomeHandler.kt b/app/src/main/java/top/fumiama/copymanga/ui/home/HomeHandler.kt index 044cfe7..c2d3085 100644 --- a/app/src/main/java/top/fumiama/copymanga/ui/home/HomeHandler.kt +++ b/app/src/main/java/top/fumiama/copymanga/ui/home/HomeHandler.kt @@ -8,7 +8,6 @@ import android.os.Message import android.util.Log import android.view.View import android.view.ViewTreeObserver -import android.widget.LinearLayout import android.widget.Toast import androidx.cardview.widget.CardView import androidx.constraintlayout.widget.ConstraintLayout @@ -28,13 +27,13 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import top.fumiama.copymanga.api.Config import top.fumiama.copymanga.json.ComicStructure import top.fumiama.copymanga.json.IndexStructure import top.fumiama.copymanga.net.template.AutoDownloadHandler -import top.fumiama.copymanga.api.Config -import top.fumiama.copymanga.view.operation.GlideHideLottieViewListener import top.fumiama.copymanga.view.interaction.Navigate import top.fumiama.copymanga.view.interaction.UITools +import top.fumiama.copymanga.view.operation.GlideHideLottieViewListener import top.fumiama.dmzj.copymanga.R import java.lang.ref.WeakReference import java.util.concurrent.atomic.AtomicInteger @@ -47,7 +46,7 @@ class HomeHandler(private val that: WeakReference) : AutoDownloadH ) { private val homeF get() = that.get() var index: IndexStructure? = null - var fhib: Banner? = null + private var fhib: Banner? = null get() { Log.d("MyHH", "Get fhib.") if (field == null) { @@ -56,7 +55,6 @@ class HomeHandler(private val that: WeakReference) : AutoDownloadH } return field } - private var indexLines = arrayOf() override fun handleMessage(msg: Message) { super.handleMessage(msg) @@ -72,16 +70,6 @@ class HomeHandler(private val that: WeakReference) : AutoDownloadH 2 -> homeF?.swiperefresh?.let { setSwipe(it) } 3 -> setBanner(fhib!!) 5 -> setBannerInfo(msg.obj as Banner) - 6 -> { - homeF?.fhl?.let { - val oa = ObjectAnimator.ofFloat(it, "alpha", 1f, 0f).setDuration(233) - oa.doOnEnd { _ -> - it.removeAllViews() - it.alpha = 1f - } - oa.start() - } - } 7 -> inflateBanner() } } @@ -108,15 +96,22 @@ class HomeHandler(private val that: WeakReference) : AutoDownloadH Toast.makeText(homeF?.context, R.string.web_error, Toast.LENGTH_SHORT).show() } } - override suspend fun doWhenFinishDownload() = withContext(Dispatchers.IO) { + override suspend fun doWhenFinishDownload(): Unit = withContext(Dispatchers.IO) { super.doWhenFinishDownload() - if(exit) return@withContext - sendEmptyMessage(2) //setSwipe - sendEmptyMessage(7) //inflateBanner - sendEmptyMessage(1) //inflateCardLines + raw?.let { + Log.d("MyHFH", "save raw: $it") + homeF?.apply { activity?.runOnUiThread { + vm.saveIndexStructure(it) + } } + } } - private fun inflateBanner() = homeF?.fhl?.addView(fhib) + private fun inflateBanner() { + homeF?.fhl?.let { it.post { + fhib = null + it.addView(fhib) + } } + } private suspend fun inflateTopics() { index?.results?.topics?.list?.let { @@ -242,29 +237,13 @@ class HomeHandler(private val that: WeakReference) : AutoDownloadH private fun inflateCardLines() { homeF?.lifecycleScope?.launch { withContext(Dispatchers.IO) { - if (indexLines.isNotEmpty()) indexLines = arrayOf() inflateRec() inflateTopics() inflateHot() inflateNew() inflateFinish() inflateRank() - homeF?.fhl?.apply { post { - for (i in indexLines.indices) { - try { - addView(indexLines[i]) - } catch (e: Exception) { - e.printStackTrace() - (indexLines[i].parent as LinearLayout).apply { - post { - removeAllViews() - homeF?.fhl?.addView(indexLines[i]) - } - } - } - } - obtainMessage(-1, false).sendToTarget() //closeLoad - } } + obtainMessage(-1, false).sendToTarget() //closeLoad } } } @@ -306,14 +285,24 @@ class HomeHandler(private val that: WeakReference) : AutoDownloadH homeF?.lifecycleScope?.launch { withContext(Dispatchers.IO) { homeF?.showKanban() - fhib?.isAutoPlay = false - index = null - fhib?.adapter?.notifyDataSetChanged() + fhib?.let { + it.isAutoPlay = false + index = null + it.adapter?.notifyDataSetChanged() + } fhib = null - indexLines = arrayOf() - this@HomeHandler.sendEmptyMessage(6) //removeAllViews delay(300) - this@HomeHandler.sendEmptyMessage(0) //setLayouts + withContext(Dispatchers.Main) { + homeF?.fhl?.let { + val oa = ObjectAnimator.ofFloat(it, "alpha", 1f, 0f).setDuration(233) + oa.doOnEnd { _ -> + it.removeAllViews() + it.alpha = 1f + homeF?.vm?.saveIndexStructure(null) // reload + } + oa.start() + } + } } } } @@ -322,15 +311,14 @@ class HomeHandler(private val that: WeakReference) : AutoDownloadH private suspend fun allocateLine( title: String, iconResId: Int, comics: Array, finish: Boolean = false, isTopic: Boolean = false, onClick: (() -> Unit)? = null - ): Int = withContext(Dispatchers.IO) { - val p = indexLines.size + ): Unit = withContext(Dispatchers.IO) { val c = comics.size / 3 homeF?.layoutInflater?.inflate( when(c){ 1 -> R.layout.line_1bookline 2 -> R.layout.line_2bookline 3 -> R.layout.line_3bookline - else -> return@withContext -1 + else -> return@withContext }, null, false)?.apply { withContext(Dispatchers.Main) { scanCards(this@apply, comics, finish, isTopic) @@ -341,9 +329,9 @@ class HomeHandler(private val that: WeakReference) : AutoDownloadH if(onClick != null) setOnClickListener { onClick() } } } - indexLines += this + homeF?.fhl?.let { it.post { it.addView(this) } } } - return@withContext p + return@withContext } private suspend fun scanCards(v: View, comics: Array, finish: Boolean, isTopic: Boolean) = withContext(Dispatchers.IO) { diff --git a/app/src/main/java/top/fumiama/copymanga/ui/home/HomeViewModel.kt b/app/src/main/java/top/fumiama/copymanga/ui/home/HomeViewModel.kt new file mode 100644 index 0000000..b096e52 --- /dev/null +++ b/app/src/main/java/top/fumiama/copymanga/ui/home/HomeViewModel.kt @@ -0,0 +1,12 @@ +package top.fumiama.copymanga.ui.home + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel + +class HomeViewModel : ViewModel() { + val indexStructure = MutableLiveData(null) + + fun saveIndexStructure(index: String?) { + indexStructure.value = index + } +} diff --git a/app/src/main/java/top/fumiama/copymanga/view/interaction/UITools.kt b/app/src/main/java/top/fumiama/copymanga/view/interaction/UITools.kt index 7e892c9..1679f04 100644 --- a/app/src/main/java/top/fumiama/copymanga/view/interaction/UITools.kt +++ b/app/src/main/java/top/fumiama/copymanga/view/interaction/UITools.kt @@ -123,16 +123,6 @@ class UITools(that: Context?, w: WeakReference? = null) { return listOf(numPerRow, w, totalWidth) } companion object { - fun toHexStr(byteArray: ByteArray) = - with(StringBuilder()) { - byteArray.forEach { - val hex = it.toInt() and (0xFF) - val hexStr = Integer.toHexString(hex) - if (hexStr.length == 1) append("0").append(hexStr) - else append(hexStr) - } - toString() - } @SuppressLint("DiscouragedApi", "InternalInsetResource") fun getNavigationBarHeight(context: Context): Int { val resources = context.resources diff --git a/app/src/main/java/top/fumiama/copymanga/view/template/NoBackRefreshFragment.kt b/app/src/main/java/top/fumiama/copymanga/view/template/NoBackRefreshFragment.kt index 3e2f899..76ecc4b 100644 --- a/app/src/main/java/top/fumiama/copymanga/view/template/NoBackRefreshFragment.kt +++ b/app/src/main/java/top/fumiama/copymanga/view/template/NoBackRefreshFragment.kt @@ -14,9 +14,8 @@ import top.fumiama.copymanga.api.Config import top.fumiama.copymanga.view.interaction.UITools import java.util.concurrent.atomic.AtomicBoolean -open class NoBackRefreshFragment(private val layoutToLoad: Int): Fragment() { +open class NoBackRefreshFragment(private val layoutToLoad: Int, private val noCacheView: Boolean = false): Fragment() { private var _rootView: View? = null - val rootView: View get() = _rootView!! var isFirstInflate = true var navBarHeight = 0 private val disableAnimation get() = Config.general_disable_kanban_animation.value @@ -25,7 +24,10 @@ open class NoBackRefreshFragment(private val layoutToLoad: Int): Fragment() { container: ViewGroup?, savedInstanceState: Bundle? ): View? { - //TODO: 支持自动重建 + navBarHeight = context?.let { UITools.getNavigationBarHeight(it) } ?: 0 + + if (noCacheView) return inflater.inflate(layoutToLoad, container, false) + if(_rootView == null) { isFirstInflate = true _rootView = inflater.inflate(layoutToLoad, container, false) @@ -34,8 +36,7 @@ open class NoBackRefreshFragment(private val layoutToLoad: Int): Fragment() { isFirstInflate = false Log.d("MyNBRF", "not first inflate") } - navBarHeight = context?.let { UITools.getNavigationBarHeight(it) } ?: 0 - return rootView + return _rootView!! } override fun onDestroy() { hideKanban() diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f920066..4b43f3b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -115,6 +115,7 @@ 网络错误 COPY/%1$s + __default_ua__ &appver; com.copymanga.app-%1$s &platform; diff --git a/app/src/main/res/xml/pref_setting.xml b/app/src/main/res/xml/pref_setting.xml index 01fbf26..b1ee72d 100644 --- a/app/src/main/res/xml/pref_setting.xml +++ b/app/src/main/res/xml/pref_setting.xml @@ -185,6 +185,7 @@ Features -1. Browse homepage, category, rank, my downloads, tag, and author. -2. View, search and read manga; log reading progress. -3. Download manga. But due to force majeure, the downloading is slow and error-prone. This is not due to bad optimization, absolutely not. -4. Read downloaded manga. -5. Check update. +1. Browse the homepage, categories, rankings, downloads, subscriptions, reading history, tags, and authors. +2. View and search for manga, and read them directly. Reading progress is saved both locally and in the cloud (cloud sync does not track the exact page). +3. Download manga. However, due to circumstances beyond our control, download speeds may be slow and errors can occur—definitely not because of optimization, absolutely not. +4. Read and delete downloaded manga, or jump to the manga detail page directly from your downloads. +5. Check for updates. +6. Log in and log out. +7. Subscribe or unsubscribe to manga. diff --git a/fastlane/metadata/android/en-US/short_description.txt b/fastlane/metadata/android/en-US/short_description.txt index 26e88c4..cb86eb9 100644 --- a/fastlane/metadata/android/en-US/short_description.txt +++ b/fastlane/metadata/android/en-US/short_description.txt @@ -1 +1 @@ -Third-party client for copymanga with better reading/downloading experience \ No newline at end of file +A third-party app for CopyManga, offering only basic features. For a more complete experience, please refer to the official version. \ No newline at end of file diff --git a/fastlane/metadata/android/zh-CN/full_description.txt b/fastlane/metadata/android/zh-CN/full_description.txt index 35c0107..9e541de 100644 --- a/fastlane/metadata/android/zh-CN/full_description.txt +++ b/fastlane/metadata/android/zh-CN/full_description.txt @@ -1,7 +1,9 @@ -拷贝漫画的第三方 APP,优化阅读/下载体验 +拷贝漫画的第三方APP,仅提供基础功能,更多丰富功能请移步官方版本

功能

-1. 浏览主页、分类、排行、我的下载、标签、作者。 -2. 查看、搜索漫画并直接阅读;记录漫画与章节的阅读进度。 +1. 浏览主页、分类、排行、我的下载、我的订阅、浏览历史、标签、作者。 +2. 查看、搜索漫画并直接阅读;在本地和云端记录漫画与章节的阅读进度 (云端不能精确到页)。 3. 下载漫画。但是由于不可抗力,下载速度较慢且容易出错,这绝对不是优化的原因,绝对不是。 -4. 阅读下载的漫画。 -5. 检查更新。 \ No newline at end of file +4. 阅读、删除下载的漫画,或从我的下载页面直接导航到漫画详情页。 +5. 检查更新。 +6. 登录,注销。 +7. 订阅、取消订阅。 \ No newline at end of file diff --git a/fastlane/metadata/android/zh-CN/short_description.txt b/fastlane/metadata/android/zh-CN/short_description.txt index 560c239..2ef88e2 100644 --- a/fastlane/metadata/android/zh-CN/short_description.txt +++ b/fastlane/metadata/android/zh-CN/short_description.txt @@ -1 +1 @@ -拷贝漫画的第三方 APP,优化阅读/下载体验 \ No newline at end of file +拷贝漫画的第三方APP,仅提供基础功能,更多丰富功能请移步官方版本 \ No newline at end of file